项目配置

设置C/C++标准

# 必须在add_executable之前添加
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED True)

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_EXTENSIONS OFF)
set(CMAKE_C_STANDARD_REQUIRED True)

启用语言支持

enable_language(C CXX ASM ASM_NASM ASM_MARMASM ASM_MASM ASM-ATT)

获取主机详细信息

cmake_host_system_information(RESULT <VARIABLE> QUERY <KEY> …)

常用的KEY:

  • NUMBER_OF_LOGICAL_CORES:逻辑核数
  • NUMBER_OF_PHYSICAL_CORES:物理核数
  • IS_64BIT:若处理器是64位,则为1
  • HAS_MMX:若处理器支持MMX指令,则返回1
  • HAS_MMX_PLUS:若处理器支持Ext.MMX指令,则返回1
  • HAS_SSE:若处理器支持SSE指令,则返回1
  • HAS_SSE2:若处理器支持SSE2指令,则返回1
  • HAS_SSE_FP:若处理器支持SSEFP指令,则返回1
  • HAS_SSE_MMX:若处理器支持SSEMMX指令,则返回1

获取系统的位宽

if(CMAKE_SIZEOF_VOID_P EQUAL 8)
    message(STATUS "Target is 64 bits")
endif()

获取系统的端序

CMAKE_C_BYTE_ORDER
CMAKE_CXX_BYTE_ORDER

可能的值为:BIG_ENDIAN或LITTLE_ENDIAN

设置输出路径

# 设置静态库文件目录
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY <static-lib-dir>)
# 动态库文件目录
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY <shared-lib-dir>)
# 可执行文件目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY <exec-dir>)

设置构建类型

CMake预先内置了四种构建类型:

  • Debug
  • Release
  • RelWithDebInfo
  • MinSizeRel

并通过预置的变量CMAKE_BUILD_TYPE表示当前的构建类型,可以通过修改它的值来改变构建类型,

CMAKE_BUILD_TYPE变量的初始值为空,表示不指定任何构建类型。

设置CMAKE_BUILD_TYPE要在添加target之前进行。

不同构建类型对应的编译选项

构建类型 默认的编译选项
不指定CMAKE_BUILD_TYPE
Debug -g
Release -O3 -DNDEBUG
RelWithDebInfo -O2 -g -DNDEBUG
MinSizeRel -Os -DNDEBUG

cmake使用CMAKE_<LANGUAGE>_FLAGS_<BULID-TYPE>的变量代表不同的构建类型

当设置了 CMAKE_BUILD_TYPE 后,对应的的编译器选项就会被应用

# cmake --system-information | grep CMAKE_CXX_FLAGS
CMAKE_CXX_FLAGS == ""
CMAKE_CXX_FLAGS_DEBUG == "-g"
CMAKE_CXX_FLAGS_MINSIZEREL == "-Os -DNDEBUG"
CMAKE_CXX_FLAGS_RELEASE == "-O3 -DNDEBUG"
CMAKE_CXX_FLAGS_RELWITHDEBINFO == "-O2 -g -DNDEBUG"
CMAKE_CXX_FLAGS ""
CMAKE_CXX_FLAGS_DEBUG "-g"
CMAKE_CXX_FLAGS_DEBUG_INIT " -g"
CMAKE_CXX_FLAGS_INIT "  "
CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG"
CMAKE_CXX_FLAGS_MINSIZEREL_INIT " -Os -DNDEBUG"
CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG"
CMAKE_CXX_FLAGS_RELEASE_INIT " -O3 -DNDEBUG"
CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g -DNDEBUG"
CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT " -O2 -g -DNDEBUG"

可以看到flags变量有:

  • CMAKE_CXX_FLAGS_INIT: 全局变量,初始化时使用,无论是什么构建类型都会生效,默认为空
  • CMAKE_CXX_FLAGS:全局变量,无论是什么构建类型都会生效

  • CMAKE_CXX_FLAG_<BULID-TYPE>: 只在相应的构建类型下生效

如果CMAKE_CXX_FLAGS_INITCMAKE_CXX_FLAGS都设置了,相应的设置会叠加一起作用到gcc,由gcc决定最终使用哪个, 比如同时指定了 -O2-O3则gcc最后会按-O3来优化。

设置链接器(linker)的选项的变量也类似:

  • CMAKE_EXE_LINKER_FLAGS_<BUILD-TYPE>:用于可执行文件链接
  • CMAKE_SHARED_LINKER_FLAGS_<BUILD-TYPE>:用于共享库链接
  • CMAKE_STATIC_LINKER_FLAGS_<BUILD-TYPE>:用于静态库链接
# cmake --system-information | grep LINKER_FLAGS
CMAKE_EXE_LINKER_FLAGS ""
CMAKE_EXE_LINKER_FLAGS_DEBUG ""
CMAKE_EXE_LINKER_FLAGS_INIT "  "
CMAKE_EXE_LINKER_FLAGS_MINSIZEREL ""
CMAKE_EXE_LINKER_FLAGS_RELEASE ""
CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO ""

CMAKE_SHARED_LINKER_FLAGS ""
CMAKE_SHARED_LINKER_FLAGS_DEBUG ""
CMAKE_SHARED_LINKER_FLAGS_INIT "  "
CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL ""
CMAKE_SHARED_LINKER_FLAGS_RELEASE ""
CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO ""

CMAKE_STATIC_LINKER_FLAGS ""
CMAKE_STATIC_LINKER_FLAGS_DEBUG ""
CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL ""
CMAKE_STATIC_LINKER_FLAGS_RELEASE ""
CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO ""

导出compile_commands.json

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

项目构建

PUBLIC,PRIVATE,INTERFACE这三个关键字指定了目标文件依赖项的使用范围或者关系传递

cmake中的链接权限

权限 定义 使用场景 与其他权限的比较
PRIVATE 只有目标自身需要此链接库时使用。
如果其他目标链接了这个目标,它们不会继承这个链接库。
静态库内部依赖,不希望外部继承的依赖。 不会被其他目标继承。
PUBLIC
(默认值)
目标自身或其他目标链接了这个目标时使用。
如果其他目标链接这个目标时,它们会继承这个链接库。
共享库的公共依赖,或者希望被其他目标继承的依赖。 会被其他目标继承。
INTERFACE 目标自身不需要此链接库,但其他目标链接了这个目标时使用。 头文件库,或者只希望传递依赖而不实际链接的情况。 只会被其他目标继承。

target_include_directories()为例,假如项目结构如下:

# 结构
./
├── app.cpp
└── bar
    ├── bar.cpp
    ├── bar.h
    └── foo
        ├── foo.cpp
        └── foo.h

# 依赖
app──>libbar.so ──>libfoo.so

PRIVATE

如果生成 libbar.so时:

  • 只在 bar.cpp 中包含了 foo.h

  • bar.h (libbar.so 对外的头文件)没有包含 foo.h
  • app没有包含foo.h

app只知道bar的存在,完全不知道foo的存在

那么,bar/CMakeLists.txt应该这样写:

target_link_libraries(bar PRIVATE foo)
target_include_directories(bar PRIVATE foo)

INTERFACE

如果生成 libhello-world.so时:

  • bar.h (libbar.so 对外的头文件)中包含了 foo.h
  • bar.cpp 中没有使用foo定义的符号
  • app包含了foo.h(app可以引用foo的符号)

bar只是作为一个接口或者桥梁,把foo传递给了app

那么,hello-world/CMakeLists.txt应该这样写:

target_link_libraries(bar INTERFACE foo)
target_include_directories(bar INTERFACE foo)

PUBLIC

PUBLIC = PRIVATE + INTERFACE,如果生成 libhello-world.so时:

  • bar.h包含了foo.h
  • bar.cpp中引用了foo的符号
  • app中包含了foo.h(app可以引用foo的符号)

那么,hello-world/CMakeLists.txt应该这样写:

target_link_libraries(bar PUBLIC foo)
target_include_directories(bar PUBLIC foo)

总结

链接选项 app是否可用foo ldd foo readelf -d foo ldd bar readelf -d bar ldd app readelf -d app
PRIVATE statically linked foo foo foo, bar bar
INTERFACE statically linked statically linked foo, bar foo, bar
PUBLIC statically linked foo foo foo, bar foo, bar
链接选项 bar链接时 app链接时
PRIVATE 传入foo 传入bar
INTERFACE 不传入foo 传入bar,传入foo
PUBLIC 传入foo 传入bar,传入foo

使用哪个关键字,取决于当前文件夹下的源文件和头文件是否用到子目录编译的链接库:

  • 仅源文件用:PRIVATE
  • 仅头文件用:INTERFACE
  • 两者都用:PUBLIC

收集源文件列表

第一种方式:手动添加源文件

set(SRC_LISTS
    main.cpp
    other.cpp
)

第二种方式:自动搜索:

  • dir: 要搜索的目录
  • variable:用于存储搜索到的所有源文件的变量
aux_source_directory(<dir> <variable>)

生成可执行文件

target在一个项目中必须全局唯一。

add_executable(<target> [<sources>])

可指定<sources>,也可省略,用target_sources()后续指定。

生成库文件

add_library(<target> [<STATIC | SHARED>] [<sources>])
  • STATIC:静态库。
  • SHARED动态库。

如果没有设置STATICSHARED ,则取决于 BUILD_SHARED_LIBS变量的值为FALSE还是TRUE

可指定<sources>,也可省略,用target_sources()后续指定。

添加子目录

add_subdirectory将子目录添加到构建中。

<sub-source-dir>为相对当前目录的相对路径。

<binary_dir> 指定放置输出文件到的目录。

  • 可以是一个绝对路径。

  • 如果是相对路径,则相对于当前输出目录。

  • 如果 <binary-dir> 没有指定,将使用 <sub-source-dir> 的值。

add_subdirectory(<sub-source-dir> [<binary-dir>])

添加源文件

通过target_sources可以更清晰地组织目标的源文件。

target_sources(<target>
    [<PUBLIC | INTERFACE | PRIVATE>] <source> ...
    [<PUBLIC | INTERFACE | PRIVATE>] <source> ...
)

target_sources(<target> [<PUBLIC | INTERFACE | PRIVATE>] <src-list>)
  • PUBLIC: 指定这些源文件对其他链接到该目标的目标是可见的,同时也包含在目标本身的构建中。
  • PRIVATE: 指定这些源文件仅对目标本身可见,不会传播到链接到该目标的其他目标。
  • INTERFACE: 指定这些源文件对其他目标是可见的,但不会包含在目标本身的构建中。这通常用于接口库。

添加头文件目录

target_include_directories指定编译目标时要使用的包含目录,这些目录是编译器用来搜索头文件的路径。

相当于g++选项中的-I参数的作用。

头文件目录为相对于当前CMakeLists.txe所在目录的路径。

使用 AFTERBEFORE,你可以选择追加或前置,默认为追加,可以通过设置CMAKE_INCLUDE_DIRECTORIES_BEFORE变量为ON来改变。

不推荐使用include_directories,它会为当前CMakeLists.txt的所有目标,以及之后添加的所有子目录的目标添加头文件搜索路径。

# Modern Cmake推荐使用
target_include_directories(<target>
    [AFTER | BEFORE]
    [<PUBLIC | INTERFACE | PRIVATE>] <include-dir> ...
    [<PUBLIC | INTERFACE | PRIVATE>] <include-dir> ...
)

# 不推荐使用
include_directories(<target>
    [AFTER | BEFORE]
    <include-dir1> [<include-dir2> ...]
)
  • PRIVATE:目录只对目标自身可见,不会传递给依赖该目标的其他目标。
  • PUBLIC:目录既对目标自身可见,也会传递给依赖该目标的其他目标。
  • INTERFACE:目录不会应用于目标本身,但会传递给依赖该目标的其他目标。

添加依赖库

target_link_libraries用于控制链接器在链接目标时(通常是可执行文件或库)应该链接哪些库。

相当于g++选项中的-l参数的作用。

链接顺序:被依赖的库放在依赖它的库的后面

被链接的库可以是:

  • add_executableadd_library创建的target名称

  • 系统库
  • find_package()发现的库
  • 手动指定的库文件的绝对路径
# Modern Cmake推荐使用
target_link_libraries(<target> <other-target> ...)

target_link_libraries(<target>
    [<PRIVATE | PUBLIC | INTERFACE>] <library> ...
    [<PRIVATE | PUBLIC | INTERFACE>] <library> ...
)

# 不推荐使用, 将库链接到所有目标。
link_libraries(
    <library1> [<library2> ...]
)
  • PUBLIC :在public后面的库会被link到你的target中,并且里面的符号也会被导出,提供给第三方使用。
  • PRIVATE :在private后面的库仅被link到你的target中,并且终结掉,第三方不能感知你调了啥库。
  • INTERFACE :在interface后面引入的库不会被链接到你的target中,只会导出符号。

添加链接库的头文件目录

target_include_directories指定在链接目标时链接器应搜索库的路径。

相当于g++选项中的-L参数的作用。

使用 AFTERBEFORE,你可以选择追加或前置,默认为追加,可以通过设置CMAKE_LINK_DIRECTORIES_BEFORE变量为ON来改变。

# Modern Cmake推荐使用
target_link_directories(<target>
    [BEFORE]
    [<INTERFACE | PUBLIC | PRIVATE>] <include-dir> ...
    [<INTERFACE | PUBLIC | PRIVATE>] <include-dir> ...
)

# 不推荐使用
link_directories(<target>
    [AFTER | BEFORE]
    <include-dir1> [<include-dir2> ...]
)
  • PRIVATE:目录只对目标自身可见,不会传递给依赖该目标的其他目标。
  • PUBLIC:目录既对目标自身可见,也会传递给依赖该目标的其他目标。
  • INTERFACE:目录不会应用于目标本身,但会传递给依赖该目标的其他目标。

添加预处理器定义

target_compile_definitions命令用于向指定目标添加预处理器定义

add_compile_definitions命令用于向当前目录以及当前目录下的所有子目录添加预处理器定义

add_definitions命令用于向给当前目录以及当前目录下的所有子目录添加定义

# 只给指定target添加
target_compile_definitions(<target>
    [<PUBLIC | INTERFACE | PRIVATE>] <item> ...
    [<PUBLIC | INTERFACE | PRIVATE>] <item> ...
)

# 给当前目录以及当前目录下的所有子目录添加预处理器定义
add_compile_definitions(FOO BAR="VALUE")


# 可以添加预处理器定义
add_definitions(-DFOO -DBAR="VALUE" ...)
# 也添加其他编译器标志(例如链接器标志)
add_definitions(-L/path/to/lib)

添加编译选项

target_compile_options向目标添加编译选项。

影响CMAKE_C_FLAGSCMAKE_CXX_FLAGS

target_compile_options(<target>
    [BEFORE]
    [<PUBLIC | INTERFACE | PRIVATE>] <option> ...
    [<PUBLIC | INTERFACE | PRIVATE>] <option> ...
)

add_compile_options(<option> ...)

PRIVATE: 编译选项将仅应用于该目标,不会传播给其他依赖于该目标的目标。

PUBLIC: 编译选项将应用于该目标及所有链接到该目标的目标。适用于库的公共部分和使用该库的其他库。

INTERFACE: 编译选项将仅应用于使用此目标的其他目标,不会应用于该目标本身。通常用于库的公共API。

添加链接选项

target_link_options向目标添加链接选项。

影响CMAKE_C_FLAGSCMAKE_CXX_FLAGS

target_link_options(<target>
    [BEFORE]
    [<PUBLIC | INTERFACE | PRIVATE>] <option> ...
    [<PUBLIC | INTERFACE | PRIVATE>] <option> ...
)

add_link_options(<option> ...)

管理目标的依赖关系

添加目标之间的依赖关系。

使顶级 <target> 依赖于其他顶级目标<target-dependency>,以确保它们在 <target> 执行之前构建。

add_dependencies(<target> <target-dependency> ...)

引入已有的第三方库

find_package(Boost 1.46.1 REQUIRED COMPONENTS filesystem system)

if(Boost_FOUND)
    message ("boost found")
else()
    message (FATAL_ERROR "Cannot find Boost")
endif()

为项目生成安装规则

目标

install(TARGETS <target>...
    [ARCHIVE | LIBRARY | RUNTIME | PRIVATE_HEADER | PUBLIC_HEADER]
    [DESTINATION <dst-dir>]
    [PERMISSIONS <permission> ...]
    [CONFIGURATIONS Debug | Release]

    <dst-dir>
)
  • DESTINATION <dst-dir>

    指定将在其中安装文件的磁盘目录。<dst-dir>为相对路径,并基于 CMAKE_INSTALL_PREFIX 变量的值进行解释。

    不推荐使用DESTINATION参数,建议配置目标类型,并设置对应的安装目录变量。

    目标类型 内容 安装目录变量 内置默认值
    RUNTIME 静态库 ${CMAKE_INSTALL_BINDIR} bin
    LIBRARY 动态库 ${CMAKE_INSTALL_LIBDIR} lib
    ARCHIVE 可执行文件 ${CMAKE_INSTALL_LIBDIR} lib
    PRIVATE_HEADER PUBLIC头文件 ${CMAKE_INSTALL_INCLUDEDIR} include
    PUBLIC_HEADER PRIVATE头文件 ${CMAKE_INSTALL_INCLUDEDIR} include
  • PERMISSIONS <permission>...

    指定安装文件的权限。有效权限为 OWNER_READOWNER_WRITEOWNER_EXECUTEGROUP_READGROUP_WRITEGROUP_EXECUTEWORLD_READWORLD_WRITEWORLD_EXECUTESETUIDSETGID。某些平台上无意义的权限会在这些平台上被忽略。如果在单次调用中多次使用此选项,其权限列表将积累。

  • CONFIGURATIONS <config>...

    指定安装规则适用的构建配置列表(Debug、Release 等)。如果此选项在单个调用中多次使用,其配置列表会累积。

    如果DEBUG和RELEASE版本的DESTINATION不同,那么DESTINATION必须在CONFIGUATIONS后面

install(TARGETS target
        CONFIGURATIONS Debug
        RUNTIME DESTINATION Debug/bin)
install(TARGETS target
        CONFIGURATIONS Release
        RUNTIME DESTINATION Release/bin)

文件和目录

install(FILES <file>...
    TYPE <type> | DESTINATION <dir>
    [PERMISSIONS <permission>...]
    [CONFIGURATIONS <config>...]
    [RENAME <name>]
)

install(DIRECTORY <dirs>...
    TYPE <type> | DESTINATION <dir>
    [FILE_PERMISSIONS <permission>...]
    [DIRECTORY_PERMISSIONS <permission>...]
)
TYPE 参数 GNUInstallDirs 变量 内置默认值
BIN ${CMAKE_INSTALL_BINDIR} bin
LIB ${CMAKE_INSTALL_LIBDIR} lib
INCLUDE ${CMAKE_INSTALL_INCLUDEDIR} include
SYSCONF ${CMAKE_INSTALL_SYSCONFDIR} etc