CMake Variable Guidelines

by | Feb 15, 2021 | CMake | 0 comments

CMake exposes a large set of built-in variables that provide the user with information about the project’s structure. Some of these variables are global and constant across the entire build process, but some change depending on the current scope (context). In this post, I discuss what variables are available and provide general advice on which one might be the best choice depending on a use-case.

Global Variables

The total number of global variables exposed by CMake is rather large. Most, but not all of them begin with the CMAKE_ prefix. All variables with the CMAKE_ prefix are reserved by CMake itself. Here I’ll be focusing strictly on variables providing a directory-context – for example the variables one might use when referencing the top of the project directory tree, current directory, etc.

The following variables are always available:

CMAKE_SOURCE_DIR – points to the root project source directory – this is the directory the top-level CMakeLists.txt is located at.

CMAKE_BINARY_DIR – points to the root of the build directory – this is the directory cmake was originally executed from, or the directory passed in with the -B flag.

CMAKE_CURRENT_SOURCE_DIR – points to the directory of the CMakeLists.txt currently being processed, or in other words the directory of the most recent add_subdirectory call.

CMAKE_CURRENT_BINARY_DIR – points to the build directory corresponding to the CMAKE_CURRENT_SOURCE_DIR – of the same name, mirroring the source tree, is created within the build directory for each add_subdirectory call. Alternatively it may be the binary directory explicitly specified to the add_subdirectory call.

Project Variables

In addition to the globally available variables another set of variables becomes available after the project command is called. These variables either refer to the most recent project command call, or are constant across the entire build process. Both can be useful, depending on the application.

The very first project command call defines the following set of variables:

CMAKE_PROJECT_NAME – name specified in the very first project call.
CMAKE_PROJECT_VERSION – full version string given in the very first project call.
CMAKE_PROJECT_VERSION_MAJOR – major component of the version string given to the very first project call.
CMAKE_PROJECT_VERSION_MINOR – minor component of the version string given to the very first project call.
CMAKE_PROJECT_VERSION_PATCH – patch component of the version string given to the very first project call.

Note that it’s also possible to specify a “_TWEAK” version component, which I’ve omitted here since it’s not semver compliant.

Additionally, the following variables become available after every project command call:

PROJECT_NAME – name given to the most recent project call.
PROJECT_SOURCE_DIR – directory of the most recent project call.
PROJECT_BINARY_DIR – build directory corresponding to the source directory of the most recent project call.
PROJECT_VERSION – full version string given to the most recent project call.
PROJECT_VERSION_MAJOR – major component of the version string given to the most recent project call.
PROJECT_VERSION_MINOR – minor component of the version string given to the most recent project call.
PROJECT_VERSION_PATCH – patch component of the version string given to the most recent project call.

Some of these may seem somewhat redundant. In a very simple project, it’s very likely that there will be only a single project command call, in that case, doesn’t really matter which of these variables you decide to use. Those decisions start to matter only in larger projects which contain multiple subprojects. In that case, it’s best to use only the variables relative to the most recent project call, unless you’re absolutely certain you wish to refer to the top-most one.

This is not all. There’s yet another set of variables defined, also with each project call:

<PROJECT-NAME>_SOURCE_DIR – directory of the project call with the name <PROJECT-NAME>.
<PROJECT-NAME>_BINARY_DIR – build directory corresponding to the source directory of the <PROJECT-NAME>_SOURCE_DIR.
<PROJECT-NAME>_VERSION – full version string given to the project call with the name <PROJECT-NAME>.
<PROJECT-NAME>_VERSION_MAJOR – major component of the version string given to project call with the name <PROJECT-NAME>.
<PROJECT-NAME>_VERSION_MINOR – minor component of the version string given to project call with the name <PROJECT-NAME>.
<PROJECT-NAME>_VERSION_PATCH – patch component of the version string given to project call with the name <PROJECT-NAME>.

Where <PROJECT-NAME> is the name given to a project command call, with case preserved. For example a project(Foo) command call would result in Foo_SOURCE_DIR being defined (etc. for the remaining variables).

The apparent redundancy continues. Yet another set of variables that provide the same information. Well, not quite. All of these variables start to make sense when you realize that any CMake project could be used as an arbitrarily nested subproject – either with the use of CMake’s FetchContent module, git submodules, or any other solution that may be used to pull in dependency sources into the build tree.

