CMake workflow? - cmake

I am learning CMake and I am having problems in understanding its multi-step workflow. So far, my understanding is that you:
write a CMakeLists.txt
run cmake from a subdirectory to generate a build file (a Makefile, in my case)
run make
However, I don't understand how you should handle different targets (Release vs Debug). By running CMake in two different subdirectories?
Also, I don't understand why you would edit CMakeCache.txt (there is also a GUI tool for that). To emulate what you would accomplish with ./configure by passing different options?

You got it pretty much right. The write CMakeLists.txt > cmake > make sequence is correct.
Regarding different configurations (Debug vs. Release), you have to differentiate between multi-config generators (Visual Studio, XCode), and single-config generators (everything else). With the multi-config generators, you generate one buildsystem (e.g. solution file) which contains all configurations, and choosing between them happens at build time.
With single-config generators, different configurations are obtained by generating different buildsystems, that is, by running CMake multiple times in different directories (and with a different value of the CMAKE_BUILD_TYPE CMake variable).
So you'd do something like this:
> cd my_project/bld/debug
> cmake ../../src -DCMAKE_BUILD_TYPE=Debug
> cd ../release
> cmake ../../src -DCMAKE_BUILD_TYPE=Release
Regarding editing the cache (usually through CMake GUI or ccmake): you're right again, this largely corresponds to passing options to ./configure from AutoMake world. This would be the typical workflow with a freshly downloaded project (using CMake GUI):
Run CMake GUI, point it to the source directory (input) and binary directory (output) you want
Configure. This will fill the cache with project-specified defaults. If CMake cannot find some dependencies of the project automatically, this will end with an error.
Inspect the cache, change any values you don't like (such as compilation options), fill in any missing options (paths to libraries CMake couldn't find etc.)
Repeat steps 2 & 3 until you're satisfied with the project's setup.
Generate the buildsystem.
Exit CMake GUI and build using the appropriate build tool.

What #Angew said. Plus here's an image of the cmake-gui:
Also note that you install it (the CMake GUI) on Ubuntu with sudo apt install cmake-qt-gui, and you run it with cmake-gui.
Source: Where is the CMake GUI for Linux?
Here's my cmake-gui image:

Related

How can I make colcon work with a plain preset-based CMake project with multiple presets in parallel?

Prologue
I have a preset-based plain CMake project so that I can build and test it with cmake --preset $PRESET && cmake --build --preset $PRESET && ctest --preset $PRESET. Note that it nicely interacts with Microsoft's CMake Tools extension for Visual Studio Code, be it for building, testing, debugging and Intellisense.
Since I want to handle multiple presets in parallel, I set CMakePresets.json's binaryDir property to ${sourceDir}/build/${presetName}/.
Issue
I want to also build this plain CMake project with colcon. colcon build --cmake-args "--preset $PRESET" doesn't work, though, as it produces
WARNING:colcon.colcon_cmake.task.cmake.build:Could not build CMake package 'root_project_name' because the CMake cache has no 'CMAKE_PROJECT_NAME' variable
root_project_name being the argument to CMake's project() command in the top CMakeLists.txt.
How can I resolve this warning and the subsequent build failure?
Straightforward solution
Not setting CMakePresets.json's binaryDir property at all works fine with colcon, but doesn't allow for multiple preset builds in parallel.
Solution with multiple preset builds in parallel
The reason for this behavior is colcon-core's build verb's passing the build base directory (default: build) suffixed by the found package's name (here: root_project_name) to the colcon-cmake extension here.
The solution is to pass the correct build base to colcon (i.e. colcon build --build-base ./build/$PRESET/ --cmake-args "--preset $PRESET") and to adapt your CMakePresets.json's binaryDir property to ${sourceDir}/build/${presetName}/root_project_name/.
Note that this then works with colcon test as well, i.e. colcon test --build-base ./build/$PRESET/ --ctest-args "--preset $PRESET".

How do I build a CMake project?

I have just acquired an arbitrary CMake project from the internet and I am not sure how to compile it. What commands do I need to run to build it from the command line?
Basic steps
If you're on a Unix-y operating system, like Linux or macOS, then you would run:
$ cmake -DCMAKE_BUILD_TYPE=Release -S /path/to/source-dir -B /path/to/build-dir
$ cmake --build /path/to/build-dir
Here, /path/to/source-dir is the directory containing the root-level CMakeLists.txt, this is most commonly the root of a source control repository. Meanwhile, /path/to/build-dir is a distinct directory (that does not need to exist yet) that CMake will use to store the generated build system and its outputs. This is called an out-of-tree build. You should never attempt an in-tree build with CMake because of the possibility of name clashes and difficulty involved with cleaning up the generated files.
When building with a single-config generator (like Make, which is the default on Unix), you specify the build type by setting the CMAKE_BUILD_TYPE variable in the first command, known as the configure step. You must always set this variable when working with a single-config generator. The built-in configs are Debug, Release, RelWithDebInfo, and MinSizeRel. See this answer for more detail on this.
After the configure step, you may build the project by either calling the underlying build tool (in this case, make) or by calling CMake's generic build launcher command (cmake --build), as I do here.
If you're on Windows, then the default generator is Visual Studio, which is a multi-config generator. This means the build type is chosen during the build step rather than the configure step, and the commands must be adjusted accordingly:
$ cmake -S /path/to/source-dir -B /path/to/build-dir
$ cmake --build /path/to/build-dir --config Release
These steps assume that the CMake build you are looking at is well behaved. If a project fails to build with the above steps and you have all of its dependencies installed to system locations (and they are well behaved), then you should open an issue with the upstream project. The most common source of bad behavior in mature CMake builds is dependency handling. Too often you will have to read the build or its documentation to determine which variables need to be set (via -D, like we did with CMAKE_BUILD_TYPE above) for the project to find its dependencies.
Advanced topics
Setting options and cache variables
Some projects offer options to enable/disable tests, components, features, etc. These are typically done by writing entries to the CMake cache during the configure step. For example, a common way to disable building tests is to set BUILD_TESTING to NO at the command line:
$ cmake -S /path/to/source-dir -B /path/to/binary-dir [...] -DBUILD_TESTING=NO
This particular variable is a convention, but is not guaranteed to be honored. Check the project's documentation to see which options are available.
Selecting a generator and toolchain
When using the Visual Studio generators specifically, you can tell CMake which platform you wish to target and which version of the compiler you would like to use. The full form of the CMake configure command for this is:
$ cmake -G "Visual Studio 16 2019" -A <ARCH> -T<TOOLSET> [...]
Valid values of <ARCH> include Win32, x64, ARM, and ARM64. If <TOOLSET> is not specified, then the 32-bit MSVC compiler will be used. Typically, you will want this to be host=x64 to ensure that 64-bit MSVC is used, which can allocate more memory for large linking steps. You can also set <TOOLSET> to ClangCL to use the Visual Studio provided ClangCL tools.
On all generators, CMake sniffs the environment for which compiler to use. It checks the CC and CXX environment variables for the C and C++ compilers, respectively. If those are empty, it will look for cc and c++ executables in the PATH. You can manually override the compilers by setting the CMAKE_C_COMPILER and CMAKE_CXX_COMPILER CMake cache (not environment) variables at the CMake command line (using -D again).
Installing & using dependencies
Once a CMake project has been built, you may install it either systemwide or (preferably) to a local prefix by running:
$ cmake --install /path/to/build-dir --prefix /path/to/install-dir [--config Release]
Where --config is only required if a multi-config generator was used. Once installed to a local prefix, a project that depends on it may be configured by setting CMAKE_PREFIX_PATH to /path/to/install-dir.

Why -DCMAKE_EXPORT_COMPILE_COMMANDS does not create compile_commands.json file

My cmake --version is 2.8.12.2.
I configure my project build with these commands:
cmake ../klein/ -DBUILD_KLEIN_DEPS=1 -DCMAKE_EXPORT_COMPILE_COMMANDS=1
cmake ../klein/ -DBUILD_KLEIN_DEPS=1 -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake ../klein/ -DBUILD_KLEIN_DEPS=1 -DCMAKE_EXPORT_COMPILE_COMMANDS=on
CMAKE_EXPORT_COMPILE_COMMANDS=1 cmake ../klein/ -DBUILD_KLEIN_DEPS=1
From a clean build, and from a directory with an existing successful build. And want to see the compiler_commands.json file, but it does not appear.
At which moment should it be created: after cmake, or after make command? Where should it be in ./, or in ../klein directory? My cmake does not say anything about this option while it always complains about unused build variables.
Should it work from in a "dirty" directory, where I've performed one successful build, or does it work only on a fresh run in an empty folder?
Edit:
I use a default generator "Unix Makefiles" on my ubuntu linux machine
Edit2:
I'm not an author of the project under the question (I just want to explore it with rtags which requires compile_commands.json file), and I'm not very familiar with CMake mechanics. However, the CMakeLists.txt is probably configured as a super-build (it indeed downloads and builds dependencies - like llvm, z3, ...), and it includes ExternalProject, however it also builds the project itself (klein) from sources. So it's a mix, as I would say.
Can you specify what generator your using? A quick scan of the cmake source from version 3.1.0 suggests that this command is still only available in the following 2 cases.
if(CMAKE_GENERATOR MATCHES "Unix Makefiles")
and
if(CMAKE_GENERATOR MATCHES "Ninja")
if you're using Visual Studio directly you're out of luck unless you want to add a patch to CMake. Otherwise, I know many Windows developers who've gone to Ninja. One advantage is that it's vastly faster than Visual Studio for building. If you are, in fact using Ninja or Unix Makefiles, then it's worth digging deeper.

Ninja equivalent of Make's "build from this directory down" feature (with CMake)?

