Post

CMake - Brief Reference

CMake is a powerful and flexible build system generator that supports the creation of makefiles, project files, and build environments across various platforms and compilers. Below is a brief reference guide to some essential commands and patterns commonly used in CMake.

1. Key Commands and Concepts

1.1. Project Definition

CMake allows for detailed configuration of your project. Below are different levels of project definition:

  • Basic Project Definition: Defines the project name.
project(MyProjectName)
  • Project with Versioning: Specifies the project name along with its version.
project(MyProjectName VERSION 1.0.0)
  • Comprehensive Project Configuration: Provides additional details such as the languages used, a brief description, and a homepage URL.
project(
  MyProjectName 
  VERSION 1.0.0
  LANGUAGES C CXX ASM Fortran CUDA
  DESCRIPTION "This is a sample project"
  HOMEPAGE_URL "https://example.com"
)

2. Variables

Variables in CMake are fundamental for managing values and passing data throughout the CMakeLists.txt files.

2.1. Definition

  • Basic Variable: Sets a variable with a specified value. Scoping can be controlled by specifying the parent scope.
set(VARIABLE_NAME "Value")                  # local scope
set(VARIABLE_NAME "Value" PARENT_SCOPE)     # parent scope
  • List Variable: Commonly used for defining lists of source files.
set(SOURCES main.cpp MyClass.cpp AnotherClass.cpp)

2.2. Appending to Variables

  • Appending Values: Adds new values to an existing list.
set(SOURCES ${SOURCES} YetAnotherClass.cpp) # Traditional way
list(APPEND SOURCES YetAnotherClass.cpp)    # using APPEND

3. Executable Configuration

Defining and managing executables within your project can be easily achieved through CMake:

  • Basic Executable: Defines an executable from a single source file.
add_executable(my_executable main.cpp)
  • Multiple Source Files: Creates an executable from multiple source files.
add_executable(my_executable ${SOURCES})
  • Conditional Source Files: Dynamically adds source files based on platform or other conditions.
if(WIN32)
    list(APPEND SOURCES win_main.cpp)
else()
    list(APPEND SOURCES unix_main.cpp)
endif()

add_executable(my_app ${SOURCES})
  • Setting Executable Properties: Configures specific properties, such as the C++ standard required.
add_executable(my_app main.cpp)
set_target_properties(my_app PROPERTIES
    CXX_STANDARD 11
    CXX_STANDARD_REQUIRED YES
    CXX_EXTENSIONS NO
)

4. Including Directories

CMake offers flexible methods to manage include directories, ensuring that the compiler can locate the necessary header files:

  • Global Include Directory: This approach sets include directories globally, but it is generally discouraged due to its potential to introduce conflicts.
include_directories(${CMAKE_SOURCE_DIR}/include)    # bad practice
  • Target-Specific Include Directory: Specifies include directories for a specific target, promoting better encapsulation and avoiding global scope issues.
add_executable(my_app main.cpp)
target_include_directories(my_app PRIVATE ${CMAKE_SOURCE_DIR}/include)

5. Library Management

Libraries are crucial in C++ projects, and CMake provides robust commands for adding and linking libraries effectively:

5.1. Adding Libraries

  • Static Library: Creates a static library from specified source files.
add_library(my_static_lib STATIC src/lib.cpp)
  • Shared Library: Creates a shared library.
add_library(my_shared_lib SHARED src/lib.cpp)
  • Module Library: Creates a library that is loaded dynamically at runtime.
add_library(my_shared_lib MODULE src/lib.cpp)
  • Object Library: Compiles sources into object files without archiving or linking them into a library.
add_library(my_shared_lib OBJECT src/lib.cpp)

5.2. Linking Libraries

  • Linking to an Executable: Links a library to an executable.
add_executable(my_app main.cpp)
add_library(my_lib STATIC src/lib.cpp)
target_link_libraries(my_app PRIVATE my_lib)
  • Linking System Libraries: Links against a system-provided library.
find_library(MATH_LIB m)
if(MATH_LIB)
    target_link_libraries(my_app PRIVATE ${MATH_LIB})
endif()
  • Importing External Libraries: Allows the use of an external library in your project.
add_library(external_lib UNKNOWN IMPORTED)
set_target_properties(external_lib PROPERTIES
  IMPORTED_LOCATION "/path/to/external_lib.a"
  INTERFACE_INCLUDE_DIRECTORIES "/path/to/includes"
)
  • Known Libraries: Examples of linking against some well-known libraries. (Examples for Threads, Catch2, OpenCV, Google Benchmark, and Eigen provided)

5.2.1. Predefined Variables

  • CMAKE_SOURCE_DIR: The top-level source directory.
message("Top-level source directory: ${CMAKE_SOURCE_DIR}")
  • CMAKE_BINARY_DIR: The top-level build directory (usually the directory where you invoked CMake).
message("Binary directory: ${CMAKE_BINARY_DIR}")
  • CMAKE_CURRENT_SOURCE_DIR: The source directory of the current CMakeLists.txt.
include(${CMAKE_CURRENT_SOURCE_DIR}/extra.cmake)
  • CMAKE_CURRENT_BINARY_DIR: The build directory corresponding to the current source directory.
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}/bin)
  • CMAKE_PROJECT_NAME: The name of the first project set in the top-level CMakeLists.txt.
message("Top level project: ${CMAKE_PROJECT_NAME}")
  • CMAKE_CXX_COMPILER: The full path to the C++ compiler.
message("Using compiler: ${CMAKE_CXX_COMPILER}")
  • CMAKE_C_COMPILER: The full path to the C++ compiler.
message("Using compiler: ${CMAKE_C_COMPILER}")
  • CMAKE_PREFIX_PATH: Directories to be searched by find_package() before its default paths.
list(APPEND CMAKE_PREFIX_PATH "/custom/path")

6. Installation

CMake provides a flexible install() command that allows you to specify which files should be installed and where they should go. This is useful for deploying your project after it has been built.

6.1. Basic Install Command

To install a target, such as a library or an executable, you can use the install() command in your CMakeLists.txt:

# Install an executable
install(TARGETS my_executable DESTINATION bin)

# Install a library
install(TARGETS my_library
        ARCHIVE DESTINATION lib
        LIBRARY DESTINATION lib
        RUNTIME DESTINATION bin)

6.2. Installing Header Files

You can also install header files using the install() command:

install(FILES my_header.h DESTINATION include)

6.3. Directory Installation

If you have multiple header files in a directory, you can install them all at once:

install(DIRECTORY include/ DESTINATION include)

6.4. Full Installation Example

Here’s an example that combines everything:

project(MyProject)

add_executable(my_executable main.cpp)
add_library(my_library STATIC my_library.cpp)

install(TARGETS my_executable my_library
        RUNTIME DESTINATION bin
        LIBRARY DESTINATION lib
        ARCHIVE DESTINATION lib)

install(FILES my_header.h DESTINATION include)
install(DIRECTORY include/ DESTINATION include)

6.5. Running the Install Command

To install the targets and files as specified, you can run the following CMake command after building:

cmake --install build

This will copy the built executables, libraries, and header files to the directories specified in your install() commands.

By understanding and utilizing these commands and best practices, developers can efficiently manage their C++ projects with CMake, ensuring scalability, maintainability, and cross-platform compatibility.

This post is licensed under CC BY 4.0 by the author.