Comprehensive Guide to CMakeLists (Part 1)

Word count: 7897, approximately 40 minutes of reading time

Comprehensive Guide to CMakeLists (Part 1)

Comprehensive Guide to CMakeLists (Part 1)

0. CMake Application Examples

Previously, we organized content on using CMake to introduce third-party libraries (header file directories, library directories, library files). However, the content organized here is not complete. Therefore, we need to further organize the usage of CMake for future engineers to reference and query.

cmake-template
├── CMakeLists.txt
└── build
    └── include
    └── src
        └── main.cpp

1. Basic CMakeLists Examples (15)

# Set the minimum required version of CMake
cmake_minimum_required(VERSION 3.15)

# Set the project name and version
project(MyProject VERSION 1.0 DESCRIPTION "An example project with CMake")

# Options, can be defined via -D on the command line
option(USE_CUSTOM_LIBRARY "Use a custom library" ON)

# Define conditional preprocessor macros
if(USE_CUSTOM_LIBRARY)
    add_definitions(-DUSE_CUSTOM_LIB)
endif()

# Find external dependencies
find_package(Threads REQUIRED)

# Specify the search path for header files
include_directories(${PROJECT_SOURCE_DIR}/include)

# Specify the search path for library files
link_directories(${PROJECT_SOURCE_DIR}/libs)

# Add subdirectories, which should also have their own CMakeLists.txt
add_subdirectory(src)
add_subdirectory(libs)

# Add an executable
add_executable(myExecutable src/main.cpp)

# Add a static library
add_library(myStaticLib STATIC src/myStaticLib.cpp)

# Add a shared library
add_library(mySharedLib SHARED src/mySharedLib.cpp)

# Set properties for static and shared libraries
set_target_properties(myStaticLib PROPERTIES
    ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/static_lib"
)
set_target_properties(mySharedLib PROPERTIES
    LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/shared_lib"
    VERSION ${PROJECT_VERSION}
)

# Set properties for the executable
set_target_properties(myExecutable PROPERTIES
    RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
    OUTPUT_NAME "myExecutableFinal"
)

# Specify linked libraries
target_link_libraries(myExecutable PRIVATE myStaticLib mySharedLib Threads::Threads)

# Installation rules
install(TARGETS myExecutable myStaticLib mySharedLib
    RUNTIME DESTINATION bin
    LIBRARY DESTINATION lib
    ARCHIVE DESTINATION lib/static
)

# Include directories
target_include_directories(myExecutable PRIVATE ${PROJECT_SOURCE_DIR}/include)

# Custom commands and dependencies
add_custom_target(run
    COMMAND myExecutable
)
add_dependencies(run myExecutable)

# Macro definition for printing messages
macro(print_details)
    message("Project details: ${PROJECT_NAME} version ${PROJECT_VERSION}")
endmacro()
print_details()

# File operation example
file(GLOB_RECURSE MY_SOURCES "src/*.cpp")
target_sources(myExecutable PRIVATE ${MY_SOURCES})

# Generate configuration file
configure_file(config.h.in ${CMAKE_BINARY_DIR}/config.h)

# Source file directory search
aux_source_directory(. DIR_SRCS)
add_executable(myProgram ${DIR_SRCS})

# Add directory, introduce other projects
add_subdirectory(external)

# Custom target, does not produce output files
add_custom_target(CustomCommand ALL
    COMMAND echo "Executing a custom command"
    COMMENT "This is a custom build step"
)

# File copy command
file(COPY ${CMAKE_SOURCE_DIR}/data.txt DESTINATION ${CMAKE_BINARY_DIR})

1.1 cmake_minimum_required

Specifies the minimum version of CMake required. It ensures that features from a specific version or higher of CMake can be utilized and guarantees that features that may cause undefined behavior in older versions of CMake are not misused.

Basic syntax:

cmake_minimum_required(VERSION major.minor[.patch][.tweak] [FATAL_ERROR])
  • • The VERSION keyword is followed by the required minimum version number.

  • FATAL_ERROR is an optional parameter that is required in older versions of CMake. If the user is using a version older than the specified VERSION, CMake will report a fatal error and stop processing. In newer versions of CMake, by default, if the version does not meet the requirements, CMake will report an error and stop.

1.2 project

Declares the project name and version, and optionally specifies supported programming languages. This command is usually located at the top of the CMakeLists.txt file, immediately following the cmake_minimum_required command.

Basic syntax:

project(<PROJECT-NAME> [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
                 [DESCRIPTION <project-description-string>]
                 [HOMEPAGE_URL <url-string>]
                 [LANGUAGES <language-name>...])
  • <PROJECT-NAME> is your project name.

  • • The VERSION keyword can be followed by the project version number.

  • DESCRIPTION can add a project description.

  • HOMEPAGE_URL can specify the URL of the project homepage.

  • • The LANGUAGES keyword can be followed by a series of programming language names, such as C, CXX (for C++), CUDA, Fortran, etc. If no language is specified, CMake defaults to enabling C and CXX.

1.3 $

The dollar sign $ in CMake is used with parentheses, in the form ${...}, to reference the value of variables. The most common usage is in relation to the project, for example:

Basic syntax:

# Set project name and version
project(MyProject VERSION 1.2.3 LANGUAGES CXX)
set(MY_VARIABLE "Hello, World")

message("Project name: ${PROJECT_NAME}")
message("Project version: ${PROJECT_VERSION}")
message(${MY_VARIABLE})

${MY_VARIABLE} will be replaced by the value of the variable MY_VARIABLE. The project command will create some variables, such as PROJECT_NAME, PROJECT_VERSION, etc., which can then be referenced.

1.4 set

In CMake, the set command is used to define and modify variables. This command is very flexible and is one of the most frequently used commands in CMake scripts, as it can be used to store various types of data, such as strings, file paths, lists (i.e., string arrays), boolean values, etc.

Basic syntax:

set(<variable> <value> [CACHE <type> <docstring> [FORCE]])

<variable> is the name of the variable you want to set. <value> is the value assigned to the variable. It can be one or more values (if multiple values, they will be treated as a list). The CACHE option is used to store the variable in CMake’s cache, which is useful for configurable options, as they remain unchanged across different builds unless modified by the user or project scripts. The <type> specifies the type of the variable in the cache, such as STRING, BOOL, etc. The <docstring> is a description of the variable, which usually appears in the CMake GUI. The FORCE option is used to force overwrite the value in the cache, even if the user has already set that value.

In CMake, the set command is used to set the value of a variable. Its basic syntax is as follows:

set(my_variable "Hello, World!")
  1. 1. Set a list variable:

set(my_list 1 2 3 4 5)
  1. 2. Set a cache variable:

set(my_cached_variable Release CACHE STRING "Set C++ Compiler Flags" FORCE) # Release or Debug

# Unset cache entry
unset(<variable> CACHE)

The CMakeCache.txt file is used to store cache entries, which is generated during the first build, and subsequent builds will not create this file. When referencing cache entries, it will look for this file and return the value.

When using the set() command, by default, if the cache entry does not exist in the CMakeCache.txt file, it will create the cache entry and write it to the CMakeCache.txt file. If the cache entry exists in the CMakeCache.txt file, the set() command will be ignored and will not overwrite the existing cache entry. However, if we want to force overwrite the existing cache entry, we can use the FORCE option.

  1. 3. Force set a cache variable:

set(my_cached_variable "Another value" CACHE STRING "Description of my_cached_variable" FORCE)

The above is the basic syntax and usage examples of the set command. You can set different types of variables according to specific needs, such as regular variables, list variables, or cache variables.

1.5 option

In CMake, the option command is used to define a boolean type option, which can be used to control certain behaviors or features during the compilation process. Basic syntax:

Basic syntax:

option(<option_variable> "<help_string>" [initial_value])

Where:

  • <option_variable> is the name of the variable for the option to be defined.

  • "<help_string>" is a description of the option, which will be displayed in the CMake GUI or command line help information.

  • [initial_value] is the initial value of the option, optional, default is OFF.

Below is an example of defining a switch option that indicates whether to enable a certain feature:

option(ENABLE_FEATURE "Enable some feature" ON)

1.6 add_definitions

In CMake, the add_definitions command is used to add predefined macro definitions to the compiler. These macro definitions will take effect when compiling source files. Basic syntax:

add_definitions(-D<macro1> [-D<macro2>] ...)

Where:

  • -D<macro> indicates the macro to be defined, with -D followed by the name of the macro. If the macro needs to have a value, it can be separated from the macro name using an equal sign (=).

Below are some examples of using the add_definitions command:

  1. 1. Define a simple macro:

add_definitions(-DENABLE_LOGGING)
  1. 2. Define a macro with a value:

add_definitions(-DDEBUG_LEVEL=2)
  1. 3. Define multiple macros:

add_definitions(-DENABLE_FEATURE_A -DENABLE_FEATURE_B)

Using add_definitions allows you to pass predefined macros to the compiler, which will take effect when compiling source files. It is important to note that macros defined using add_definitions will apply to all source files in the project.

1.7 find_package

When developing a project using CMake, the find_package() command is very important as it helps you find and use external libraries, simplifying the configuration and management of libraries. Suppose you are developing a project and suddenly need to use an external library, such as Boost, SQLite, or OpenCV. Manually setting the paths for these libraries, header files, and linking library files is a lot of work, especially in cross-platform development. Fortunately, CMake provides the find_package() command, which can automatically find these libraries and configure the relevant paths.

Basic syntax

find_package(<package_name> [version] [EXACT] [QUIET] [MODULE] [REQUIRED]
             [COMPONENTS [components...]]
             [OPTIONAL_COMPONENTS components...]
             [NO_POLICY_SCOPE])

Parameter description

  • <package_name>: The name of the package to find.

  • version: An optional parameter used to specify the version of the package.

  • EXACT: An optional parameter that requires the version of the package found to match the specified version exactly.

  • QUIET: An optional parameter that suppresses error messages if the package cannot be found.

  • MODULE: An optional parameter indicating that the package to be found is a module file rather than a package.

  • REQUIRED: An optional parameter that generates an error message and stops the configuration process if the package cannot be found.

  • COMPONENTS [components...]: An optional parameter used to specify components of the package.

  • OPTIONAL_COMPONENTS components...: An optional parameter used to specify optional components of the package.

  • NO_POLICY_SCOPE: An optional parameter indicating that CMake should not modify CMake policies.

Usage examples

  1. 1. Find the Boost library, requiring at least version 1.70 (version >= 1.70):

    find_package(Boost 1.70 REQUIRED)
    

    This line of code tells CMake, “Help me find the Boost library, at least version 1.70, and report an error if it cannot be found.” If CMake successfully finds Boost, it will automatically set some variables, such as:

    Then, you can pass this information to CMake to handle the compilation process. For example, if you have a target my_target (such as the executable or library you want to compile), you can tell CMake that my_target needs to use Boost:

    # Tell CMake that my_target needs to use Boost's header files
    target_include_directories(my_target PRIVATE ${Boost_INCLUDE_DIRS})
    
    # Tell CMake that my_target needs to link the Boost library
    target_link_libraries(my_target PRIVATE ${Boost_LIBRARIES})
    

    This code means: target_include_directories(): Add the path to Boost’s header files to the compiler’s search path, ensuring that the compiler can find Boost’s header files during compilation. target_link_libraries(): Link the Boost library to my_target, ensuring that the generated program can correctly link to the Boost library.

  • Boost_INCLUDE_DIRS: Path to Boost’s header files

  • Boost_LIBRARIES: Path and name of Boost’s library files

  • 2. Find the OpenSSL package, requiring it to be found:

    find_package(OpenSSL REQUIRED)
    
  • 3. Find the MPI package, specifying the C++ component:

    find_package(MPI REQUIRED COMPONENTS CXX)
    
  • 4. Find the HDF5 package, but do not require it to be found:

    find_package(HDF5 QUIET)
    if(HDF5_FOUND)
        message(STATUS "HDF5 found: ${HDF5_VERSION}")
    else()
        message(STATUS "HDF5 not found")
    endif()
    
  • 5. Find the PythonInterp package, specifying the version, and print a warning if it cannot be found:

    find_package(PythonInterp 3.6 REQUIRED)
    if(NOT PythonInterp_FOUND)
        message(WARNING "Python 3.6 or later is required!")
    endif()
    
  • 6. Find the CUDA package, specifying the version and specific components:

    find_package(CUDA 11.0 REQUIRED COMPONENTS Runtime)
    
  • Custom path for finding dependent libraries

    In actual development, some libraries are not installed in the system’s default paths (such as /usr/lib or /usr/local/lib), which causes find_package() to be unable to find them. In this case, you need to tell CMake where to look for these libraries.

    Default search order for find_package()

    1. 1. User-specified hint paths: If the HINTS or PATHS parameters are used in find_package(), these paths will be prioritized.

    2. 2. Globally configured paths: CMake will prioritize searching globally specified paths:

    • CMAKE_PREFIX_PATH: This is a global variable that can be configured via the command line or in CMakeLists.txt.

    • • Environment variables specific to library support. For example: Boost supports the BOOST_ROOT environment variable. OpenCV supports the OpenCV_DIR environment variable.

  • 3. Library’s own configuration file paths: If the library is built using CMake, it usually comes with <PackageName>Config.cmake or <lowercasename>-config.cmake files.

  • 4. Standard system paths: If none of the above paths are found, CMake will search the system’s default library paths:

    • • Linux: /usr/lib, /usr/local/lib

    • • macOS: /opt/local/lib, /usr/local/lib

    • • Windows: C:/Program Files/, C:/Program Files (x86)/

    If no suitable library is found in the above paths, CMake will report an error and stop the configuration process.

    How to solve

    Simply tell CMake the custom paths:

    1. 1. Use HINTS or PATHS to provide path hints:

      find_package(MyLib REQUIRED HINTS /custom/path/to/mylib)
      
    • HINTS provides additional search paths to CMake, which have a higher priority than the system default paths.

    • PATHS is used to specify explicit paths, which have a higher priority.

  • 2. Set the CMAKE_PREFIX_PATH variable:

    set(CMAKE_PREFIX_PATH "/custom/path/to/mylib")
    find_package(MyLib REQUIRED)
    
  • Summary

    • • Using find_library() and find_path() can help you find specific library files and header files, especially when the library does not provide a Config.cmake file.

    • find_library() is used to find library files, while find_path() is used to find header files.

    Using package managers

    Using package managers (such as vcpkg and Conan) can make it easier to manage external dependencies, automatically downloading, installing, and configuring libraries, simplifying the development process. With package managers, you can avoid manually handling library installations and path configurations.

    1. 1. vcpkg: A cross-platform package manager that simplifies library installation and management.

      vcpkg install opencv
      

      Set the vcpkg toolchain file in CMake:

      set(CMAKE_TOOLCHAIN_FILE "path_to_vcpkg/scripts/buildsystems/vcpkg.cmake")
      find_package(OpenCV REQUIRED)
      target_link_libraries(my_target PRIVATE ${OpenCV_LIBS})
      
    2. 2. Conan: Another popular C++ package manager.

      pip install conan
      conan install boost/1.70.0@
      

      Use Conan in CMake files:

      include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
      conan_basic_setup()
      target_link_libraries(my_target PRIVATE Boost::Boost)
      
    3. 3. Using Git submodules: Add external dependency libraries as Git submodules to the project, automatically downloading and building them.

      git submodule add <https://github.com/boostorg/boost.git> external/boost
      git submodule update --init
      

      Introduce submodules in CMake:

      add_subdirectory(external/boost)
      target_link_libraries(my_target PRIVATE boost)
      

    Through these methods, you can manage external library dependencies in your project more efficiently, reducing the complexity of manual configurations.

    1.8 include_directories

    The include_directories command is used to add header file search paths to a CMake project, allowing you to reference header files directly using relative paths or filenames during the compilation process. Below is the basic syntax and detailed usage examples, using OpenCV and Boost libraries as examples:

    Basic syntax:

    include_directories([AFTER|BEFORE] [SYSTEM] directory1 [directory2 ...])
    

    Where:

    • AFTER and BEFORE are optional parameters used to specify the order of adding directories, defaulting to AFTER, meaning added after other include directories.

    • SYSTEM is an optional parameter used to mark the header files in these directories as system header files, which the compiler may ignore warning messages from.

    • directory1, directory2, … are the header file search paths to be added.

    Add the header file search path for OpenCV:

    # Find the OpenCV package
    find_package(OpenCV REQUIRED)
    
    set (INCLUDE_PATH D:\\ProgramFiles\\Qt\\qt5_7_lib_shared_64)
    include_directories(${INCLUDE_PATH}/include)
    
    # Add external library header directories
    include_directories(/path/to/my_library/include)
    # Add OpenCV's header file search path
    include_directories(${OpenCV_INCLUDE_DIRS})
    

    In these two examples, we first used find_package to find the OpenCV package and obtain its relevant information. Then, by using include_directories, we added their header file search paths to the project. This setup allows us to directly use the functionalities provided by the OpenCV library in the source code without specifying the full path or filename.

    find_package is used to find and configure external packages in CMake projects. When you call find_package(OpenCV REQUIRED), CMake searches for the installed OpenCV package in the system and sets relevant variables, including OpenCV_INCLUDE_DIRS and OpenCV_LIBS.

    • • The OpenCV_INCLUDE_DIRS variable contains the search paths for OpenCV header files.

    • • The OpenCV_LIBS variable contains the paths and names of OpenCV library files. Therefore, when you use ${OpenCV_INCLUDE_DIRS} in CMakeLists.txt, you are actually telling CMake to look for the location of OpenCV header files, so that the compiler can find the corresponding header files during compilation.

    1.9 link_libraries

    The link_libraries command is used to link libraries to target files. This command has been marked as deprecated in CMake version 3.13, and it is recommended to use the target_link_libraries command instead. However, I will provide the basic syntax and a simple usage example of link_libraries.

    Basic syntax:

    link_libraries(library1 [library2 ...])
    

    Where:

    • library1, library2, … are the names of the libraries to be linked.

    Detailed usage example:

    # Link a library to an executable
    link_libraries(my_library)
    

    In this example, we use the link_libraries command to link a library named my_library to the executable. Although link_libraries can accomplish this task, its usage is relatively simple and not flexible enough. Therefore, it is recommended to use the more modern target_link_libraries command instead, especially in large projects.

    • link_libraries indicates adding the search path for third-party lib library files. If the project needs to use a certain third-party library’s lib file during compilation, link_libraries can be used.

    • • The target_link_libraries command specifies which lib library file is needed by the subproject. (The lib library file must be found in the search path.)

    1.10 target_link_libraries

    The target_link_libraries command is used to link target files (such as executables, library files) to one or more libraries. This is the recommended way to use in CMake. Below is the basic syntax and detailed usage examples:

    Basic syntax:

    target_link_libraries(target_name library1 [library2 ...])
    

    Where:

    • target_name is the name of the target file, which can be an executable file, library file, etc.

    • library1, library2, … are the names of the libraries to be linked to the target file.

    1. 1. Basic usage

    # Add source files
    add_executable(my_program main.cpp)
    
    # Find and link external libraries (using OpenCV as an example)
    find_package(OpenCV REQUIRED)
    target_link_libraries(my_program ${OpenCV_LIBS})
    

    In this example, we first use add_executable to add an executable file named my_program. Then, we use find_package to find and configure the OpenCV package and obtain its relevant information. Finally, we use target_link_libraries to link the OpenCV library to the my_program executable file.

    The target_link_libraries command is used to link the target with one or more libraries, which can be static libraries, dynamic libraries, or executable files.

    Basic syntax:

    target_link_libraries(target_name
        item1
        item2
        ...
    )
    
    • target_name: Target name, which can be an executable file, static library, or dynamic library.

    • item1, item2, …: Names of the libraries to be linked, which can be the names of libraries, paths, or variables.

    1. 1. Link a static library:

    # Assume there is a static library named my_library
    add_library(my_library STATIC ${LIBRARY_SOURCES})
    
    # Link the executable my_executable with the static library my_library
    target_link_libraries(my_executable my_library)
    
    1. 1. Link a dynamic library:

    # Assume there is a dynamic library named my_dynamic_library
    add_library(my_dynamic_library SHARED ${DYNAMIC_LIBRARY_SOURCES})
    
    # Link the executable my_executable with the dynamic library my_dynamic_library
    target_link_libraries(my_executable my_dynamic_library)
    
    1. 1. Link both static and dynamic libraries:

    # Link the executable my_executable with both static library my_library and dynamic library my_dynamic_library
    target_link_libraries(my_executable my_library my_dynamic_library)
    

    In the above examples, the target_link_libraries command links the specified target (my_executable) with the given libraries (my_library, my_dynamic_library). This allows the target to access the functions and variables defined in the libraries.

    It is important to note that when linking dynamic libraries, you also need to ensure that the dynamic libraries can be correctly found at runtime, which can be done by setting environment variables like LD_LIBRARY_PATH or placing the dynamic libraries in appropriate paths at runtime.

    export LD_LIBRARY_PATH=/mnt/xxx/usr/lib/
    

    1.11 include

    The include command is used to include other CMake script files in a CMake script so that the variables, functions, and commands defined in the included file can be used in the current script.

    Basic syntax:

    include(filename)
    
    • filename: The path of the file to be included. It can be a relative or absolute path.

    Usage example:

    Suppose your project structure is as follows:

    project/
    │
    ├── CMakeLists.txt
    └── cmake/
        └── protobuf-generate.cmake
    

    You want to include the protobuf-generate.cmake file in the main project’s CMakeLists.txt, you can do it like this:

    # project/CMakeLists.txt
    
    cmake_minimum_required(VERSION 3.0)
    project(MyProject)
    
    # Include other CMake script files
    include(cmake/protobuf-generate.cmake)
    
    # Other project configuration and build logic
    

    Then, in the included cmake/protobuf-generate.cmake file, you can define your generation logic:

    # project/cmake/protobuf-generate.cmake
    
    # Define generation logic
    # Here you can define variables, functions, add targets, etc.
    
    # For example, define a variable
    set(PROTOBUF_FILES
        ${CMAKE_CURRENT_SOURCE_DIR}/protobuf/file1.proto
        ${CMAKE_CURRENT_SOURCE_DIR}/protobuf/file2.proto
    )
    
    # For example, add a custom command and target to generate protobuf files
    add_custom_command(
        OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated_protobuf_files.cpp
        COMMAND protoc ${PROTOBUF_FILES} --cpp_out=${CMAKE_CURRENT_BINARY_DIR}
        DEPENDS ${PROTOBUF_FILES}
        COMMENT "Generating Protobuf files"
    )
    
    add_custom_target(generate_protobuf_files
        DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/generated_protobuf_files.cpp
    )
    

    By using the include command, you can include other CMake script files in the main project’s CMakeLists file, making your project structure clearer and allowing you to reuse existing code logic.

    1.12 add_executable

    The add_executable command is used to create an executable target, which compiles source files into an executable file. Below is the basic syntax and detailed usage examples:

    Basic syntax:

    add_executable(target_name source1 [source2 ...])
    

    Where:

    • target_name is the name of the executable file.

    • source1, source2, … are the source files to be compiled into the executable file.

    Detailed usage example:

    Suppose we have a simple project that includes two source files main.cpp and functions.cpp, and we want to compile them into an executable file.

    # Add executable target
    add_executable(my_program main.cpp functions.cpp)
    

    1.13 add_library

    The add_library command is used to add a new library (static library, dynamic library, or module library) to a CMake project.

    Basic syntax:

    add_library(<lib_name> [STATIC | SHARED | MODULE | OBJECT]
                [EXCLUDE_FROM_ALL]
                source1.cpp source2.cpp ...)
    
    • <lib_name>: The name of the library.

    • STATIC: Generates a static library (default).

    • SHARED: Generates a dynamic library.

    • MODULE: Generates a module library.

    • OBJECT: Generates an object library.

    • EXCLUDE_FROM_ALL: Indicates that the library will not be depended on by the default build target.

    Usage examples:

    # Add source files
    set(SOURCES source1.cpp source2.cpp)
    
    # Generate static library
    add_library(my_static_lib STATIC ${SOURCES})
    
    # Generate dynamic library
    add_library(my_dynamic_lib SHARED ${SOURCES})
    
    # Generate module library
    add_library(my_module_lib MODULE ${SOURCES})
    
    # Generate object library
    add_library(my_object_lib OBJECT ${SOURCES})
    

    In the above examples, the add_library command is used to generate a static library my_static_lib, a dynamic library my_dynamic_lib, a module library my_module_lib, and an object library my_object_lib, specifying the corresponding source file lists.

    The type of these libraries is determined by the optional parameters, defaulting to STATIC, which means generating a static library. If you need to generate a dynamic library, module library, or object library, simply specify the type accordingly in the command.

    1.14 target_include_directories

    The target_include_directories command is used to add include directories to a specific target so that header files can be found during compilation.

    Basic syntax:

    target_include_directories(target_name [SYSTEM] [BEFORE]
                               PRIVATE|PUBLIC|INTERFACE
                               [items1...]
                               [items2...]
                               ...)
    
    • target_name: The name of the target to which include directories are to be added.

    • SYSTEM: An optional parameter that specifies whether the included directories are treated as system header file directories.

    • BEFORE: An optional parameter that specifies that the include directories should be added to the beginning of the target’s header file search path.

    • PRIVATE|PUBLIC|INTERFACE: Specifies the scope of the include directories.

    • items1, items2, …: The include directories to be added, which can be directory paths, variables, generator expressions, etc.

    Usage example:

    Suppose you have a library target named my_library, and you want to add the include directory to the include path of that target.

    target_include_directories(my_library PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
    

    In this example, my_library is the target name, and PUBLIC indicates that the include path will be added to the public interface of my_library so that other targets can access it. ${CMAKE_CURRENT_SOURCE_DIR}/include is the include directory path to be added.

    You can also add multiple directories:

    target_include_directories(my_library PUBLIC
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${SOME_OTHER_DIR}/include
    )
    

    In this example, in addition to ${CMAKE_CURRENT_SOURCE_DIR}/include, ${SOME_OTHER_DIR}/include is also added to the public interface of my_library. This target_include_directories has transitive properties; if a library is built and then other executable files call that library, using target_include_directories with PUBLIC allows access to the corresponding include paths.

    1.15 install

    The install command is used to install the generated files (executables, library files, header files, etc.) to specified locations. This command can be used to install your software into the system, making it convenient for other users to use your software.

    Basic syntax:

    install(TARGETS target_name
            [EXPORT <export-name>]
            [ARCHIVE|LIBRARY|RUNTIME|OBJECTS|FRAMEWORK|BUNDLE|DESTINATION <dir>]
            [PERMISSIONS permissions...]
            [CONFIGURATIONS [Debug|Release|...]]
            [COMPONENT <component>]
            [OPTIONAL]
            [INCLUDES DESTINATION [<dir> ...]]
    )
    
    install(FILES files...
            [DESTINATION <dir>]
            [PERMISSIONS permissions...]
            [CONFIGURATIONS [Debug|Release|...]]
            [COMPONENT <component>]
            [OPTIONAL]
    )
    
    install(DIRECTORY dirs...
            [DESTINATION <dir>]
            [FILE_PERMISSIONS permissions...]
            [DIRECTORY_PERMISSIONS permissions...]
            [USE_SOURCE_PERMISSIONS]
            [CONFIGURATIONS [Debug|Release|...]]
            [COMPONENT <component>]
            [OPTIONAL]
    )
    
    install(EXPORT <export-name>
            DESTINATION <dir>
            [NAMESPACE <namespace>]
            [FILE <filename>]
    )
    
    1. TARGETS: Install targets, which can be executables, library files, etc. This option is used to specify which targets to install, which can be created using add_executable, add_library, or add_custom_target. It tells CMake which build targets to install.

    • EXPORT <export-name>: Exports the target for use in other projects so that other projects can reference these targets.

      • ARCHIVE: Specifies that the install target is a static library archive file (.a file).

      • LIBRARY: Specifies that the install target is a shared library file (dynamic library, .so file).

      • RUNTIME: Specifies that the install target is an executable file.

      • OBJECTS: Specifies that the install target is an object library file (.o file).

      • FRAMEWORK: Specifies that the install target is a framework file on macOS.

      • BUNDLE: Specifies that the install target is a bundle file on macOS.

      • DESTINATION <dir>: Specifies the target path for the install target.

    • PERMISSIONS permissions...: Specifies the permissions for the install target.

    • CONFIGURATIONS [Debug|Release|...]: Specifies that the target should only be installed under the specified build configuration.

    • COMPONENT <component>: Specifies the component of the install target.

    • OPTIONAL: Specifies whether the install target is optional.

    • INCLUDES DESTINATION [<dir> ...]: Specifies the header files for the install target.

  • 2. FILES: List of files to install. This option is used to specify the list of files to install. You can list each file to be installed and then use install(FILES ...) to install them to the specified location.

    • DESTINATION <dir>: Specifies the target path for installation.

    • PERMISSIONS permissions...: Specifies the permissions for the install files.

    • CONFIGURATIONS [Debug|Release|...]: Specifies that the files should only be installed under the specified build configuration.

    • COMPONENT <component>: Specifies the component of the install files.

    • OPTIONAL: Specifies whether the install files are optional.

  • 3. DIRECTORY: List of directories to install. This option is used to specify the list of directories to install. It will recursively copy all files and subdirectories in the directory.

    • DESTINATION <dir>: Specifies the target path for installation.

    • FILE_PERMISSIONS permissions...: Specifies the permissions for the install files.

    • DIRECTORY_PERMISSIONS permissions...: Specifies the permissions for the install directories.

    • USE_SOURCE_PERMISSIONS: Use the permissions of the source files.

    • CONFIGURATIONS [Debug|Release|...]: Specifies that the directories should only be installed under the specified build configuration.

    • COMPONENT <component>: Specifies the component of the install directories.

    • OPTIONAL: Specifies whether the install directories are optional.

  • 4. EXPORT: Exports targets for use in other projects. This option is used to export targets for use in other projects so that other projects can reference these targets.

    • DESTINATION <dir>: Specifies the target path for the exported targets.

    • NAMESPACE <namespace>: Specifies the namespace for the exported targets.

    • FILE <filename>: Specifies the filename for the exported targets.

    Usage examples:

    Install an executable:

    install(TARGETS my_executable DESTINATION bin)
    

    Install a library file:

    install(TARGETS my_library DESTINATION lib)
    

    Install a header file:

    install(FILES my_header.h DESTINATION include)
    

    Install an entire directory:

    install(DIRECTORY my_directory DESTINATION share/my_project)
    

    Export targets:

    install(EXPORT my_targets DESTINATION lib/my_project)
    

    These examples demonstrate how to install executable files, library files, header files, entire directories, and how to export targets to other projects.

    1.16 Efficient CMake Configuration: Conditional Judgments, Loops, and String Operations

    As projects become more complex, CMake needs to handle various situations more intelligently. By learning conditional judgments, loops, and string operations, you can make CMake more flexible and efficient!

    1.16.1 Conditional Judgments

    CMake acts like a smart decision-maker, able to make choices based on different conditions. For example, suppose you are compiling a cross-platform project that requires linking different libraries on Windows, additional configurations on macOS, and none on Linux. CMake can easily solve these problems through conditional judgments.

    For example, suppose we need to use different libraries on different platforms:

    if(WIN32)
        # Use specific libraries on Windows
        set(MY_LIBRARY "C:/libs/windows_lib.lib")
        # Print debug information
        message("Using library for Windows")
    elseif(APPLE)
        # Use other libraries on macOS
        set(MY_LIBRARY "/usr/local/lib/mac_lib.dylib")
        message("Using library for macOS")
    else()
        # Use another library on Linux
        set(MY_LIBRARY "/usr/lib/linux_lib.so")
        message("Using library for Linux")
    endif()
    
    # Finally link the library
    target_link_libraries(my_project ${MY_LIBRARY})
    

    Explanation: On Windows: If on Windows, CMake will set MY_LIBRARY to the Windows-specific library path. On macOS: If on macOS, CMake will set it to the macOS version of the library. On Linux: If on Linux, CMake will choose the library for Linux. In this way, regardless of which operating system you are on, CMake can automatically select the correct library without requiring you to manually change the code each time.

    1.16.2 Loops: foreach and while

    1. 1. foreach loop: The foreach loop is particularly suitable for iterating over a list. For example, suppose you have a bunch of source files and want to perform some operations on each file, such as printing the filename or setting compilation options.

    set(SOURCES main.cpp utils.cpp helper.cpp)
    
    foreach(SRC ${SOURCES})
        message("Compiling source file: ${SRC}")
        # More operations can be done here, such as adding each source file to the target
        target_sources(hello PRIVATE ${SRC})
    endforeach()
    

    Understanding: Here, foreach takes each filename from SOURCES one by one, executing message() to print the filename each time. You can also perform other operations in the loop, such as setting compilation options for each file or adding these files to the target. foreach is suitable when you have a bunch of files, options, or other content that needs to be processed one by one.

    1. 2. while loop: The while loop executes repeatedly based on a condition until the condition is no longer met. In simple terms, you set a “threshold” and check this condition each time until it is satisfied.

    set(COUNT 1)
    
    while(COUNT LESS 5)
        message("Current count: ${COUNT}")
        math(EXPR COUNT "${COUNT} + 1")
    endwhile()
    

    Understanding: In this example, while first checks if COUNT is less than 5. If it is, it prints it and then increments COUNT by 1. It continues checking until COUNT reaches 5 and stops. This is a continuous loop until you meet a certain condition (in this case, COUNT being less than 5). while is suitable when you need to do something repeatedly based on certain conditions until they are no longer met.

    1.17 String Operations: Concatenation, Splitting, and Searching

    1. 1. String Concatenation (STRING(CONCAT …)): Concatenation means combining two or more strings into one larger string.

    set(DIR "/home/user/project/")
    set(FILE_NAME "main.cpp")
    
    # Concatenate strings
    set(FULL_PATH "${DIR}${FILE_NAME}")
    
    message("Full path: ${FULL_PATH}")
    

    Explanation: We concatenate the path and filename, ultimately forming /home/user/project/main.cpp. The method used for concatenation is to insert the two strings together using ${}, and CMake will automatically concatenate them.

    1. 1. String Splitting (STRING(REGEX REPLACE …)): Sometimes, we need to extract a part from a long string.

    set(PATH "/home/user/project/main.cpp")
    
    # Get the filename
    string(REGEX REPLACE ".*/" "" FILE_NAME ${PATH})
    
    message("File name: ${FILE_NAME}")
    

    Explanation: Here, we used string(REGEX REPLACE ...) to remove everything in the path, leaving only the filename part. The regular expression ^.*/ means “match all characters from the beginning to the last /“. REPLACE "^.*/" "" means to replace the matched string (the path part) with an empty string, thus easily obtaining the filename main.cpp from the path.

    1. 1. String Searching (STRING(FIND …)): Sometimes, you need to search for a substring within a string.

    set(PATH "/home/user/project/")
    
    # Search for a substring in the string
    string(FIND ${PATH} "user" POSITION)
    
    if(POSITION GREATER -1)
        message("Found 'user' in the path at position: ${POSITION}")
    else()
        message("'user' not found in the path.")
    endif()
    

    Explanation: string(FIND ...) returns the position of the substring in the string; if not found, it returns -1. In this example, CMake searches for the occurrence of user in the path, returning position 5, indicating it starts from the 6th character. You can use this return value to determine whether a substring was found and then perform corresponding actions.

    2. CMake Variables

    Below are some commonly used CMake variables and their corresponding content, along with usage examples in CMakeLists.txt and output:

    Variable Name Content Usage and Output in CMakeLists.txt Output Result
    CMAKE_BINARY_DIR The top-level binary directory of the project currently being built message("CMAKE_BINARY_DIR: ${CMAKE_BINARY_DIR}") CMAKE_BINARY_DIR: /path/to/build
    PROJECT_BINARY_DIR Same as CMAKE_BINARY_DIR message("PROJECT_BINARY_DIR: ${PROJECT_BINARY_DIR}") PROJECT_BINARY_DIR: /path/to/build
    _BINARY_DIR Same as CMAKE_BINARY_DIR message("_BINARY_DIR: ${_BINARY_DIR}") _BINARY_DIR: /path/to/build
    CMAKE_SOURCE_DIR The top-level source code directory of the project currently being configured message("CMAKE_SOURCE_DIR: ${CMAKE_SOURCE_DIR}") CMAKE_SOURCE_DIR: /path/to/source
    PROJECT_SOURCE_DIR Same as CMAKE_SOURCE_DIR message("PROJECT_SOURCE_DIR: ${PROJECT_SOURCE_DIR}") PROJECT_SOURCE_DIR: /path/to/source
    _SOURCE_DIR Same as CMAKE_SOURCE_DIR message("_SOURCE_DIR: ${_SOURCE_DIR}") _SOURCE_DIR: /path/to/source
    CMAKE_CURRENT_SOURCE_DIR The source code directory where the currently processed CMakeLists.txt is located message("CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}") CMAKE_CURRENT_SOURCE_DIR: /path/to/source/current
    CMAKE_CURRENT_BINARY_DIR The build directory where the currently processed CMakeLists.txt is located message("CMAKE_CURRENT_BINARY_DIR: ${CMAKE_CURRENT_BINARY_DIR}") CMAKE_CURRENT_BINARY_DIR: /path/to/build/current
    CMAKE_CURRENT_LIST_FILE The full path of the currently processed CMakeLists.txt file message("CMAKE_CURRENT_LIST_FILE: ${CMAKE_CURRENT_LIST_FILE}") CMAKE_CURRENT_LIST_FILE: /path/to/source/CMakeLists.txt
    CMAKE_CURRENT_LIST_LINE The line number of the current line in CMakeLists.txt message("CMAKE_CURRENT_LIST_LINE: ${CMAKE_CURRENT_LIST_LINE}") CMAKE_CURRENT_LIST_LINE: 10
    CMAKE_MODULE_PATH Additional paths for CMake to find module files message("CMAKE_MODULE_PATH: ${CMAKE_MODULE_PATH}") CMAKE_MODULE_PATH: /path/to/cmake/modules
    EXECUTABLE_OUTPUT_PATH Sets the output path for generated executable files message("EXECUTABLE_OUTPUT_PATH: ${EXECUTABLE_OUTPUT_PATH}") EXECUTABLE_OUTPUT_PATH: /path/to/build/bin
    PROJECT_NAME The name of the current project message("PROJECT_NAME: ${PROJECT_NAME}") PROJECT_NAME: MyProject
    CMAKE_BUILD_TYPE The current build type (Release, Debug, etc.) message("CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") CMAKE_BUILD_TYPE: Release
    CMAKE_CXX_FLAGS C++ compiler options message("CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS}") CMAKE_CXX_FLAGS: -std=c++11 -Wall
    CMAKE_CXX_FLAGS_RELEASE C++ compiler options for Release build type message("CMAKE_CXX_FLAGS_RELEASE: ${CMAKE_CXX_FLAGS_RELEASE}") CMAKE_CXX_FLAGS_RELEASE: -O3
    CMAKE_MAJOR_VERSION The major version number of CMake message("CMAKE_MAJOR_VERSION: ${CMAKE_MAJOR_VERSION}") CMAKE_MAJOR_VERSION: 3
    CMAKE_SYSTEM The type of the current operating system message("CMAKE_SYSTEM: ${CMAKE_SYSTEM}") CMAKE_SYSTEM: Linux
    CMAKE_SYSTEM_VERSION The version of the current operating system message("CMAKE_SYSTEM_VERSION: ${CMAKE_SYSTEM_VERSION}") CMAKE_SYSTEM_VERSION: 18.04
    CMAKE_SYSTEM_PROCESSOR The architecture of the current system’s processor message("CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR}") CMAKE_SYSTEM_PROCESSOR: x86_64
    LIBRARY_OUTPUT_PATH Sets the output path for generated library files message("LIBRARY_OUTPUT_PATH: ${LIBRARY_OUTPUT_PATH}") LIBRARY_OUTPUT_PATH: /path/to/build/lib
    CMAKE_SIZEOF_VOID_P The size of a void pointer (in bytes), which tells us whether the CPU is 32-bit or 64-bit message("CMAKE_SIZEOF_VOID_P: ${CMAKE_SIZEOF_VOID_P}") CMAKE_SIZEOF_VOID_P: 8
    CMAKE_HOST_SYSTEM_PROCESSOR The architecture of the host system’s processor message("CMAKE_HOST_SYSTEM_PROCESSOR: ${CMAKE_HOST_SYSTEM_PROCESSOR}") CMAKE_HOST_SYSTEM_PROCESSOR: x86_64

    👉 Follow us to discover more interesting content on autonomous driving/embodied intelligence/GitHub! 🚀

    Leave a Comment