How to make CMake compile a specific target without parallel jobs - cmake

I have a big CMakeLists with a lot of targets. One of them (specifically one C++ file in one library) is taking a lot of memory to compile and is making my CI pipeline run out of memory.
I would like the compilation to reduce to one simultaneous job when compiling this file, then resume parallel compilation when finished.
I looked at Ninja job pools, but it will only allow me to compile a specific target (e.g. the library) in one job, without constraining the other targets.
Is there a way to do so, if possible without manually adding dependencies to this library?

Let's call the target that you want to build without object-level parallelism "heavy_target". If the problem is specifically with heavy_target and not with the combination of itself and its dependencies, then you can do the following: build all the dependencies of heavy_target first with whatever parallelism you want, then build heavy_target with no parallelism, then build everything else (or build everything and let the buildsystem detect that heavy_target and all its dependencies have been built).
cmake --build <binary_dir> --target <dep1> <dep2> <dep3> <...> <other args>
cmake --build <binary_dir> --target heavy_target --parallel 1 <other args>
cmake --build <binary_dir> <other args>
When building heavy_target's dependencies, you should only need to list the direct dependencies, and the generated buildsystem will know about the transitive ones.
<other args> might be things like --config <config>, --parallel <jobs>, etc. see the docs on arguments to the cmake command.
If it's inconvenient to read through the cmake files to find out all the dependencies of heavy_target, you can get a list of the link-libraries of heavy_target by reading the LINK_LIBRARIES target property:
get_property(heavy_target_link_libraries TARGET heavy_target PROPERTY LINK_LIBRARIES)
message("direct deps of heavy_target: ${heavy_target_link_libraries}")
Inlining the asker's comment: "It should also be possible to create a custom phony target depending on all heavy_target's dependencies."

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.

Compiling targets sequencially instead of parallel in cmake

I have 10 targets in cmake. If i give
gmake -j4
it is compiling all the targets paralllely. I want all the cores to be work on single target and after finishing that it should go for next target. I know that it can be achieved with adding dependency between targets. but i don't want to do that because sometimes i want to compile only one target. if dependency is there, it will compile all the dependency targets before compiling the required target.
how to make the compilation faster but in the sequential manner ?

Use find_package() on external project

