Table of Contents
概述
直接写makefile有时显得过于繁琐,而且维护跨平台的项目时显得比较麻烦,这时候就可以考虑使用cmake。
cmake的核心是targets(动态库、静态库、可执行程序或者所谓的custom target),cmake通过解析这些这些targets的依赖决定构建的顺序和生成构建每个target的动作(makefile中的target则更加抽象和随意,甚至可以有phony targets)。
简单实例
一个简单的CMakeLists.txt:
#规定一个适用的最低cmake版本,非必须
cmake_minimum_required(VERSION 3.6)
#为此CMakeLists所属项目取一个名字,非必须
project(demo)
构建步骤:
假设此CMakeLists.txt的路径为A/CMakeLists.txt,则可以在A目录下创建一个build目录,然后在A/build目录下执行:
cmake ..
则cmake会依据A/CMakeLists.txt生成相应的makefile文件在A/build目录下,只要在build目录下执行make
就可以构建此CMakeLists.txt中添加的target(这个CMakeLists中没有写任何targets,所以就什么都不构建,其实作为一个demo,这个CMakeListst.txt甚至可以是空的。。。)。cmake生成的Makefile应该还有clean等目标,具体可以查看cmake生成的Makefile文件内容。当修改了A/CMakeListst.txt的内容时,可以再次执行cmake ..
让cmake生成新的Makefile。
CMakeLists中的一些语法及相关概念
- 普通变量(Normal Variable)
和常见编程语言中的变量概念差不多,一个CMakeListst.txt就相当于一个作用域。在父CMakeLists.txt中定义的变量在子CMakeLists.txt中可见,且CMakeLists.txt可以定义一个同名的变量来覆盖此变量(不会影响父CMakeLists.txt中的变量定义)。 - Cache Entry
全局唯一的变量。
CMakeListst中的常用指令(Project Commands)
设置项目名字
# 有时会用此命令指明项目是C或者C++项目
project(<PROJECT-NAME> [LANGUAGES] [<language-name>...])
project(<PROJECT-NAME>
[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
[DESCRIPTION <project-description-string>]
[LANGUAGES <language-name>...])
允许编译汇编代码
# 打开编译汇编程序的选项:enable_language(ASM)
enable_language(<lang> [OPTIONAL] )
添加库
# 添加要构建的静态库/动态库/MODULE
add_library(<name> [STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
source1 [source2 ...])
# 导入一个已经编译好了的库,一般会结合set_target_properties(<name> PROPERTIES IMPORTED_LOCATION <library_file_path>)使用,指明导入的库所对应的so或.a文件,且文件路径最好为绝对路径。
add_library(<name> <SHARED|STATIC|MODULE|OBJECT|UNKNOWN> IMPORTED
[GLOBAL])
# 创建目标文件的引用,其他的library或者executable可以通过
# add_library(... $<TARGET_OBJECTS:objlib> ...)或者add_executable(... $<TARGET_OBJECTS:objlib> ...)
# 来引用objlib所代表的一系列目标文件(假设下面这条指令中的name取为objlib的话)
add_library(<name> OBJECT <src>...)
# 为library创建别名,不能为导入的库创建别名
add_library(<name> ALIAS <target>)
# interface library,没用过,但是先记在这里
add_library(<name> INTERFACE [IMPORTED [GLOBAL]])
注意事项:cmake中非导入的库(add_library添加的库)和可执行文件(add_executable添加的可执行程序)是全局可见且必须唯一的。全局可见指的是可以在父CMakeLists.txt中引用子CMakeLists.txt中创建的target(当然子CMakeLists.txt可以使用父CMakeLists.txt中的target)。导入的库(add_library且为IMPORTED的库)的可见性范围默认是当前CMakeLists.txt和子CMakeLists.txt,但是如果在导入时添加了GLOBAL修饰,那么这个导入的库也将变为全局可见。唯一指两个全局可见的target不能拥有相同的名字。
添加可执行程序
add_executable(<name> [WIN32] [MACOSX_BUNDLE]
[EXCLUDE_FROM_ALL]
source1 [source2 ...])
add_executable(<name> IMPORTED [GLOBAL])
add_executable(<name> ALIAS <target>)
添加源文件
最直接的添加源文件的方式当然是通过add_library,add_executable方法直接添加源文件。不过有时候可能需要在符合一定条件的情况下才添加某些源文件,这时候可以用
target_sources(<target>
<INTERFACE|PUBLIC|PRIVATE> [items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
aux_source_directory
可以用来将一个目录下的源文件一次性加入到指定变量中。然而这个命令存在一个问题,就是用cmake生成的具体构建文件(makefile之类的构建系统的文件)无法感知该目录中有新的源文件加入。
aux_source_directory(<dir> <variable>)
我在两个地方用到过set_source_files_properties
这条指令,一个是没有通过enable_language
允许编译汇编程序时可以用这个指令指示cmake按照编译C/C++文件的方式编译汇编程序(否则cmake会提示判断不了汇编程序的语言,因为cmake是通过文件名后缀来判断语言类型的,而汇编程序的后缀为.S/.s,不再C/C++程序的后缀列表里面);另一个是通过这个指令单独为某个源文件添加编译选项。
set_source_files_properties([file1 [file2 [...]]]
PROPERTIES prop1 value1
[prop2 value2 [...]])
添加头文件
include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])
注意事项:此指令添加的用于搜寻头文件的目录对子CMakeLists.txt也是生效的。为了避免这种困扰,最好使用target_include_directories指令。
target_include_directories(<target> [SYSTEM] [BEFORE]
<INTERFACE|PUBLIC|PRIVATE> [items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
链接库
# Specify libraries or flags to use when linking any targets created later in the current directory or below
link_libraries([item1 [item2 [...]]]
[[debug|optimized|general] <item>] ...)
# 指明编译target时需链接的静态库/动态库
target_link_libraries(<target> ... <item>... ...)
注意事项:指定linker flag时应该使用此命令而不是用target_compile_options。比如:
target_link_libraries(${LIB_NAME} -Wl,-Bsymbolic)
指定链接时搜寻静态库/动态库的搜索路径
link_directories(directory1 directory2 ...)
定义宏
add_definitions(-DFOO -DBAR ...)
注意事项:此命令定义的宏对子CMakeLists.txt可见,甚至对调用此命令之前加入的子CMakeLists.txt都有效(?)。因此为避免困扰,最好是使用target_compile_definitions。
target_compile_definitions(<target>
<INTERFACE|PUBLIC|PRIVATE> [items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
注意事项:像target_compile_definitions
这类命令里的PUBLIC/INTERFACE/PRIVATE修饰的含义:PRIVATE只会将相应的设置应用于此target,而PUBLIC和INTERFACE则会将相应的设置应用于本target和链接了此target的target。
设置编译选项
add_compile_options(<option> ...)
与add_definitions类似,因此为了避免困扰,最好使用target_compile_definitions代替。
target_compile_options(<target> [BEFORE]
<INTERFACE|PUBLIC|PRIVATE> [items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
添加一个子项目
add_subdirectory(source_dir [binary_dir]
[EXCLUDE_FROM_ALL])
注意事项:如果子项目不是在当前项目的子目录,必须手动指定该目录的构建目录(即所谓的binary_dir),为了方便,可以将该构建目录设置为${CMAKE_CURRENT_BINARY_DIR}/subproject_name
。
cmake中的变量设置及control flow
设置变量
# set normal variable
set(<variable> <value>... [PARENT_SCOPE])
# set cache entry
set(<variable> <value>... CACHE <type> <docstring> [FORCE])
# 设置环境变量
set(ENV{<variable>} <value>...)
条件语句
if(expression)
# then section.
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
#...
elseif(expression2)
# elseif section.
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
#...
else(expression)
# else section.
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
#...
endif(expression)
从文件、目录或者函数中返回
return()
字符串操作
####################SEARCH AND REPLACE#####################
#FIND
string(FIND <string> <substring> <output variable> [REVERSE])
#REPLACE
string(REPLACE <match_string>
<replace_string> <output variable>
<input> [<input>...])
####################REGULAR EXPRESSIONS#####################
#REGEX MATCH
string(REGEX MATCH <regular_expression>
<output variable> <input> [<input>...])
#REGEX MATCHALL
string(REGEX MATCHALL <regular_expression>
<output variable> <input> [<input>...])
#REGEX REPLACE
string(REGEX REPLACE <regular_expression>
<replace_expression> <output variable>
<input> [<input>...])
####################STRING MANIPULATION#####################
#APPEND
string(APPEND <string variable> [<input>...])
#PREPEND
string(PREPEND <string variable> [<input>...])
#CONCAT
string(CONCAT <output variable> [<input>...])
#TOLOWER
string(TOLOWER <string1> <output variable>)
#TOUPPER
string(TOUPPER <string1> <output variable>)
#LENGTH
string(LENGTH <string> <output variable>)
#SUBSTRING
string(SUBSTRING <string> <begin> <length> <output variable>)
#STRIP
string(STRIP <string> <output variable>)
#GENEX_STRIP
string(GENEX_STRIP <input string> <output variable>)
#Comparison
tring(COMPARE LESS <string1> <string2> <output variable>)
string(COMPARE GREATER <string1> <string2> <output variable>)
string(COMPARE EQUAL <string1> <string2> <output variable>)
string(COMPARE NOTEQUAL <string1> <string2> <output variable>)
string(COMPARE LESS_EQUAL <string1> <string2> <output variable>)
string(COMPARE GREATER_EQUAL <string1> <string2> <output variable>)
#HASHING
string(<HASH> <output variable> <input>)
###############################GENERATION#####################
#ASCII
string(ASCII <number> [<number> ...] <output variable>)
#CONFIGURE
string(CONFIGURE <string1> <output variable>
[@ONLY] [ESCAPE_QUOTES])
#RANDOM
string(RANDOM [LENGTH <length>] [ALPHABET <alphabet>]
[RANDOM_SEED <seed>] <output variable>)
#TIMESTAMP
string(TIMESTAMP <output variable> [<format string>] [UTC])
#UUID
string(UUID <output variable> NAMESPACE <namespace> NAME <name>
TYPE <MD5|SHA1> [UPPER])
打印消息
message([<mode>] "message to display" ...)
设置选项
option(<option_variable> "help string describing option"
[initial value])
注意事项:比较坑爹的是option是一个cache entry,而且option的一个规则是在任意一个CMakeLists.txt中设设置了某个option后,在后面的CMakeLists.txt中再尝试设置这个option的操作都会被cmake忽略掉。这就导致了在父CMakeLists.txt中设置选项A为ON,然后在不同的子CMakeLists.txt中分别设置选项A为ON和OFF的想法是用option实现不了的,想要实现这种操作需要用normal variable。适用于option的设置是那种真正全局同一的设置。
文件操作
在cmake中用file命令可以对文件进行读写甚至还可以从网上下载文件!不过我只用过下面的文件拷贝命令:
file(<COPY|INSTALL> <files>... DESTINATION <dir>
[FILE_PERMISSIONS <permissions>...]
[DIRECTORY_PERMISSIONS <permissions>...]
[NO_SOURCE_PERMISSIONS] [USE_SOURCE_PERMISSIONS]
[FILES_MATCHING]
[[PATTERN <pattern> | REGEX <regex>]
[EXCLUDE] [PERMISSIONS <permissions>...]] [...])
查找库
find_library (<VAR> name1 [path1 path2 ...])
find_library (
<VAR>
name | NAMES name1 [name2 ...] [NAMES_PER_DIR]
[HINTS path1 [path2 ... ENV var]]
[PATHS path1 [path2 ... ENV var]]
[PATH_SUFFIXES suffix1 [suffix2 ...]]
[DOC "cache documentation string"]
[NO_DEFAULT_PATH]
[NO_CMAKE_PATH]
[NO_CMAKE_ENVIRONMENT_PATH]
[NO_SYSTEM_ENVIRONMENT_PATH]
[NO_CMAKE_SYSTEM_PATH]
[CMAKE_FIND_ROOT_PATH_BOTH |
ONLY_CMAKE_FIND_ROOT_PATH |
NO_CMAKE_FIND_ROOT_PATH]
)
在Android Studio的向项目添加C/C++代码教程中使用过的命令。不知道这条命令存在的意义是什么,感觉要么直接用target的名字来引用库,要么用IMPORT库的方式来引用库就可以了,可能存在的意义在于不用指明库的具体路径,只需要指出库可能存在的文件目录,让写cmake的人可以偷点懒。
编写宏
macro(<name> [arg1 [arg2 [arg3 ...]]])
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
endmacro(<name>)
可以用${ARGV}
引用参数列表,${ARGC}
参数个数,${ARGV0},${ARGV1},${ARGV2}...
引用传入的第1、2、3个参数。
编写函数
function(<name> [arg1 [arg2 [arg3 ...]]])
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
endfunction(<name>)
cmake中函数与宏的辨析
简言之,宏在引用传入的形式参数的值时使用的是字符串替换的方式;而函数才是真正的引用变量。
踩过的坑
1.改变CmakeLists.txt中的option,不会导致更新CmakeCache.txt中的option的值。
https://stackoverflow.com/a/35745766/5357784
https://stackoverflow.com/a/47325758/5357784
按照上面的回答,这个option只会在生成CmakeCache.txt时起作用,后续想要更改CMakeCache.txt中option的值,只有把CmakeCache.txt删掉重新生成(或者手动改CMakeCache.txt这个文件?)。
option的作用应该是说明有这样一个选项和提供初始值,后续想要变更这个选项可以通过上面的删除/修改CMakeCache.txt的方式或者在build时通过命令行参数指定option的值。
近期评论