CMAKE 学习
CMAKE 学习
1. 初始cmake
1.1 特点
- 开源
- 跨平台
- 管理大型项目
- 简化编译
- 高效
- 可扩展
1.2 问题
需要编写CMakeLists.txt
,与pkgconfig配合不好。1.3 适用
适用C/C++的大型工程,qt不需要。2. 安装
官网下载
包管理工具下载
3. 初试cmake
3.1 写一个简单的main.c和CMakeLists.txt
1 | //main.c |
1 | PROJECT (HELLO) |
3.2 开始构建
1 | cmake . #生成Makefile等文件 |
3.3 简单解释
1 | PROJECT(projectname [CXX] [C] [Java]) |
3.4 基本语法
- 变量使用${}取值,if中直接变量名
- command(arg1 arg2 …)
- 指令大小写无关,参数和变量大小写相关。但是推荐全大写指令。
3.5 关于语法的疑惑
如果文件名中间没有空格,可以不加引号。但是如果有空格,需要加引号。1
2# 例如
SET(SRC_LIST "fu nc.c")
另外可以忽略source列表的源文件后缀,但是不建议这么做。
参数也可以使用;隔开。
3.6 清理工程
make clean
即可对结果进行清理
3.7 其他问题
- cmake 不支持make distclean
- cmake强烈建议外部构建(out-of-source build)
3.8 内部构建与外部构建
外部编译举例(编译wxGTX动态库和静态库)1
2
3
4
5
6
7
8
9
10
11
12# 解压wxGTX
mkdir static shared
cd static
../configure --enable-static # 在static 生成静态库
cd ../shared
../configure --enable-shared # 在shared生成动态库
# 修改以前的编译方式
mkdir build
cd build
cmake ..
make
注意编译后的HELLO_BINARY_DIR指代的路径改变为<src目录>/build
3.9 小结
这一节讲了3个显示命令的用法和变量。后面开始构架工程。
4. 一个更好的helloworld
本章任务:
- 为工程添加src子目录,用来放置工程源代码
- 添加一个doc,放置工程文档hello.txt
- 在工程目录添加文本文件COPYWRITE,README
- 在工程目录添加一个runhello.sh,用来调试hello二进制
- 将构建后的目标文件放入构建目录的bin子目录
- 最终安装这些文件:将hello二进制和runhello.sh安装至/usr/bin,将doc目录的内容以及COPYWRITE/README安装到
<工程目录>/doc/cmake/t2
4.1 准备工作
- 创建t2并把源码和CMakeLists.txt拷贝到目录下
- 创建src,并把源码cut到目录下,添加CMakeLists.txt
- 修改工程的CMakeLists.txt
- 建立build目录并外部编译。
1
2
3
4
5
6# src/CMakeLists.txt
ADD_EXECUTABLE(hello main.c)
# t1/CMakeLists.txt
PROJECT(HELLO)
ADD_SUBDIRECTORY(src bin)
4.2 语法解释
1 | ADD_SUBDIRECTORY(source_dir [binary_dir][EXCLUDE_FROM_ALL]) |
4.3 另存为bin文件
1 | SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) |
4.4 如何安装
- 编译后make install安装
- 打包时的指定目录安装
1
2
3
4
5
6
7
8
9
10# 例如一个简单的Makefile
DESTDIR=
install:
mkdir -p $(DESTDIR)/usr/bin
install -m 755 hello $(DESTDIR)/usr/bin
# 通过make install 安装到/usr/bin
# 或者make install DESTDIR=/tmp/test 将他安装在 /tmp/test/usr/bin 目录,打包时这个方式经常被使用。
# 或者定义PREFIX使用autotools
# ./configure --prefix=/usr 或者./configure --prefix=/usr/local
对于我们的Helloworld工程,可以引入一个新的cmake指令INSTALL
和一个变量CMAKE_INSTALL_PREFIX
CMAKE_INSTALL_PREFIX类似configure命令的–prefix。
常用方法如下1
cmake -DCMAKE_INSTALL_PREFIX=/usr
INSTALL指令用于定义安装规则,安装内容包括目标二进制、动态库以及文件、目录和脚本
INSTALL指令包含了各种安装类型。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# 目标文件的安装:
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 其实就无效了。如果你希望使用 CMAKE_INSTALL_PREFIX 来定义安装路径,就要写成相对路径,即不要以/开头,那么安装后的路径就是${CMAKE_INSTALL_PREFIX}/<DESTINATION 定义的路径>
# 普通文件的安装
INSTALL(FILES files... DESTINATION <dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[RENAME <name>] [OPTIONAL])
# 安装一般文件,并可以指定访问权限,文件名是此指令所在路径下的相对路径。如果默认不定义权限 PERMISSIONS,安装后的权限为:644
# 非目标文件的可执行程序安装(比如脚本之类):
INSTALL(PROGRAMS files... DESTINATION <dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[RENAME <name>] [OPTIONAL])
# 跟上面的 FILES 指令使用方法一样,唯一的不同是安装后权限为:755
# 目录的安装:
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,如果目录名以/结尾,代表将这个目录中的内容安装到目标路径,但不包括这个目录本身。
# PATTERN 用于使用正则表达式进行过滤,PERMISSIONS 用于指定 PATTERN 过滤后的文件权限。
# 安装时CMAKE 脚本的执行:
INSTALL([[SCRIPT <file>] [CODE <code>]] [...])
# SCRIPT 用于安装时调用cmake脚本文件(<abc>.cmake文件)
# CODE 用于执行CMAKE指令,必须以""括起来
4.5 修改Project并支持安装。
- 根目录下,添加doc,在doc下建立一个hello.txt
- 根目录下添加runhello.sh脚本,COPYWRITE和README
- 改写根目录的CMakeLists.txt
- 在build目录使用
cmake -DCMAKE_INSTALL_PREFIX=<想安装的路径> ..
进行外部编译 make && make install
1 | # /CMakeLists.txt |
4.6 小结
如果没有定义CMAKE_INSTALL_PREFIX
,那么会默认安装到/usr/local
5. 静态库和动态库的构建
本章任务:
- 建立一个静态库和动态库,提供HelloFunc函数供其他程序编程使用,HelloFunc向终端输出HelloWorld字符串
- 安装头文件与共享库
5.1 建立并搭建一个动态库
- 在工程目录下建立t3目录
- 在t3下创建CMakeLists.txt
- 在t3下创建lib目录,并创建
hello.c,hello.h,lib/CMakeLists.txt
- 根目录下建立build目录并编译。
1
2
3
4
5
6
7# /CMakeLists.txt
PROJECT(HELLOLIB)
ADD_SUBDIRECTORY(lib)
# lib/CMakeLists.txt
SET(LIBHELLO_SRC hello.c)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
5.2 语法解释
1 | ADD_LIBRARY(libname [SHARED|STATIC|MODULE] |
库类型有三种:
SHARED,动态库
STATIC,静态库
MODULE,在使用dyld的OS有效,如果不支持dyld,则被当做SHARED对待。
EXCLUDE_FROM_ALL 意思是库不会被默认构建,除非有其他组件依赖或者手工构建
5.3 添加一个静态库
修改lib 下的CMakeLists.txt来实现这个过程1
2
3
4
5
6
7
8
9
10# lib/CMakeLists.txt
SET(LIBHELLO_SRC hello.c)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
# 先添加一个静态库,名字为hello_static
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
# 为了得到同名的静态库和动态库,所以设置输出的名称
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
# 为了避免删除已经存在的hello.so(生成新target会清除同名的文件)所以,定义了CLEAN_DIRECT_OUTPUT 属性。
5.4 动态库版本号
按照规则,动态库是应该包含一个版本号的,我们可以看一下系统的动态库,一般情况是1
2
3libhello.so.1.2
libhello.so ->libhello.so.1
libhello.so.1->libhello.so.1.2
为了实现这一功能,所以我们修改了lib/CMakeList.txt1
2
3
4
5# lib/CMakeLists.txt
...
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
# VERSION指代动态库版本
# SOVERSION指代API版本
5.5 安装共享库和头文件
为了把libhello.so.x和libhello.a,hello.h安装到系统目录让其他人开发使用,本例中将hello共享库安装到< prefix >/lib
目录,将hello.h安装到< prefix >/include/hello
目录。
利用INSTALL命令可以做到这一点。1
2
3
4# lib/CMakeLists.txt
...
INSTALL(TARGETS hello hello_static LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)
INSTALL(FILES hello.h DESTINATION include/hello)
然后bash执行1
2
3cmake -DCMAKE_INSTALL_PREFIX=/usr ..
make
make install
可将头文件和共享库安装到/usr下
5.6 小结
本小节,我们谈到了:
如何通过 ADD_LIBRARY 指令构建动态库和静态库。
如何通过 SET_TARGET_PROPERTIES 同时构建同名的动态库和静态库。
如何通过 SET_TARGET_PROPERTIES 控制动态库版本
最终使用上一节谈到的 INSTALL 指令来安装头文件和动态、静态库。
6. 如何使用外部共享库和头文件
6.1 准备工作
- 建立t4目录,并在目录下建立src目录
- 进入src建一个main.c和CMakeLists.txt
- 在根目录建立工程的CMakeLists.txt
1 | /* src/main.c */ |
1 | # src/CMakeLists.txt |
6.2 外部构建
- 建立build目录
- 执行外部构建,发现make报错
error: hello.h: 没有那个文件或目录。
因为hello.h位于/usr/include/hello 目录中,系统不能直接找到hello这个目录
6.3 引入头文件的搜索位置
在src/CMakeLists.txt
添加命令,引入头文件的搜索位置。1
2
3
4
5
6# src/CMakeLists.txt
INCLUDE_DIRECTORIES(/usr/include/hello)
ADD_EXECUTABLE(main main.c)
# INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
# 默认的行为是追加到当前的头文件搜索路径的后面
然后重新进行外部编译,结果出现不一样的报错。
error:undefined reference to `HelloFunc’
因为没有link到共享库libhello上
6.4 为 target 添加共享库
将目标文件链接到libhello,需要引入两个新指令。1
2
3
4# 添加非标准的共享库搜索路径,这里不使用
LINK_DIRECTORIES(directory1 directory2 ...)
# 为target添加需要链接的共享库。
TARGET_LINK_LIBRARIES(target library1 <debug | optimized> library2 ...)
所以修改src/CMakeLists.txt
1
2
3INCLUDE_DIRECTORIES(/usr/include/hello)
ADD_EXECUTABLE(main main.c)
TARGET_LINK_LIBRARIES(main hello)
重新外部构建,正常运行。
可以使用ldd查看外部依赖。1
2
3
4
5ldd src/main
linux-gate.so.1 => (0xb7ee7000)
libhello.so.1 => /usr/lib/libhello.so.1 (0xb7ece000)
libc.so.6 => /lib/libc.so.6 (0xb7d77000)
/lib/ld-linux.so.2 (0xb7ee8000)
如果链接到静态库也是一样,就是把hello改成libhello.a
6.5 特殊的环境变量 CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH
这两个变量主要是用来解决以前 autotools 工程中–extra-include-dir 等参数的支持的。
也就是,如果头文件没有存放在常规路径(/usr/include, /usr/local/include 等),则可以通过这些变量就行弥补。1
2
3
4
5
6
7
8
9
10
11为了将程序更智能一点,我们可以使用 CMAKE_INCLUDE_PATH来进行,使用 bash 的方法
如下:
export CMAKE_INCLUDE_PATH=/usr/include/hello
然后在头文件中将 INCLUDE_DIRECTORIES(/usr/include/hello)替换为:
FIND_PATH(myHeader hello.h)
IF(myHeader)
INCLUDE_DIRECTORIES(${myHeader})
ENDIF(myHeader)
如果你不使用 FIND_PATH,CMAKE_INCLUDE_PATH 变量的设置是没有作用的,你不能指望它会直接为编译器命令添加参数-I<CMAKE_INCLUDE_PATH>。
以此为例,CMAKE_LIBRARY_PATH 可以用在 FIND_LIBRARY中。
6.6 小节
本节我们探讨了:
如何通过 INCLUDE_DIRECTORIES 指令加入非标准的头文件搜索路径。
如何通过 LINK_DIRECTORIES 指令加入非标准的库文件搜索路径。
如果通过 TARGET_LINK_LIBRARIES 为库或可执行二进制加入库链接。
并解释了如何链接到静态库。
7. cmake常用变量和常用环境变量
7.1 变量定义和引用
显式定义:
SET(VARIABLE_NAME file)
隐式定义:
比如PROJECT会隐式定义< projectname >_BINARY_DIR
和< projectname >_SOURCE_DIR
两个变量。
变量引用
使用${}俩引用变量,而在IF等语句中直接使用变量名。
7.2 CMake常用变量
CMAKE_BINARY_DIR | PROJECT_BINARY_DIR|< projectname >_BINARY_DIR
三个变量指代同样的内容,如果是内部构建,就是工程的根目录。如果是外部构建,指的是编译发生的目录。CMAKE_SOURCE_DIR|PROJECT_SOURCE_DIR|<projectname>_SOURCE_DIR
这三个变量指代的内容是一致的,不论哪一种构建方式,都是工程的根目录。CMAKE_CURRENT_SOURCE_DIR
当前处理的CMakeLists.txt所在的路径,比如上面提到的src子目录。CMAKE_CURRENT_BINARY_DIR
如果是内部构建,它跟CMAKE_CURRENT_SOURCE_DIR
一致,如果是外部构建,他指的是 target 编译目录。
使用我们上面提到的ADD_SUBDIRECTORY(src bin)
可以更改这个变量的值。CMAKE_CURRENT_LIST_FILE
输出调用这个变量的 CMakeLists.txt 的完整路径CMAKE_CURRENT_LIST_LINE
输出这个变量所在的行CMAKE_MODULE_PATH
这个变量用来定义自己的 cmake 模块所在的路径。如果你的工程比较复杂,有可能会自己编写一些 cmake 模块,这些 cmake 模块是随你的工程发布的,为了让 cmake 在处理
CMakeLists.txt 时找到这些模块,你需要通过 SET 指令,将自己的 cmake 模块路径设置一下。
比如1
SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
这时候可以通过 INCLUDE 指令来调用自己的模块了。
EXECUTABLE_OUTPUT_PATH
和LIBRARY_OUTPUT_PATH
分别用来重新定义最终结果的存放目录PROJECT_NAME
返回通过 PROJECT 指令定义的项目名称。7.3 CMake调用环境变量的方式
1 | $ENV{NAME} |
7.4 系统信息的变量
- CMAKE_MAJOR_VERSION,CMAKE 主版本号,比如 2.4.6 中的 2
- CMAKE_MINOR_VERSION,CMAKE 次版本号,比如 2.4.6 中的 4
- CMAKE_PATCH_VERSION,CMAKE 补丁等级,比如 2.4.6 中的 6
- CMAKE_SYSTEM,系统名称,比如 Linux-2.6.22
- CMAKE_SYSTEM_NAME,不包含版本的系统名,比如 Linux
- CMAKE_SYSTEM_VERSION,系统版本,比如 2.6.22
- CMAKE_SYSTEM_PROCESSOR,处理器名称,比如 i686.
- UNIX,在所有的类 UNIX 平台为 TRUE,包括 OS X 和 cygwin
- WIN32,在所有的 win32 平台为 TRUE,包括 cygwin
7.5 主要的开关选项
CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS
用来控制IF ELSE语句的书写方式,语法部分会讲到BUILD_SHARED_LIBS
用来控制默认的库编译方式,如果不设置,使用ADD_LIBRARY
并没有指定库类型的情况下,默认编译生成的都是静态库。
如果SET(BUILD_SHARED_LIBS ON)
后,默认生成的是动态库。CMAKE_C_FLAGS
设置C编译选项,也可以通过指令ADD_DEFINITIONS()
添加CMAKE_CXX_FLAGS
设置C++编译选项,也可以通过指令ADD_DEFINITIONS()
添加7.6 小结
本章介绍了一些较常用的 cmake 变量,这些变量仅仅是所有 cmake 变量的很少一部分。
8. CMake常用命令
8.1 基本指令
ADD_DEFINITATIONS
向C/C++编译器添加-D定义,比如1
ADD_DEFINITATIONS(-DENABLE_DEBUG -DABC)
如果代码中定义了#ifdef #endif
代码块,那么会生效。
ADD_DEPENDENCIES
定义target依赖的其他target,确保在这个target编译前,其他target已经被构建。1
ADD_DEPENDENCIES(target-name depend-target1)
8.4 控制指令
IF指令
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
35IF(expression)
#THEN section
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
ELSE(expression)
COMMAND1(ARGS...)
COMMAND2(ARGS...)
ENDIF(expression)
# 表达式使用方法
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)或者 IF(EXISTS file)
# 当目录名或者文件名存在时为真。
IF(file1 IS_NEWER_THAN file2)
# 当 file1 比 file2 新,或者 file1/file2 其中有一个不存在时为真,文件名请使用完整路径。
IF(IS_DIRECTORY dirname)
# 当 dirname 是目录时,为真。
IF(variable MATCHES regex)
# 当给定的变量或者字符串能够匹配正则表达式 regex 时为真。
IF(variable LESS|GREATER|EQUAL number)
# 数字比较
IF(string STRLESS|STRGREATER|STREQUAL string)
# 字典序比较
IF(DEFINED variable)
# 如果被定义为真while
WHILE 指令的语法是:1
2
3
4
5WHILE(condition)
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ENDWHILE(condition)FOREACH
FOREACH 指令的使用方法有三种形式:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16# 列表
FOREACH(loop_var arg1 arg2 ...)
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ENDFOREACH(loop_var)
# 范围
FOREACH(loop_var RANGE total)
ENDFOREACH(loop_var)
# 从 0 到 total 以1为步进
# 范围和步进
FOREACH(loop_var RANGE start stop [step])
ENDFOREACH(loop_var)
# 这个指令需要注意的是,直到遇到 ENDFOREACH 指令,整个语句块才会得到真正的执行。
8.5 小结
本小节基本涵盖了常用的 cmake 指令,包括基本指令、查找指令、安装指令以及控制语句等,特别需要注意的是,在控制语句条件中使用变量,不能用${}引用,而是直接应用变量名。
10.参考
《Cmake 实践》