致谢:参考自<Cmake 实践 Cmake Practice -- Cjacker.pdf> (opens new window)
# 1.简单例子
PROJECT(TEST_CPP)
MESSAGE(STATUS "Source Dir: " ${PROJECT_SOURCE_DIR})
MESSAGE(STATUS "Source Dir: " ${TEST_CPP_SOURCE_DIR})
MESSAGE(STATUS "Binary Dir: " ${PROJECT_BINARY_DIR})
MESSAGE(STATUS "Binary Dir: " ${TEST_CPP_BINARY_DIR})
SET(SRC_LIST main.cpp)
ADD_EXECUTABLE(main ${SRC_LIST})
PROJECT
指令,PROJECT(projectname [CXX] [C] [Java])
- 隐式定义了两个cmake变量
<projectname>_BINARY_DIR
和<projectname>_SOURCE_DIR
cmake
系统也预定义了PROJECT_BINARY_DIR
和PROJECT_SOURCE_DIR
- 建议使用
PROJECT_SOURCE_DIR
,避免修改工程名称导致错误
- 隐式定义了两个cmake变量
SET
指令,SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
,SET 指令可以用来显式的定义变量。MESSAGE 指令
- 格式
MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display" ...)
- 用于向终端输出用户定义的信息,
SATUS ,输出前缀为 -- 的信息
,FATAL_ERROR,立即终止所有 cmake 过程.
- 格式
ADD_EXECUTABLE
的指令,格式:ADD_EXECUTABLE(main ${SRC_LIST})
${}
来引用变量,这是cmake
的变量应用方式,但是,有一些例外,比
如在 IF 控制语句,变量是直接使用变量名引用,而不需要${}
指令是大小写无关的,参数和变量是大小写相关的
# 2.管理工程
工程目录
├── CMakeLists.txt
├── COPYRIGHT.md
├── doc
├── README.md
├── run.sh
└── src
├── CMakeLists.txt
└── main.cpp
src
文件中的CMakeLists.txt
ADD_EXECUTABLE(main main.c)
project
中的CMakeLists.txt
PROJECT(HELLO)
ADD_SUBDIRECTORY(src bin)
构建完成后,会发现生成的目标文件 main
位于 build/bin 目录中
ADD_SUBDIRECTORY 指令
格式:ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
- 该指令用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置。
换个地方保存目标二进制
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
- 上面两个指令分别定义了可执行二进制的输出路径为
build/bin
和库的输出路径为build/lib
- 在哪里
ADD_EXECUTABLE
或ADD_LIBRARY
,如果需要改变目标存放路径,就在哪里加入上述的定义。
INSTALL命令和
CMAKE_INSTALL_PREFIX
变量INSTALL
指令用于定义安装规则,安装的内容可以包括目标二进制、动态库、静态库以及文件、目录、脚本等。INSTALL(TARGETS targets... [[ARCHIVE|LIBRARY|RUNTIME] [DESTINATION <dir>] [PERMISSIONS permissions...] [CONFIGURATIONS [Debug|Release|...]] [COMPONENT <component>] [OPTIONAL] ] [...])
TARGETS
后面跟的就是我们通过ADD_EXECUTABLE
或者ADD_LIBRARY
定义的目标文件,可能是可执行二进制、动态库、静态库。目标类型就相对应的有三种:
ARCHIVE 特指静态库
,LIBRARY 特指动态库
,RUNTIME特指可执行目标二进制
。DESTINATION
定义了安装的路径,如果路径以/
开头,那么指的是绝对路径,这时候CMAKE_INSTALL_PREFIX
其实就无效了。安装普通文件:默认权限
644
INSTALL(FILES files... DESTINATION <dir> [PERMISSIONS permissions...] [CONFIGURATIONS [Debug|Release|...]] [COMPONENT <component>] [RENAME <name>] [OPTIONAL])
非目标文件的可执行程序安装(比如脚本之类): 与普通文件安装的区别是,默认权限
755
INSTALL(PROGRAMS files... DESTINATION <dir> [PERMISSIONS permissions...] [CONFIGURATIONS [Debug|Release|...]] [COMPONENT <component>] [RENAME <name>] [OPTIONAL])
安装目录:
INSTALL(DIRECTORY dirs... DESTINATION <dir> [FILE_PERMISSIONS permissions...] [DIRECTORY_PERMISSIONS permissions...] [USE_SOURCE_PERMISSIONS] [CONFIGURATIONS [Debug|Release|...]] [COMPONENT <component>] [[PATTERN <pattern> | REGEX <regex>] [EXCLUDE] [PERMISSIONS permissions...]] [...])
DIRECTORY 后面连接的是所在 Source 目录的相对路径,
abc
和abc/
有很大的区别,一个是把abc
文件夹安装到DESTINATION
路径下,一个是将abc/
目录下的所有文件及目录安装到DEST``路径下。PATTERN
用于使用正则表达式进行过滤,PERMISSIONS
用于指定PATTERN
过滤后的文件权限。安装时 CMAKE 脚本的执行:
INSTALL([[SCRIPT <file>] [CODE <code>]] [...])
SCRIPT
参数用于在安装时调用cmake
脚本文件(也就是<abc>.cmake
文件)
CODE
参数用于执行CMAKE
指令,必须以双引号括起来。比如:
INSTALL(CODE "MESSAGE(\"Sample install message.\")")
CMAKE_INSTALL_PREFIX 的默认定义是/usr/local
# 3.生成和使用共享库
# 3.1生成动态共享库
ADD_LIBRARY
命令
ADD_LIBRARY(libname
[SHARED|STATIC|MODULE]
[EXCLUDE_FROM_ALL]
source1 source2 ... sourceN)
SHARED
动态库,STATIC
静态库。
EXCLUDE_FROM_ALL
参数的意思是这个库不会被默认构建,除非有其他的组件依赖或者手
工构建。
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
根据以上命令,会发现静态库没有被创建,这是因为一个 target
是不能重名的,所以,静态库构建指令无效。
可以修改静态库的名字,但按照一般的习惯,静态库名字跟动态库名字应该是一致的,这样就需要用到另外一个指令,SET_TARGET_PROPERTIES
,其基本语法为:
SET_TARGET_PROPERTIES(target1 target2 ...
PROPERTIES prop1 value1 prop2 value2 ...)
添加一句
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
就可以同时得到 libhello.so/libhello.a 两个库了。
# cmake 在构建一个新的 target 时,会尝试清理掉其他使用这个名字的库,为了回避这个问题,做如下配置
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT
1)
# 动态库版本号
# VERSION 指代动态库版本,SOVERSION 指代 API 版本。
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
INSTALL共享库和头文件,
INSTALL(TARGETS hello hello_staticLIBRARY DESTINATION lib ARCHIVE DESTINATION lib)
INSTALL(FILES hello.h DESTINATION include/hello)
静态库要使用 ARCHIVE
关键字。
# 3.2引用库文件
设置头文件路径
INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
这条指令可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径
中包含了空格,可以使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的
后面,你可以通过两种方式来进行控制搜索路径添加的方式:
CMAKE_INCLUDE_DIRECTORIES_BEFORE
,通过 SET 这个 cmake 变量为 on,可以将添加的头文件搜索路径放在已有路径的前面- 通过
AFTER
或者BEFORE
参数,也可以控制是追加还是置前
设置库文件路径,引入两个新的指令
LINK_DIRECTORIES
和 TARGET_LINK_LIBRARIES
.
TARGET_LINK_LIBRARIES(target library1 <debug | optimized> library2 ...)
为 target 添加需要链接的共享库。
CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH这两个是环境变量而不是 cmake
变量。使用方法是要在 bash
中用 export
或者在 csh
中使用 set
命令设置或者CMAKE_INCLUDE_PATH=/home/include cmake ..
等方式。
FIND_PATH 用来在指定路径中搜索文件名,比如:
FIND_PATH(myHeader NAMES hello.h PATHS /usr/include /usr/include/hello)
IF(myHeader)
INCLUDE_DIRECTORIES(${myHeader})
ENDIF(myHeader)
# 4.其他
# 4.1环境变量
- 获取环境变量:
SET(ENV_PATH $ENV{PATH})
MESSAGE(STATUS ${ENV_PATH})
- 设置环境变量
SET(ENV{变量名} 值)
# 4.2系统信息
CMAKE_MAJOR_VERSION
,CMAKE 主版本号,比如 2.4.6 中的 2CMAKE_MINOR_VERSION
,CMAKE 次版本号,比如 2.4.6 中的 4CMAKE_PATCH_VERSION
,CMAKE 补丁等级,比如 2.4.6 中的 6CMAKE_SYSTEM
系统名称CMAKE_SYSTEM_NAME
不包含版本的系统名CMAKE_SYSTEM_VERSION
系统版本CMAKE_SYSTEM_PROCESSOR
处理器名称UNIX
在所有的类UNIX
平台为TRUE
,包括OS X
和cygwin
WIN32
在所有的win32
平台为TRUE
,包括cygwin
MESSAGE("CMake Major Version: " ${CMAKE_MAJOR_VERSION})
MESSAGE("CMake Minor Version: " ${CMAKE_MINOR_VERSION})
MESSAGE("CMake Patch Version: " ${CMAKE_PATCH_VERSION})
MESSAGE("CMake System: " ${CMAKE_SYSTEM})
MESSAGE("CMake System Name: " ${CMAKE_SYSTEM_NAME})
MESSAGE("CMake System Version: " ${CMAKE_SYSTEM_VERSION})
MESSAGE("CMake System Processor: " ${CMAKE_SYSTEM_PROCESSOR})
MESSAGE("UNIX: " ${UNIX})
MESSAGE("WIN32: " ${WIN32})
# CMake Major Version: 3
# CMake Minor Version: 16
# CMake Patch Version: 3
# CMake System: Linux-5.15.0-56-generic
# CMake System Name: Linux
# CMake System Version: 5.15.0-56-generic
# CMake System Processor: x86_64
# UNIX: 1
# WIN32:
# 4.3 主要的开关选项
BUILD_SHARED_LIBS
,这个开关用来控制默认的库编译方式,如果不进行设置,使用ADD_LIBRARY
并没有指定库类型的情况下,默认编译生成的库都是静态库。如果SET(BUILD_SHARED_LIBS ON)
后,默认生成的为动态库。CMAKE_C_FLAGS
:设置C
编译选项,也可以通过指令ADD_DEFINITIONS()
添加。CMAKE_CXX_FLAGS
设置C++
编译选项,也可以通过指令ADD_DEFINITIONS()
添加。
# 5.常用指令
# 5.1ADD_DEFINITIONS
,向C/C++
编译器添加-D
定义,比如:
OPTION(ENABLE_DEBUG "Build the project using macro" OFF)
IF(ENABLE_DEBUG)
ADD_DEFINITIONS(-DENABLE_DEBUG -DABC)
ENDIF(ENABLE_DEBUG)
如果代码中定义了
#ifdef ENABLE_DEBUG
#endif
这个代码块就会生效。
如果要添加其他的编译器开关,可以通过 CMAKE_C_FLAGS 变量和 CMAKE_CXX_FLAGS 变
量设置。
# 5.2 ADD_DEPENDENCIES
ADD_DEPENDENCIES
,定义target
依赖的其他target
,确保在编译本target
之前,其他的target
已经被构建。
ADD_DEPENDENCIES(target-name depend-target1 depend-target2 ...)
# 5.3 AUX_SOURCE_DIRECTORY
AUX_SOURCE_DIRECTORY(dir VARIABLE)
作用是发现一个目录下所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来
自动构建源文件列表。
AUX_SOURCE_DIRECTORY(. SRC_LIST)
ADD_EXECUTABLE(main ${SRC_LIST})
# 5.4 EXEC_PROGRAM
在 CMakeLists.txt
处理过程中执行命令,并不会在生成的 Makefile
中执行。具体语法
为:
EXEC_PROGRAM(Executable [directory in which to run]
[ARGS <arguments to executable>]
[OUTPUT_VARIABLE <var>]
[RETURN_VALUE <var>])
用于在指定的目录运行某个程序,通过 ARGS 添加参数,如果要获取输出和返回值,可通过
OUTPUT_VARIABLE
和 RETURN_VALUE
分别定义两个变量.
这个指令可以帮助你在 CMakeLists.txt
处理过程中支持任何命令,比如根据系统情况去
修改代码文件等等。
例如:在 src
目录执行 ls
命令,并把结果和返回值存下来
EXEC_PROGRAM(ls ARGS "*.c" OUTPUT_VARIABLE LS_OUTPUT RETURN_VALUE LS_RVALUE)
IF(not LS_RVALUE)
MESSAGE(STATUS "ls result: " ${LS_OUTPUT})
ENDIF(not LS_RVALUE)
# 5.5文件操作指令FILE 指令
FILE(WRITE filename "message to write"... )
FILE(APPEND filename "message to write"... )
FILE(READ filename variable)
FILE(GLOB expressions]...) variable [RELATIVE path] [globbing]...)
FILE(GLOB_RECURSE variable [RELATIVE path] [globbing expressions]...)
FILE(REMOVE [directory]...)
FILE(REMOVE_RECURSE [directory]...)
FILE(MAKE_DIRECTORY [directory]...)
FILE(RELATIVE_PATH variable directory file)
FILE(TO_CMAKE_PATH path result)
FILE(TO_NATIVE_PATH path result)
# 5.6 INCLUDE指令
用来载入 CMakeLists.txt
文件,也用于载入预定义的 cmake
模块。
INCLUDE(file1 [OPTIONAL])
INCLUDE(module [OPTIONAL])
OPTIONAL
参数的作用是文件不存在也不会产生错误。可以指定载入一个文件,如果定义的是一个模块,那么将在 CMAKE_MODULE_PATH
中搜索这个模块并载入。载入的内容将在处理到 INCLUDE
语句是直接执行。
# 5.7 FIND_指令
FIND_
系列指令主要包含一下指令:
FIND_FILE(<VAR> name1 path1 path2 ...)
VAR
变量代表找到的文件全路径,包含文件名。
FIND_LIBRARY(<VAR> name1 path1 path2 ...)
VAR
变量表示找到的库全路径,包含库文件名。
FIND_PATH(<VAR> name1 path1 path2 ...)
VAR
变量代表包含这个文件的路径。
FIND_PROGRAM(<VAR> name1 path1 path2 ...)
VAR
变量代表包含这个程序的全路径。
FIND_PACKAGE(<name> [major.minor] [QUIET] [NO_MODULE] [[REQUIRED|COMPONENTS] [componets...]])
用来调用预定义在 CMAKE_MODULE_PATH
下的 Find<name>.cmake
模块,你也可以自己定义 Find<name>
模块,通过 SET(CMAKE_MODULE_PATH dir)
将其放入工程的某个目录中供工程使用。
例如
在编译的时候需要使用curl
库,需要添加 curl
的头文件路径和库文件:
- 方法1:直接通过
INCLUDE_DIRECTORIES
和TARGET_LINK_LIBRARIES
指令添加
INCLUDE_DIRECTORIES(/usr/include)
TARGET_LINK_LIBRARIES(curltest curl)
- 方法2: 使用
cmake
提供的FindCURL
模块
FIND_PACKAGE(CURL)
IF(CURL_FOUND)
INCLUDE_DIRECTORIES(${CURL_INCLUDE_DIR})
TARGET_LINK_LIBRARIES(curltest ${CURL_LIBRARY})
ELSE(CURL_FOUND)
MESSAGE(FATAL_ERROR ”CURL library not found”)
ENDIF(CURL_FOUND)
系统预定义的 Find<name>.cmake
模块,使用方法一般如上例所示,每一个模块都会定义以下几个变量
<name>_FOUND
,来判断模块是否被找到,如果没有找到,按照工程的需要关闭某些特性、给出提醒或者中止编译<name>_INCLUDE_DIR or <name>_INCLUDES
<name>_LIBRARY or TARGET_LINK_LIBRARIES
FIND_PACKAGE
有两种查找模式:
一种是
Module
模式,在这种模式下cmake
会查找Find<PackageName>.cmake
文件,Find<PackageName>.cmake
的查找路径是先在工程指定的CMAKE_MODULE_PATH
,然后再去cmake
安装路径下查找,如/usr/share/cmake-3.16/Modules/
路径。Find<PackageName>.cmake
不一定是对应的库工程中自带的,有可能是外部生成的,如系统、cmake、甚至是调用库的工程。另一种是
Config
模式,在这种模式下,CMake
或找寻<lowercasePackageName>-config.cmake
或者<PackageName>Config.cmake
文件,如果version
信息指定后,其也会搜寻<lowercasePackageName>-config-version.cmake
或者<PackageName>ConfigVersion.cmake
。Config
搜寻模式搜寻路径比Module
模式要复杂,config
和version
文件通常会作为包安装的一部分,因此可以认为这种模式比Module
要靠谱。Config
搜寻模式会从变量<PackageName>_DIR
中找寻.cmake
文件,譬如在find_package(OpenCV)
中可以在上一行定义set(OpenCV_DIR xxx/opencv/build/)
。如果<PackageName>_DIR
中没有Config.cmake
文件,将会跳过<PackageName>_DIR
继续在默认路径中查找。
查找路径 | |
---|---|
<prefix>/(lib/<arch>_or_lib*share)/cmake/<name>*/ | |
<prefix>/(lib/<arch>_or_lib*_or_share)/<name>*/ | |
<prefix>/(lib/<arch>_or_lib*_or_share)/<name>*/(cmake|CMake)/ |
如OpenCV
默认安装后会在一下目录生成Config.cmake
文件:
# /usr/lib/x86_64-linux-gnu/cmake/opencv4
.
├── OpenCVConfig.cmake
├── OpenCVConfig-version.cmake
├── OpenCVModules.cmake
└── OpenCVModules-release.cmake
# /usr/local/share/OpenCV
├── OpenCVConfig.cmake
├── OpenCVConfig-version.cmake
├── OpenCVModules.cmake
└── OpenCVModules-release.cmake
通过<PackageName>_CONFIG
变量,可以查看使用的是哪个Config.cmake
,
如:
message(STATUS "OpenCV_CONFIG: ${OpenCV_CONFIG}")
-- OpenCV_CONFIG: /usr/local/share/OpenCV/OpenCVConfig.cmake
自定义cmake模块
如前面,自定义共享库,并安装到指定目录,当想在cmake
中通过find_package
命令找到并使用时,需要自定义Find<NAME>.cmake
模块。
自定义cmake/FindHELLO.cmake
模块:
FIND_PATH(HELLO_INCLUDE_DIR hello.h /media/lx/data/code/test_cpp/hello/hello/include)
FIND_LIBRARY(HELLO_LIBRARY hello /media/lx/data/code/test_cpp/hello/hello/lib)
IF (HELLO_INCLUDE_DIR AND HELLO_LIBRARY)
SET(HELLO_FOUND TRUE)
ENDIF (HELLO_INCLUDE_DIR AND HELLO_LIBRARY)
IF (HELLO_FOUND)
IF (NOT HELLO_FIND_QUIETLY)
MESSAGE(STATUS "Found Hello: ${HELLO_LIBRARY}")
ENDIF (NOT HELLO_FIND_QUIETLY)
ELSE (HELLO_FOUND)
IF (HELLO_FIND_REQUIRED)
MESSAGE(FATAL_ERROR "Could not find hello library")
ENDIF (HELLO_FIND_REQUIRED)
ENDIF (HELLO_FOUND)
FIND_PATH
和FIND_LIBRARY
后面所跟的是头文件和库文件的安装路径。
# FIND_PACKAGE命令格式
FIND_PACKAGE(<name> [major.minor] [QUIET] [NO_MODULE] [[REQUIRED|COMPONENTS] [componets...]])
FIND_PACKAGE
的QUIET
参数,对应与我们编写的 FindHELLO
中的 HELLO_FIND_QUIETLY
,如果不指定这个参数,就会执行:
MESSAGE(STATUS "Found Hello: ${HELLO_LIBRARY}")
REQUIRED
参数,其含义是指这个共享库是否是工程必须的,如果使用了这个参数,说明这个链接库是必备库,如果找不到这个链接库,则工程不能编译。对应于FindHELLO.cmake
模块中的 HELLO_FIND_REQUIRED
变量。
同样,在上面的模块中定义了 HELLO_FOUND
,
HELLO_INCLUDE_DIR
,HELLO_LIBRARY
变量供开发者在 FIND_PACKAGE
指令中使用。
# 5.8控制指令
- IF指令
IF(var) # 如果变量不是:空,0,N, NO, OFF, FALSE, NOTFOUND 或 <var>_NOTFOUND 时,表达式为真。
IF(NOT var ) # 与上述条件相反
IF(var1 AND var2) # 当两个变量都为真是为真
IF(var1 OR var2) # 当两个变量其中一个为真时为真
IF(COMMAND cmd) # 当给定的 cmd 确实是命令并可以调用是为真
IF(EXISTS dir/file) # 当目录名或者文件名存在时为真
IF(file1 IS_NEWER_THAN file2)# 当 file1 比 file2 新,或者 file1/file2 其中有一个不存在时为真
IF(IS_DIRECTORY dirname) # 当 dirname 是目录时,为真
# 当给定的变量或者字符串能够匹配正则表达式 `regex` 时为真
IF(variable MATCHES regex)
IF(string MATCHES regex)
# 数字比较表达式
IF(variable LESS number)
IF(string LESS number)
IF(variable GREATER number)
IF(string GREATER number)
IF(variable EQUAL number)
IF(string EQUAL number)
# 按照字母序的排列进行比较
IF(variable STRLESS string)
IF(string STRLESS string)
IF(variable STRGREATER string)
IF(string STRGREATER string)
IF(variable STREQUAL string)
IF(string STREQUAL string)
# 如果变量被定义,为真
IF(DEFINED variable)
WHILE 指令
WHILE(condition) COMMAND1(ARGS ...) COMMAND2(ARGS ...) ... ENDWHILE(condition)
真假判断条件可以参考
IF
指令。FOREACH
FOREACH
指令的使用方法有三种形式
列表
FOREACH(loop_var arg1 arg2 ...)
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ENDFOREACH(loop_var)
如:
AUX_SOURCE_DIRECTORY(. SRC_LIST)
FOREACH(F ${SRC_LIST})
MESSAGE(${F})
ENDFOREACH(F)
范围
FOREACH(loop_var RANGE total)
ENDFOREACH(loop_var)
从 0 到 total 以1为步进
FOREACH(VAR RANGE 10)
MESSAGE(${VAR})
ENDFOREACH(VAR)
范围和步进
FOREACH(loop_var RANGE start stop [step])
ENDFOREACH(loop_var)
从 start
开始到 stop
结束,以 step
为步进。
FOREACH(A RANGE 5 15 3)
MESSAGE(${A})
ENDFOREACH(A)
# 6.LIST指令
cmake
中的list
变量是用分号;
分隔的一组字符串。
set(var a b c d)
foreach(F ${var})
message(STATUS ${F})
endforeach(F)
# -- a
# -- b
# -- c
# -- d
LIST指令是为CMAKE中的列表执行的一系列操作,命令使用格式为:
list (subcommand <list> [args...])
subcommand
是待执行的子命令,<list>
是待操作的列表变量,[args...]
是对<list>
执行操作subcommand
所需要的参数。
list
其所支持的子命令有:
LENGTH
获取list
中元素个数
list(LENGTH var len)
message(STATUS ${len})
# 4
APPEND
往list
中追加元素
list(APPEND var 'v')
GET
获取list
中指定id的元素
list(GET var 0 e)
JOIN
用连接字符串将list
中元素连接起来,返回字符串SUBLIST
用于获取列表中的一部分(子列表)FIND
用于查找列表是否存在指定的元素FILTER
用于根据正则表达式包含或排除列表中的元素INSERT
用于在指定位置将元素REVERSE
将整个列表反转SORT
用于对列表进行排序TRANSFORM
对list中的每个元素,或根据条件选中的元素近进行变换
# 7.macro和function的定义
这是两个相似的关键字,都是创建一段有名字的代码以供稍后调用。
marco
定义的格式
macro(<macro_name> [arg1 arg2 ...])
command1(args ...)
command2(args ...)
...
endmacro(<macro_name>)
function
定义的格式
macro(<fucntion_name> [arg1 arg2 ...])
command1(args ...)
command2(args ...)
...
endmacro(<fucntion_name>)
从上面可以看到,两者的定义格式基本相同。
定义一个名为name
的macro
或者function
, arg1...
传入的是参数,除了使用${arg1}
来引用变量外,系统还自定义一些特殊的变量(大写):
变量 | 描述 |
---|---|
ARGV# | #是一个下标,例如ARGV0指向第一个参数 |
ARGV | 所有定义时要求传入的参数 |
ARGN | 定义时要求传入的参数以外的参数,譬如定义宏(函数)时,要求输入1个参数,实际输入了3个,则剩下的两个会以数组形式存储在ARGN中 |
ARGC | 实际传入参数的个数 |
示例:
macro(foo arg1 arg2 arg3)
message(STATUS "this is arg1: ${arg1}")
message(STATUS "this is arg2: ${arg2}")
message(STATUS "this is arg3: ${arg3}")
message(STATUS "this is argc: ${ARGC}")
message(STATUS "this is argn: ${ARGN}")
message(STATUS "this is argv: ${ARGV}")
set(v "")
list(APPEND v ${arg1})
list(APPEND v ${arg2})
list(APPEND v ${arg2})
message(STATUS "list v: ${v}")
foreach(arg IN LISTS v)
message(STATUS "this is in macro ${arg}")
endforeach()
endmacro()
foo(1 2 3)
// -- this is arg1: 1
// -- this is arg2: 2
// -- this is arg3: 3
// -- this is argc: 5
// -- this is argn: 4;5
// -- this is argv: 1;2;3;4;5
// -- list v: 1;2;2
// -- this is in macro 1
// -- this is in macro 2
// -- this is in macro 2
macro
和function
的不同- 宏的
ARGN
、ARGV
等参数不是通常CMake
意义上的变量。 它们是字符串替换,很像C预处理器对宏的处理。所以在宏中打印时,是没有输出的。foreach(arg IN LISTS ARGV) message(STATUS "this is in macro ${arg}") endforeach()
- 在
function
中打印会输出每个参数foreach(arg IN LISTS ARGV) message(STATUS "this is in macro ${arg}") endforeach() // -- this is in func 1 // -- this is in func 2 // -- this is in func 3 // -- this is in func 4 // -- this is in func 5
- 宏的
# 8..cmake
文件
在.cmake
文件中可以定义函数function
和macro
,定义变量等。
这和其他语言中将源码分成不同的模块和头文件类似。
在CMakeLists.txt
文件中直接通过include(xx.cmake)
来引用。
可以通过include
包含各种格式的文件,.cmake
后缀是一种通用格式。
除了给cmake
编译代码分块外,.cmake
文件还有个用处,在发布一个library
时,至少需要包含一个<name>Config.cmake
的文件,如此可以通过find_package
的Config
模式找到你所定义的库。
参考How to create a ProjectConfig.cmake file (opens new window)
# 9.target_*
target_include_directories/target_link_libraries
设置目标的include/link
时有三个属性PRIVATE/PUBLIC/INTERFACE
,分别介绍如下,
PUBLIC
: 如果头文件和源码文件中都需要使用第三方库头文件中的内容,就设置成PUBLIC
,使用当前库的人也需要先包含第三方库的头文件PRIVATE
: 如果当前库只在源码文件中包含第三方库的头文件和第三方库的实现,当前库在包含和引用第三方库时,应该设置成PRIVATE
,这样使用当前库时不用关心第三方库,不需要包含第三方库的头文件。INTERFACE
,如果只是头文件包含第三方库,而源码中没有包含第三方库,应该设置成INTERFACE
,这告诉当前库的使用者,当前库源码中没有使用第三方库,而链接了当前库的目标文件,可以直接使用第三方库头文件中的API
而无需包含第三方库的头文件。也就是说,设置成INTERFACE
只是声明链接当前库的目标文件可以直接使用第三方库的API
无需再在include_directories
中添加头文件依赖。
不推荐使用include_directories
,是因为其是全局包含,会对整个工程及其子工程的包含路径都会产生影响。
# 10.CMAKE_SKIP_RPATH
变量
CMake中的默认变量,如果设置成TRUE
,意味者不向target
中设置RUNPATH
。
如,在编译一个目标时会自动向其中设置动态链接库的RUNPATH
.
# default
ldd test_cmake
# linux-vdso.so.1 (0x00007ffe6a7d9000)
# libhello-cmake.so => /xx/file/test_cmake/build/hello-cmake/libhello-cmake.so (0x00007f289b3d8000)
# libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f289b1b0000)
# libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f289afbe000)
# libhello.so => /xx/file/test_cmake/build/hello/libhello.so (0x00007f289afb9000)
# libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f289ae6a000)
# /lib64/ld-linux-x86-64.so.2 (0x00007f289b3e4000)
# libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f289ae4d000)
设置变量为TRUE
时
set(CMAKE_SKIP_RPATH TRUE)
执行ldd
命令查看动态链接路径
ldd test_cmake
# linux-vdso.so.1 (0x00007ffcddd3e000)
# libhello-cmake.so => not found
# libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fdfbc2e4000)
# libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fdfbc0f2000)
# libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fdfbbfa3000)
# /lib64/ld-linux-x86-64.so.2 (0x00007fdfbc513000)
# libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fdfbbf88000)
在这种情况下,如果要运行生成的可执行文件,需要手动设置
LD_LIBRARY_PATH
路径,并执行sudo ldconfig
命令生效。
# 参考工程
以上代码汇总示例工程见:https://gitee.com/lx_r/cmake_turorials (opens new window)
# 参考资料
← C++内存序 CMake中定义编译宏 →