Command Line
$ 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>]
内置变量
PROJECT_SOURCE_DIR 项目目录
CMAKE_CURRENT_LIST_DIR 当前 cmake 文件所在目录
CMAKE_STATIC_LIBRARY_PREFIX 静态库前缀, 例如 lib
CMAKE_STATIC_LIBRARY_SUFFIX 静态库后缀, 例如 .a
CMakeLists
设置cmake最小版本
cmake_minimum_required(VERSION 2.8)
设置项目名称
project("...")
判断OS
if (APPLE)
# do something
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
elseif (UNIX)
# do something
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
endif()
生成共享库
add_library(name SHARED src)
使用静态库
SET(CMAKE_FIND_LIBRARY_SUFFIXES ".a") # 查找库文件后缀
SET(BUILD_SHARED_LIBS OFF) # 关闭使用共享库
SET(CMAKE_EXE_LINKER_FLAGS "-static") # 连接时使用静态库
生成可执行文件
add_executable(MAIN src/main.cpp)
包含cmake文件
include(path/to/cmake)
打印消息
MESSAGE("msg...")
指定compiler
set(CMAKE_C_COMPILER "gcc-5")
set(CMAKE_CXX_COMPILER "g++-5")
编译类型
set(CMAKE_BUILD_TYPE=Release) # or Debug
指定FLAGS
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
遍历
set(L 1 2 3)
foreach(ITEM IN LISTS L)
messag(STATUS "item: ${ITEM}")
endforeach()
循环
set (L A B C)
foreach (V IN LISTS L)
... ${V}
endforeach()
打印 Target 属性
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)
使用
PrintTargetProperties(spdlog::spdlog)
添加定义(add_definitions)
添加字符串
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}") # 添加字符串
添加定义
add_definitions(-DDEBUG_MODE) # 添加字符串
使用
#ifdef DEBUG_MODE
...
#else
...
#endif
添加文件
file(GLOB_RECURSE SRCS src/**.cpp) # 递归添加 src 下所有的 cpp 文件
变量
判断变量是否定义
if (DEFINED VAR_NAME) # NOT DEFINED VAR_NAME
...
endif()
转换字符串为大写
string(TOUPPER ${ORIGIN_VAR} DEST_VAR)
判断变量为空
if (${V} STREQUAL "")
...
endif()
变量默认值
# 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" )
编译和安装
# 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
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
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")
修改库搜索路径
export CMAKE_PREFIX_PATH="$CUSTOME_LIBRARY_PATH"
export CMAKE_LIBRARY_PATH="$CUSTOME_LD_LIBRARY_PATH"
查找&链接库
# 将库路径写入 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>)
示例
find_package(Snappy REQUIRED)
target_link_libraries(brpc Snappy::snappy)
自定义 Find Cmake 文件
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,移动之后可能会出现找不到库的问题。
cmake# 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 会被设置
示例
include(FindPkgConfig)
pkg_check_modules(brpc REQUIRED IMPORTED_TARGET brpc)
target_link_libraries(target PkgConfig::brpc)
使用 PkgConfig 文件添加库
简版
文档。
# 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
# 完整签名
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 命令
find_package(PackageName CONFIG)
CONFIG
命令会尝试搜索包提供的 <PackageName>Config.cmake
或 <lowercasePackageName>-config.cmake
文件,并把包含该文件的文件夹路径赋值给 <PackageName>_DIR
,<PackageName>_CONFIG
保存配置文件的完整路径。
默认搜索路径
指定搜索路径
find_package (<package> PATHS paths... NO_DEFAULT_PATH)
# NO_DEFAULT_PATH: 不使用默认路径
设置变量
# 不管是否找到都会设置
{PackageName}_FOUND
库管理
ExternalProject_Add
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
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
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 指定本地文件
FetchContent_Declare(
boost
URL file:///tmp/boost-submodule-boost-1.80.0-1.tar.gz
)
指定 CMakeLists.txt 路径
如果 CMakeLists.txt
文件不在仓库根目录下,可以用 SOURCE_SUBDIR
来指定子路径。
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)
function(FNAME)
endfunction(FNAME)
作用域
方法有独立的作用域,可以访问父级作用域内的变量。在函数内定义的变量,对父级作用域不可访问。如果需要修改父级作用域变量,需要使用 PARENT_SCOPE。
SET(VAR vALUEe PARENT_SCOPE)
参数
参数列表指定
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 ...
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)
macro(MName)
endmacro(MName)
Macro 和 function 比较相似,区别如下。
- macro 和调用域共享变量的作用域,function 则有独立的作用域
参考
编译
使用 preset
cmake --preset=default # 读取 CMakePresets.json 中的 default 配置项
CMakePresets.json 示例。
{
"version": 2,
"configurePresets": [
{
"name": "default",
"generator": "Ninja",
"binaryDir": "${sourceDir}/vcpkg-build",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
}
}
]
}
指定 Target
# pwd: {project}/build
cmake ..
cmake --build . --target {target}
cmake --build . --target mongoc_mongoc
优化
ccache
# 安装
sudo yum install ccache
# 配置
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache
安装
指定安装目录。
# 系统路径
cmake --install {build-dir} --prefix "/usr"
# 示例
cmake --install build --prefix "$PWD"
安装头文件
# 使用 install
install(DIRECTORY include/ DESTINATION include) # FILES_MATCHING PATTERN "*.h"