How to structure include()-s

I’m using include()-s all around even for the same module or file, eg. include(TinyHelpers), I’m including it in the main CMakeLists.txt file at the top as it’s needed there, but I’m also including it in deeper modules or add_subtirectory()-ies.

One example for all, I have include(TinyTestCommon) in every test project ~46, I can remove all of them and instead use one include in the tests/CMakeLists.txt or a little deeper in my case like:

include(TinyTestCommon)
add_subdirectory(functional)
add_subdirectory(unit)

In C++ you can #include in a deeper header file and then you can omit this #include in the parent file (this works in CMake too).

But in CMake, you can include() in top-level CMakeLists.txt and you don’t have to include() anywhere else then (this doesn’t work eg. in C++).

Now I’m wondering if it’s a good idea to remove include()-s I described in the paragraph above.

I suppose that CMake simply includes everywhere and doesn’t track whether the include is already in the scope, which means it can also increase performance? (No performance gain, I tried to remove ~56 include()-s and no performance gain).

But anyway should I look at it like they are duplicate includes or like every project should be self-contained and should be able to configure standalone?

Even when I removed all includes and they are only in the parent files I’m still able to rebuild sub-projects which doesn’t contain these includes like:

cmake.exe --build <buildPath> --target mysql_querybuilder

This only supports the claim they are duplicate includes as I’m still able to build these sub-projects without these includes.

I searched in Scott book, docs, and also forum and found nothing about this problem.

How do you manage your includes?

1 Like

There won’t be a “one approach to rule them all” here. The best approach will depend on the project. Speaking of project…

But anyway should I look at it like they are duplicate includes or like every project should be self-contained and should be able to configure standalone?

What do you mean by “project” here? I ask because I often see folks pepper calls to project() throughout their build, where really there should just be one project() call at the top level. But some repositories allow sub-parts to be built standalone, in which case the top of each standalone sub-tree will need a CMakeLists.txt with its own call to project(). The top of the repo might still allow all sub-parts to be pulled together in a single build, in which case you’ll have many project() calls. That’s fine, I’m just trying to clarify what you mean by “project”. Here, whether you want the ability to build such sub-directories standalone is directly relevant to where you need to put your include() calls.

A key thing to be careful of is to ensure that something in the current or parent scope has included whatever the thing is you need to use. Don’t rely on it having been included in some other sibling scope that isn’t an ancestor. Some things in CMake have global visibility (e.g. functions, macros, non-imported targets), but some are only visible if defined in the current or parent scope (e.g. variables, directory properties). Depending on what an included file defines, it might matter whether it is included in a parent scope or not.

It generally isn’t a problem if a file is included multiple times, unless it is specifically designed to do something when it is included and it only expects to be included once. Modules like CPack are designed to do something when it is included, rather than defining functions the project can call later. Such modules might be more problematic if included multiple times, but they are less common.

FWIW, I tend to find collecting together includes in the top level CMakeLists.txt can simplify some things, but it can also pull conditional logic away from the lower levels where it might otherwise be more relevant. An alternative would be to include files unconditionally even if not needed, which might be an acceptable strategy for some projects, but might break things for others (e.g. an included file doesn’t support a particular toolchain or target platform).

So the rather unsatisfactory answer I’d give is “it depends”.

4 Likes

What do you mean by “project” here?

CMakeLists.txt file with the project() call. I have 4 libraries, 2 exe-s, and ~40 test cases (executables) in my repo/project, so there is ~46 projects in my CMake build (every project in its own sub-folder of course). I have 47 project() calls in this repo exactly. (OT: is this a problem? that every exe, library has its own project() call?)

I don’t have any conditional includes(), virtually all of the includes that I have only include .cmake files, which contain functions (abstracted logic) without any state.

Don’t rely on it having been included in some other sibling scope that isn’t an ancestor.

This is clear.

Depending on what an included file defines, it might matter whether it is included in a parent scope or not.

I didn’t realize this, maybe some included files define some variable outside of a function, thx for clarifying this. (I will have to check this). Especially macro()-s can cause this problem, there are some macros() inside these includes (I think this will be the biggest problem if there will be any).

it can also pull conditional logic away from the lower levels where it might otherwise be more relevant

Thx, need to check also.

I will have to check variables outside functions and macros(), but it looks like defining these includes in the top-level CMakeLists.txt might be a good idea in my case, it shouldn’t harm anything. Especially that include(TinyTestCommon) as it is included in 40 test cases (projects) and it only contains one function inside.

Thx for the detailed reply, I think is much more clear now for me.

1 Like

Projects are not targets. A project is often closely associated with a package, and a package might provide multiple targets, CMake functions, etc.

Something you will commonly see in online tutorials and examples is to use ${PROJECT_NAME} as the name of a target. Don’t do that, they are separate, distinct things. It may happen to be the case that a project has only one target, and it might be that the project name and that one target name are the same, but projects and targets are separate concepts.

2 Likes

If you look at the boost project, it is the worst case scenario.

Every boost submodule is buildable standalone, but the price is high, if it depends on other boost library. And that is very common! In worst case, there are hundreds of include paths needed.

In contrast, if you setup a build from the source tar file distribution, only one include path is needed.

The build is very fast and simple.

With Ninja, you may build a single big project with many subdirectories and libraries.
But even if you want to build only one unit test for a single library, it is possible and fast.

2 Likes

I have defined all my test cases virtually identically like this, should there be this project()? You can also see that include there I would like to get rid of :grin::

project(mysql_querybuilder
    LANGUAGES CXX
)

add_executable(mysql_querybuilder
    tst_mysql_querybuilder.cpp
)

add_test(NAME mysql_querybuilder COMMAND mysql_querybuilder)

include(TinyTestCommon)
tiny_configure_test(mysql_querybuilder)
1 Like

If you want to be able to build with that as a standalone project (i.e. that CMakeLists.txt file would be the top level of the build), then yes you need project() there. You would also need a call to cmake_minimum_required() before it. If you don’t need to be able to build it as a standalone project, then remove the project() call.

2 Likes