从零开始学cmake
find_package和pkg-config的用法和原理, get_filename_component已经废弃,现在使用 cmake_path获取路径
CMake内置变量
Variable | Info |
---|---|
PROJECT_NAME | 由当前project()设置的项目名称 |
CMAKE_PROJECT_NAME | 由project()命令设置的第一个项目的名称,即顶级项目 |
PROJECT_SOURCE_DIR | 当前项目的源目录 |
PROJECT_BINARY_DIR | 当前项目的生成目录 |
name_SOURCE_DIR | 名为“name”的项目的源目录。在本例中,创建的源目录将是sublibrary1_SOURCE_DIR、sublibrary2_SOURCE_DIR和subbinary_SOURCE_DIR |
name_BINARY_DIR | 名为“name”的项目的二进制目录。在本例中,创建的二进制目录为sublibrary1_BINARY_DIR 、sublibrary2_BINARY_DIR和subbinary_BINARY_DIR。 |
CMAKE_CURRENT_SOURCE_DIR | 当前处理的 CMakeLists.txt 所在的路径 |
CMAKE_CURRENT_BINARY_DIR | target 编译目录;使用 ADD_SURDIRECTORY(src bin) 可以更改此变量的值;SET(EXECUTABLE_OUTPUT_PATH ) 并不会对此变量有影响,只是改变了最终目标文件的存储路径新路径> |
设置目标二进制生成路径
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${nodeOutDir}) |
FIND_PACKAGE
引入官方库
为了方便我们在项目中引入外部依赖包,cmake官方为我们预定义了许多寻找依赖包的Module,他们存储在path_to_your_cmake/share/cmake-<version>/Modules
目录下。每个以Find<LibaryName>.cmake
命名的文件都可以帮我们找到一个包。我们也可以在官方文档中查看到哪些库官方已经为我们定义好了,我们可以直接使用find_package函数进行引用官方文档:Find
Modules。
我们以curl库为例,假设我们项目需要引入这个库,从网站中请求网页到本地,我们看到官方已经定义好了FindCURL.cmake。所以我们在CMakeLists.txt中可以直接用find_pakcage进行引用。
find_package(CURL) |
对于系统预定义的 Find<LibaryName>.cmake
模块,使用方法一般如上例所示。每一个模块都会定义以下几个变量
<LibaryName>_FOUND |
引入非官方库
假设此时我们需要引入glog库来进行日志的记录,我们在Module目录下并没有找到
FindGlog.cmake
。所以我们需要自行编译安装glog库,再进行引用。
# clone该项目 |
此时我们便可以通过与引入curl库一样的方式引入glog库了
Module模式与Config模式
find_package有两种模式,一种是Module模式,也就是我们引入curl库的方式。另一种叫做Config模式,也就是引入glog库的模式。下面我们来详细介绍着两种方式的运行机制。
在Module模式中,cmake需要找到一个叫做Find<LibraryName>.cmake
的文件。这个文件负责找到库所在的路径,为我们的项目引入头文件路径和库文件路径。cmake搜索这个文件的路径有两个,一个是上文提到的cmake安装目录下的share/cmake-<version>/Modules
目录,另一个使我们指定的CMAKE_MODULE_PATH的所在目录。
如果Module模式搜索失败,没有找到对应的Find<LibraryName>.cmake
文件,则转入Config模式进行搜索。它主要通过<LibraryName>Config.cmake
or
<lower-case-package-name>-config.cmake
这两个文件来引入我们需要的库。以我们刚刚安装的glog库为例,在我们安装之后,它在/usr/local/lib/cmake/glog/
目录下生成了glog-config.cmake
文件,而/usr/local/lib/cmake/<LibraryName>/
正是find_package函数的搜索路径之一。另一个路径是/usr/share/<Libraryname>/cmake/<Libraryname>Config.cmake
由以上的例子可以看到,对于原生支持Cmake编译和安装的库通常会安装Config模式的配置文件到对应目录,这个配置文件直接配置了头文件库文件的路径以及各种cmake变量供find_package使用。
编写自己的Find<LibraryName>.cmake
模块
非由cmake编译的项目,我们通常会编写一个Find<LibraryName>.cmake
,通过脚本来获取头文件、库文件等信息。
在cmake文件夹下新建一个FindAdd.cmake的文件。我们的目标是找到库的头文件所在目录和共享库文件的所在位置。
# 在指定目录下寻找头文件和动态库文件的位置,可以指定多个目标路径 |
这时我们便可以像引用curl一样引入我们自定义的库了。在CMakeLists.txt中添加
# 将项目目录下的cmake文件夹加入到CMAKE_MODULE_PATH中,让find_pakcage能够找到我们自定义的函数库 |
PKG-CONFIG
pkg-config 就是一个命令工具,可以查询已安装库的基本信息,包括版本号、库路径、头文件路径、编译选项、链接选项等, 是一个在源代码编译时查询已安装的库的使用接口的计算机工具软件。
有些项目安装后不提供cmake文件,之生成了.pc文件,这时候我们需要再cmakelists中找到相关信息
pkg-config --modversion glew # 查询glew版本号 |
cmake调用pkg-config
pkg-config是如何找到这些信息的呢?其实,我们在安装第三方库时,会带有一个 .pc文件,比如glew库,.pc文件在本机中位于/usr/local/Cellar/glew/2.1.0_1/lib/pkgconfig/glew.pc,该文件包含了库的基本信息
既然第三方库已经包含了基本信息,那么直接将这些配置信息应用到CMakeLists.txt中即可。具体写法如下:
find_package(PkgConfig) |
和find_package异同
#pkg-config 方式 |
构建cmake子项目
文件结构如下
├── CMakeLists.txt |
[CMakeLists.txt] - 顶级CMakeLists.txt
cmake_minimum_required (VERSION 3.5) |
- [subbinary/CMakeLists.txt] - 生成可执行文件
project(subbinary) |
如果某个子项目创建库,则其他项目可以通过在target_link_library()命令中调用该项目的名称来引用该库。这意味着你不必引用新库的完整路径,它将作为依赖项被添加。
当从子项目添加库时,从cmake v3
开始,不需要使用它们在二进制文件的include目录中添加项目include目录。
这由创建库时target_include_directory()命令中的作用域控制。在本例中,因为子二进制可执行文件链接了subibrary1和subibrary2库,所以它将自动包括${subibrary1_source_DIR}/include
和${subibrary2_source_DIR}/include
文件夹,因为它们是随库的PUBLIC和INTERFACE范围导出的。
[sublibrary1/CMakeLists.txt] - 创建静态库
# Set the project name |
[sublibrary2/CMakeLists.txt] - 设置仅含头文件的库
# Set the project name |
include命令
从文件或模块加载并运行CMake代码.
include(<file|module> [OPTIONAL] [RESULT_VARIABLE <var>] |
从给定的文件加载并运行CMake代码。变量读写访问调用者的范围(动态作用域)。如果存在
OPTIONAL ,则如果文件不存在,则不会引发任何错误。如果给定
RESULT_VARIABLE ,则变量 <var>
将设置为已包含的完整文件名,否则将设置为 NOTFOUND 。
如果指定模块而不是文件,则首先在 CMAKE_MODULE_PATH 中搜索名称为
<modulename>.cmake
的文件,然后在CMake模块目录中搜索。
对此有一个例外:如果调用 include() 的文件本身位于CMake内置模块目录中,则首先搜索CMake内置模块目录,然后搜索 CMAKE_MODULE_PATH 。
如果module为本地编译不安装的cmake项目,则从
<modulename>_PATH
中寻找cmake项目
编译选项和链接选项
# 1. 通过include的方式导入本文件:include(path/flags_gccxxx_armxxx_xxx.cmake) |
各级目录中共享变量
set
一般用set命令定义的变量能从父目录传递到子目录,但opencl与facedetect和facefeature在同级目录,所以用set定义的变量无法共享,要用set(variable value CACHE INTERNAL docstring )这种方式定义的变量会把变量加入到CMakeCache.txt然后各级目录共享会访问到这个变量
set(ICD_LIBRARY "${PROJECT_BINARY_DIR}/lib" CACHE INTERNAL "ICD Library location" ) |
每次运行cmake都会更新这个变量,你会在CMakeCache.txt中找到这个变量
set_property/get_property
使用set_property实现共享变量的方法,不会将变量写入CMakeCache.txt,应该是内存中实现的。
当用set_property定义的property时,第一个指定作用域(scope)的参数设为GLOBAL,这个property在cmake运行期间作用域就是全局的。
然后其他目录下的CMakeLists.txt可以用get_property来读取这个property
set_property(GLOBAL PROPERTY INCLUDE_OPENCL_1_2 "${CMAKE_CURRENT_LIST_DIR}/include/1.2" ) |
获取目录名
get_filename_component
# 当前目录名 |
cmake_path
在新版本上get_filename_component已经不推荐使用了,cmake_path代替
cmake_path(GET <path-var> ROOT_NAME <out-var>) |