Skip to content

Commit

Permalink
CMake modernization
Browse files Browse the repository at this point in the history
Re-write the CMake project from scratch up to some extent using modern
CMake conventions. Tested with GLFW, OpenGL 3 and rlottie.
Available samples: demo, lottie
  • Loading branch information
hobyst committed Apr 12, 2023
1 parent 281edd1 commit 379ba3a
Show file tree
Hide file tree
Showing 16 changed files with 711 additions and 0 deletions.
48 changes: 48 additions & 0 deletions CMake/Dependencies.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#[[
Function to print a message to the console indicating a dependency hasn't been found
Arguments:
- friendly_name: Friendly name of the target
- target_name: Name of the CMake target the project is supposed to link against
]]
function(report_not_found_dependency friendly_name target_name)
message(FATAL_ERROR
"${friendly_name} has not been found by CMake. If you are consuming RmlUi as a subdirectory "
"inside another CMake project, please ensure that ${friendly_name} can be linked using \"${target_name}\" "
"as its target name. You can create an ALIAS target to offer an alternative name for a CMake target."
)
endfunction()

# Freetype
if(NOT RMLUI_NO_FONT_INTERFACE_DEFAULT)
# Declaring Freetype as a soft dependency so that it doesn't error out if the package
# is declared by other means
find_package("Freetype")

# Instead of relying on the Freetype_NOTFOUND variable, we check directly for the target
if(NOT TARGET Freetype::Freetype)
report_not_found_dependency("Freetype" Freetype::Freetype)
endif()

# Warn about problematic versions of the library with MSVC
if(DEFINED FREETYPE_VERSION_STRING)
if((${FREETYPE_VERSION_STRING} VERSION_GREATER_EQUAL "2.11.0") AND (${CMAKE_CXX_COMPILER_ID} STREQUAL "MSVC"))
message(WARNING "Using Freetype 2.11.0 or greater with MSVC can cause issues.")
endif()
else()
if(${CMAKE_CXX_COMPILER_ID} STREQUAL "MSVC")
message(WARNING "Using Freetype 2.11.0 or greater with MSVC can cause issues.")
endif()
endif()
endif()

# rlottie
if(RMLUI_ENABLE_LOTTIE_PLUGIN)
# Declaring rlottie as a soft dependency so that it doesn't error out if the package
# is declared by other means
find_package("rlottie")

# Instead of relying on the rlottie_NOTFOUND variable, we check directly for the target
if(NOT TARGET rlottie::rlottie)
report_not_found_dependency("rlottie" rlottie::rlottie)
endif()
endif()
23 changes: 23 additions & 0 deletions CMake/SamplesDependencies.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#[[
Function to print a message to the console indicating a dependency hasn't been found
Arguments:
- friendly_name: Friendly name of the target
- target_name: Name of the CMake target the project is supposed to link against
]]
function(report_not_found_dependency friendly_name target_name)
message(FATAL_ERROR
"${friendly_name} has not been found by CMake. If you are consuming RmlUi as a subdirectory "
"inside another CMake project, please ensure that ${friendly_name} can be linked using \"${target_name}\" "
"as its target name. You can create an ALIAS target to offer an alternative name for a CMake target."
)
endfunction()

# GLFW
# Declaring GLFW as a soft dependency so that it doesn't error out if the package
# is declared by other means
find_package("glfw3" "3.3")

# Instead of relying on the glfw3_NOTFOUND variable, we check directly for the target
if(NOT TARGET glfw)
report_not_found_dependency("GLFW" glfw)
endif()
37 changes: 37 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Using CMake 3.10.2 as minimum to support all platforms of interest
# https://github.com/mikke89/RmlUi/issues/198#issuecomment-1246957062
cmake_minimum_required(VERSION "3.10.2")

# Define CMake project
project("RmlUi"
VERSION "6.0"
DESCRIPTION "C++ user interface package based on the HTML and CSS standards"
LANGUAGES "C" "CXX"
)

# Set minimum required C++ standard
set(CMAKE_CXX_STANDARD_REQUIRED "14")

