mikke89 / RmlUi

RmlUi - The HTML/CSS User Interface library evolved
https://mikke89.github.io/RmlUiDoc/
MIT License
2.87k stars 312 forks source link

Modernize CMake #198

Closed mikke89 closed 6 months ago

mikke89 commented 3 years ago

Our CMakeLists.txt is really a patchwork on top of something written using legacy CMake conventions. It could really need a rewrite or at least a larger review. I've seen several users writing their own CMakelists to consume the library, which is understandable but not an ideal situation.

Some ways to consume the library that should ideally be supported without too much trouble for users:

Questions:

Some issue that have been reported by users:

What do you think about all of this? Please do contribute with your own use cases, requirements, and issues. Generally, we would like to keep our current CMake options, but we can break some of them if it leads to improvements. I'm all for bumping the minimum CMake version to something more recent.

I am not a CMake expert myself, so I'd be very happy if a volunteer who is more comfortable with the task could lend us a helping hand.

andreasschultes commented 3 years ago

I write my ideas down and some informations:

I hope this helps a bit for the discussion. I am also not a CMake expert.

mikke89 commented 3 years ago

Thank you, those are valuable considerations! They all sound reasonable to me.

SamVanheer commented 2 years ago

I took a look at the main CMakeLists.txt, here's my feedback.

First, note that the minimum CMake version is 3.1.

There are a few CMake policies that are set to NEW. If the minimum CMake version is newer than the version the policy was introduced in the explicit setting can be removed.

Notes:

You should figure out what the minimum CMake version you want to use is so you can decide what to do about these things.

SamVanheer commented 2 years ago

I looked up which CMake versions are used by various platforms:

So you can safely switch to 3.10.2 and still support every platform listed, unless there are platforms supported that i'm not aware of.

If you're ok with requiring users to install a newer version then you can probably pick any version, but you should probably check with users to see what version they're limited to.

mikke89 commented 2 years ago

Thank you for the detailed feedback and investigation! This is very helpful.

So I think based on this I would choose 3.10.2 as minimum. I don't want compilation to require any more steps than necessary, so avoiding custom CMake installs would be preferable on systems that already provide an older version.

I guess then we're just missing out on some nice, modern CMake techniques. Maybe in a year or two we should be able to go for 3.16, and perhaps also move towards C++17.

All of your notes sound reasonable to me, I think this is a very good outline of what needs to be done. I do agree with renaming the variable names, they're not consistent right now. It would be a pretty big breaking change so we would have to merge that during a new major version.

cathaysia commented 1 year ago

Hi friends. What I'm want to say is that if you want to partition your projects in Modern Cmake, don't use static libraries to partition your projects. This bring problems with dynamic library symbol export. A more reasonable approach is to use the OBJECT library to divide submodules. However, this requires upgrading the CMake version.

cathaysia commented 1 year ago

Another problem is dependency lookup related. I think dependent lookups should follow:

if find_package(freetype) then
    link_target(freetype)
    include_target(freetype)
else if try_add_subdirectory(freetype) then
   -- ....
else fetch_content(freetype) then
    -- ...
end
hobyst commented 1 year ago

Hi friends. What I'm want to say is that if you want to partition your projects in Modern Cmake, don't use static libraries to partition your projects. This bring problems with dynamic library symbol export. A more reasonable approach is to use the OBJECT library to divide submodules. However, this requires upgrading the CMake version.

@cathaysia What issues does it cause? I've heard about issues that occur when compiling a library as a static library and such library uses the MSVC-specific __declspec(dllimport) and __declspec(dllexport) preprocessor macros, but these are more about not properly guarding them to make the libary compatible with both static and dynamic linking than a CMake issue.

hobyst commented 1 year ago

To modernize the CMake script entirely, I would rather start from scratch with a new script that would follow Modern CMake as much as possible, then port functionality (options, compilation flags...) over the new one as soon as we are able to get it compiling for desktop platforms (maybe create a cmake branch so people can contribute to it without breaking master). This way we could re-make the script taking advantage of modern features of CMake without having to care about how the old script was structured.

As time went on, many CMake scripts of open-source many projects that were made before CMake 3.0 accumulated a lot of boilerplate code that might not be needed for the newer CMake versions, and that could be the case of RmlUi as well.

My advice would be to:

cathaysia commented 1 year ago

dllexport is not necessary for dynamic lib, cmake has this to export all symbols. What I say is this case:

add_library(rmlui_lua STATIC rmlui_lua.cpp)
add_library(rmlui SHARED core.cpp)
target_link_library(rmlui PRIVATE rmlui_lua)

in this case, I split rmlui to two libraries, a static, a dynamic, then it should produce a rmlui.so. In this case, rmlui_lua is not a dependency of rmlui, and it is written just to divide sub-projects. In other words, I just simply hope that rml_lua is merged into the dynamic library rml

Then we use rmlui.so like this:

add_executable(my_prog main.cpp)
target_link_library(my_prog PRIVATE rmlui.so)
#include <rmlui_lua.h>

//....

then you will find all symbols of rml_lua are missing, this is because the compiler will only export the symbols used by rmlui in rmlui_lua.

emm, maybe you just giva an eyes on https://stackoverflow.com/questions/11429055/cmake-how-create-a-single-shared-library-from-all-static-libraries-of-subprojec , it should has a more clear content.

cathaysia commented 1 year ago

Investigate a more flexible approach to how dependencies are required by the project: While the technique @cathaysia proposed would be the ideal approach, that isn't possible on CMake due to how find_package() works internally. Once a package hasn't been detected, no further attempts to locate it will happen unless the CMake cache is completely deleted and re-generated from scratch. An actual solution should be investigated based on the information about consuming dependencies on CMake projects. The integration between FetchContent and find_package() looks promising but unfortunately is only available on CMake 3.24+.

may we can use this cmake script:

function(add_deps target_name pkg_name) # such as params ares freetype::freetype and freetype
    if(TARGET ${target_name}) # if already has freetype::freetype
        set(${target_name}_LIBRARY ${target_name})
        return
    endif()
   if(EXISTS ${PROJECT_DIR}/Deps/${pkg_name}/CMakeLists.txt) # check rmlui/Deps/freetype dir
        add_subdirectory(${PROJECT_DIR}/Deps/${pkg_name})
        set(${target_name}_LIBRARY ${target_name})
        return
   endif()
  # we has no freetype::freetype and freetype
  FetchContent() # .......
endfunction()

then we can add deps by:

add_deps(freetype::freetype freetype)

target_link_library(rmlui PRIVATE freetype_LIBRARY)
hobyst commented 1 year ago

dllexport is not necessary for dynamic lib, cmake has this to export all symbols. What I say is this case:

add_library(rmlui_lua STATIC rmlui_lua.cpp)
add_library(rmlui SHARED core.cpp)
target_link_library(rmlui PRIVATE rmlui_lua)

in this case, I split rmlui to two libraries, a static, a dynamic, then it should produce a rmlui.so. In this case, rmlui_lua is not a dependency of rmlui, and it is written just to divide sub-projects. In other words, I just simply hope that rml_lua is merged into the dynamic library rml

Then we use rmlui.so like this:

add_executable(my_prog main.cpp)
target_link_library(my_prog PRIVATE rmlui.so)
#include <rmlui_lua.h>

//....

then you will find all symbols of rml_lua are missing, this is because the compiler will only export the symbols used by rmlui in rmlui_lua.

emm, maybe you just giva an eyes on https://stackoverflow.com/questions/11429055/cmake-how-create-a-single-shared-library-from-all-static-libraries-of-subprojec , it should has a more clear content.

In that case, if the RmlUi Lua header is meant to be used by the library consumer as well, that means that consumers can also call to code defined in rmlui_lua and therefore the linkage is wrongly set up. rmlui_lua should be linked publicly so that projects consuming RmlUi can also call code found in rmlui_lua.

One way to do so would be to make rmlui_lua static and make the link public:

add_library(rmlui_lua STATIC rmlui_lua.cpp)
add_library(rmlui SHARED core.cpp)
target_link_library(rmlui PUBLIC rmlui_lua)

And the other way, which should be the default one since rmlui_lua is meant to be used by several foreign targets at the same time, is to make rmlui_lua a shared library. This way it would be much easier for consumers to tell when an RmlUi build includes the Lua bindings:

add_library(rmlui_lua SHARED rmlui_lua.cpp)
add_library(rmlui SHARED core.cpp)
target_link_library(rmlui PUBLIC rmlui_lua)
hobyst commented 1 year ago

may we can use this cmake script:

function(add_deps target_name pkg_name) # such as params ares freetype::freetype and freetype
    if(TARGET ${target_name}) # if already has freetype::freetype
        set(${target_name}_LIBRARY ${target_name})
        return
    endif()
   if(EXISTS ${PROJECT_DIR}/Deps/${pkg_name}/CMakeLists.txt) # check rmlui/Deps/freetype dir
        add_subdirectory(${PROJECT_DIR}/Deps/${pkg_name})
        set(${target_name}_LIBRARY ${target_name})
        return
   endif()
  # we has no freetype::freetype and freetype
  FetchContent() # .......
endfunction()

then we can add deps by:

add_deps(freetype::freetype freetype)

target_link_library(rmlui PRIVATE freetype_LIBRARY)

That approach could actually work!

I've been investigating a bit and found that using CMAKE_PREFIX_PATH (CMake variable) and CMAKE_PREFIX_PATH (CMake environment variable) could be used by both the project and consumers to set up lookup paths for every find_...() function. With this, all it should take for dependencies to be detected is:

hobyst commented 1 year ago

@mikke89 I've successfully re-written the CMake project up to some extent using modern CMake and is currently able to build the all-rounder "demo" sample as well as the "lottie" sample. Could you create a cmake branch on the repo for me to PR to it? That way more people will be able to contribute to it without completely breaking the master branch.

mikke89 commented 1 year ago

Very cool, a cmake branch sounds like a good idea, I just pushed it now.

aquawicket commented 1 year ago

As a side note... CMakeLists.txt:310 target_compile_features(${NAME} PUBLIC cxx_std_14) Fails if "cxx_std_14" is not listed in CMAKE_CXX_COMPILE_FEATURES

I encounter this issue while targeting earlier Android NDK 23.1.7779620 due to c++14 being flagged as -std=c++1z instead. Maybe a check against CMAKE_CXX_COMPILE_FEATURES would work as a pitfall.

cmak_error

It may be beneficial to target against platforms that don't yet introduce the c++14 flag, yet can be obtained by c++1z. Especially if you're looking for backward compatibility.

P.S. Besides that.. The newest CMakeLists.txt is working for me across all platforms amazingly.

hobyst commented 1 year ago

@aquawicket That looks as if CMake wasn't even detecting the Android compiler. Usually in between the brackets CMake would show the friendly display name of the detected compiler. On top of that, the message itself means CMake doesn't have proper integration with the Android compiler. Are you using a standalone version of CMake or the CMake that ships as a part of the Android NDK? If you haven't already, you should try again using the CMake that ships with the NDK.

aquawicket commented 1 year ago

Standard CMake..

against android\23.1.7779620\build\cmake\android.toolchain.cmake

I get around this by commenting out target_compile_features(${NAME} PUBLIC cxx_std_14) and using cmake compiler flags to ask for -std=c++1z instead.

then everything works

mikke89 commented 1 year ago

If the compiler isn't listed as supporting C++14, then we don't "officially" support it, so some workarounds should be expected. I'm hoping to move towards C++17 eventually, so my suggestion is that we don't specifically handle this case.

P.S. Besides that.. The newest CMakeLists.txt is working for me across all platforms amazingly.

Just to clarify, did you mean the one in the pull request, or the one on master? In any case, that is good to hear!

aquawicket commented 1 year ago

@mikke89, I'm using the master branch. :) Everything is working for me with my workaround, so false alarm. Thanks.

mikke89 commented 7 months ago

Hey, just letting everyone who contributed here know that the pull request is now up: #606 😈