I have spent some very intense weeks this March to completely overhaul and refactor my CMake scripts. We are talking about more than two thousand lines of code!
Android
I started by changing the way I cross-compile on Android. I dropped the use of CMake integrated NDK support introduced with version 3.7 and went back using the toolchain file provided by the NDK itself. It seems the way to go in order to provide full control and flexibility to each NDK update.
I have also used this opportunity to add support for NDK r19 and its embedded stand-alone toolchain, the new 3.x Gradle plugin and LLVM’s libc++_shared
.
Target properties and expressions
I then started to broaden the use of more target related CMake commands, as target_sources
or target_link_options
. This last command raised the minimum CMake version to 3.13.
Taking full advantage of CMake targets allowed me to completely remove any set
command involving variables like CMAKE_CXX_FLAGS
or CMAKE_SHARED_LINKER_FLAGS
.
Besides that I also started to rely more deeply on generator expressions for things like $<TARGET_FILE:tgt>
, $<BUILD_INTERFACE:...>
or $<CONFIG:cfg>
. This last expression got rid of every if(CMAKE_BUILD_TYPE MATCHES "Debug")
conditional check.
Imported targets
But I didn’t stop there and I went on to convert my scripts to use targets everywhere. I now use imported targets for every external dependency library.
In the end it’s a better and more flexible way of handling the problem but it put a lot of stress on testing as not all find_package
scripts correctly set targets and some platforms need additional tweaking too.
- For example MSYS/MinGW needed a custom CMake function to find the DLL and set the
IMPORTED_LOCATION
target property as the correspondingfind_package
scripts only discovered the import library. - As another example on macOS I needed a different custom CMake function to split the link options set by
find_package
scripts into the ones representing framework directories and the ones being-framework
link directives. There is also a need to append the library symlink name to the framework directory as that is what theIMPORTED_LOCATION
target property expects when dealing with macOS frameworks (someone has even opened an issue ticket about that).
Discoverability and exported targets
In an attempt to make nCine discoverability more robust for external projects I have changed a lot of code based on cascaded conditions to a bunch of a lot cleaner find_path
calls.
I now also always use exported targets and this has two very important benefits:
- It is now possible to link to the
ncine
target and automatically have access to interface properties like for exampleINTERFACE_INCLUDE_DIRECTORIES
. - By using the
nCine_DIR
CMake variable when configuring a project it is possible to import the targets from a build directory.
The outcome of this last change is that there is no more NCINE_HOME
custom variable, all the discovery use cases are handled by the CMake way of doing things: using a <package>_DIR
variable that points to where the nCineConfig.cmake
script is. This will in turn include the nCineTargets.cmake
script to bring all the imported nCine targets into the namespace of an external package CMakeLists.txt
file.
Bonus
A couple of additional changes are:
- I now build a new
ncine_main
static library and export it as a target, instead of distributing amain.cpp
file that the user never really had the need to modify. - I have added an interface library pseudo target for the code coverage compiler and linker options as a way, as the CMake manual states, “to employ an entirely target-focussed design for usage requirements”.
That’s all for this update, I hope you enjoyed this in-depth review of CMake changes.