# Declare project-specific options
# "RMLUI_" prefix is included in order to take advantage of the fact that the
# CMake GUI can group variables based on their prefix to make more clear
# which options are specific to this project
option(RMLUI_BUILD_SAMPLES "Build samples of the library." OFF)
option(RMLUI_NO_FONT_INTERFACE_DEFAULT "Do not include the default font engine in the build. Allows building without the FreeType dependency, but a custom font engine must be created and set." OFF)
option(RMLUI_ENABLE_LOTTIE_PLUGIN "Enable plugin for Lottie animations. Requires the rlottie library." OFF)
option(RMLUI_ENABLE_SVG_PLUGIN "Enable plugin for SVG images. Requires the lunasvg library." OFF)
set(RMLUI_SAMPLES_BACKEND "GLFW_GL3" CACHE STRING "Backend ID to use when building the RmlUi samples. Choose one from ./CMake/BackendFileList.cmake.")


# Add custom CMake modules path for external dependencies
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake/Modules")

# Set up external dependencies
include("CMake/Dependencies.cmake")

# Add CMake subdirectories
add_subdirectory("Source")

if(RMLUI_BUILD_SAMPLES)
add_subdirectory("Samples")
endif()
45 changes: 45 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# RmlUi contribution guidelines

## CMake

The RmlUi project aims to be both compiled standalone and as a subfolder inside a bigger CMake project, allowing other CMake projects to integrate the library in their build pipeline without too much hassle. For this reason, the following conventions must be followed when editing CMake code for the project:

- **Follow the [Modern CMake](https://cliutils.gitlab.io/modern-cmake/) conventions**

- **Go simple:** CMake already allows to do many things without reinventing the wheel. Most code often written in a CMake build script is
not necessarily related to the project itself but to covering specific compilation scenarios and to set certain flags that aren't really
necessary as a means to pre-configure the project without having to input the options at configure time via the CMake CLI. This is a bad
practice that quickly increases the complexity of the build script. Instead:
- **For flags and options related to cross-compilation scenarios**, [CMake toolchain files](https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html) should be used to set such flags and options.
- **If a compiler or compiler version is being problematic** about something, use a [CMake initial cache script](https://cmake.org/cmake/help/latest/manual/cmake.1.html#options) or CMake preset and advise the user to use it. This way, the consumer will always know when the compilation settings are being diverted from the compiler's default settings.

Although not recommended, [CMake toolchain files](https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html) can be used as well as long as the [`CMAKE_SYSTEM_NAME`](https://cmake.org/cmake/help/latest/variable/CMAKE_SYSTEM_NAME.html) variable doesn't get set in the toolchain file.

- **For platform-specific and case-specific flags** like building shared libraries or building framework packages for iOS and macOS, this
should be specified by the consumer, not by the project itself. For this, CMake toolchain files, CMake presets and initial cache scripts can be used.

- **To share CMake flag configurations to save consumers time** use [CMake presets](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html), [CMake initial cache scripts](https://cmake.org/cmake/help/latest/manual/cmake.1.html#options) and ready-to-copy CLI commands in the documentation.

- **Assume the person building the library is a consumer:** To save trouble to consumers, the default behavior of the CMake project should be oriented towards the bare minimum needed for the library to work (tests disabled, compilation of examples disabled, additional plugins disabled...). If the consumer needs anything more, they should be the ones tweaking the behavior of the project to suit their needs via CMake options.

- **Use quotes to declare string literals:**
If not quoted and depending on the scenario, CMake and some of its functions might read a string literal might get misunderstood as a variable reference. It also helps with readability.

- **Avoid setting global variables at all costs:** These should be set, if necessary, by the consumer via the CLI, a CMake configure preset, a CMake initial cache script or CMake variables coming from a parent CMake project, not by the project itself. This includes, among others, the widely used `CMAKE_<LANG>_FLAGS`.

- **Avoid setting compiler and/or linker flags at all costs:**
Many projects have the habit of setting
compiler flags for things like preventing certain irremediable compiler warnings from appearing
in the compiler logs and to mitigate other compiler-specific issues, often creating an unnecessary
dependency on certain compilers, increasing the complexity of build scripts and potentially
creating issues with the project consuming the library.

Both the code and the CMake project should aim to be as toolchain-agnostic as possible and therefore avoid
any kind of compiler-specific code as much as possible. **In the event that compiler flags need to be set and used in every possible use case of the library, please do so on a target basis** using either [CMake compile features](https://cmake.org/cmake/help/latest/manual/cmake-compile-features.7.html)
and [`target_compile_features()`](https://cmake.org/cmake/help/latest/command/target_compile_features.html) when possible or [`target_compile_options()`](https://cmake.org/cmake/help/latest/command/target_compile_options.html). The use of such flags by the project by default needs to be noted in the documentation in order to help consumers predict and mitigate any issues that may arise when consuming the library.

If the goal is to save time for consumers to set certain options, these should be instead reproduced in a [CMake configure preset](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html#configure-preset), a [CMake initial cache script](https://cmake.org/cmake/help/latest/manual/cmake.1.html#options) (for CMake versions without preset support) and specified in the documentation so that the consumer is always aware of which options are being used to compile the library.

* **Do not reference [`CMAKE_SOURCE_DIR`](https://cmake.org/cmake/help/latest/variable/CMAKE_SOURCE_DIR.html):** RmlUi aims to be consumable by other CMake projects when being included as a CMake sub-project using [`add_subdirectory()`](https://cmake.org/cmake/help/latest/command/add_subdirectory.html). In this scenario, `CMAKE_SOURCE_DIR` won't point to the top source directory of the RmlUi source, but to the top source directory of the parent CMake project consuming RmlUi. For this reason, **reference [`PROJECT_SOURCE_DIR`](https://cmake.org/cmake/help/latest/variable/PROJECT_SOURCE_DIR.html) instead**.

- **Use [generator expressions](https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html) when relevant instead of their CMake variable counterparts:** To find the folder where the executable has been built or to simply find the current build type, CMake variables like `CMAKE_BUILD_TYPE` and `CMAKE_BINARY_DIR` have been used, but this behavior is not recommended as every build system has its own conventions when it comes to folder paths and not all details are known at configure time, specially when multi-config build systems like MSBuild are used. For this reason, it is strongly advised to use CMake generator expressions whenever possible to ensure the project can be compiled regardless of the build system used. If a CMake feature you need to use doesn't work with generator expressions, try making a CMake script and [calling it at build time](https://cmake.org/cmake/help/latest/manual/cmake.1.html#run-a-script) using [`add_custom_command()`](https://cmake.org/cmake/help/latest/command/add_custom_command.html) or create an issue or discussion in the RmlUi directory.
12 changes: 12 additions & 0 deletions Samples/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Declare dependencies for samples
include("${PROJECT_SOURCE_DIR}/CMake/SamplesDependencies.cmake")

# Change the runtime output directory of all target declared in this scope
# This is needed so that the shell library can find the samples asset files
# when running them from a CMake binary directory
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")

# Add shell library for the samples
add_subdirectory("shell")

add_subdirectory("basic")
2 changes: 2 additions & 0 deletions Samples/basic/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
add_subdirectory("demo")
add_subdirectory("lottie")
5 changes: 5 additions & 0 deletions Samples/basic/demo/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
add_executable(rmlui_samples_demo
src/main.cpp
)

target_link_libraries(rmlui_samples_demo PRIVATE rmlui_samples_shell)
5 changes: 5 additions & 0 deletions Samples/basic/lottie/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
add_executable(rmlui_samples_lottie
src/main.cpp
)

target_link_libraries(rmlui_samples_lottie PRIVATE rmlui_samples_shell)
20 changes: 20 additions & 0 deletions Samples/shell/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Quoting the source file paths isn't necessary because add_library() doesn't read
# CMake variables, just strings
add_library(rmlui_samples_shell
src/PlatformExtensions.cpp
src/RendererExtensions.cpp
src/Shell.cpp
src/ShellFileInterface.cpp
)

# Add sources for the backend
include("${PROJECT_SOURCE_DIR}/CMake/BackendFileList.cmake")
target_sources(rmlui_samples_shell PRIVATE ${${RMLUI_SAMPLES_BACKEND}_SRC_FILES} ${${RMLUI_SAMPLES_BACKEND}_HDR_FILES})

target_include_directories(rmlui_samples_shell PUBLIC "include" "${PROJECT_SOURCE_DIR}/Backends")

# Link against required libraries for backend
target_link_libraries(rmlui_samples_shell PRIVATE glfw)

target_link_libraries(rmlui_samples_shell PUBLIC RmlUi::RmlUi RmlUi::Debugger)

7 changes: 7 additions & 0 deletions Source/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Add plugin directories
add_subdirectory("Lottie")

# Add main RmlUi directory
add_subdirectory("Core")

add_subdirectory("Debugger")
Loading

0 comments on commit 379ba3a

Please sign in to comment.