3.basic_of_cmakelists
CMake basics
Minimum Version
-
The first line in every
CMakeLists.txtfile must include:cmake_minimum_required(VERSION 3.1) -
Command name:
cmake_minimum_requiredis case insensitive, but the common practice is to use lowercase. -
VERSIONkeyword: 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
VERSIONcan define a range, likeVERSION 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:
VERSIONsets variables likeMyProject_VERSIONandPROJECT_VERSION.DESCRIPTION(introduced in CMake 3.9) adds a project description.LANGUAGESdefines the programming languages used in the project.
Making an Executable
add_executable(one two.cpp three.h)
oneis 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.
- Only source files (
- 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_LIBSto choose betweenSTATICandSHARED.
-
Header-only libraries:
- For libraries that don't require compilation (e.g., header-only libraries), use the
INTERFACElibrary type:add_library(one INTERFACE) - An
INTERFACElibrary doesn't include any source files but defines an interface for other targets.
- For libraries that don't require compilation (e.g., header-only libraries), use the
-
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.
- You can create an ALIAS for an existing target:
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
SOURCESproperty will hold the filesimple.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, orMODULElibraries:- Default: If no type is specified, CMake uses the
BUILD_SHARED_LIBSsetting to decide between static and shared libraries.
- Default: If no type is specified, CMake uses the
- Non-built libraries: Targets like
INTERFACEorALIASallow 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, orINTERFACE) 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,
INTERFACErefers 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:
MyHeaderOnlyLibis declared as anINTERFACElibrary.- Any target that links to
MyHeaderOnlyLibwill 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
INTERFACEto 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 anincludedirectory to the target.PUBLICkeyword:- For libraries, it means any target that links to this target also needs the
includedirectory. - For executables,
PUBLIChas limited impact.
- For libraries, it means any target that links to this target also needs the
- 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: Linksanotherwithone. Ifoneis a target, it adds a dependency; otherwise, it links to a library namedoneon 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.
- A target (like
-
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") # Equivalentlist()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
BOOLtypes:option(MY_OPTION "This is settable from the command line" OFF)- Multiple wordings for
ONandOFFexist. See CMake Variables for more.
- Multiple wordings for
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:This sets theset(CMAKE_CXX_STANDARD 11)CXX_STANDARDproperty for all new targets created after setting this variable.
Setting Properties
There are two common ways to set properties in CMake:
-
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.
-
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 theCMakeLists.txtis 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 currentCMakeLists.txtoperates.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.
Variables related to find package
- 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.cmakeor*.cmakefiles). - 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>orshare/<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_PATHto help CMake locate the required configuration files. - Example:
This command tells CMake to search
set(CMAKE_PREFIX_PATH "/opt/external_libs;/usr/local/custom_libs")/opt/external_libsand/usr/local/custom_libsfor library configuration files.
2. CMAKE_MODULE_PATH
- Description: This is another list of directories where CMake will search for
Find<Library>.cmakemodules. These are custom CMake modules written to locate libraries and their configurations. UnlikeCMAKE_PREFIX_PATH, which looks forconfig.cmakefiles,CMAKE_MODULE_PATHlooks for CMake'sFind<Library>.cmakefiles. - Use Case: If you're writing or using custom
Find<Library>.cmakescripts for libraries that don’t provide their own CMake configuration, you would add their paths here. - Example:
This command tells CMake to look for
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules")Find<Library>.cmakemodules in thecmake/modulesdirectory 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:
This command instructs CMake to search within these root directories when cross-compiling.
set(CMAKE_FIND_ROOT_PATH "/usr/arm-linux-gnueabi" "/opt/arm-toolchain")
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
/usrand/usr/localon 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:
This appends
list(APPEND CMAKE_SYSTEM_PREFIX_PATH "/opt/my_custom_libs")/opt/my_custom_libsto 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 aconfig.cmakeorFind<Library>.cmakefile to configure and link the library to your project.- Config Mode: Searches for
<Library>Config.cmakeor<lowercase_library>Config.cmakein directories defined by the variables above. - Module Mode: Looks for
Find<Library>.cmakein paths specified byCMAKE_MODULE_PATH.
Example:
find_package(MyLibrary REQUIRED)CMake will search for
MyLibraryConfig.cmakeorFindMyLibrary.cmakeusing paths specified inCMAKE_PREFIX_PATH,CMAKE_MODULE_PATH, or system-wide default paths. - Config Mode: Searches for
Search Order:
- CMAKE_PREFIX_PATH: Custom user-defined paths (highest priority).
- CMAKE_MODULE_PATH: Custom paths for finding
Find<Library>.cmakescripts. - System Prefix Paths:
/usr/local,/usr, etc., on Unix-like systems. - Package Registry (on Windows, unless disabled with
CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY).