1. Running cmake

1.1. cmake选项

–build 指定输出目录

-j 指定并行度

–target 指定编译目标

1.2. 选择编译器

EXPORT CC=clang CXX=clang++ cmake ..

1.3. 选择生成器

默认为make 通过cmake -G Ninja指定ninja

1.4. 指定编译选项

cmake -DXXX=YYY

cmake会将各种编译选项缓存到一个文件中,即CMakeCache.txt

1.5. 调试

调试makefile: VERBOSE=1 make

调试cmake:

  • cmake –trace, 打印出cmake运行的每一行脚本
  • cmake –trace-source= 打印出中运行的每一行脚本

1.6. 指定编译类型

-DCMAKE_BUILD_TYPE:Debug或Release或RelWithDebInfo

-DCMAKE_INSTALL_PREFIX: 指定安装路径,默认为/usr/local

-DBUILD_SHARED_LIBS: 是否编译共享库,ON or OFF

-DBUILD_TESTING: 是否编译tests

2. Do's and Don'ts

2.1. 不要做

  • 不要使用全局函数: link_directories, include_libraries等
  • 非必要不使用PULIBC限制
  • 不要用glob匹配文件:当新增文件时,除非重新跑一遍cmake, make或其他编译工具将不会感知到新文件
  • lib不要直接link到最终的target
  • link lib时不要忽略PUBLIC/PRIVATE关键字

2.2. 要做

  • 把cmake当做正经代码维护
  • 站在target的角度思考
  • 善用alias
  • 将通用功能抽象成函数或宏
  • 函数名统一小写
  • 善用cmake_policy

2.3. 选择cmake版本

2.3.1. os支持

  • 3.4: The bare minimum. Never set less.
  • 3.7: Debian old-stable.
  • 3.10: Ubuntu 18.04.
  • 3.11: CentOS 8 (use EPEL or AppSteams, though)
  • 3.13: Debian stable.
  • 3.16: Ubuntu 20.04.
  • 3.19: First to support Apple Silicon.
  • latest: pip/conda-forge/homebew/chocolaty, ect.

笔者目前用的是3.16

2.3.2. 特性

  • 3.8: C++ meta features, CUDA, lots more
  • 3.11: IMPORTED INTERFACE setting, faster, FetchContent, COMPILE_LANGUAGE in IDEs
  • 3.12: C++20, cmake --build build -j N, SHELL:, FindPython
  • 3.14/3.15: CLI, FindPython updates
  • 3.16: Unity builds / precompiled headers, CUDA meta features
  • 3.17/3.18: Lots more CUDA, metaprogramming

3. What's new in in CMake

  • cmake 3.0: interface libraries, 允许将一组头文件抽象成一个lib
  • cmake 3.1: 支持c++11和compile features
  • cmake 3.2: 支持UTF-8
  • cmake 3.4: 支持swift语言,支持ccache
  • cmake 3.5: 支持ARM平台
  • cmake 3.6: 支持clang-tidy
  • 其他:略

4. 基础知识

指定最小版本

cmake_minimum_required(VERSION 3.1)

创建工程

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

新增executable

add_executable(one two.cpp three.h)

新增library

add_library(one STATIC two.cpp three.h)

用户可指定STATIC、SHARED、MODULE 。缺省使用BUILD_SHARED_LIBS选项。

INTERFAC用于创建头文件库

ALIAS用于指定库的别名.

绑定头文件路径

target_include_directories(one PUBLIC include)

PUBLIC表示所有依赖lib的target都自动绑定了该头文件路径

PRIVATE表示该头文件路径仅对本target有效

INTERFACE表示该头文件路径仅对依赖该lib的target生效

PUBLIC = PRIVATE + INTERFACE

5. 变量和cache

定义变量

set(MY_VARIABLE "value")

访问变量时使用${MY_VARIABLE}

变量仅在当前作用域内有效。

定义变量(多个值)

set(MY_LIST "one" "two")

等效于

set(MY_LIST "one;two")

属性

set_property(TARGET TargetName
             PROPERTY CXX_STANDARD 11)

set_target_properties(TargetName PROPERTIES
                      CXX_STANDARD 11)
get_property(ResultVariable TARGET TargetName PROPERTY CXX_STANDARD)

6. cmake编程

6.1. 控制流

if(variable)
    # If variable is `ON`, `YES`, `TRUE`, `Y`, or non zero number
else()
    # If variable is `0`, `OFF`, `NO`, `FALSE`, `N`, `IGNORE`, `NOTFOUND`, `""`, or ends in `-NOTFOUND`
endif()
# If variable does not expand to one of the above, CMake will expand it then try again

There are a variety of keywords as well, such as:

  • Unary: NOT, TARGET, EXISTS (file), DEFINED, etc.
  • Binary: STREQUAL, AND, OR, MATCHES (regular expression), VERSION_LESS, VERSION_LESS_EQUAL (CMake 3.7+), etc.
  • Parentheses can be used to group

generator-expressions

target_compile_options(MyTarget PRIVATE "$<$<CONFIG:Debug>:--my-flag>")

当使用debug编译时,加上–my-flag编译选项

That last one is very common. You'll see something like this in almost every package that supports installing:

target_include_directories(
    MyTarget
  PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

6.2. 宏和函数

function(SIMPLE REQUIRED_ARG)
    message(STATUS "Simple arguments: ${REQUIRED_ARG}, followed by ${ARGN}")
    set(${REQUIRED_ARG} "From SIMPLE" PARENT_SCOPE)
endfunction()

simple(This Foo Bar)
message("Output: ${This}")

The output would be:

-- Simple arguments: This, followed by Foo;Bar
Output: From SIMPLE

7. cmake交互

7.1. 配置文件

Version.h.in

#pragma once

#define MY_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define MY_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define MY_VERSION_PATCH @PROJECT_VERSION_PATCH@
#define MY_VERSION_TWEAK @PROJECT_VERSION_TWEAK@
#define MY_VERSION "@PROJECT_VERSION@"
configure_file (
    "${PROJECT_SOURCE_DIR}/include/My/Version.h.in"
    "${PROJECT_BINARY_DIR}/include/My/Version.h"
)

7.2. 读取文件

# Assuming the canonical version is listed in a single line
# This would be in several parts if picking up from MAJOR, MINOR, etc.
set(VERSION_REGEX "#define MY_VERSION[ \t]+\"(.+)\"")

# Read in the line containing the version
file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/include/My/Version.hpp"
    VERSION_STRING REGEX ${VERSION_REGEX})

# Pick out just the version
string(REGEX REPLACE ${VERSION_REGEX} "\\1" VERSION_STRING "${VERSION_STRING}")

# Automatically getting PROJECT_VERSION_MAJOR, My_VERSION_MAJOR, etc.
project(My LANGUAGES CXX VERSION ${VERSION_STRING})

8. 如何组织工程

- project
  - .gitignore
  - README.md
  - LICENCE.md
  - CMakeLists.txt
  - cmake
    - FindSomeLib.cmake
    - something_else.cmake
  - include
    - project
      - lib.hpp
  - src
    - CMakeLists.txt
    - lib.cpp
  - apps
    - CMakeLists.txt
    - app.cpp
  - tests
    - CMakeLists.txt
    - testlib.cpp
  - docs
    - CMakeLists.txt
  - extern
    - googletest
  - scripts
    - helper.py

将cmake/目录加入到工程中.

set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})

9. 运行其他程序

9.1. 在配置时运行命令

find_package(Git QUIET)

if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git")
    execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
                    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
                    RESULT_VARIABLE GIT_SUBMOD_RESULT)
    if(NOT GIT_SUBMOD_RESULT EQUAL "0")
        message(FATAL_ERROR "git submodule update --init --recursive failed with ${GIT_SUBMOD_RESULT}, please checkout submodules")
    endif()
endif()

9.2. 在编译时运行命令

find_package(PythonInterp REQUIRED)
add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/include/Generated.hpp"
    COMMAND "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/scripts/GenerateHeader.py" --argument
    DEPENDS some_target)

add_custom_target(generate_header ALL
    DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/include/Generated.hpp")

install(FILES ${CMAKE_CURRENT_BINARY_DIR}/include/Generated.hpp DESTINATION  include)

10. 一个简单示例

# Almost all CMake files should start with this
# You should always specify a range with the newest
# and oldest tested versions of CMake. This will ensure
# you pick up the best policies.
cmake_minimum_required(VERSION 3.1...3.22)

# This is your project statement. You should always list languages;
# Listing the version is nice here since it sets lots of useful variables
project(
  ModernCMakeExample
  VERSION 1.0
  LANGUAGES CXX)

# If you set any CMAKE_ variables, that can go here.
# (But usually don't do this, except maybe for C++ standard)

# Find packages go here.

# You should usually split this into folders, but this is a simple example

# This is a "default" library, and will match the *** variable setting.
# Other common choices are STATIC, SHARED, and MODULE
# Including header files here helps IDEs but is not required.
# Output libname matches target name, with the usual extensions on your system
add_library(MyLibExample simple_lib.cpp simple_lib.hpp)

# Link each target with other targets or add options, etc.

# Adding something we can run - Output name matches target name
add_executable(MyExample simple_example.cpp)

# Make sure you link your targets with this command. It can also link libraries and
# even flags, so linking a target that does not exist will not give a configure-time error.
target_link_libraries(MyExample PRIVATE MyLibExample)

11. 使用技巧

选择c++标准

set_target_properties(myTarget PROPERTIES
    CXX_STANDARD 11
    CXX_STANDARD_REQUIRED YES
    CXX_EXTENSIONS NO
)

启用pisition independent code

set(CMAKE_POSITION_INDEPENDENT_CODE ON)

集成ccache

find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
    set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
    set(CMAKE_CUDA_COMPILER_LAUNCHER "${CCACHE_PROGRAM}") # CMake 3.9+
endif()

集成clang-tidy

~/package # cmake -S . -B build-tidy -DCMAKE_CXX_CLANG_TIDY="$(which clang-tidy);-fix"

打印变量

message(STATUS "MY_VARIABLE=${MY_VARIABLE}")
include(CMakePrintHelpers)
cmake_print_variables(MY_VARIABLE)
cmake_print_properties(
    TARGETS my_target
    PROPERTIES POSITION_INDEPENDENT_CODE
)

trace cmake

cmake -S . -B build --trace-source=CMakeLists.txt

debug编译

-DCMAKE_BUILD_TYPE=Debug

12. 测试

add_test(
  NAME
    ExampleCMakeBuild
  COMMAND
    "${CMAKE_CTEST_COMMAND}"
             --build-and-test "${My_SOURCE_DIR}/examples/simple"
                              "${CMAKE_CURRENT_BINARY_DIR}/simple"
             --build-generator "${CMAKE_GENERATOR}"
             --test-command "${CMAKE_CTEST_COMMAND}"
)