Van-Sama
Van-Sama

Reputation: 1284

CMake with subdirectories

I'm trying to set up my project to compile correctly using CMake.

My directory looks like this:

root
 |- bin
 |   |- // Where I want to build CMake from - using 'cmake ..'
 |- build
 |   |-
 |- include
 |   |- database
 |   |    |- database.h
 |- src
     |- database
     |    |- database.cpp
     |- main
          |- main.cpp

My sub-directories will definitely grow as my project grows larger and figured CMake would probably a good idea to learn. At the moment I can only get CMake to work with no sub-directories inside my src/. However, I do expect this project to grow into many sub-directories.

Would I need multiple CMakeLists.txt inside each directory with .cpp files? Can anyone point me in the right direction?

Upvotes: 66

Views: 119513

Answers (2)

Craig Scott
Craig Scott

Reputation: 10177

I'll refer you to this article for an in-depth discussion of the two main approaches, but there's a few ways you could structure a project such as this.

  • One CMakeLists.txt file at the top level which lists all the source files out in all the different subdirectories. You typically only see this for very simple projects.
  • One CMakeLists.txt file in each directory, each one brought in by its parent using add_subdirectory(). This approach is very common and is usually the recommended one.
  • One CMakeLists.txt file at the top level, each subdirectory having its own file that lists its own source files and targets. The top level CMakeLists.txt file brings in the subdirectory files with include(). This method is less common, but can have advantages over the other two in some circumstances. Not the recommended approach in general though.

Each has its pros and cons. Having just one top level CMakeLists.txt file would only be recommended if there were very few files and subdirectories. Once the project grows, keeping everything in the top level can become too much and make the resultant CMakeLists.txt file harder to follow. It also has the disadvantage that a change for a file addition or removal isn't restricted to a particular directory. That may not seem like a big deal, but if multiple people are working on a project and you want to easily see what part of a project someone else's changes affect (e.g. in a git history), it is harder. This is especially true if you both add/remove a file, thereby both change the top level CMakeLists.txt file and have the possibility of a conflict.

Once a project becomes non-trivial in size, most choose to add a CMakeLists.txt file in each subdirectory and use add_subdirectory() to bring them all together. @TheQuantumPhysicist's answer gives a good example of how this can be useful, so I won't repeat most of those details here. This structure offers you the ability to turn on/off whole sections of the build tree easily, but more importantly it gives each subdirectory its own variable scope. This can be important if you want to set variables, etc. in one part of the source tree but not have those changes visible in another part. Each subdirectory also gets its own directory property scope. These separate scopes make it easier to apply things like compiler flags and other temporary values to just one section of a complex directory structure.

The third option of one top level CMakeLists.txt file with each subdirectory providing a file brought in by include() is less common, but it has similarities to using one CMakeLists.txt file in each subdirectory. Both localise details about files in a directory to a CMakeLists.txt or other similarly named file in just that directory. Changes therefore become easier to merge and to understand in version control histories, etc. When using CMake 3.12 or earlier, one thing that this third approach allows which the second doesn't is you can use target_link_libraries() a bit more freely when using target_sources() to specify the source files in each subdirectory. The article linked at the top of this answer goes into detail about why target_sources() can be advantageous. If your minimum CMake version is 3.13 or later though, the third approach is inferior to the second and should be avoided in general.

Lastly, I'd suggest you don't pick up the habit of putting your build tree inside your source tree. Rather, create your build tree(s) as siblings of the source tree. Aim to keep your source tree untouched by a build. All it takes is to have someone create a directory in the source tree with the same name as whatever you've used for the build tree for things to go awry (I've seen this more than once!). You may also want to set up multiple build trees for the one source tree, such as one for a Debug build and another as a Release build, so having these outside the source tree also helps keep your source tree less cluttered.

Upvotes: 90

The Quantum Physicist
The Quantum Physicist

Reputation: 26346

Your project does not seem to need multiple CMakeLists.txt. The purpose of having multiple CMakeLists.txt multiple times is multi-fold, but it doesn't seem to fit in your project. For example, one benefit would be hierarchical organization, so that you can separate different units to be built separately, and linked eventually.

Example

Consider the situation if you have another directory in your root called tests. That directory contains unit tests that you want the developer to have the choice whether to compile it or not. In that case, you put one CMakeLists.txt in tests, and you enable this in your primary CMakeLists.txt with a add_subdirectory(tests) as so:

if(testing_enabled)
    add_subdirectory(tests)
endif()

In tests, there would be another CMakeLists.txt. This way, you separate concerns. The development of your unit tests is independent of the development of your project, and also the build process is separate.

Another example

Consider the situation if you have two libraries where the user can choose from. In this case, you have in your root two directories, lib1 and lib2. Now you can do something like this in your primary CMakeLists.txt:

if(IWantLib1)
    add_subdirectory(lib1)
else()
    add_subdirectory(lib2)
endif()

And both lib1 and lib2 directories contain CMakeLists.txt.

Upvotes: 47

Related Questions