Cpp Notes

3.basic_of_cmakelists

CMake basics

Minimum Version

  • The first line in every CMakeLists.txt file must include:

    cmake_minimum_required(VERSION 3.1)
  • Command name: cmake_minimum_required is case insensitive, but the common practice is to use lowercase.

  • VERSION keyword: A special keyword for this function, followed by the version number.

  • The CMake version defines policies, which impact behavior. Check the official documentation for details on policies.

  • From CMake 3.12 onwards, the VERSION can define a range, like VERSION 3.1...3.15.

    • Newer policies are especially important for macOS and Windows users, who often have more recent versions of CMake.
  • Recommended practice for new projects:

    cmake_minimum_required(VERSION 3.7...3.25)
    
    if(${CMAKE_VERSION} VERSION_LESS 3.12)
        cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
    endif()

Setting a Project

Every top-level CMake file will typically include the following line:

project(MyProject VERSION 1.0
                  DESCRIPTION "Very nice project"
                  LANGUAGES CXX)

Key points:

  • Strings are quoted: e.g., "Very nice project".
  • Whitespace doesn't matter: CMake ignores extra spaces or newlines.
  • Positional argument: The project name (MyProject) is the first argument.
  • Optional keyword arguments:
    • VERSION sets variables like MyProject_VERSION and PROJECT_VERSION.
    • DESCRIPTION (introduced in CMake 3.9) adds a project description.
    • LANGUAGES defines the programming languages used in the project.

Making an Executable

add_executable(one two.cpp three.h)
  • one is the executable name: It represents both the generated executable file and the CMake target created.
  • Source file list: Follows the target name. You can list multiple source files as needed.
    • Only source files (.cpp, .c) will be compiled.
    • Header files (.h, etc.) are mostly ignored for compilation but listed to appear in IDEs.
  • Targets in IDEs: Targets typically appear as folders or groups in IDEs for better project structure.

For more information on CMake's general build system and target handling, check the official build system documentation.

Making a Library

add_library(one STATIC two.cpp three.h)
  • Library types:

    • STATIC: Compiles into a static library (.lib, .a).
    • SHARED: Compiles into a shared library (.dll, .so).
    • MODULE: Used for plugins, not linked into other targets.
    • Default: If no type is specified, CMake uses the value of BUILD_SHARED_LIBS to choose between STATIC and SHARED.
  • Header-only libraries:

    • For libraries that don't require compilation (e.g., header-only libraries), use the INTERFACE library type:
      add_library(one INTERFACE)
    • An INTERFACE library doesn't include any source files but defines an interface for other targets.
  • ALIAS libraries:

    • You can create an ALIAS for an existing target:
      add_library(new_alias ALIAS existing_target)
    • This allows you to create a new name (possibly with :: in it) for easier usage across projects.

Targets

Targets in CMake represent components of a project, like executables or libraries, and they store properties similar to objects in programming languages.

Creating an Executable Target

add_executable(myexample simple.cpp)
  • Executable target: This defines a target named myexample.
  • Properties: For instance, the SOURCES property will hold the file simple.cpp.
  • Unique names: Target names must be unique within a project (though the actual executable name can be set differently if needed).

Creating a Library Target

add_library(mylibrary simplelib.cpp)
  • Library types: You can specify STATIC, SHARED, or MODULE libraries:
    • Default: If no type is specified, CMake uses the BUILD_SHARED_LIBS setting to decide between static and shared libraries.
  • Non-built libraries: Targets like INTERFACE or ALIAS allow creating targets without actual compilation (e.g., header-only libraries).

Linking

To define the relationship between targets, use target_link_libraries along with one of the keywords: PUBLIC, PRIVATE, or INTERFACE.

target_link_libraries(target_name PUBLIC/PRIVATE/INTERFACE library_name)
  • PUBLIC: Links the library to both the target and anything that depends on the target.
  • PRIVATE: Links the library only to the target itself.
  • INTERFACE: Links the library only to targets that depend on it, without linking to the target itself.

Important Note

  • Always include one of the keywords (PUBLIC, PRIVATE, or INTERFACE) when linking libraries.
  • Omitting the keyword may trigger an old compatibility mode, which can cause issues in the build system.

What is INTERFACE?

  • In CMake, INTERFACE refers to properties or dependencies that are propagated to other targets that use (or depend on) a particular target, but the original target itself is not directly linked to the properties.
  • This concept is especially useful when dealing with header-only libraries or libraries that do not require direct linking to be used but may provide important configuration, include directories, or compile flags that need to be passed on to other targets.
  • Example: Let's say you have a header-only library with configuration settings, and you want to make it available to other targets that use it, but there is no need to link any actual library files because it only consists of headers.
add_library(MyHeaderOnlyLib INTERFACE)
target_include_directories(MyHeaderOnlyLib INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)

In this case:

  • MyHeaderOnlyLib is declared as an INTERFACE library.
  • Any target that links to MyHeaderOnlyLib will inherit the include directories (${CMAKE_CURRENT_SOURCE_DIR}/include), but no actual linking is done because there is no compiled code to link to.

When to Use INTERFACE:

  • Header-only Libraries: If you are working with a library that consists only of headers (no compiled code), use INTERFACE to make sure that the include paths and any required compile options are passed to dependent targets, but no linking happens.
  • Propagating Compile Definitions or Flags: When you have certain compile definitions or flags that must be applied to all targets depending on the library, but not the library itself.
  • Configuration Libraries: If you have a library that sets up some configuration or provides utility macros that other targets need but doesn't itself contain object code to link.

Targets are your friend

Once a target is created, such as with add_library(one STATIC two.cpp three.h), you can add further information to it.

Why Use Targets?

  • Modern CMake encourages targets to represent all components, including things like OpenMP, which aren't true libraries.
  • Targets make CMake more modular and maintainable, allowing you to easily manage dependencies and project structure.

Targets Can Have

  • Include directories: Using target_include_directories.
  • Linked libraries/targets: Using target_link_libraries.
  • Compile options, definitions, and features: Such as C++11 features.

Example: Adding an include directory

target_include_directories(one PUBLIC include)
  • target_include_directories: Adds an include directory to the target.
  • PUBLIC keyword:
    • For libraries, it means any target that links to this target also needs the include directory.
    • For executables, PUBLIC has limited impact.
  • Other options:
    • PRIVATE: Only affects the current target.
    • INTERFACE: Affects only dependencies of the target.

Example: Chaining targets

add_library(another STATIC another.cpp another.h)
target_link_libraries(another PUBLIC one)
  • target_link_libraries: Links another with one. If one is a target, it adds a dependency; otherwise, it links to a library named one on the path.

Important Notes on Linking

  • You can link to:

    • A target (like one).
    • A library (if no target exists by that name).
    • A full path to a library.
    • A linker flag.
  • Always use keywords (PUBLIC, PRIVATE, INTERFACE) to avoid compatibility issues. Skipping keywords, as allowed in older CMake versions, can cause errors if mixed styles are used.

Variables and the Cache

Local Variables

  • Set a local variable using:

    set(MY_VARIABLE "value")
    • Variable names are typically all caps.
    • Access a variable with ${} syntax: ${MY_VARIABLE}.
    • Scope: Variables remain accessible in the same scope where they were set. Once you leave a scope (e.g., a function or subdirectory), the variable is no longer defined.
    • To set a variable in the parent scope, use PARENT_SCOPE:
      set(MY_VARIABLE "value" PARENT_SCOPE)
  • Lists: A list is a series of values, either space-separated or ;-separated:

    set(MY_LIST "one" "two")
    set(MY_LIST "one;two")  # Equivalent
    • list() provides utilities for working with lists.
    • Unquoted vs. Quoted Values: Values without spaces don’t need quotes, but always quote paths to handle potential spaces:
      set(MY_PATH "/some/path with spaces")
      echo "${MY_PATH}"  # Always quote path variables

Cache Variables

  • Cache variables can be set from the command line and retain their values between CMake runs.

    set(MY_CACHE_VARIABLE "VALUE" CACHE STRING "Description")
    • Cache variables don’t overwrite existing values, making them ideal for setting values externally via the command line.
    • To force a cache variable to always be set:
      set(MY_CACHE_VARIABLE "VALUE" CACHE STRING "" FORCE)
      mark_as_advanced(MY_CACHE_VARIABLE)  # Hides from cmake -L or GUI
  • Internal Cache: For non-visible, forced cache variables:

    set(MY_CACHE_VARIABLE "VALUE" CACHE INTERNAL "")
  • Boolean Variables: For common BOOL types:

    option(MY_OPTION "This is settable from the command line" OFF)

Environment Variables

  • You can set and get environment variables, though it's generally advised to avoid them in CMake:
    set(ENV{variable_name} "value")
    get(ENV{variable_name} result)

The Cache

  • CMakeCache.txt: A text file created in the build directory that stores all cache variables.
    • This file saves any options set, so you don’t need to re-enter them on every CMake run.

Properties

Properties in CMake are like variables but are attached to objects such as targets, directories, or files. They allow you to store information in a more contextual manner.

Key Points

  • Global properties can serve as uncached global variables.
  • Many target properties are initialized from corresponding CMAKE_ variables. For example:
    set(CMAKE_CXX_STANDARD 11)
    This sets the CXX_STANDARD property for all new targets created after setting this variable.

Setting Properties

There are two common ways to set properties in CMake:

  1. set_property: More general and flexible, can apply to multiple objects at once.

    set_property(TARGET TargetName
                 PROPERTY CXX_STANDARD 11)
    • This allows setting properties for targets, directories, files, and more.
  2. set_target_properties: A shorthand for setting multiple properties on a single target.

    set_target_properties(TargetName PROPERTIES
                          CXX_STANDARD 11
                          VERSION 1.0)

Retrieving Properties

To get the value of a property, use get_property:

get_property(ResultVariable TARGET TargetName PROPERTY CXX_STANDARD)

For a comprehensive list of properties, see the CMake properties manual. You can also define custom properties in some cases.

Commonly use cmake variable

  • Source Directories:

    • CMAKE_SOURCE_DIR: Root of the source tree.
    • CMAKE_CURRENT_SOURCE_DIR: Current directory where the CMakeLists.txt is located.
    • PROJECT_SOURCE_DIR: Source directory for the current project.
  • Build Directories:

    • CMAKE_BINARY_DIR: Root of the build tree.
    • CMAKE_CURRENT_BINARY_DIR: Build directory where the current CMakeLists.txt operates.
    • PROJECT_BINARY_DIR: Build directory for the current project.
  • Output Directories:

    • CMAKE_RUNTIME_OUTPUT_DIRECTORY: Directory for executables.
    • CMAKE_LIBRARY_OUTPUT_DIRECTORY: Directory for libraries.
    • CMAKE_ARCHIVE_OUTPUT_DIRECTORY: Directory for static libraries/archives.
  • Installation:

    • CMAKE_INSTALL_PREFIX: Base directory for installation.
  • In CMake, when you're working with external libraries, you often need to specify the paths where CMake should search for those libraries' configuration files (usually config.cmake or *.cmake files).
  • These files are used to find the correct include directories, libraries, and other settings required to link and compile against those external dependencies.

1. CMAKE_PREFIX_PATH

  • Description: This is a list of directories that CMake will search for packages, libraries, and configuration files. CMake will look in these directories for lib/cmake/<library_name> or share/<library_name> folders, which typically contain the CMake configuration files for a library.
  • Use Case: When you have external libraries installed in non-standard directories, you can add those directories to CMAKE_PREFIX_PATH to help CMake locate the required configuration files.
  • Example:
    set(CMAKE_PREFIX_PATH "/opt/external_libs;/usr/local/custom_libs")
    This command tells CMake to search /opt/external_libs and /usr/local/custom_libs for library configuration files.

2. CMAKE_MODULE_PATH

  • Description: This is another list of directories where CMake will search for Find<Library>.cmake modules. These are custom CMake modules written to locate libraries and their configurations. Unlike CMAKE_PREFIX_PATH, which looks for config.cmake files, CMAKE_MODULE_PATH looks for CMake's Find<Library>.cmake files.
  • Use Case: If you're writing or using custom Find<Library>.cmake scripts for libraries that don’t provide their own CMake configuration, you would add their paths here.
  • Example:
    set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules")
    This command tells CMake to look for Find<Library>.cmake modules in the cmake/modules directory of the source tree.

3. CMAKE_FIND_ROOT_PATH

  • Description: This variable is used to specify the root directories for searching when cross-compiling. It helps CMake locate libraries and packages in specific directories when targeting different architectures or platforms.
  • Use Case: Primarily used for cross-compilation, where you might want to specify directories that contain the target platform's libraries.
  • Example:
    set(CMAKE_FIND_ROOT_PATH "/usr/arm-linux-gnueabi" "/opt/arm-toolchain")
    This command instructs CMake to search within these root directories when cross-compiling.

4. CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY

  • Description: This variable disables the use of the CMake package registry. The package registry is typically used on Windows and stores locations of CMake configuration files. If set to TRUE, it prevents CMake from using the registry during package searches.
  • Use Case: Use this if you want to limit CMake’s search to specific directories or avoid using cached paths for libraries.
  • Example:
    set(CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY TRUE)

5. CMAKE_SYSTEM_PREFIX_PATH

  • Description: This is a list of directories where CMake looks by default when searching for external libraries or packages. It typically includes system-wide directories like /usr and /usr/local on Unix-based systems.
  • Use Case: If your libraries are installed in system directories, this variable will include them in the search paths by default. You can also extend it to include other directories.
  • Example:
    list(APPEND CMAKE_SYSTEM_PREFIX_PATH "/opt/my_custom_libs")
    This appends /opt/my_custom_libs to the default search directories.

6. find_package() Command and Search Behavior

  • The find_package() command is used to search for libraries in CMake. It looks for a config.cmake or Find<Library>.cmake file to configure and link the library to your project.

    • Config Mode: Searches for <Library>Config.cmake or <lowercase_library>Config.cmake in directories defined by the variables above.
    • Module Mode: Looks for Find<Library>.cmake in paths specified by CMAKE_MODULE_PATH.

    Example:

    find_package(MyLibrary REQUIRED)

    CMake will search for MyLibraryConfig.cmake or FindMyLibrary.cmake using paths specified in CMAKE_PREFIX_PATH, CMAKE_MODULE_PATH, or system-wide default paths.

Search Order:

  1. CMAKE_PREFIX_PATH: Custom user-defined paths (highest priority).
  2. CMAKE_MODULE_PATH: Custom paths for finding Find<Library>.cmake scripts.
  3. System Prefix Paths: /usr/local, /usr, etc., on Unix-like systems.
  4. Package Registry (on Windows, unless disabled with CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY).