cmake 使用

Command Line

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ Generate a Project Buildsystem
# cmake [<options>] <path-to-source>
# cmake [<options>] <path-to-existing-build>
# cmake [<options>] -S <path-to-source> -B <path-to-build>

$ Build a Project
# cmake --build <dir> [<options>] [-- <build-tool-options>]

$ Install a Project
# cmake --install <dir> [<options>]

$ Open a Project
# cmake --open <dir>

$ Run a Script
# cmake [{-D <var>=<value>}...] -P <cmake-script-file>

$ Run a Command-Line Tool
# cmake -E <command> [<options>]

$ Run the Find-Package Tool
# cmake --find-package [<options>]

$ View Help
# cmake --help[-<topic>]

内置变量

1
2
3
4
PROJECT_SOURCE_DIR           项目目录
CMAKE_CURRENT_LIST_DIR 当前 cmake 文件所在目录
CMAKE_STATIC_LIBRARY_PREFIX 静态库前缀, 例如 lib
CMAKE_STATIC_LIBRARY_SUFFIX 静态库后缀, 例如 .a

CMakeLists

设置cmake最小版本

1
cmake_minimum_required(VERSION 2.8)

设置项目名称

1
project("...")

判断OS

1
2
3
4
5
6
7
if (APPLE)
# do something
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
elseif (UNIX)
# do something
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
endif()

生成共享库

1
add_library(name SHARED src)

使用静态库

1
2
3
SET(CMAKE_FIND_LIBRARY_SUFFIXES ".a")    # 查找库文件后缀
SET(BUILD_SHARED_LIBS OFF) # 关闭使用共享库
SET(CMAKE_EXE_LINKER_FLAGS "-static") # 连接时使用静态库

生成可执行文件

1
add_executable(MAIN src/main.cpp)

包含cmake文件

1
include(path/to/cmake)

打印消息

1
MESSAGE("msg...")

指定compiler

1
2
set(CMAKE_C_COMPILER "gcc-5")
set(CMAKE_CXX_COMPILER "g++-5")

编译类型

1
set(CMAKE_BUILD_TYPE=Release)	# or Debug

指定FLAGS

1
2
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")

遍历

1
2
3
4
set(L 1 2 3)
foreach(ITEM IN LISTS L)
messag(STATUS "item: ${ITEM}")
endforeach()

循环

1
2
3
4
set (L A B C)
foreach (V IN LISTS L)
... ${V}
endforeach()

打印 Target 属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
FUNCTION(PrintTargetProperties _tgt)
IF (NOT CMAKE_PROPERTY_LIST)
EXECUTE_PROCESS(COMMAND cmake --help-property-list OUTPUT_VARIABLE CMAKE_PROPERTY_LIST)

# Convert command output into a CMake list
STRING(REGEX REPLACE ";" "\\\\;" CMAKE_PROPERTY_LIST "${CMAKE_PROPERTY_LIST}")
STRING(REGEX REPLACE "\n" ";" CMAKE_PROPERTY_LIST "${CMAKE_PROPERTY_LIST}")
LIST(REMOVE_DUPLICATES CMAKE_PROPERTY_LIST)
ENDIF ()

IF (NOT TARGET ${_tgt})
MESSAGE(STATUS "[TargetProperties] There is no target named '${_tgt}'")
RETURN()
ENDIF ()

FOREACH (property ${CMAKE_PROPERTY_LIST})
STRING(REPLACE "<CONFIG>" "${CMAKE_BUILD_TYPE}" property ${property})

# Fix https://stackoverflow.com/questions/32197663/how-can-i-remove-the-the-location-property-may-not-be-read-from-target-error-i
IF (property STREQUAL "LOCATION" OR property MATCHES "^LOCATION_" OR property MATCHES "_LOCATION$")
CONTINUE()
ENDIF ()

GET_PROPERTY(_was_set TARGET ${_tgt} PROPERTY ${property} SET)
IF (_was_set)
GET_TARGET_PROPERTY(value ${_tgt} ${property})
MESSAGE("[TargetProperties] ${_tgt} ${property} = ${value}")
ENDIF ()
ENDFOREACH ()
ENDFUNCTION(PrintTargetProperties)

使用

1
PrintTargetProperties(spdlog::spdlog)

添加定义(add_definitions)

添加字符串

1
2
3
4
5
6
7
8
9
execute_process(
COMMAND sh -c "git rev-parse --short HEAD"
OUTPUT_VARIABLE TAG_REVERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if (TAG_REVERSION STREQUAL "")
set(TAG_REVERSION "UNKNOWN")
endif ()
add_definitions(-DTAG_REVERSION="${TAG_REVERSION}") # 添加字符串

添加定义

1
add_definitions(-DDEBUG_MODE) # 添加字符串

使用

1
2
3
4
5
#ifdef DEBUG_MODE
...
#else
...
#endif

添加文件

1
file(GLOB_RECURSE SRCS src/**.cpp)  # 递归添加 src 下所有的 cpp 文件

变量

判断变量是否定义

1
2
3
if (DEFINED VAR_NAME) # NOT DEFINED VAR_NAME
...
endif()

转换字符串为大写

1
string(TOUPPER ${ORIGIN_VAR} DEST_VAR)

判断变量为空

1
2
3
if (${V} STREQUAL "")
...
endif()

变量默认值

1
2
3
4
5
6
# option, 只对 BOOL 类型, 默认 OFF
option(BUILD_THIRD_PARTY "build third party library" ON)

# cache, STRING 类型等
set(BUILD_THIRD_PARTY ON CACHE BOOL "build third party library")
set(DEPS_DIR "/tmp/cpp-external-lib" CACHE STRING "library install prefix" )

编译和安装

1
2
3
4
5
6
7
8
9
10
# build director
$ mkdir build
$ cd build
# cmake configure
$ cmake ..
$ cmake -DCMAKE_INSTALL_PREFIX=$PWD ..
# build
$ cmake --build .
# install
$ cmake --install . --config Release # debug...

PkgConfig

{library}.pc.in

1
2
3
4
5
6
7
8
9
10
11
12
prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=${prefix}
includedir=@PKG_CONFIG_INCLUDEDIR@
libdir=@PKG_CONFIG_LIBDIR@

Name: lib@PROJECT_NAME@
Description: cpp common
URL: https://github.com/sunzhenkai/cpp-common
Version: @CPP_COMMON_VERSION@
CFlags: -I${includedir}
Libs: -L${libdir}
Requires: @PKG_CONFIG_REQUIRES@

cmake config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SET(PKG_CONFIG ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pc)

IF (IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}")
SET(PKG_CONFIG_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}")
ELSE ()
SET(PKG_CONFIG_INCLUDEDIR "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}")
ENDIF ()
IF (IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}")
SET(PKG_CONFIG_LIBDIR "${CMAKE_INSTALL_LIBDIR}")
ELSE ()
SET(PKG_CONFIG_LIBDIR "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}")
ENDIF ()
CONFIGURE_FILE("cmake/${PROJECT_NAME}.pc.in" "${PKG_CONFIG}" @ONLY)
INSTALL(FILES "${PKG_CONFIG}" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")

修改库搜索路径

1
2
export CMAKE_PREFIX_PATH="$CUSTOME_LIBRARY_PATH"
export CMAKE_LIBRARY_PATH="$CUSTOME_LD_LIBRARY_PATH"

查找&链接库

1
2
3
4
5
# 将库路径写入 CMAKE_PREFIX_PATH
set(CMAKE_PREFIX_PATH ${PATH_TO_LIB} ${CMAKE_PREFIX_PATH})
find_package(<library-name> REQUIRED)
# 使用
target_link_libraries(<library-name> <target-name>)

示例

1
2
find_package(Snappy REQUIRED)
target_link_libraries(brpc Snappy::snappy)

自定义 Find Cmake 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
find_path(THRIFT_INCLUDE_DIR
NAMES
thrift/Thrift.h
HINTS
/usr/local
PATH_SUFFIXES
include
)

find_library(THRIFT_LIBRARIES
NAMES
thrift libthrift
HINTS
/usr/local
PATH_SUFFIXES
lib lib64
)

find_program(THRIFT_COMPILER
NAMES
thrift
HINTS
/usr/local
PATH_SUFFIXES
bin bin64
)

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(THRIFT DEFAULT_MSG THRIFT_LIBRARIES THRIFT_INCLUDE_DIR THRIFT_COMPILER)
# 设置变量为高级,在 GUI 模式下默认不展示
mark_as_advanced(THRIFT_LIBRARIES THRIFT_INCLUDE_DIR THRIFT_COMPILER)

引入库

引入第三方库

引入第三方库的几种方式。第一种,find_path 查找头文件,find_library 查找库文件,分别使用 include_directories(DEP_INCLUDE_DIR)、target_link_libraries(target library) 链接库,这种方式一般用于没有 Find*.cmake 的库。第二种,对于有 Find cmake 的库,可以使用 find_package(Library REQUIRED) 来 import 库,然后使用 target_link_libraries 来链接库。第三种,自定义 Find Cmake 文件,借助 find_package_handle_standard_args 实现。对于有 pkgconfig 的库来说,也可以用 pkg_check_modules 来导入,但是有个问题,pkgconfig 内可能有写死的 prefix,移动之后可能会出现找不到库的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# snappy
find_package(Snappy REQUIRED)
target_link_libraries(brpc-static Snappy::snappy)

# thrift
find_path(THRIFT_INCLUDE_DIR NAMES thrift/Thrift.h PATH_SUFFIXES include)
find_library(thrift thrift REQUIRED CONFIG)
include_directories(${THRIFT_INCLUDE_DIR})
target_link_libraries(brpc-static thrift)

# pkg_check_modules
include(FindPkgConfig)
pkg_check_modules(Curl libcurl REQUIRED)
# Curl_INCLUDE_DIR、Curl_LIBRARIES、Curl_FOUND 会被设置

示例

1
2
3
include(FindPkgConfig)
pkg_check_modules(brpc REQUIRED IMPORTED_TARGET brpc)
target_link_libraries(target PkgConfig::brpc)

使用 PkgConfig 文件添加库

简版

文档

1
2
3
4
5
6
7
8
# search
find_package(PkgConfig REQUIRED)
pkg_check_modules(SDL2 REQUIRED sdl2)

# link
target_link_libraries(testapp ${SDL2_LIBRARIES})
target_include_directories(testapp PUBLIC ${SDL2_INCLUDE_DIRS})
target_compile_options(testapp PUBLIC ${SDL2_CFLAGS_OTHER})

自己添加 target

find_package

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 完整签名
find_package(<PackageName> [version] [EXACT] [QUIET]
[REQUIRED] [[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[CONFIG|NO_MODULE]
[GLOBAL]
[NO_POLICY_SCOPE]
[BYPASS_PROVIDER]
[NAMES name1 [name2 ...]]
[CONFIGS config1 [config2 ...]]
[HINTS path1 [path2 ... ]]
[PATHS path1 [path2 ... ]]
[REGISTRY_VIEW (64|32|64_32|32_64|HOST|TARGET|BOTH)]
[PATH_SUFFIXES suffix1 [suffix2 ...]]
[NO_DEFAULT_PATH]
[NO_PACKAGE_ROOT_PATH]
[NO_CMAKE_PATH]
[NO_CMAKE_ENVIRONMENT_PATH]
[NO_SYSTEM_ENVIRONMENT_PATH]
[NO_CMAKE_PACKAGE_REGISTRY]
[NO_CMAKE_BUILDS_PATH] # Deprecated; does nothing.
[NO_CMAKE_SYSTEM_PATH]
[NO_CMAKE_INSTALL_PREFIX]
[NO_CMAKE_SYSTEM_PACKAGE_REGISTRY]
[CMAKE_FIND_ROOT_PATH_BOTH |
ONLY_CMAKE_FIND_ROOT_PATH |
NO_CMAKE_FIND_ROOT_PATH])

CONFIG 命令

1
find_package(PackageName CONFIG)

CONFIG 命令会尝试搜索包提供的 <PackageName>Config.cmake<lowercasePackageName>-config.cmake 文件,并把包含该文件的文件夹路径赋值给 <PackageName>_DIR<PackageName>_CONFIG 保存配置文件的完整路径。

默认搜索路径

指定搜索路径

1
2
find_package (<package> PATHS paths... NO_DEFAULT_PATH)
# NO_DEFAULT_PATH: 不使用默认路径

设置变量

1
2
# 不管是否找到都会设置
{PackageName}_FOUND

库管理

ExternalProject_Add

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
include(ExternalProject)

set(target spdlog)
set(CMAKE_ARGS
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_INSTALL_PREFIX=${DEPS_PREFIX}
-DCMAKE_INSTALL_LIBDIR=lib
-DBUILD_STATIC_LIB=ON
-DBUILD_SHARED_LIB=OFF)
ExternalProject_Add(
${target}_build
GIT_REPOSITORY https://github.com/gabime/spdlog.git
GIT_TAG v1.9.2
CMAKE_ARGS ${CMAKE_ARGS}
)

# 指定 libary 安装文件夹,统一在 lib/lib64
-DCMAKE_INSTALL_LIBDIR=lib

# 参数
ExternalProject_Add(
<target-name>
GIT_REPOSITORY <git-repo-address>
GIT_TAG <git-tag>
PREFIX <prefix-path> # 创建 build、src 等目录所在的位置, 不是安装的路径
INSTALL_DIR <install_dir> # 不是安装的位置,作为属性,可用 ExternalProject_Get_Property 获取,在 CONFIGURE_COMMAND 等中指定 prefix
CMAKE_ARGS ${CMAKE_ARGS}
)

传入 CMAKE_C_FLAGS / CMAKE_CXX_FLAGS

1
2
3
4
5
6
7
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -lstdc++ -ldl")
ExternalProject_Add(... CMAKE_ARGS -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS})

# 注意: 下面方式均不可
ExternalProject_Add(... CMAKE_ARGS -DCMAKE_C_FLAGS=-lstdc++ -ldl)
ExternalProject_Add(... CMAKE_ARGS -DCMAKE_C_FLAGS="-lstdc++ -ldl")
ExternalProject_Add(... CMAKE_ARGS -DCMAKE_C_FLAGS='-lstdc++ -ldl')

AddLibrary

1
2
3
4
5
6
7
8
9
add_library(${TGT} STATIC IMPORTED GLOBAL)
set_target_properties(${TGT} PROPERTIES
IMPORTED_LOCATION "${TGT_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}${TGT}${CMAKE_STATIC_LIBRARY_SUFFIX}"
INCLUDE_DIRECTORIES ${TGT_PREFIX}/include
INTERFACE_INCLUDE_DIRECTORIES ${TGT_PREFIX}/include
)

# INTERFACE_INCLUDE_DIRECTORIES
set_target_propterties 添加 INTERFACE_INCLUDE_DIRECTORIES, 在 target_link_libraries 时,不需要再 include 库的头文件

FetchContent_Declare

FetchContent_Declare 通常和 FetchContent_MakeAvailable 一块使用。

FetchContent_MakeAvailable 是 cmake 3.14 引入的。

FetchContent_Declare 的参数参考 ExternalProject_Add,和 ExternalProject_Add 相比,屏蔽了下面的命令。

  • CONFIGURE_COMMAND
  • BUILD_COMMAND
  • INSTALL_COMMAND
  • TEST_COMMAND

URL 指定本地文件

1
2
3
4
FetchContent_Declare(
boost
URL file:///tmp/boost-submodule-boost-1.80.0-1.tar.gz
)

指定 CMakeLists.txt 路径

如果 CMakeLists.txt 文件不在仓库根目录下,可以用 SOURCE_SUBDIR 来指定子路径。

1
2
3
4
5
6
7
include(FetchContent)
FetchContent_Declare(
protobuf
GIT_REPOSITORY https://github.com/protocolbuffers/protobuf.git
GIT_TAG ae50d9b9902526efd6c7a1907d09739f959c6297 # v3.15.0
SOURCE_SUBDIR cmake
)

Configure 阶段让 Target 可用

方法 (function)

1
2
function(FNAME)
endfunction(FNAME)

作用域

方法有独立的作用域,可以访问父级作用域内的变量。在函数内定义的变量,对父级作用域不可访问。如果需要修改父级作用域变量,需要使用 PARENT_SCOPE。

1
SET(VAR vALUEe PARENT_SCOPE)

参数

参数列表指定

1
2
3
4
5
function(ARG version url flag)
message(STATUS "version: ${version}, url: ${url}, flag: ${flag}")
endfunction(ARG)

ARG(1.0.0 www.so.com true)

非参数列表

首先了解在函数内定义的默认变量。

  • ARGC,参数数量
  • ARGN,参数,去掉声明的参数的参数列表
  • ARGV,参数,全部参数
  • ARG0,ARG1 …
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

function(ARG4)
set(options OPTIONAL FAST)
set(oneValueArgs NAME URL)
set(multiValueArgs KEY)
cmake_parse_arguments(PREFIX "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
message(STATUS "FAST=${PREFIX_FAST} NAME=${PREFIX_NAME} URL=${PREFIX_URL} KET=${PREFIX_KEY}")
endfunction(ARG4)

ARG4(
FAST
NAME beijing
URL www.so.com
KEY weight price
)
# FAST=TRUE NAME=beijing URL=www.so.com KET=weight;price

ARG4(
URL www.so.com
KEY band price
)
# FAST=FALSE NAME= URL=www.so.com KET=band;price

宏 (macro)

1
2
macro(MName)
endmacro(MName)

Macro 和 function 比较相似,区别如下。

  • macro 和调用域共享变量的作用域,function 则有独立的作用域

参考

编译

指定 Target

1
2
3
4
# pwd: {project}/build
cmake ..
cmake --build . --target {target}
cmake --build . --target mongoc_mongoc

优化

ccache

1
2
3
4
5
# 安装
sudo yum install ccache

# 配置
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache

安装

指定安装目录。

1
2
3
4
# 系统路径
cmake --install {build-dir} --prefix "/usr"
# 示例
cmake --install build --prefix "$PWD"

安装头文件

1
2
# 使用 install
install(DIRECTORY include/ DESTINATION include) # FILES_MATCHING PATTERN "*.h"