CMake构建跨平台项目与包管理

前言

跨平台的c&c++开发离不开cmake。

各个平台下都有自己的make工具
GUN:Make
微软:MSBuild、NMake
BSD:Make

Cmake允许开发者编写平台无关的CMakeList.txt 文件来定制整个编译流程。然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。

职责是生成可编译项目,不进行编译。

使用

手册:
https://www.zybuluo.com/khan-lau/note/254724

单个源文件

1
2
3
4
5
6
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo1)
# 指定生成目标
add_executable(Demo main.cc)

由命令、注释和空格组成,其中命令是不区分大小写的。符号 # 后面的内容被认为是注释。命令由命令名称、小括号和参数组成,参数之间使用空格进行间隔。

多个源文件

1
2
3
4
5
# ...相同
# 指定生成目标
add_executable(Demo main.cc MathFunctions.cc)

可以直接在add_executable中添加。

添加目录下所有文件:

1
2
3
4
5
6
7
8
# ...相同
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 指定生成目标
add_executable(Demo ${DIR_SRCS})

aux_source_directory(< dir> < variable>):该命令会查找指定目录下的所有源文件,然后将结果存进指定变量名。

多目录

每个目录各放一个CMakeLists.txt
可以每个目录里的文件编译为静态库

1
2
3
4
5
6
7
8
|
+--- main.cc
|
+--- math/
|
+--- MathFunctions.cc
|
+--- MathFunctions.h

根:

1
2
3
4
5
6
7
8
9
10
11
12
13
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo3)
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 添加 math 子目录
add_subdirectory(math)
# 指定生成目标
add_executable(Demo ${DIR_SRCS})
# 添加链接库,链接库的名字是MathFunctions
target_link_libraries(Demo MathFunctions)

子:

1
2
3
4
5
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_LIB_SRCS 变量
aux_source_directory(. DIR_LIB_SRCS)
# 生成链接库
add_library (MathFunctions ${DIR_LIB_SRCS})

add_library:将源文件编译为静态链接库。

编译选项

这些编译选项可以被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
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo4)
# 加入一个配置头文件,用于处理 CMake 对源码的设置
configure_file (
"${PROJECT_SOURCE_DIR}/config.h.in"
"${PROJECT_BINARY_DIR}/config.h"
)
# 是否使用自己的 MathFunctions 库
option (USE_MYMATH
"Use provided math implementation" ON)
# 是否加入 MathFunctions 库
if (USE_MYMATH)
include_directories ("${PROJECT_SOURCE_DIR}/math")
add_subdirectory (math)
set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 指定生成目标
add_executable(Demo ${DIR_SRCS})
target_link_libraries (Demo ${EXTRA_LIBS})

configure_file用于根据配置生成头文件。
option:添加选项

选项可用在cmake时传入或者可视化选择

添加第三方依赖

使用:

1
2
3
4
# 添加链接目录
link_directories(${LINK_DIR})
# 添加链接目标
target_link_libraries(test_boost boost_filesystem boost_system)

只写库名就可以,就是libxxx.so的中间xxx的部分

或者直接用add_library、find_library

常见函数

具体用法可在:
https://www.zybuluo.com/khan-lau/note/254724
手册上查找

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
加入头文件
命令: include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])
作用:
把dir1, [dir2 …]这(些)个路径添加到当前CMakeLists及其子CMakeLists的头文件包含路径中;
AFTER 或者 BEFORE 指定了要添加的路径是添加到原有包含列表之前或之后
若指定 SYSTEM 参数,则把被包含的路径当做系统包含路径来处理
加入源文件
命令:aux_source_directory(<dir> <variable>)
作用:查找dir路径下的所有源文件,保存到variable变量中.
加入库目录
命令:link_directories(directory1 directory2 ...)
指定连接器查找库的路径。
链接库文件
命令:target_link_libraries(<target> [item1 [item2 [...]]] [[debug|optimized|general] <item>] ...)
作用:仅需知道,名字叫${PROJECT_NAME}这个target需要链接util这个库,会优先搜索libutil.a(windows上就是util.lib), 如果没有就搜索libutil.so(util.dll, util.dylib)’
添加编译器选项
set(CMAKE_CXX_COMPILER "clang++" ) # 显示指定使用的C++编译器
set(CMAKE_CXX_FLAGS "-std=c++11") # c++11
set(CMAKE_CXX_FLAGS "-g") # 调试信息
set(CMAKE_CXX_FLAGS "-Wall") # 开启所有警告
set(CMAKE_CXX_FLAGS_DEBUG "-O0" ) # 调试包不优化
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -DNDEBUG " ) # release包优化
CMAKE_CXX_FLAGS 是CMake传给C++编译器的编译选项,通过设置这个值就好比 g++ -std=c++11 -g -Wall
CMAKE_CXX_FLAGS_DEBUG 是除了CMAKE_CXX_FLAGS外,在Debug配置下,额外的参数
CMAKE_CXX_FLAGS_RELEASE 同理,是除了CMAKE_CXX_FLAGS外,在Release配置下,额外的参数
创建库目标文件
add_library(<name> [STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
[source1] [source2 ...])
name文件名、创建库的类型、源。
add_library命令也可以用来创建导入的库目标
add_library(<name> <SHARED|STATIC|MODULE|UNKNOWN> IMPORTED)
导入的库目标是引用了在工程外的一个库文件的目标。没有生成构建这个库的规则。这个目标名字的作用域在它被创建的路径及以下有效。他可以向任何在该工程内构建的目标一样被引用。
寻找库
find_library(<VAR> name1 [path1 path2 ...])
查找库文件,存于变量中
添加命令:
add_custom_command 命令有两种主要的功能;第一种是为了生成输出文件,添加一条自定义命令。
添加目标
add_custom_target 添加一个目标,它没有输出;这样它就总是会被构建。
添加define flag
add_definitions 为源文件的编译添加由-D引入的define flag。
add_dependencies 为顶层目标引入一个依赖关系。
add_executable 使用给定的源文件,为工程引入一个可执行文件。
add_library 使用指定的源文件向工程中添加一个库。
add_subdirectory(source_dir [binary_dir]
[EXCLUDE_FROM_ALL])
这条命令的作用是为构建添加一个子路径。source_dir选项指定了CMakeLists.txt源文件和代码文件的位置。
add_test添加测试
aux_source_directory查找某路径下所有源文件
configure_file 将一份文件拷贝到另一个位置并修改它的内容。
configure_file的作用是让普通文件也能使用CMake中的变量。
define_property 定义并描述(Document)自定义属性。
在一个域(域(scope))中定义一个可以用set_property和get_property命令访问的属性。这个命令对于把文档和可以通过get_property命令得到的属性名称关联起来非常有用。
execute_process执行子进程
file文件操作
find_file 查找文件
find_library 查找库文件
include 从给定的文件中读取CMake的清单文件代码。在清单文件中的命令会被立即处理,就像它们是写在这条include命令展开的地方一样。
option:为用户提供可选项
project 为整个工程设置工程名
set 将一个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
32
33
34
35
36
37
38
39
40
41
函数
function(<name> [arg1 [arg2 [arg3 ...]]])
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
endfunction(<name>)
每个变量执行
foreach(loop_var arg1 arg2 ...)
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
endforeach(loop_var)
条件
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)
macro(<name> [arg1 [arg2 [arg3 ...]]])
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
  ...
endmacro(<name>)
循环
while(condition)
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
endwhile(condition)

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
-C <initial-cache>: 预加载一个脚本填充缓存文件。
  当cmake在一个空的构建树上第一次运行时,它会创建一个CMakeCache.txt文件,然后向其中写入可定制的项目设置数据。-C选项可以用来指定一个文件,在第一次解析这个工程的cmake清单文件时,从这个文件加载缓存的条目(cache entries)信息。被加载的缓存条目比项目默认的值有更高的优先权。参数中给定的那个文件应该是一个CMake脚本,其中包含有使用CACHE选项的SET命令;而不是一个缓存格式的文件。
-D <var>:<type>=<value>: 创建一个CMake的缓存条目。
  当cmake第一次运行于一个空的构建数时,它会创建一个CMakeCache.txt文件,并且使用可定制的工程设置来填充这个文件。这个选项可以用来指定优先级高于工程的默认值的工程设置值。这个参数可以被重复多次,用来填充所需要数量的缓存条目(cache entries)。
-U <globbing_expr>: 从CMake的缓存文件中删除一条匹配的条目。
  该选项可以用来删除CMakeCache.txt文件中的一或多个变量。文件名匹配表达式(globbing expression)支持通配符*和?的使用。该选项可以重复多次以删除期望数量的缓存条目。使用它时要小心,你可能因此让自己的CMakeCache.txt罢工。
-G <generator-name>: 指定一个makefile生成工具。
  在具体的平台上,CMake可以支持多个原生的构建系统。makefile生成工具的职责是生成特定的构建系统。可能的生成工具的名称将在生成工具一节给出。
-Wno-dev: 抑制开发者警告。
  抑制那些为CMakeLists.txt文件的作者准备的警告信息。
-Wdev: 使能开发者警告信息输出功能。
  允许那些为CMakeLists.txt文件的作者准备的警告信息。
-E: CMake命令行模式。
  为了真正做到与平台无关,CMake提供了一系列可以用于所有系统上的的命令。以-E参数运行CMake会帮助你获得这些命令的用法。可以使用的命令有:chdir, copy, copy_if_different copy_directory, compare_files, echo, echo_append, environment, make_directory, md5sum, remove_directory, remove, tar, time, touch, touch_nocreate, write_regv, delete_regv, comspec, create_symlink。
-i: 以向导模式运行CMake。
  向导模式是在没有GUI时,交互式地运行cmake的模式。cmake会弹出一系列的提示,要求用户回答关于工程配置的一行问题。这些答复会被用来设置cmake的缓存值。
-L[A][H]: 列出缓存的变量中的非高级的变量。
  -L选项会列出缓存变量会运行CMake,并列出所有CMake的内有被标记为INTERNAL或者ADVANCED的缓存变量。这会显示当前的CMake配置信息,然后你可以用-D选项改变这些选项。修改一些变量可能会引起更多的变量被创建出来。如果指定了A选项,那么命令也会显示高级变量。如果指定了H选项,那么命令会显示每个变量的帮助信息。
  
