Featured image of post learning Cmake

learning Cmake

这些天来学习cmake的一点小结。

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命令会清理变量原先的值
commandargs... #命令不分大小写,参数使用空格分隔,使用双引号引起参数中的空格
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类型DebugRelease),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_VERSIONCMAKE 主版本号,比如 2.4.6 中的 2
CMAKE_MINOR_VERSIONCMAKE 次版本号,比如 2.4.6 中的 4
CMAKE_PATCH_VERSIONCMAKE 补丁等级,比如 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
file
 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>) #排序自身
  1. Declare a target
  2. Declare target’s traits
  3. 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)

未完待续….

哦吼是一首歌。
Built with Hugo
Theme Stack designed by Jimmy