CMake 概述
CMake是一个跨平台的编译(Build)工具,可以用简单的语句来描述所有平台的编译过程。
CMake 常见的宏
后面会用到,先在前面做个记录
宏 | 功能 |
---|---|
PROJECT_SOURCE_DIR | 使用cmake命令后紧跟的目录,一般是工程的根目录 |
PROJECT_BINARY_DIR | 执行cmake命令的目录 |
CMAKE_CURRENT_SOURCE_DIR | 当前处理的CMakeLists.txt所在的路径 |
CMAKE_CURRENT_BINARY_DIR | target 编译目录 |
EXECUTABLE_OUTPUT_PATH | 重新定义目标二进制可执行文件的存放位置 |
LIBRARY_OUTPUT_PATH | 重新定义目标链接库文件的存放位置 |
PROJECT_NAME | 返回通过PROJECT指令定义的项目名称 |
CMAKE_BINARY_DIR | 项目实际构建路径,假设在build目录进行的构建,那么得到的就是这个目录的路径 |
CMake 使用
注释
- 注释行
CMake 使用#
进行注释,可以放在任何位置# 这是一个 CMakeLists.txt 文件 cmake_minimum_required(VERSION 3.0.0)
- 注释块
CMake 使用#[[ ]]
进行块注释#[[这是一个 CMakeLists.txt 文件。 这是一个 CMakeLists.txt 文件 这是一个 CMakeLists.txt 文件]] cmake_minimum_required(VERSION 3.0.0)
CMake测试
- 文件结构
leon@leon:prj1$ tree . ├── CMakeLists.txt ├── build └── src ├── main.cpp └── operator ├── add.cpp ├── add.hpp ├── sub.cpp └── sub.hpp 3 directories, 6 files
- CMakeLists.txt
对上面用到的CMake语句的解释cmake_minimum_required(VERSION 3.0) project(CALC) include_directories(src) add_executable(app src/main.cpp src/operator/add.cpp src/operator/sub.cpp)
-
cmake_minimum_required
指定使用的 cmake 的最低版本。 可选,非必须,如果不加可能会有警告 -
project
定义工程名称,并可指定工程的版本、工程描述、web主页地址、支持的语言(默认情况支持所有语言),如果不需要这些都是可以忽略的,只需要指定出工程名字即可。# PROJECT 指令的语法是: project(<PROJECT-NAME> [<language-name>...]) project(<PROJECT-NAME> [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]] [DESCRIPTION <project-description-string>] [HOMEPAGE_URL <url-string>] [LANGUAGES <language-name>...])
-
include_directories
将给定的目录添加到编译器用于搜索包含文件的目录中。相对路径被解释为相对于当前源目录。# include_directories 指令的语法是: include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])
BEFORE | AFTER
:可选参数,它们用于指定包含目录的添加顺序。如果使用AFTER
修饰符,那么添加的目录将会放在已有包含目录的后面;如果使用BEFORE
修饰符,那么添加的目录将会放在已有包含目录的前面。默认情况下,新的包含目录会放在已有目录的后面。
SYSTEM
:是一个可选的修饰符,用于指定所包含的目录是系统级别的目录。当使用SYSTEM
修饰符时,编译器会将这些目录视为系统级别的头文件目录,这意味着编译器不会产生关于这些目录的警告信息。 -
add_executable
定义工程会生成一个可执行程序# 源文件名可以是一个也可以是多个,如有多个可用空格或;间隔 add_executable(可执行程序名 源文件名称)
-
-
编译效果
进一步优化CMakeLists.txt
-
定义变量
# SET 指令的语法是: # [] 中的参数为可选项, 如不需要可以不写 SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
VAR
: 变量名称
VALUE
: 变量值
例如将所有的源文件都存储到同一个变量中SET(SRC_LIST src/main.cpp src/operator/add.cpp src/operator/sub.cpp) add_executable(app ${SRC_LIST})
-
指定C++标准
#增加-std=c++11 set(CMAKE_CXX_STANDARD 11) #增加-std=c++14 set(CMAKE_CXX_STANDARD 14) #增加-std=c++17 set(CMAKE_CXX_STANDARD 17)
#增加-std=c++11 cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=11 #增加-std=c++14 cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=14 #增加-std=c++17 cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=17
-
指定可执行文件输出路径
在CMake中指定可执行程序输出的路径,也对应一个宏,叫做EXECUTABLE_OUTPUT_PATH,它的值还是通过set命令进行设置:# 如果这个路径中的子目录不存在,会自动生成,无需自己手动创建 set(HOME /mnt/d/workspace/code/cmaketest/prj1) set(EXECUTABLE_OUTPUT_PATH ${HOME}/workspace)
-
文件搜索
如果项目中的可执行文件太多,手动添加太过繁琐
方式一
在 CMake 中使用aux_source_directory 命令可以查找某个路径下的所有源文件,命令格式为aux_source_directory(< dir > < variable >)
dir
:要搜索的目录
variable
:将从dir目录下搜索到的源文件列表存储到该变量中
方式二
在 CMake 中使用file命令可以查找某个路径下的所有源文件,命令格式为:file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型)
GLOB
: 将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其存储到变量中。
GLOB_RECURSE
:递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中。 -
使用文件搜索对上面使用的CMakeLists.txt进行改造
cmake_minimum_required(VERSION 3.0) project(CALC) include_directories(src) set(HOME /mnt/d/workspace/code/cmaketest/prj1) set(EXECUTABLE_OUTPUT_PATH ${HOME}/workspace) file(GLOB_RECURSE SRC_LIST ${HOME}/src/*.cpp) add_executable(app ${SRC_LIST})
制作静态库
- 语法
在Linux中,静态库名字分为三部分:lib+库名字+.a,此处只需要指定出库的名字就可以了,另外两部分在生成该文件 的时候会自动填充。add_library(库名称 STATIC 源文件1 [源文件2] ...)
- 测试制作静态库
文件结构
CMakeLists.txt. ├── CMakeLists.txt ├── build ├── src │ ├── main.cpp │ └── operator │ ├── add.cpp │ ├── add.hpp │ ├── sub.cpp │ └── sub.hpp └── workspace └── app 4 directories, 7 files
这样最终就会生成对应的静态库文件libcalc.a。cmake_minimum_required(VERSION 3.0) project(CALC) set(CMAKE_CXX_STANDARD 11) include_directories(src) set(HOME /mnt/d/workspace/code/cmaketest/prj1) set(EXECUTABLE_OUTPUT_PATH ${HOME}/workspace) file(GLOB_RECURSE SRC_LIST ${HOME}/src/operator/*.cpp) # add_executable(app ${SRC_LIST}) add_library(calc STATIC ${SRC_LIST})
制作动态库
-
语法
add_library(库名称 SHARED 源文件1 [源文件2] ...)
在Linux中,动态库名字分为三部分:lib+库名字+.so,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充。
-
测试制作动态库
文件结构. ├── CMakeLists.txt ├── build ├── src │ ├── main.cpp │ └── operator │ ├── add.cpp │ ├── add.hpp │ ├── sub.cpp │ └── sub.hpp └── workspace └── app 4 directories, 7 files
CMakeLists.txt
cmake_minimum_required(VERSION 3.0) project(CALC) set(CMAKE_CXX_STANDARD 11) include_directories(src) set(HOME /mnt/d/workspace/code/cmaketest/prj1) set(EXECUTABLE_OUTPUT_PATH ${HOME}/workspace) file(GLOB_RECURSE SRC_LIST ${HOME}/src/operator/*.cpp) # add_executable(app ${SRC_LIST}) add_library(calc SHARED ${SRC_LIST})
这样最终就会生成对应的静态库文件libcalc.so。
设置库文件生成路径
- 方式1
对于生成的库文件来说和可执行程序一样都可以指定输出路径。由于在Linux下生成的动态库默认是有执行权限的,所以可以按照生成可执行程序的方式去指定它生成的目录set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
- 方式2
由于在Linux下生成的静态库默认不具有可执行权限,所以在指定静态库生成的路径的时候就不能使用EXECUTABLE_OUTPUT_PATH宏了,而应该使用LIBRARY_OUTPUT_PATH,这个宏对应静态库文件和动态库文件都适用。set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
同时生成静态库和动态库
文件结构
.
├── CMakeLists.txt
├── build
├── lib
├── src
│ ├── main.cpp
│ └── operator
│ ├── add.cpp
│ ├── add.hpp
│ ├── sub.cpp
│ └── sub.hpp
└── workspace
5 directories, 6 files
CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(CALC)
set(CMAKE_CXX_STANDARD 11)
include_directories(src)
set(HOME /mnt/d/workspace/code/cmaketest/prj1)
set(LIBRARY_OUTPUT_PATH ${HOME}/lib)
file(GLOB_RECURSE SRC_LIST src/operator/*.cpp)
add_library(calc_shared SHARED ${SRC_LIST})
add_library(calc_static STATIC ${SRC_LIST})
set_target_properties(calc_shared PROPERTIES OUTPUT_NAME "calc")
set_target_properties(calc_static PROPERTIES OUTPUT_NAME "calc")
-
set_target_properties
用于重新定义库的输出名称,如果不使用set_target_properties
也可以,那么库的名称就是add_library
里面定义的名称,只是连续两次使用add_library
指定库名称时,这个名称不可以相同,而set_target_properties
可以将库名称设置为相同,只是最终生成的库文件的后缀不同(.so .a),这样相对来说好看一点
CMake链接库文件
CMake链接静态库和动态库的区别
- 链接静态库
静态库会在生成可执行程序的链接阶段被打包到可执行程序中,所以可执行程序启动,静态库就被加载到内存中了。 - 链接动态库
动态库在生成可执行程序的链接阶段不会被打包到可执行程序中,当可执行程序被启动并且调用了动态库中的函数的时候,动态库才会被加载到内存。因此,在cmake中指定要链接的动态库的时候,应该将命令写到生成了可执行文件之后
CMake链接静态库
- 语法
link_libraries(<static lib> [<static lib>...])
<static lib>
:指定出要链接的静态库的名字。可以是全名 libxxx.a,也可以是掐头(lib)去尾(.a)之后的名字 xxx
[<static lib>...]
:要链接的其它静态库的名字。
如果该静态库不是系统提供的(自己制作或者使用第三方提供的静态库)可能出现静态库找不到的情况,此时可以将 静态库的路径也指定出来:link_directories(<lib path>)
CMake链接动态库
- 语法
target_link_libraries( <target> <PRIVATE|PUBLIC|INTERFACE> <item>... [<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
target
:指定要加载动态库的文件的名字
该文件可能是一个源文件
该文件可能是一个动态库文件
该文件可能是一个可执行文件
PRIVATE|PUBLIC|INTERFACE
:动态库的访问权限,默认为PUBLIC
如果该动态库不是系统提供的(自己制作或者使用第三方提供的静态库)可能出现动态库找不到的情况,此时可以将 动态库的路径也指定出来:link_directories(<lib path>)
测试库文件链接
-
静态库
文件结构. ├── CMakeLists.txt ├── build ├── lib │ └── libcalc.a ├── src │ ├── main.cpp │ └── operator │ ├── add.cpp │ ├── add.hpp │ ├── sub.cpp │ └── sub.hpp └── workspace 5 directories, 8 files
测试链接静态库,在编译时没有用到operator文件夹中的cpp文件
CMakeLists.txt
cmake_minimum_required(VERSION 3.0) project(CALC) set(CMAKE_CXX_STANDARD 11) include_directories(src) set(HOME /mnt/d/workspace/code/cmaketest/prj1) set(EXECUTABLE_OUTPUT_PATH ${HOME}/workspace) # 这里只用mainc.cpp file(GLOB_RECURSE SRC_LIST src/main.cpp) link_directories(${HOME}/lib) link_libraries(calc) add_executable(app ${SRC_LIST})
-
动态库
文件结构. ├── CMakeLists.txt ├── build ├── lib │ └── libcalc.so ├── src │ ├── main.cpp │ └── operator │ ├── add.cpp │ ├── add.hpp │ ├── sub.cpp │ └── sub.hpp └── workspace 5 directories, 8 files
测试链接静态库,在编译时没有用到operator文件夹中的cpp文件
CMakeLists.txt
cmake_minimum_required(VERSION 3.0) project(CALC) set(CMAKE_CXX_STANDARD 11) include_directories(src) set(HOME /mnt/d/workspace/code/cmaketest/prj1) set(EXECUTABLE_OUTPUT_PATH ${HOME}/workspace) # 这里只用mainc.cpp file(GLOB_RECURSE SRC_LIST src/main.cpp) link_directories(${HOME}/lib) link_libraries(calc) add_executable(app ${SRC_LIST})
-
通过
find_library
搜索库文件
find_library
则是一个更基本的方法,用于在系统中搜索特定的库文件。它不依赖于库提供的CMake配置文件,而是直接查找库文件。使用find_library
时,需要手动指定库文件路径、头文件路径等。find_library
更适合于较小或没有CMake配置文件的库。- 语法
find_library (<VAR> name [path1 path2 ...])
find_library ( <VAR> name | NAMES name1 [name2 ...] [NAMES_PER_DIR] [HINTS [path | ENV var]... ] [PATHS [path | ENV var]... ] [PATH_SUFFIXES suffix1 [suffix2 ...]] [DOC "cache documentation string"] [NO_CACHE] [REQUIRED] [NO_DEFAULT_PATH] [NO_PACKAGE_ROOT_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] )
-
<VAR>
用于存储该命令执行的结果,也就是找到的库的全路径(包含库名- <var>可以是普通变量(需要指定NO_CACHE选项),也可以是缓存条目(意味着会存放在CMakeCache.txt中,不删除该文件或者用set重新设置该变量,其存储的值不会再刷新);
- 当库能被找到,<var>会被存放正常的库路径,当库未被找到,<var>中存放的值为"<var>-NOTFOUND"。只要<var>中的值不是"<var>-NOTFOUND",那么即使多次调用find_library,<var>也不会再刷新;
-
name
用于指定待查找的库名称,库名称可以使用全称,例如libmymath.a(优先会当成全名搜索);也可以不带前缀(例如前缀lib)和后缀(例如Linux中的.so、.a,Mac中的.dylib等),直接使用mymath。 -
path
用于指定库的查找的路径
-
- 使用
find_library
链接测试
文件结构
CMakeLists.txt 链接静态库. ├── CMakeLists.txt ├── build ├── lib │ ├── libcalc.a │ └── libcalc.so ├── src │ ├── main.cpp │ └── operator │ ├── add.cpp │ ├── add.hpp │ ├── sub.cpp │ └── sub.hpp └── workspace 5 directories, 8 files
CMakeLists.txt 链接动态库cmake_minimum_required(VERSION 3.0) project(CALC) set(CMAKE_CXX_STANDARD 11) include_directories(src) set(HOME /mnt/d/workspace/code/cmaketest/prj1) set(EXECUTABLE_OUTPUT_PATH ${HOME}/workspace) ## 这里只用mainc.cpp file(GLOB SRC_LIST src/*.cpp) find_library(CALC_LIB libcalc.a ${HOME}/lib) link_libraries(${CALC_LIB}) add_executable(app ${SRC_LIST})
使用find_library可以在cmake的时候判断需要的库文件是否存在,如果不存在会直接报错。如下图所示cmake_minimum_required(VERSION 3.0) project(CALC) set(CMAKE_CXX_STANDARD 11) include_directories(src) set(HOME /mnt/d/workspace/code/cmaketest/prj1) set(EXECUTABLE_OUTPUT_PATH ${HOME}/workspace) ## 这里只用mainc.cpp file(GLOB SRC_LIST src/*.cpp) find_library(CALC_LIB libcalc.so ${HOME}/lib) add_executable(app ${SRC_LIST}) target_link_libraries(${CALC_LIB})
- 语法
CMake日志
- 语法
message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "message to display" ...)
(无)
:重要消息
STATUS
:非重要消息
WARNING
:CMake 警告, 会继续执行
AUTHOR_WARNING
:CMake 警告 (dev), 会继续执行
SEND_ERROR
:CMake 错误, 继续执行,但是会跳过生成的步骤
FATAL_ERROR
:CMake 错误, 终止所有处理过程
变量操作
-
拼接
set 拼接set(变量名 ${变量名1} ${变量名2} ...)
关于上面的命令其实就是将从第二个参数开始往后所有的字符串进行拼接,最后将结果存储到第一个参数中,如果第一个参数中原来有数据会对原数据就行覆盖。
list 拼接
list(APPEND <list> [<element> ...])
list命令的功能比set要强大,字符串拼接只是它的其中一个功能,所以需要在它第一个参数的位置指定出我们要做的操作,APPEND表示进行数据追加,后边的参数和set就一样了。
在CMake中,使用set命令可以创建一个list。一个在list内部是一个由分号;分割的一组字符串。例如,set(var a b c d e)命令将会创建一个list:a;b;c;d;e,但是最终打印变量值的时候得到的是abcde
set(tmp1 a;b;c;d;e) set(tmp2 a b c d e) message(${tmp1}) message(${tmp2})
-
移除
用途:对于一些需要编写成为动态库或者静态库的代码,一般是不需要main函数的,可以通过remove移除该函数所在文件list(REMOVE_ITEM <list> <value> [<value> ...])
宏定义
在进行程序测试的时候,我们可以在代码中添加一些宏定义,通过这些宏来控制这些代码是否生效。如下所示
#include <stdio.h>
#define NUMBER 3
int main()
{
int a = 10;
#ifdef DEBUG
printf("我是一个程序猿, 我不会爬树...\n");
#endif
for(int i=0; i<NUMBER; ++i)
{
printf("hello, GCC!!!\n");
}
return 0;
}
在CMake中可以通过add_definitions
命令来添加宏。语法如下
add_definitions(-D宏名称)
CMake配置交叉编译工具链
在CMakeLists 同级目录下新建一个 toolchain.cmake 文件,在这个文件里加上如下内容
# 指定目标系统
set(CMAKE_SYSTEM_NAME Linux)
# 指定目标平台
set(CMAKE_SYSTEM_PROCESSOR arm)
# 指定交叉编译工具链的根路径
set(CROSS_CHAIN_PATH /path/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf)
# 指定C编译器
set(CMAKE_C_COMPILER "${CROSS_CHAIN_PATH}/bin/arm-linux-gnueabihf-gcc")
# 指定C++编译器
set(CMAKE_CXX_COMPILER "${CROSS_CHAIN_PATH}/bin/arm-linux-gnueabihf-g++")
编译时指定
cmake .. -DCMAKE_TOOLCHAIN_FILE=../toolchain.cmake
CMAKE_BUILD_TYPE
CMAKE_BUILD_TYPE
是 CMake 中一个用于指定构建类型的内置变量。在使用 CMake 构建项目时,可以使用这个变量来指定编译器使用的编译选项和构建类型。
CMAKE_BUILD_TYPE
的默认值是空字符串
, 可以使用命令查看
cmake --system-information | grep CMAKE_BUILD_TYPE
leon@leon:build$ cmake --system-information | grep CMAKE_BUILD_TYPE
CMAKE_BUILD_TYPE == ""
CMAKE_BUILD_TYPE ""
CMAKE_BUILD_TYPE:STRING=
- 系统预定义可选值
Debug
:这个构建类型启用了调试信息,并且通常关闭了优化,以便于程序员调试和定位问题。编译出的代码可能运行较慢,但包含了详细的调试信息(例如变量的值、函数调用栈等)。
Release
:Release
构建类型启用了各种优化选项,以提高代码的性能和运行速度。
RelWithDebInfo
:这个构建类型类似于Release
,但是同时包含了一些调试信息,例如函数名和行号等,以便在出现问题时进行调试。
MinSizeRel
:这个构建类型旨在最小化可执行文件的尺寸,因此关闭了大部分的调试和优化选项 - 工作原理
CMAKE_BUILD_TYPE
的取值会影响程序的编译和链接
具体来说,cmake中预定义了一批格式为:
CMAKE_<LANGUAGE>_FLAGS_<CONFIG>
的变量
代表不同的构建类型,比如Debug
、Release
、RelWithDebInf
o、MinSizeRel
等。这些变量用于设置 C 和 C++ 编译器的选项。当设置了CMAKE_BUILD_TYPE
后,对应的 下的编译器选项就会被应用
可以通过cmake --system-information | grep CMAKE_CXX_FLAGS
来打印出当前cmake版本预定义的变量值:CMAKE_CXX_FLAGS == "" CMAKE_CXX_FLAGS_DEBUG == "-g" CMAKE_CXX_FLAGS_MINSIZEREL == "-Os -DNDEBUG" CMAKE_CXX_FLAGS_RELEASE == "-O3 -DNDEBUG" CMAKE_CXX_FLAGS_RELWITHDEBINFO == "-O2 -g -DNDEBUG" CMAKE_CXX_FLAGS "" CMAKE_CXX_FLAGS_DEBUG "-g" CMAKE_CXX_FLAGS_DEBUG_INIT " -g" CMAKE_CXX_FLAGS_INIT " " CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG" CMAKE_CXX_FLAGS_MINSIZEREL_INIT " -Os -DNDEBUG" CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG" CMAKE_CXX_FLAGS_RELEASE_INIT " -O3 -DNDEBUG" CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g -DNDEBUG" CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT " -O2 -g -DNDEBUG" CMAKE_CXX_FLAGS:STRING= CMAKE_CXX_FLAGS_DEBUG:STRING=-g CMAKE_CXX_FLAGS_MINSIZEREL:STRING=-Os -DNDEBUG CMAKE_CXX_FLAGS_RELEASE:STRING=-O3 -DNDEBUG CMAKE_CXX_FLAGS_RELWITHDEBINFO:STRING=-O2 -g -DNDEBUG //ADVANCED property for variable: CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS-ADVANCED:INTERNAL=1 //ADVANCED property for variable: CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_DEBUG-ADVANCED:INTERNAL=1 //ADVANCED property for variable: CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 //ADVANCED property for variable: CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_RELEASE-ADVANCED:INTERNAL=1 //ADVANCED property for variable: CMAKE_CXX_FLAGS_RELWITHDEBINFO CMAKE_CXX_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
- 编译时指定
cmake .. -DCMAKE_BUILD_TYPE=Release