一步一莲花祈祷
- 0. 官方文档
- 1. 一些概念
- 2. CMakeLists 如何编写?
- 3. cmake命令如何使用?
- 4. 官方文档如何阅读?
- 5. 知识点列表
-
- 构建、编译、链接
- 顶层、父层、子层CMakeLists.txt
- CMake_minimum_required()
- project()
- add_executable()
- add_library()
- cmake变量
- 编译定义(宏定义)
- 变量的作用域
- cmake -Dxx=xx
- set()
- option()
- block()
- execute_process()
- ADD_DEFINITIONS()
- ADD_DEPENDENCIES()
- 查找依赖
- find_package()
- find_library()
- find_path()
- 注释
- 操作符优先级
- if条件语句
- while循环
- foreach循环遍历
- 命令(函数)
- 多核编译加速 -j4
- 编译目录build结构
- 编译模式
- 宏定义、编译定义、编译选项、cmake变量、cmake -D
- - - target 都有哪些参数?什么是target?
- 编译库文件
- 编译可执行文件
- 设置target属性
- get_target_property 和 set_target_properties
- 添加源文件
- 编译结果后缀
- INTERFACE库、OBJECT库、IMPORTED目标
- 6. 多文件项目示例
- 7. CCMake(可视化的cmake)
- 8. cmake 提示 Could NOT find XXX 引起的 CMake Error
- 明明安装了openblas,但是cmake项目编译过程中却提示 Could not find
- 编辑tips
- 参考文献
0. 官方文档
首页:CMake: A Powerful Software Build System
开发文档:CMake Reference Documentation
1. 一些概念
-
CMake是构建C++代码的标准;
-
支持构建多平台的C++代码,如unix,windows,ios…
-
以linux系统为例,make使用Makefile来构建代码,cmake使用CMakeLists.txt来构建Makefile文件,因此,学习cmake就是:(1)学习如何编写 CMakeLists.txt !(2)学习如何使用cmake命令!(有哪些参数?如何使用?)注意:在cmake问世的初期,cmake的功能仅支持为项目生成Makefile文件,编译和安装还需使用make命令,但是cmake自2000年问世发展至今,已经具备编译和安装的功能(其实底层调用了make、msbuild、ninja等构建工具实现构建功能)!
-
cmake允许每个源代码树具有多个构建树:
-
CMake支持许多流行的C++IDE系统以及命令行构建工具如:Visual Studio、Xcode、ninja、make和VSCode等;
-
cmake除了可以构建库文件和可执行文件外,CMake还允许在构建时运行任意cmd命令。
-
尽管CMake支持大写、小写和混合大小写命令,但小写命令是首选;
-
CMake的发展历史:https://cmake.org/history/
-
Forum、FAQs、Support、Issue、Documentation
CMake Discourse Forum
CMake FAQs
Advanced Support
Issue Tracker
CMake Reference Documentation -
如何阅读文档?
(1)将不懂的指令或函数放入搜索框中搜索阅读。
(2)有哪些工具行命令?用法?:Command-Line Tools;如cmake、cpack。
(3)有哪些函数?用法?:cmake-commands(7);如set。
(4)有哪些变量?用法?:cmake-variables(7);如CMAKE_CXX_STANDARD。 -
只有包含main函数的xx.cpp才可以被cmake构建成可执行程序!其余的会被构建成库文件 xx.so 或 xx.o 或 xx.a(因操作系统而异)。
-
构建、编译、链接
构建 = 编译 + 链接
构建:Build,指生成可执行文件的全过程,包括预编译、编译、和链接等。
编译:Compile,将main.cpp, main.h
编译成目标文件main.o
;将xx.cpp, xx.h
编译成库文件xx.so
链接:Link,将库文件xx.so
链接到目标文件main.o
得到最终的可执行文件main.exe
-
构建可执行程序的过程
-
顶层CMakeLists.txt和子层CMakeLists.txt
cmake的对象是顶层CMakeLists.txt文件。因此要么在顶层CMakeLists.txt所在目录下执行cmake命令,要么执行cmake命令时使用顶层CMakeLists.txt文件所在的绝对或相对路径。- 顶层CMakeLists.txt的作用
- 解决依赖问题:findpakage()
- 告知cmake要编译哪些cpp源文件
1)编译默认路径下的源文件,即与顶层CMakeLists.txt同级路径下的源文件。
2)编译通过add_subdirectory()指定的目录下的源文件。如,add_subdirectory(subdirectoryName)
- 告知cmake如何编译cpp源文件
1)将哪些cpp文件构建成可执行文件xx.exe:add_executable()
2)将哪些cpp文件编译成库文件xx.so:add_library(xxso_name xx1.cpp xx2.cpp)
,其中xx1.cpp和xx2.cpp可以是相互调用的关系也可以毫无关系。 - 告知cmake如何将库文件xx.so链接到目标文件main.o以得到最后的可执行文件main.exe
1)告知cmake该库的.h文件所在:target_include_directories()
2)告知cmake该库的.so文件所在:target_link_libraries()
- 其他
声明项目名称:project()
声明cmake版本要求:cmake_minimum_required()
C++版本要求:
CMAKE_CXX_STANDARD
CMAKE_CXX_STANDARD_REQUIRED
set()
- 子层CMakeLists.txt的作用
将同级目录中的xx.h, xx.cpp
编译成库文件libxx.so。
add_library(libxx )
- 顶层CMakeLists.txt的作用
-
在cmake中,
[[...]]
或[*[...]*]
表示换行符,其中*
表示某个任意字符。 -
在CMake中的路径分隔符总是应当使用
/
,因为CMake会对字符串中的\
转义,CMake在对接VS时会自动处理路径分隔符的替换问题。 -
在较复杂的项目中,我们可以在不同的子目录下使用多个CMakeLists.txt,在根目录下的CMakeLists.txt是最顶级的,例如可以使用add_subdirectory(source)命令,进入source文件夹,然后自动执行source目录下的CMakeLists.txt,执行完毕后返回上一级,还可以继续前往其它子目录执行相应的CMakeLists.txt。
2. CMakeLists 如何编写?
2.1 创建一个最简单的CMake项目:1 xx.cpp + 1 CMakeLists.txt
-
使用场景:项目结构 = 1 xx.cpp + 1 CMakeLists.txt
-
目录结构(文件路径):CMakeLists.txt是否必要与写有main函数的cpp文件同一目录?不一定,但一般一个CMakeLists.txt与它负责的xx.cpp、xx.h文件会放在同一个目录下。如果没放在同一个目录,则在CMakeLists.txt中要写相对路径!
-
学习目标:语法、变量、命令
-
最小 CMakeLists.txt:至少包含3行代码(命令)
- 1)使用CMake_minimum_required() 指定最低CMake版本。
项目的最高层CMakeLists.txt都必须通过使用CMake_minimum_required() 来指定最低CMake版本。 - 2)使用 project() 设置项目名称、语言、版本号等。
每个项目都需要此调用,并且应在cmake_minimum_required()之后立即调用。 - 3)使用 add_executable() 命令告诉CMake将哪些
.cpp
构建成可执行程序。
注意:只有包含main函数的xx.cpp才可以被cmake编译成可执行程序!
- 1)使用CMake_minimum_required() 指定最低CMake版本。
-
如何编写项目的顶层CMakeLists.txt ?
例子:项目结构 = 1 xx.cpp + 1 CMakeLists.txt
目录结构(文件路径):CMakeLists.txt 与 xx.cpp 放在同一目录下。
CMakeLists.txt的写法如下:cmake_minimum_required(VERSION 3.10)#cmake的最低版本 project(Tutorial)#cmake项目名称 add_executable(Tutorial tutorial.cpp)#将源文件tutorial.cpp编译成可执行程序Tutorial
注意:只有包含main函数的xx.cpp才可以被cmake编译成可执行程序!add_executable的更多用法请参考:cmake-commands。
-
编译(构建)项目
写完CMakeLists.txt后如何用cmake命令构建项目?- 1)创建一个build目录:
mkdir Step1_build
cd Step1_build
- 2)根据CMakeLists.txt构建Makefile:
格式:cmake <顶层CMakeLists.txt的路径>
如:cmake ../Step1
其中../Step1
是项目最顶层CMakeLists.txt的所在路径。 - 3)编译:
格式:cmake --build <Makefile所在路径>
如:cmake --build .
或用make
命令进行编译。
其中.
是Makefile的所在目录
- 1)创建一个build目录:
2.2 指定 C++ 标准
- 学习目标:1. cmake变量;2. 使用set函数设置cmake变量;
- cmake中的变量:CMake中的系统变量如
CMAKE_CXX_STANDARD
和CMAKE_CXX _STANDARD_REQUIRED
可以用于指定构建项目所需的C++标准。
CMake机制中许多变量以CMAKE_
开头。 - 使用场景:某
.cpp
文件中使用了C++11的语法,CMakeLists.txt 中必须要指定构建程序时使用C++11标准。C++其他版本则同理。 - 涉及命令:
CMAKE_CXX_STANDARD
CMAKE_CXX_STANDARD_REQUIRED
set()
- CMakeLists.txt的写法:
在add_executable()之前添加代码:
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
- 编译方法同理。
2.3 显示编译版本号
- 使用场景:有时,在源代码中希望可以使用CMakelists.txt文件中定义的变量。例如,我们希望根据每次编译来作为项目版本的依据。
- 涉及命令
<PROJECT-NAME>_VERSION_MAJOR
<PROJECT-NAME>_VERSION_MINOR
configure_file()
target_include_directories()
- 其本质是.cpp文件与cmake变量之间的交互!
- 非重点,待续…
- 详细请参考:Exercise 3 - Adding a Version Number and Configured Header File
2.4 使用库来构建可执行程序
- 参考:Adding a Library
- 使用场景:一个项目,包含多个目录、子目录,源文件被放在不同的目录中,顶层的cpp代码引用了子层的cpp,或互引用。
- 说明:这种场景下,包含main函数的cpp所在目录即项目的顶层目录,顶层目录拥有一个CMakeLists.txt文件,称为顶层CMakeLists,其他层级的目录也拥有属于自己的CMakeLists.txt文件,称为子层CMakeLists,它们文件名相同但路径不同!功能上也略有不同。
- 子层CMakeLists在构建程序时会将该层级的cpp编译成库文件xx.o(linux平台),顶层CMakeLists负责将整个项目代码编译成最后的可执行程序,在顶层CMakeLists文件中,使用
add_subdirectory
命令将子目录添加到构建中,最后通过target_include_directories
和target_link_libraries
将库文件xx.o 链接(link) 到最后的可执行程序。 - 涉及命令:
add_library()
add_subdirectory()
target_include_directories()
target_link_libraries()
PROJECT_SOURCE_DIR
- 例子:
顶层目录: 1 CMakeLists.txt + 1 tutorial.cpp(入口函数main所在的cpp)
库目录(顶层目录的子目录):CMakeLists.txt + 2 xx.cpp + 2 xx.h
调用关系:顶层目录中的代码调用了库目录中的代码(函数)。
子层CMakeLists写法:
1)创建一个名为MathFunctions的库文件(编译结果应该是MathFunctions.o),库文件包含这些MathFunctions.cxx mysqrt.cxx源文件的代码。
add_library(MathFunctions MathFunctions.cxx mysqrt.cxx)
顶层CMakeLists写法:
1)添加要被编译成库的子目录(名称):
add_subdirectory(MathFunctions)
2)将库链接到可执行程序:
target_link_libraries(Tutorial PUBLIC MathFunctions)
3)使用这些库的必要依赖信息(指定库的头文件位置):
target_include_directories(Tutorial PUBLIC
"${PROJECT_SOURCE_DIR}/MathFunctions"
)
如果链接的库比较多时,还可以这样写:
# 添加要被编译成库都的子目录
add_subdirectory(MathFunctions1)
add_subdirectory(MathFunctions2)
list(APPEND EXTRA_INCLUDES1 "${PROJECT_SOURCE_DIR}/MathFunctions1")
list(APPEND EXTRA_INCLUDES2 "${PROJECT_SOURCE_DIR}/MathFunctions2")
target_link_libraries(Tutorial PUBLIC MathFunctions1)
target_link_libraries(Tutorial PUBLIC MathFunctions2)
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
${EXTRA_INCLUDES1}
${EXTRA_INCLUDES2}
)
- 完整的CMakeLists
顶层CMakeLists:
cmake_minimum_required(VERSION 3.10)
# set the project name and version
project(Tutorial VERSION 1.0)
# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# configure a header file to pass some of the CMake settings
# to the source code
configure_file(TutorialConfig.h.in TutorialConfig.h)
# TODO 2: Use add_subdirectory() to add MathFunctions to this project
add_subdirectory(MathFunctions)
# add the executable
add_executable(Tutorial tutorial.cxx)
# TODO 3: Use target_link_libraries to link the library to our executable
target_link_libraries(Tutorial PUBLIC MathFunctions)
# TODO 4: Add MathFunctions to Tutorial's target_include_directories()
# Hint: ${PROJECT_SOURCE_DIR} is a path to the project source. AKA This folder!
# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
"${PROJECT_SOURCE_DIR}/MathFunctions"
)
子层CMakeLists:
# TODO 1: Add a library called MathFunctions with sources MathFunctions.cxx
# and mysqrt.cxx
# Hint: You will need the add_library command
add_library(MathFunctions MathFunctions.cxx mysqrt.cxx)
2.5 cmake变量、编译定义(宏定义)
- 使用场景:在使用cmake编译代码时需要输入自定义选项来控制代码块的选择性编译,如
cmake -DTHIS_MYOPTION=ON
。 - 涉及命令
if()
option()
target_compile_definitions()
- 学习目标:
- 添加cmake自定义选项:
option(USE_MYOPTION "my diy option" ON)
什么是自定义选项?如cmake -DUSE_MYOPTION =ON
中的 -DUSE_MYOPTION就是自定义选项!其中选项的默认前缀为-D
- 添加编译定义:
target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
什么是编译定义?原名词为 compile definition,可以理解为编译某块代码的使能,如 xx.cpp、xx.h 中:
#ifdef MY_ COMPILE_DEFINITION
#else
#endif
其中的 MY_ COMPILE_DEFINITION 就是编译定义(compile definition)!! - 条件编译语句:
如 xx.cpp、xx.h 中:
#ifdef USE_MYMATH
#else
#endif
- 添加cmake自定义选项:
- 用法、语法
- 添加自定义选项
对于cmake命令行工具来说是选项;对于CMakeLists.txt来说是变量!
1)在CMakeLists.txt中添加一个变量:option(USE_MYOPTION "my diy option" ON)
2)该变量USE_MYOPTION
的赋值可以通过命令行选项的形式输入,如cmake -DUSE_MYOPTION =OFF
3)然后就可以在同一个CMakeLists.txt中使用该变量(选项),如
if (USE_MYMATH)
target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
endif()
- 为库添加编译定义
在xx.cpp、xx.h中,除了可以用#define
来添加(声明)编译定义之外,还可以在CMakeLists.txt中用target_compile_definitions添加,如:
target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
其中第一个参数的含义是要为哪个target添加,target可以是库(Library),也可以是可执行程序;第二个参数是作用域;第三个参数是编译定义的名称,并且"USE_MYMATH"与USE_MYMATH、-DUSE_MYMATH都会被认定为名称为USE_MYMATH !
- 添加自定义选项
2.6 库的使用依赖(usage requirement)
- usage requirement 表示使用某个库时必须要包含的依赖(如:库的源码路径等信息)。
- 解决的痛点:
在之前,任何想要链接到某个库如MathFunctions的target都需要被告知该库的源码路径信息,例如可执行程序Tutorial要链接库MathFunctions,在顶层CMakeLists.txt中必须包含这些代码:
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
"${PROJECT_SOURCE_DIR}/MathFunctions"
)
其中${PROJECT_BINARY_DIR}
是可执行程序Tutorial的源码目录,${PROJECT_SOURCE_DIR}/MathFunctions
是库MathFunctions的源码目录;
为了节省这些不必要的源码路径声明,可以使用INTERFACE依赖项来实现!
- 用法
- 为库添加依赖项(usage requirement)
(1)为库添加 INTERFACE usage requirement
在该库的CMakeLists.txt中添加:
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
其中,CMAKE_CURRENT_SOURCE_DIR是该库所在的绝对路径。
这样就为该库指定了一个INTERFACE usage requirement。
INTERFACE usage requirement 表示调用者需要而库本身不必须得东西。
usage requirement 被称为“使用依赖”,而 INTERFACE 是其中一项。
(2)在调用层的 CMakeLists.txt 删除该库的依赖信息
list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions")
target_include_directories(Tutorial PUBLIC
${EXTRA_INCLUDES}
)
或:
target_include_directories(Tutorial PUBLIC
"${PROJECT_SOURCE_DIR}/MathFunctions"
)
(3)这样,在调用层的CMakeLists.txt仅这几行代码,需要就可以实现对某库的调用(链接):
add_subdirectory(MathFunctions)
target_link_libraries(Tutorial PUBLIC MathFunctions)
- 为库添加依赖项(usage requirement)
2.7 为库指定C++标准
- 使用场景:为不同的库指定不同的C++版本。
- 技术路线:使用接口库(interface library)。
- 用法
- (1)在顶层CMakeLists.txt中,删除全局配置:
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
- (2)创建一个接口库,并为接口库添加特性(指定C++版本)
add_library(tutorial_compiler_flags INTERFACE)
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)
- (3)将可执行程序、库、接口库链接到一起
target_link_libraries(Tutorial PUBLIC MathFunctions tutorial_compiler_flags)
target_link_libraries(SqrtLibrary PUBLIC tutorial_compiler_flags)
target_link_libraries(MathFunctions PUBLIC tutorial_compiler_flags)
- (1)在顶层CMakeLists.txt中,删除全局配置:
2.8 添加生成器表达式
- 使用场景:生成时添加编译器警告标志。
- 条件:CMake 3.15以上。
- 非重点,略…
- 参考文献:Step 4: Adding Generator Expressions
2.9 Installing and Testing
- 使用场景:通常,仅仅构建一个可执行文件是不够的,它还应该是可安装的。使用CMake,我们可以使用 install 命令指定安装规则。在CMake中支持构建的本地安装通常很简单,只需指定安装位置以及要安装的目标和文件即可。
- 学习目标:安装可执行程序Tutorial 以及库 MathFunctions
- 涉及语法:
cmake --install <dir-build>
或make install
- 条件:cmake3.15或以上版本;老版本的安装只支持
make install
- 关于安装路径
可以通过设置变量CMAKE_INSTALL_PREFIX
来指定安装路径,也可用--prefix
选项来指定,如:cmake --install ./ --prefix "/home/myuser/installdir"
, --prefix指定的目录将会覆盖CMAKE_INSTALL_PREFIX的值成为最终的安装路径! - 待续…
- 参考:Installing and Testing
2.10 Selecting Static or Shared Libraries
- 使用场景:如何构建非显式类型的库(如静态库)。
- 涉及语法
set
option
BUILD_SHARED_LIBS - 参考:Step 10: Selecting Static or Shared Libraries
2.11 Selecting Static or Shared Libraries
待续…
3. cmake命令如何使用?
参考
功能
- Generate a Project Buildsystem
cmake [<options>] -B <path-to-build> [-S <path-to-source>]
cmake [<options>] <path-to-source | path-to-existing-build>
- Build a Project
cmake --build <dir> [<options>] [-- <build-tool-options>]
- Install a Project
cmake --install <dir> [<options>]
- Open a Project
cmake --open <dir>
- Run a Script
cmake [-D <var>=<value>]... -P <cmake-script-file>
- Run a Command-Line Tool
cmake -E <command> [<options>]
- Run the Find-Package Tool
cmake --find-package [<options>]
- Run a Workflow Preset
cmake --workflow [<options>]
- View Help
cmake --help[-<topic>]
- 什么是Project Buildsystem?
例如,Project Buildsystem可以是与make一起使用的Makefile文件,也可以是某IDE的项目文件,总之,它描述了如何使用构建工具(如make)从源代码编译成最终的可执行文件或可被调用的库文件。
标准语法
cmake命令的所有可用选项可在这 cmake-options 查阅,因cmake版本而异。注意:cmake options 分为:(1)Generate 阶段的option;(2)build阶段的option;(3)install阶段的option。使用顺序不同会影响构建结果!
cmake命令的执行可以分为3个阶段(步骤):
1)Generate 阶段(配置阶段)
参考:Generate a Project Buildsystem
实现:项目配置、依赖检测与安装、生成makefile文件以供后续阶段调用!
阶段1常用语法:cmake [其他选项]