I have an External project called messages. I am using ExternalProject_Add in order to fetch and build the project.
If i use find_package(messages REQUIRED) in top level CMakeLists.txt the cmake .. fails because it couldn't find the package installation files, which is logical as they are only build during make command invocation.
I am not sure, if there is way use find_package() on ExternalProjects. If so, please point me to an example.
Thanks
BhanuKiran
You have misunderstood how ExternalProject is supposed to work. You cannot find_package(messages REQUIRED) because it hasn't been built yet. ExternalProject merely creates the build steps necessary to build the subproject.
You have two options:
Use add_subdirectory or FetchContent in place of ExternalProject. In this case, you don't need a find_package call. This effectively adds the sub-project to the main build and imports the subproject's targets.
Use two ExternalProject calls: one for messages and another for main_project, which depends on messages. If messages uses the export(EXPORT) function, you can point CMAKE_PREFIX_PATH or messages_ROOT to the build directory. Otherwise you'll need to run the install step for messages and set up an install prefix inside your build directory. Then the find_project(messages REQUIRED) call inside main_project will succeed. This will likely require re-structuring your build.
Generally speaking, ExternalProject is only useful for defining super-builds, which are chains of CMake builds that depend on one another. And super builds are only useful when you need completely different configure-time options, like different toolchains (eg. you're cross compiling, but need a code generator to run on the build machine). If that's not the case, prefer FetchContent or add_subdirectory with a git submodule.
It is best to use FetchContent with CMake 3.14+ since it adds the FetchContent_MakeAvailable macro that cuts down on boilerplate.
Docs:
https://cmake.org/cmake/help/latest/module/ExternalProject.html
https://cmake.org/cmake/help/latest/module/FetchContent.html
Since I like keeping my CMake file agnostic on how I get my packages.
I was using FetchContent and added this file (Findalib.cmake):
if(NOT alib_POPULATED)
set(alib_BUILD_TESTS OFF CACHE BOOL INTERNAL)
set(alib_BUILD_EXAMPLES OFF CACHE BOOL INTERNAL)
set(alib_BUILD_DOCS OFF CACHE BOOL INTERNAL)
FetchContent_MakeAvailable(alib)
endif()
set(alib_FOUND TRUE)
Then, in my CMake files:
find_package(alib REQUIRED)
target_link_libraries(my-executable PUBLIC alib::alib)
That way, packages are only declared in my file in which I declare dependencies, and I fetch them only if I try to find them.

"ctest" versus "make check": bad build time versus broken option passing

To register tests under CMake, we need
enable_testing()
or
include(CTest)
and then for each single test (name fooTest, executable foo)
add_executable(foo <foo_sources>)
add_test(fooTest foo)
Tests can then be run with the command ctest.
Additionally, we can run tests with the command make check, provided we add once
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND})
and for each test we extend the above by a keyword EXCLUDE_FROM_ALL and a command add_dependencies:
add_executable(foo EXCLUDE_FROM_ALL <foo_sources>)
add_test(fooTest foo)
add_dependencies(check foo)
Ideally, this would make make check an alias of ctest. It does not so for at least two reasons:
(1) make check is flawed because it does not pass options to ctest [2]. In particular, ctest -j4 will run 4 tests in parallel, whereas make -j4 check will work in one thread on target check, and the other three threads will remain idle.
(2) ctest is flawed [3,4] because all tests are build under the all target, i.e. along with the main application. This may be desired behavior in some situations, but in other situations it ought to be possible to postpone the build until the tests are to be run.
Does this correctly summarize the current state of affairs?
Is there any way around (to eat the cake and have it)?
[1] https://cmake.org/Wiki/CMakeEmulateMakeCheck
[2] http://comments.gmane.org/gmane.comp.programming.tools.cmake.user/47300
[3] CMake & CTest : make test doesn't build tests
[4] http://public.kitware.com/Bug/view.php?id=8774
First, let me remark that ctest and make test are only simple command line tools, for simple testing tasks. If you want a tool for serious testing, use CDash, Buildbot, Jenkins or whatever.
Concerning the flaws of CTest: It is intentional, that the call for CTest does not build the tests. It is a bad idea in several scenarios:
Compiling tests can take more resources then running the tests itself. This might be true with respect to memory consumption, read/writes to the hard disk or compilation time. So compiling and linking in parallel might be bad, but executing the tests in parallel might be beneficial.
How to handle compilation or linking failure? Report it as failing? Report is as not compiling? Continuing with compiling the other tests or aborting immediately?
Autotools did it the way you want it and people got used to it. But why should it be a unit? Why not having two commands? What's the benefit of mixing two tasks and making it more difficult for project with special needs?
I came to the conclusion, to create a target build-tests or similar, and follow the decision made by the CMake developers to decouple building test and executing tests. Then I can decide whether I want parallel builds, how to treat compilation failures (e.g., passing -k to make) and so on.
The only downside is, that this target is only present in the top level directory and cannot be used in sub-directories.
To get such a target built-in by CMake would be a good feature request. Ranting on SO does no good.
CTest is not flawed at all, but the way you use CMake and CTest seems "flawed". The invocation of the command line interface (CLI) tool ctest is in general not related to the invokation of a CMake build targets (with the exception of the target test).
In my opinion the custom check target solution described in the CMake Wiki should not be used, since it changes the default behavior of CMake and is not configurable.
Instead the following approach using the built-in option BUILD_TESTING should be used:
include(CTest)
if(BUILD_TESTING)
find_package(GTest MODULE REQUIRED)
add_executable(example_test example_test.cpp)
target_link_libraries(
example_test
PRIVATE
GTest::GTest
GTest::Main
)
add_test(NAME example_test COMMAND example_test)
endif()
include(CTest) defines in the option BUILD_TESTING, which allows to control whether to build all tests of the project or not.
Quote from the official documentation:
CMake will generate tests only if the enable_testing() command has been invoked. The CTest module invokes the command automatically when the BUILD_TESTING option is ON.
The above can be used on the CLI as follows:
Create tests (default):
cmake -Hexample-testing -B_builds/example-testing/release -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=Release
cmake --build _builds/example-testing/release --config Release
In this case the commands cd _builds/example-testing/release and ctest / cmake --build . --target test build and run the test(s).
Do not create tests, setting -DBUILD_TESTING=OFF:
cmake -Hexample-testing -B_builds/example-testing/release-no-tests -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF
cmake --build _builds/example-testing/release-no-tests --config Release
In this case the commands cd _builds/example-testing/release-no-tests and ctest run no test(s), since no test(s) have been built.
The command cmake --build . --target test fails since it has not been created during configure phase of CMake.
We are only scratching the surface here. Refer to ctest --help, e.g. there are a lot of --build-<...> options that allow finer control regarding testing/building, though I have not any experience with that.
I highly recommend reading the following:
CTest - CMake Documentation
CTest Commands - CMake Documentation
ctest(1) - CMake Documentation
add_test - CMake Documentation
If you really want to enable building of tests, but via a separate target that is not invoked by default and run the test not via CTest but directly you can do the following:
include(CTest)
if(BUILD_TESTING)
find_package(GTest MODULE REQUIRED)
option(
BUILD_TESTING_EXCLUDE_FROM_ALL
"Do not build the testing tree together with the default build target."
OFF
)
if(BUILD_TESTING_EXCLUDE_FROM_ALL)
set(add_executable_args_for_test EXCLUDE_FROM_ALL)
endif()
# The "build_test" target is used to build all test executables.
add_custom_target(
build_test
# Workaround for printing the COMMENT, it does not work without a NOOP
# COMMAND.
COMMAND ${CMAKE_COMMAND} -E echo
COMMENT "Building tests..."
VERBATIM
)
add_executable(example_test ${add_executable_args_for_test} example_test.cpp)
target_link_libraries(
example_test
PRIVATE
GTest::GTest
GTest::Main
)
add_test(NAME example_test COMMAND example_test)
add_dependencies(build_test example_test)
# The "check" target is used to build AND run all test executables.
add_custom_target(
check
# Either invoke the test(s) indirectly via "CTest" (commented) or directly.
# COMMAND ${CMAKE_CTEST_COMMAND}
COMMAND example_test
COMMENT "Building and running test..."
VERBATIM
)
# Alternative to the COMMAND in the add_custom_target. Leads to the same
# behavior as calling "CTest" directly.
# add_custom_command(
# TARGET check
# COMMAND ${CMAKE_COMMAND} ARGS --build ${CMAKE_BINARY_DIR} --target test
# VERBATIM
# )
add_dependencies(check build_test)
endif()
Note that the above code does not invoke CTest or the target test in order to run the test, but the test directly.
Please read the comments and the commented code for alternative approaches using CTest that are similar to the approach described in the question.
It's easy to enhance the above code to support more than one test executable.
IMHO, Kitware should remove the entire CMake Wiki, since the Wiki contains almost only out-of-date information for CMake versions < 3.0. Most information in it cannot be considered as Modern CMake.