--build <dir>: 构建由CMake生成的工程的二进制树。(这个选项的含义我不是很清楚—译注)
该选项用以下的选项概括了内置构建工具的命令行界面
<dir> = 待创建的工程二进制路径。
--target <tgt> = 构建<tgt>,而不是默认目标。
--config <cfg> = 对于多重配置工具,选择配置<cfg>。
--clean-first = 首先构建目标的clean伪目标,然后再构建。
(如果仅仅要clean掉,使用--target 'clean'选项。)
-- = 向内置工具(native tools)传递剩余的选项。
运行不带选项的cmake --build来获取快速帮助信息。
-N: 查看模式。
仅仅加载缓存信息,并不实际运行配置和生成步骤。
-P <file>: 处理脚本模式。
将给定的cmake文件按照CMake语言编写的脚本进行处理。不执行配置和生成步骤,不修改缓存信息。如果要使用-D选项定义变量,-D选项必须在-P选项之前。
--graphviz=[file]: 生成依赖的graphviz图。
生成一个graphviz软件的输入文件,其中包括了项目中所有库和可执行文件之间的依赖关系。
--system-information [file]: 输出与该系统相关的信息。
输出范围比较广的、与当前使用的系统有关的信息。如果在一个CMake工程的二进制构建树的顶端运行该命令,它还会打印一些附加信息,例如缓存,日志文件等等。
--debug-trycompile: 不删除“尝试编译”路径。
不删除那些为try_compile调用生成的路径。这在调试失败的try_compile文件时比较有用。不过,因为上一次“尝试编译”生成的旧的垃圾输出文件也许会导致一次不正确通过/不通过,且该结果与上次测试的结果不同,所以该选项可能会改变“尝试编译”的结果。对于某一次“尝试编译”,该选项最好只用一次;并且仅仅在调试时使用。
--debug-output: 将cmake设置为调试模式。
在cmake运行时,打印额外的信息;比如使用message(send_error)调用得到的栈跟踪信息。
--trace: 将cmake设置为跟踪模式。
用message(send_error)调用,打印所有调用生成的跟踪信息,以及这些调用发生的位置。(这句话含义不是很确定—译注。)
--help-command cmd [file]: 打印单个命令cmd的帮助信息,然后退出。
显示给定的命令的完整的文档。如果指定了[file]参数,该文档会写入该文件,其输出格式由该文件的后缀名确定。支持的文件类型有:man page,HTML,DocBook以及纯文本。
--help-command-list [file]: 列出所有可用命令的清单,然后退出。
该选项列出的信息含有所有命令的名字;其中,每个命令的帮助信息可以使用--help-command选项后跟一个命令名字得到。如果指定了[file]参数,帮助信息会写到file中,输出格式依赖于文件名后缀。支持的文件格式包括:man page,HTML,DocBook以及纯文本。
--help-commands [file]: 打印所有命令的帮助文件,然后退出。
显示所有当前版本的命令的完整文档。如果指定了[file]参数,帮助信息会写到file中,输出格式依赖于文件名后缀。支持的文件格式包括:man page,HTML,DocBook以及纯文本。
--help-compatcommands [file]: 打印兼容性命令(过时的命令—译注)的帮助信息。
显示所有关于兼容性命令的完整文档。如果指定了[file]参数,帮助信息会写到file中,输出格式依赖于文件名后缀。支持的文件格式包括:man page,HTML,DocBook以及纯文本。
--help-module module [file]: 打印某单一模块的帮助信息,然后退出。
打印关于给定模块的完整信息。如果指定了[file]参数,帮助信息会写到file中,且输出格式依赖于文件名后缀。支持的文件格式包括:man page,HTML,DocBook以及纯文本。
--help-module-list [file]: 列出所有可用模块名,然后退出。
列出的清单包括所有模块的名字;其中,每个模块的帮助信息可以使用--help-module选项,后跟模块名的方式得到。如果指定了[file]参数,帮助信息会写到file中,且输出格式依赖于文件名后缀。支持的文件格式包括:man page,HTML,DocBook以及纯文本。
--help-modules [file]: 打印所有模块的帮助信息,然后退出。
显示关于所有模块的完整文档。如果指定了[file]参数,帮助信息会写到file中,且输出格式依赖于文件名后缀。支持的文件格式包括:man page,HTML,DocBook以及纯文本。
--help-custom-modules [file]: 打印所有自定义模块名,然后退出。
显示所有自定义模块的完整文档。如果指定了[file]参数,帮助信息会写到file中,且输出格式依赖于文件名后缀。支持的文件格式包括:man page,HTML,DocBook以及纯文本。
--help-policy cmp [file]: 打印单个策略的帮助信息,然后退出。
显示给定的策略的完整文档。如果指定了[file]参数,帮助信息会写到file中,且输出格式依赖于文件名后缀。支持的文件格式包括:man page,HTML,DocBook以及纯文本。
--help-policies [file]: 打印所有策略的帮助信息,然后退出。
显示所有策略的完整文档。如果指定了[file]参数,帮助信息会写到file中,且输出格式依赖于文件名后缀。支持的文件格式包括:man page,HTML,DocBook以及纯文本。
--help-property prop [file]: 打印单个属性的帮助信息,然后退出。
显示指定属性的完整文档。如果指定了[file]参数,帮助信息会写到file中,且输出格式依赖于文件名后缀。支持的文件格式包括:man page,HTML,DocBook以及纯文本。
--help-property-list [file]: 列出所有可用的属性,然后退出。
该命令列出的清单包括所有属性的名字;其中,每个属性的帮助信息都可以通过--help-property选项后跟一个属性名的方式获得。如果指定了[file]参数,帮助信息会写到file中,且输出格式依赖于文件名后缀。支持的文件格式包括:man page,HTML,DocBook以及纯文本。
--help-properties [file]: 打印所有属性的帮助信息,然后退出。
显示所有属性的完整文档。如果指定了[file]参数,帮助信息会写到file中,且输出格式依赖于文件名后缀。支持的文件格式包括:man page,HTML,DocBook以及纯文本。
--help-variable var [file]: 打印单个变量的帮助信息,然后退出。
显示指定变量的完整文档。如果指定了[file]参数,帮助信息会写到file中,且输出格式依赖于文件名后缀。支持的文件格式包括:man page,HTML,DocBook以及纯文本。
--help-variable-list [file]: 列出文档中有记录的变量,然后退出。
该命令列出的清单包括所有变量的名字;其中,每个变量的帮助信息都可以通过--help-variable选项后跟一个变量名的方式获得。如果指定了[file]参数,帮助信息会写到file中,且输出格式依赖于文件名后缀。支持的文件格式包括:man page,HTML,DocBook以及纯文本。
--help-variables [file]: 打印所有变量的帮助信息,然后退出。
显示所有变量的完整帮助文档。如果指定了[file]参数,帮助信息会写到file中,且输出格式依赖于文件名后缀。支持的文件格式包括:man page,HTML,DocBook以及纯文本。
--copyright [file]: 打印CMake的版权信息,然后退出。
如果指定了[file]参数,版权信息会写到这个文件中。
--help: 打印用法信息,然后退出。
用法信息描述了基本的命令行界面及其选项。
--help-full [file]: 打印完整的帮助信息,然后退出。
显示大多数UNIX man page提供的帮助信息。该选项是为非UNIX平台提供的;但是如果man手册页没有安装,它也能提供便利。如果制定了[file]参数,帮助信息会写到这个文件中。
--help-html [file]: 以HTML格式打印完整的帮助信息,然后退出。
CMake的作者使用该选来帮助生成web页面。如果指定了[file]参数,帮助信息会写到这个文件中。
--help-man [file]: 以UNIX的man手册页格式打印完整的帮助信息,然后退出。
cmake使用该选生成UNIX的man手册页。如果指定了[file]参数,帮助信息会写到这个文件中。
--version [file]: 显示程序名/版本信息行,然后退出。
如果指定了[file]参数,版本信息会写到这个文件中。