This is, or at least should be, a serious consideration for any library developer.

Let’s see an example that illustrates the use of variables from all three of these sets.

Project Variables in Practice

Let’s say that we’re working on a library Foo, which already incorporates a subproject Bar. A simplified directory structure could look as follows:

Foo
├── Bar
│   └── CMakeLists.txt
└── CMakeLists.txt

Assume that the Foo CMakeLists.txt declares a project(Foo) and incorporates the Bar subproject as a subdirectory:

cmake_minimum_required(VERSION 3.19)

project(Foo VERSION 0.1.0)

add_subdirectory(Bar)

While the Bar CMakeLists.txt only declares a project(Bar):

cmake_minimum_required(VERSION 3.19)

project(Bar VERSION 0.1.0)

Variables defined in each subdirectory would have the following values:

after project(Foo):
--------------------------------------------------------
  CMAKE_SOURCE_DIR:         /home/user/Foo
  CMAKE_BINARY_DIR:         /home/user/Foo/build
  CMAKE_CURRENT_SOURCE_DIR: /home/user/Foo
  CMAKE_CURRENT_BINARY_DIR: /home/user/Foo/build
  CMAKE_PROJECT_NAME:       Foo
  CMAKE_PROJECT_VERSION:    0.1.0
--------------------------------------------------------
  PROJECT_NAME:             Foo
  PROJECT_SOURCE_DIR:       /home/user/Foo
  PROJECT_BINARY_DIR:       /home/user/Foo/build
  PROJECT_VERSION:          0.1.0
--------------------------------------------------------
  Foo_SOURCE_DIR:           /home/user/Foo
  Foo_BINARY_DIR:           /home/user/Foo/build
  Foo_VERSION:              0.1.0

after project(Bar):
--------------------------------------------------------
  CMAKE_SOURCE_DIR:         /home/user/Foo
  CMAKE_BINARY_DIR:         /home/user/Foo/build
  CMAKE_CURRENT_SOURCE_DIR: /home/user/Foo/Bar
  CMAKE_CURRENT_BINARY_DIR: /home/user/Foo/build/Bar
  CMAKE_PROJECT_NAME:       Foo
  CMAKE_PROJECT_VERSION:    0.1.0
--------------------------------------------------------
  PROJECT_NAME:             Bar
  PROJECT_SOURCE_DIR:       /home/user/Foo/Bar
  PROJECT_BINARY_DIR:       /home/user/Foo/build/Bar
  PROJECT_VERSION:          0.2.0
--------------------------------------------------------
  Bar_SOURCE_DIR:           /home/user/Foo/Bar
  Bar_BINARY_DIR:           /home/user/Foo/build/Bar
  Bar_VERSION:              0.2.0
  Foo_SOURCE_DIR:           /home/user/Foo
  Foo_BINARY_DIR:           /home/user/Foo/build
  Foo_VERSION:              0.1.0

Nothing surprising at this point. It may seem that the CMAKE_-prefixed variables are constant and it’s safe to use them, when referring to the Foo project and it’s directory. This also seems to be the most common practice among many open source projects. Especially the CMAKE_SOURCE_DIR and CAMKE_PROJECT_NAME variables are often treated as absolutely constant, unfortunately, this is not the case. Once we realize that the Foo project may be included in another project (just like Foo includes Bar), it becomes obvious that these variables may change, and should not be relied on, unless we really mean “arbitrary project’s root directory/name”, and not treat these as synonims for “Foo project’s root directory” and “Foo project’s name”.

Let’s now assume that a project Baz includes the project Foo as a subproject. In the real world this would most likely be done either using a git submodule, or CMake’s FetchContent or ExternalProject modules. Here, for simplicity, we can just use add_subdirectory.

The project tree would look as follows:

Baz
├── CMakeLists.txt
└── Foo
    ├── Bar
    │   └── CMakeLists.txt
    └── CMakeLists.txt

And the Baz CMakeLists.txt:

cmake_minimum_required(VERSION 3.19)

project(Baz VERSION 0.1.0)

add_subdirectory(Foo)