When building a project using CMake and Make, you can execute make from a subdirectory of your build tree (i.e. from a directory below whatever directory contains your top-level Makefile), and make will (as far as I can tell) build all targets at or below that directory. This is because CMake generates a Makefile for every directory that contains targets, so when you're in a directory with targets, make finds the Makefile for building those targets.
When CMake generates Ninja files, however, it only generates one build.ninja file, which is at the top level of the build tree. So calling ninja from a directory other than the top-level directory fails (even the -f option doesn't work because ninja can't find the rules.ninja file).
Is there any way to emulate the "make-like" behavior of building targets at and below a directory? As far as I can tell, there are no Ninja targets that correspond to "all targets at and below a particular directory." (This could be emulated using phony targets named after each directory that depend on all targets at and below that directory, but CMake does not generate such targets by default.)
ninja <DIR>/all works with recent versions of Ninja (1.7.2). Version 1.3.4 does not allow this.
I could not find a reference to this on the manual. However, CMake has this documented here:
Recent versions of the ninja program can build the project through the “all” target. An “install” target is also provided.
For each subdirectory sub/dir of the project, additional targets are generated:
sub/dir/all
Depends on all targets required by the subdirectory.
sub/dir/install
Runs the install step in the subdirectory, if any.
sub/dir/test
Runs the test step in the subdirectory, if any.
sub/dir/package
Runs the package step in the subdirectory, if any.
This worked for me:
cd <build-root>
DIRECTORY=<path-relative-to-build-root>
ninja -t targets all | egrep "^${DIRECTORY}/" | egrep CXX_EXECUTABLE_LINKER | \
sed -e 's/:.*//g' | xargs ninja
ninja -t targets all - lists all targets (including target type)
egrep "^${DIRECTORY}/" - filters list of targets to only include those in desired directory
egrep CXX_EXECUTABLE_LINKER - limits the targets to just C++ executables. You can remove or tweak this to get the set of targets you're interested in.
sed -e 's/:.*//g' - removes the target type e.g. ": CXX_EXECUTABLE_LINKER"
xargs ninja - invokes ninja to build the targets
Good question. I would like to know the answer if you find it.
I am just in the process of transitioning to cmake+ninja myself.
I found that I could not create targets with the same name at different levels
(if there is a way I would be interested to know).
So I adopted a naming convention for different targets
E.g.
name - builds program or library
test.name - runs tests for the named program or library
doxygen.name - build doxygen for the named program or library
For deeper hierarchies you can do something like:
doxygen.subproject
doxygen.subproject.name
Using this pattern you can control precisely what is built but you have to issue the command from the top-level build directory.
I think after I get used to this I will find it more productive as there is no need to change directory before you build or run something and though there is sometimes a little extra typing required the shell history generally has it covered.
This is implemented under the hood by using add_custom_target() and adding appropriate dependencies. I use a macro to do this automatically so that
a macro "add_doxygen()" will add the doxygen target for the program and make the doxygen target at each higher level depend on it using add_dependencies().

What is the CMake install time?

A quote from the official documentation:
"Specify rules to run at install time."
What exactly is install time?
The problem for me: I am on Linux, software is installed from packages that are just dependencies and data. There is no CMake that can do anything here. So installation time of software is out of scope from CMake. So what exactly do they mean?
Building a CMake project can roughly be divided into three phases:
Configure time. This includes everything that happens while running cmake itself. This phase is concerned with inspecting certain properties of the host system and generating the specific build files for that platform under the selected configuration.
Build time. This includes everything that happens while actually building your project from the files generated by CMake (like, when running cmake --build or make). This is where all of the actual compilation and linking happens, so at the end of the build phase, you have a usable binary.
Install time. This includes everything that happens when running the INSTALL target generated by CMake (like, when running cmake --build --target install or make install). This takes care of copying the binaries that were generated into the build tree to a different directory. Note that the build tree contains a lot of stuff that is no longer needed once the build is completed if you are only interested in running the binary. Examples include all intermediate build artifacts, like the build files generated during the configure phase or the intermediate object files created during the build phase. Furthermore, the install phase might include additional steps to ensure that the binaries produced during the build are portable. For instance, on Linux systems you might want to remove the build directory from the shared library search path in the binary and replace it with a portable equivalent. So the install phase might do more than just copy all the important files to a new directory. It could also include additional steps that change the binaries to make them more portable.
Note that the last phase is optional. If you do not want to support calling make install but prefer another deployment mechanism, you simply don't use the install command in your CMake script and no INSTALL target will be generated.
I'd like to expand the answer, which ComicSansMS gave you, a little bit.
As he mentioned - CMake generates an extra target called install for the make tool (when you use a Makefile-based generator).
It may look weird for you as a package system is used for Linux. However the install target is still useful or even necessary:
When you develop your application you may need to install (move binaries and possibly some include files) to a certain location so some of your projects may see each other. For example, you may develop a library and a set of non-related applications which use it. Then this library must be installed somewhere to be visible. It doesn't mean you need to put it to the /usr directory; you may use your /home.
The process of Linux package preparation requires an install step. For example, the RPM packaging system does three main steps when the rpm package file is being built: the project is configured, then is compiled and linked and finally is being installed to a certain location. All files from this location are being packed to the rpm file.