生成器:可以生成的目标平台工程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Borland Makefiles: 生成Borland makefile。
MSYS Makefiles: 生成MSYS makefile。
生成的makefile用use /bin/sh作为它的shell。在运行CMake的机器上需要安装msys。
MinGW Makefiles: 生成供mingw32-make使用的make file。
生成的makefile使用cmd.exe作为它的shell。生成它们不需要msys或者unix shell。
NMake Makefiles: 生成NMake makefile。
NMake Makefiles JOM: 生成JOM makefile。
Unix Makefiles: 生成标准的UNIX makefile。
在构建树上生成分层的UNIX makefile。任何标准的UNIX风格的make程序都可以通过默认的make目标构建工程。生成的makefile也提供了install目标。
Visual Studio X YYYY:VS各版本
Watcom WMake: 生成Watcom WMake makefiles。
CodeBlocks - 各平台: 生成CodeBlock工程文件。
在顶层目录以及每层子目录下为CodeBlocks生成工程文件,生成的CMakeList.txt的特点是都包含一个PROJECT()调用。除此之外还会在构建树上生成一套层次性的makefile。通过默认的make目标,正确的make程序可以构建这个工程。makefile还提供了install目标。
Eclipse CDT4 - 各平台: 生成Eclipse CDT 4.0 工程文件。
在顶层目录下为Eclipse生成工程文件。在运行源码外构建时,一个连接到顶层源码路径的资源文件会被创建。除此之外还会在构建树上生成一套层次性的makefile。通过默认的make目标,正确的make程序可以构建这个工程。makefile还提供了install目标。

集成开源库的方式

本地连编

基于cmake的本地连编,依据第三方库的编译形式分类:
第三方使用cmake时:
集成第三方cmake文件在自己的cmake结构树中。

第三方使用自己的分发脚本时:如ffmpeg
一般自己的分发脚本还需要执行环境,所以还需要将其环境集成到项目中
难于集成环境时可以预先将其按不同平台分发后拉取,在本地仅做编译

第三方无平台分发或不跨平台:如wtl
直接自己写cmake文件集成,分平台控制即可

远端包依赖管理

类似maven、linux包管理-不过这种都是二进制包管理
注意模块间不要循环依赖,应该是树形依赖
简单基于cmake完成包管理:
https://github.com/imbaya2466/cmakeDependencyManagement

这种实现是非常不适合c/c+++的,因为各种平台、系统、win的(MT、MTD、MD、MDd)、debug、release…..
这种交叉形成的种类根本不可能用二进制管理。

因此必须本地联编,而本地连编+远端依赖管理=下拉源码本地编译

那怎么解决呢。。。。
神器:Vcpkg!!!!!!!!
官方库+自定义下拉+自定义cmake编译脚本=自定义远端依赖源码本地联编
https://github.com/microsoft/vcpkg
https://blog.csdn.net/cjmqas/article/details/79282847
经测试,即使是ffmpeg这样复杂的环境也可以在win上编译

使用杂记

cmake中的list就是每个项用;分割