Again, printing the relevant variables during project configuration would result in the following output – limited to just the SOURCE variables, as these best illustrate the point:

after project(Baz):
after project(Baz):
--------------------------------------------------------
  CMAKE_SOURCE_DIR:         /home/user/Baz
  CMAKE_CURRENT_SOURCE_DIR: /home/user/Baz
  CMAKE_PROJECT_NAME:       Baz
--------------------------------------------------------
  PROJECT_NAME:             Baz
  PROJECT_SOURCE_DIR:       /home/user/Baz
--------------------------------------------------------
  Baz_SOURCE_DIR:           /home/user/Baz

after project(Foo):
--------------------------------------------------------
  CMAKE_SOURCE_DIR:         /home/user/Baz
  CMAKE_CURRENT_SOURCE_DIR: /home/user/Baz/Foo
  CMAKE_PROJECT_NAME:       Baz
--------------------------------------------------------
  PROJECT_NAME:             Foo
  PROJECT_SOURCE_DIR:       /home/user/Baz/Foo
--------------------------------------------------------
  Baz_SOURCE_DIR:           /home/user/Baz
  Foo_SOURCE_DIR:           /home/user/Baz/Foo

after project(Bar):
--------------------------------------------------------
  CMAKE_SOURCE_DIR:         /home/user/Baz
  CMAKE_CURRENT_SOURCE_DIR: /home/user/Baz/Foo/Bar
  CMAKE_PROJECT_NAME:       Baz
--------------------------------------------------------
  PROJECT_NAME:             Bar
  PROJECT_SOURCE_DIR:       /home/user/Baz/Foo/Bar
--------------------------------------------------------
  Baz_SOURCE_DIR:           /home/user/Baz
  Foo_SOURCE_DIR:           /home/user/Baz/Foo
  Bar_SOURCE_DIR:           /home/user/Baz/Foo/Bar

As you can see, the CMAKE_SOURCE_DIR now reffers to the directory of Baz and CMAKE_PROJECT_NAME refers to the name of the Baz project. The only truly constant and unambiguous variables are Baz_SORUCE_DIR, Foo_SOURCE_DIR and Bar_SOURCE_DIR.

This is not really surprising, once one realizes that any CMake project may be included in another CMake project as a subproject. Keeping that use case in mind, I’d propose the following set of guidelines.

Guidelines

  1. CMAKE_CURRENT_SOURCE_DIR – use this variable whenever you refer to the current directory. It’s the most readable. This should be the most common case.
  2. PROJECT_SOURCE_DIR – use this variable whenever you need to refer to the directory of the current project from a nested CMakeLists.txt, that doesn’t call the project command itself. This should also be used for most projects to refer to the root of the project tree. The only exception are projects that declare multiple projects already.
  3. <PROJECT-NAME>_SOURCE_DIR – use this variable to unambiguously refer to the root of the project tree for projects that declare multiple projects. This can also be used for scripting, when you know a project <PROJECT-NAME> exists.
  4. CMAKE_SOURCE_DIR – use this variable only for generic scripts, that are meant to be re-used by arbitrary projects and need to refer to the root of the project tree. Use of this variable in CMakeLists.txt should be avoided in favor of the alternatives.
  5. PROJECT_NAME – use this variable whenever you need to refer to the name of the project in the CMakeLists.txt that called the project command or in any nested CMakeLists.txt
  6. CMAKE_PROJECT_NAME – use this variable whenever you need to refer to the name of the top-level project in a generic script. There should be no need to use this variable in a CMakeLists.txt.

The same rules apply to the _BINARY_DIR and _VERSION variables.

In short:

Always use the CMAKE_CURRENT_SOURCE_DIR when you mean the current directory. Prefer the relative PROJECT_ variables whenever possible, consider the <PROJECT-NAME>_ variables when the former might be ambiguous and use the CMAKE_PROJECT_ variables only when you truly mean ANY top-level project, also when the project directory tree changes.

Summary

In this post I briefly discussed a subset of the large number of globally available CMake variables and distilled a set of guidelines for their use. Would you agree with the guidelines I proposed? Have you ever ran into issues with these variables before yourself. What is your personal preference and why? Feel free to share in the comments section.

 

References

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *

Share This