I am working on a project, let’s call it A
. It comes with its own CMakeLists.txt
. This projects also depends on other two projects, B
and C
. Each one has its own CMakeLists.txt
. Projects C
also depends on project D
and it also has its own CMakeLists.txt
. Projects B
, C
and D
are also my projects and they reside as separate GitHub repositories. They produce some libraries that are needed by project A
. I want a solution that meets the following criteria:
-
I can compile everything with one command. Normally, I would do
cd build/ && cmake ../B/ && make .
for each project before doing the same thing forA
. I am aiming for something likecd build/ && cmake ../A/ && make .
. -
I can write code to any project I want and then recompile everything. Again, I own all projects and they all reside as GitHub submodules.
-
The dependencies don’t need to be installed. I do not want to do that because it has already created enough confusion. I just want to be able to read my headers and libraries directly from the source tree and
build
directory respectively. -
Allows for different build configurations for each project. If I am working only on project
A
, I want it on debug, butB
should be on release (and irrelevant for the other dependencies).
I already tried using Visual Studio Code’s cmake-tools
. It creates a separate directory named build
, but this means out-of-tree compilation for all dependencies and it fails for all projects except A
. It is a tedious job to reconfigure all CMakeLists.txt
to point to build
and I still could not fix all errors. I have inherited the codebase for B
, C
and D
and do not want to mess with their CMake configurations.
This is my current directory structure:
A/
├── build/
├── CMakeLists.txt
│
├── B/
│ └── CMakeLists.txt
├── C/
│ ├── CMakeLists.txt
│ └── D/
│ └── CMakeLists.txt
|
├── include/
├── src/
│ └── CMakeLists.txt
└── test/
└── CMakeLists.txt
Are my goals achievable? Is my source tree structure correct? Is there anything smart I could do to fix these problems?
2
Answers
Yes. Take a look at CMake presets. With presets you can predefine your typical builds in a flexible way(s) and perform a build w/ just one CLI command (e.g.,
cmake --workflow --preset make-everything-the-way-I-like
:).However, the feature is a bit raw yet, so writing correct presets (and workflows) could be tricky due to the lack of human-friendly diagnostic %)
There is no correct or incorrect 🙂 It’s a matter of the way you use it, how convenient it is, and easy to understand/pre-setup/maintain/etc. this usage/layout is personally for you, other developers in your team, or anyone else trying to build your project. IMHO, if any random guy familiar w/ CMake CLI can in a few minutes (less than 10 🙂 start building it (the project) from scratch with desired build variants and options… when as a maintainer you can be proud of yourself 🙂
Sure 🙂
Basic Intuition
If you want to build multiple projects in a single commandline invokation, you either need to make them all part of the same generated buildsystem, or use the ExternalProject module, or write a wrapper script that invokes multiple buildsystems for you.
As far as I know, in general, a single generated single-config buildsystem can only have one overarching build configuration ("Debug", "Release", etc.), and a single generated multi-config can only have multiple "homogeneous" configs (no "mixed" configs: the "Debug" config is "Debug" through and through, etc.). If you want different projects to each be able to have their own build configs, that means you need to give them each their own generated buildsystem. And either I’m missing something, or the only way to get around that is to do some ugly hacks, which I don’t know of, and wouldn’t recommend even if I knew them.
When working with mixed build configurations and multi-config buildsystems, you probably want to take a look at the
MAP_IMPORTED_CONFIG_<CONFIG>
target property and its associated initialization variable.add_subdirectory()
One solution is to
add_subdirectory
B and C in A, and D in C. This will cause CMake to generate a buildsystem that builds all of A, B, C, and D.export()
+include()
Another solution is to use
export()
in each dependency project, and theninclude()
the target export file to add the dependency. This means a separate generated buildsystem for each project.ExternalProject
I think you’d be able to get everything you want with the ExternalProject module if you combine it with
export()
+include()
(you could also use it with an approach that uses installation, which actually wouldn’t add manual steps on your part, which I’m assuming is what you meant you wanted to avoid). Each project would get its own buildsystem, but the invocation of configure and build would be driven by the dependent project (though you should be able to invoke individual ones too in the normal way withcmake --build <build_dir>
). Of particular interest to you would be theSOURCE_DIR
andBUILD_DIR
options. You’ll probably also want to use theBUILD_ALWAYS
build step option.Misc Commentary
Note that in the Pitchfork Layout spec (not that you necessarily care about it), your B and C directories would probably go under A/external/, and your D directory would probably go under C/external/.
The VS Code CMake Tools extension allows you to configure the build directory. See its settings docs. It also supports CMake Presets. See its CMake Presets support docs.
Out-of-source builds aren’t a bad thing. They make it easier to delete an entire buildsystem and its artifacts because they separate those from the source tree.