skip to Main Content

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:

  1. I can compile everything with one command. Normally, I would do cd build/ && cmake ../B/ && make . for each project before doing the same thing for A. I am aiming for something like cd build/ && cmake ../A/ && make ..

  2. 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.

  3. 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.

  4. Allows for different build configurations for each project. If I am working only on project A, I want it on debug, but B 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


  1. Are my goals achievable?

    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 %)

    Is my source tree structure correct?

    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 🙂

    Is there anything smart I could do to fix these problems?

    Sure 🙂

    1. understand the modern CMake way
    2. do not make problems for yourself (typically caused by misunderstanding CMake or build process and wanting smth that is usually done in another way in CMake %)
    3. get familiar w/ out-of-source builds and forget about in-source build ASAP
    4. fix all your projects to behave properly for out-of-source builds (typically ‘cuz CMLs have too much hardcode, and assumptions about the build process/environment).
    5. in my experience Git sub-modules (and any other "solutions" like mono repo, fetch during builds, etc.) are a great source of trouble and make the build process (and pre-setup) harder than needed (especially in maintenance). Having independent dependencies makes everything much easier… but this is a separate story 😉
    Login or Signup to reply.
  2. 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.

    1. I can compile everything with one command. ✅
    2. I can write code to any project I want and then recompile everything. ✅
    3. The dependencies don’t need to be installed. ✅
    4. Allows for different build configurations for each project. ❌

    export() + include()

    Another solution is to use export() in each dependency project, and then include() the target export file to add the dependency. This means a separate generated buildsystem for each project.

    1. I can compile everything with one command. ❌ You’d either need to invoke each buildsystem individually, or write a wrapper script that invokes all of them.
    2. I can write code to any project I want and then recompile everything. ✅
    3. The dependencies don’t need to be installed. ✅
    4. Allows for different build configurations 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 with cmake --build <build_dir>). Of particular interest to you would be the SOURCE_DIR and BUILD_DIR options. You’ll probably also want to use the BUILD_ALWAYS build step option.

    1. I can compile everything with one command. ✅
    2. I can write code to any project I want and then recompile everything. ✅
    3. The dependencies don’t need to be installed. ✅
    4. Allows for different build configurations for each project. ✅

    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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search