Cmake 学习
CMake(cross platform Make)是一个跨平台、开源的构建工具。CMake是MakeFile的上层工具,目的是为了构建可移植的makefile实现跨平台的编译。
Write once, run everywhere.
learning by doing.
参考
cmake-examples-Chinese
cmake-examples-github
CMake 教程 | CMake 从入门到应用
CMake 入门实战
开始前的Demo
文件树
1
2
3
|
// ..demo
|-- CMakeLists.txt
|-- main.cpp
|
main.cpp
1
2
3
4
5
|
#include<iostream>
int main(int argc, char* agrv[]) {
std::cout<<" hello CMAKE demo."<<std::endl;
return 0;
}
|
CMakeLists.txt
1
2
3
4
5
6
|
# CMake 最低版本要求
cmake_minimum_required(VERSION 2.8)
#工程名称
project(demo)
#指定生成目标文件
add_executable(hello_demo main.cpp)
|
使用CMake( Linux)
1
2
3
|
cmake PATH // 如: cmake . 在当前目录执行cmake
make // 编译生成
./hello_demo // 执行目标文件
|
Learning CMake
依赖CMakeLists.txt,项目主目标只有一个,主目录中可指定包含的子目录;
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#注释
变量:使用set命令显示定义及赋值,在非if语句中使用${}引用,if中直接使用变量名引用,后续的set命令会清理变量原先的值;
command(args...) #命令不分大小写,参数使用空格分隔,使用双引号引起参数中的空格
add_executable(${var}) <==> add_executable(a.c b.c c.c)
条件语句: if(var)#var 非empty 0 N NO OFF FLASE
...
else()/elseif()
...
endif(var)
循环语句:set(var a b c)
foreach(f ${var})
endforeach(f)
while()....endwhile()
|
内部变量
1
2
3
4
5
6
7
8
9
|
CMAKE_C_COMPILER: 指定C编译器
CMAKE_CXX_COMPILER:指定C++编译器
CMAKE_C_FLAGS:编译C文件时的选项,如 -g;亦可通过add_definition 添加编译选项
EXECUTABLE_OUTPUT_PATH:可执行文件的存放路径
LIBRARY_OUTPUT_PATH:库文件路径
CMAKE_BUILD_TYPE:build类型(Debug、Release),CMAKE_BUILD_TYPE = Debug
BUILD+SHARED_LIBS
在CMakeLists.txt中指定使用set
在cmake命令行中使用:cmake -....
|
cmake 常用变量
名称 | 描述 |
CMAKE_BINARY_DIR,PROJECT_BINARY_DIR,<project_name>_BINARY_DIR | 如果是 in source编译,指的是工程顶层目录,如果是out-of-source编译,指的是工程编译发生的目录 |
CMAKE_SOURCE_DIR, PROJECT_SOURCE_DIR, <projectname> _SOURCE_DIR | 工程顶层目录。 |
CMAKE_CURRENT_SOURCE_DIR | 当前处理的 CMakeLists.txt 所在的路径,比如上面我们提到的 src 子目录。 |
CMAKE_CURRRENT_BINARY_DIR | 如果是 in-source 编译,它跟 CMAKE_CURRENT_SOURCE_DIR 一致,如果是 out-of-source 编译,他指的是 target 编译目录。 |
EXECUTABLE_OUTPUT_PATH , LIBRARY_OUTPUT_PATH | 最终目标文件存放的路径。 |
PROJECT_NAME | 通过 PROJECT 指令定义的项目名称。 |
cmake系统信息
名称 | 描述 |
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. |
cmake编译选项
编译控制开关名 | 描述 |
BUILD_SHARED_LIBS | 使用 ADD_LIBRARY 时生成动态库 |
BUILD_STATIC_LIBS | 使用 ADD_LIBRARY 时生成静态库 |
CMAKE_C_FLAGS | 设置 C 编译选项,也可以通过指令 ADD_DEFINITIONS()添加。 |
CMAKE_CXX_FLAGS | 设置 C++编译选项,也可以通过指令 ADD_DEFINITIONS()添加。 |
命令
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
|
project(projectName) # set the project name
-> as: ${projectName_SOURCE_DIR} 表示项目根目录
inlcude_directories:指定头文件的搜索路径,相当于gcc的-l参数
-> as: include_directories(${projectName_SOURCE_DIR}/header)#增加header目录为include头文件目录
link_directories:动态链接库或静态链接库的搜索路径,相当于gcc的-L参数
-> as: link_diretories(${projectName}/link)
add_subdirectory: 包含子目录
-> as: add_subdirectory(math)
add_executable: 编译可执行程序,指定编译
-> as:add_executalble(out main.cpp hello.cpp) out为二进制可执行文件
add_definitions:添加编译参数
-> as: add_definitions(-DDEBUG)将在gcc命令行中添加DEBUG宏定义
target_link_libraries: 添加链接库,相同于指定-l参数
-> as: target_link_librabies(demo lib)
add_library:将hello.cpp编译成静态库
-> as : add_library(hello hello.cpp)
add_custom_target:
aux_source_directory(<dir> <variable>):获得一个目录下所有源文件
-> as: aux_sourcedirectory(. DIR)
add_dependencies: 定义 target 依赖的其他 target,确保在编译本 target 之前,其他的 target 已经被构建。
foreach()
message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR])
STATUS:正常消息
WARNING:警告,继续执行;
AUTHOR_WARNING:警告,继续执行;
FATAL_ERROR:错误,终止所有处理过程;
SEND_ERROR:错误,继续执行,跳过生成的步骤;
|
1
2
3
|
string
# 返回当前目录的上层路径
string(REGEX REPLACE <regular expression> <replace expression> <ouput variable> <input> [<input> ...])
|
1
2
3
4
5
6
7
8
9
10
11
|
list
list(LENGTH <list> <output variable>) #列表长度
list(GET <list> <index> [<index> ...] <output variable>) #返回指定下标的元素
list(APPEND <list> <element>[<element> ...]) #添加新元素
list(FIND <list> <index> [<index> ...]) #
list(INSERT <list> <index> <element> [<element> ...]) #插入元素至指定位置
list(REMOVE_ITEM <list> <value> [<value> ...])#删除元素
list(REMOVE_AT <list> <index> [<index>...]) # 删除指定下标的元素
list(REMOVE_DUPLICATES <list>) #删除重复元素
list<REVERSE <list>) #反转自身
list(SORT <list>) #排序自身
|
- Declare a target
- Declare target’s traits
- It’s all about targets
边做边学,由浅入深,以问题驱动自己去做。比如如何使用CMake创建一个可执行程序,如何创建一个动态库/静态库,如何配合第三方库,如何支持不同平台不同编译器以及其参数,如何用CMake组织多层目录的项目,如何自定义CMake Target,如何使用CMake调用外部工具等等.
静态链接库
add_library(lib STATIC| SHARED )
target_include_directories(lib PRIVATE|PUBLIC|INTERFACE <include_source>)
add_library(hello::library ALIAS hello_library) # 给hello_library 起别名 hello::library
TIPs(private、public、interface)
1
2
3
|
PRIVATE -- the directory is added to this target's include directories
INTERFACE -- the directory is added to the include directories for any targets that link this library.
PUBLIC -- As above, it is included in this library and also any targets that link this library.
|
PRIVATE: 目录被添加到目标库的包含路径下.
INTERFACE: 目录没有被添加到目标库的包含路径下,而是链接了这个库的其他目标(库或可执行程序)包含路径中
PUBLIC:目录既被 添加到目录的包含路径中,同时添加了到链接这个库的其他目标的包含路径中
包含第三方库
使用find_package()
从CMAKE_MODULE_PATH中的文件夹列表中搜索"FindXXX.cmake"中的CMake模块。
1
2
3
4
5
|
find_package(Boost 1.46.1 REQUIRED COMPONENTS filesystem system)
Boost 库名称
1.46.1 需要的boost库最低版本
required 必需的 找不到报错
components - 要查找的库列表
|
checking if the package:大多数被包含的包都将设置变量XX_FOUND,该变量可用于检查软件包在系统上时候可用。
总结与举例
添加文件目录
1
2
3
4
5
6
7
8
|
文件目录树
+- CMakeLists.txt
+- main.cpp
+- hello.cpp
|- header
+- hello.cpp
//先定义一下CMakeLists.txt中的关键字
demo-static :最终built的可执行文件
|
将main.cpp和hello.cpp添加到生成可执行文件,可以是add_executable(hello main.cpp hello.cpp)
也可以使用变量set(SOURCE_CPP main.cpp hello.cpp) 和add_executable(hello ${SOURCE_CPP})
实现;添加header目录则是target_include_directories(hello private ${PROJECT_SOURCE_DIR}/header)
这里的private
和常用变量PROJECT_SOURCE_DIR
上面已经提过。首先,在这添加目录意味着编译时将知晓该目录下的头文件,那么在main.cpp和hello.cpp
中使用#include"hello.h"
就不会出现未定义
,否则需要写成#include"header/hello.h"
。
1
2
3
4
5
6
7
|
error example
[../build]$ cmake ..
[../build]$ make
Scanning dependencies of target header
[ 33%] Building CXX object CMakeFiles/header.dir/main.cpp.o
/root/code/hello-headers/main.cpp:1:9: fatal error: hello.h: 没有那个文件或目录
1 | #include"hello.h
|
1
2
3
4
5
|
# CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(demo-add-dir)
add_executable(demo main.cpp hello.cpp)
target_include_directories(demo private ${PROJECT_SOURCE_DIR}/header)
|
使用静态库/动态库
1
2
3
4
5
6
7
8
9
|
文件目录树
+- CMakeLists.txt
+- main.cpp
+- hello.cpp
|- header
+- hello.cpp
//先定义一下CMakeLists.txt中的关键字
demo-static :最终built的可执行文件
hellolib :添加的静态链接库
|
将hello.cpp作为静态链接库,链接到最终的可执行文件;必然是add_library(hellolib static hello.cpp)
;由于头文件hello.h在header目录下,所以需要添加目录,那么是target_include_directories(demo-static public ${PROJECT_SOURCE_DIR}/header)
还是target_include_directories(hellolib public ${PROJECT_SOURCE_DIR}/header)
呢?首先,在这里我们使用的PUBLIC
添加目录,意味着目录既被添加到目标(库)的包含路径中,同时添加到了链接了这个库的其他目标(库或者可执行程序)的包含路径中
,所以当添加header
目录到hellolib
时,也被添加到了链接demo-static
中,所以在main.cpp
中使用#include"hello.h"
不会出错.但是,如果是被添加到了demo-static
时呢?却会如上的错误,找不到hello.h
头文件,demo-static
不是库所以在public
时不会传递。
1
2
3
4
5
6
7
8
|
# CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
project(hello-lib-static)
add_executable(demo main.cpp)
add_library(hellolib STATIC hello.cpp)
target_include_directories(hellolib
PUBLIC ${PROJECT_SOURCE_DIR}/header)
target_link_libraries(demo PUBLIC hellolib)
|
1
2
3
4
5
6
7
8
|
Scanning dependencies of target hellolib
[ 25%] Building CXX object CMakeFiles/hellolib.dir/hello.cpp.o
[ 50%] Linking CXX static library libhellolib.a
[ 50%] Built target hellolib
Scanning dependencies of target demo
[ 75%] Building CXX object CMakeFiles/demo.dir/main.cpp.o
[100%] Linking CXX executable demo
[100%] Built target demo
|
动态库的使用类似,把static
改成shared
即可。
1
2
3
4
5
6
|
Scanning dependencies of target hellolib
[ 25%] Building CXX object CMakeFiles/hellolib.dir/src/hello.cpp.o
[ 50%] Linking CXX shared library libhellolib.so
[ 50%] Built target hellolib
[ 75%] Linking CXX executable demo
[100%] Built target demo
|
设置构建类型
CMake具有许多内置的构建配置,可用于编译工程。这些配置指定了代码优化的级别,以及调用信息是否包含在二进制文件中。
- Release 不可以打断点调试,程序开发完成后发行使用的版本,占体积小。对代码做了优化;
-03 -DNDEBUG
- Debug 调试版本,体积大;
-g
- MinSizeRel 最小体积版本
-Os -DNDEBUG
- RelWithDebInfo 优化且调试
-02 -g -DNDEBUG
设置编译方式 Compile Flags
1
2
3
4
5
6
7
8
9
10
|
target_compile_definitions(target [private|public|interface] [items...])
比如在WIN和LINUX下就编译时选择的头文件不同,可以使用如:
#ifdef WIN
#include<xxx>
#include<xxx>
#endif
#ifdef LINUX
#include<xxx>
#include<xxx>
#endif
|
然后再命令行使用: cmake .. -DCMAKE_CXX_FLAGS="-WIN"
SET 变量使用
1
2
|
set(<variable> <value>
[[CACHE <type> <docstring> [FORCE]] | PARENT_SCOPE])
|
-
set一般变量(normal variable)
-
set缓存变量(cache variable)
1
|
缓存变量可以理解为第一次cmake时,这些变量缓存到一份文件中(CMakeCache.txt)。再次运行cmake时候,这些变量会直接使用缓存值,缓存变量在整个cmake运行过程中都起作用。
|
-
set环境变量(environment variable)
未完待续….