CMake Tutorial
一个简单但全全面的CMake指南。
Introduction
CMake是一个可扩展的开源系统,它以一种与编译器无关的方式在操作系统中管理构建过程。
和跨平台系统不同,CMake被设计为在本机系统上使用。
CMake支持in-place
(在CMAKE_SOURCE_DIR
目录下直接构建)构建和out-space
(在自定义的目录下构建)编译,也即在一个源码树下进行多个构建工程。
CMake支持构建动态和静态库。
CMake会生成一个缓存文件供图形编辑器(CMake-GUI)使用。
Popular Open Source Project with CMake
以下是一个当前比较流行的使用CMake进行构建的开源项目列表:
OpenCV: https://github.com/opencv/opencv
Caffe2: https://github.com/caffe2/caffe2
MySql Server: https://github.com/mysql/mysql-server
可以浏览Wikipedia以获得更多使用CMake进行构建的开源项目。
阅读开源项目总是一个好的实践,我在入职伊始阅读了一小部分的Linux Kernel源码,这帮助我养成了良好的编码习惯,以及初步形成了编码规范的意识。在以上的开源项目中,OpenCV作为最流行的开源计算机视觉库,无论是源码还是其完备的CMake构建链,都十分值得深入了解,后续关于package-management
的章节我将以OpenCV为例进行讲解。
CMake 是一种脚本语言
CMake的目标是成为一个跨平台的构建过程管理器,所以它定义了自己的脚本语言,具有特定的语法和内置特性。同时CMake又是一个软件程序,所以开发者需要调用它的脚本文件来解释和生成真正的构建文件。
CMakeLists.txt
和<project_name>.cmake
都是CMake的脚本文件,但是通常使用CMakeLists.txt
作为主脚本(就我目前短浅的经历而言<project_name>.name
这种格式更多应用于第三方库管理中)。
CMakeLists.txt
通常位于要构建的项目源代码中。CMakeLists.txt
可以位于任何它将起作用的应用程序、库的源码树的根目录下。如果工程由多个模块并且每个模块可以独自构建和编译,
CMakeLists.txt
可以被插入到各个子目录下。.cmake
文件也是CMake脚本文件,它运行cmake命令来准备环境预处理或分离可以写在CMakeLists.txt
之外的任务。.cmake
也可用于为项目定义模块。这些模块可以是单独构建的库活复杂的多模块项目额外的方法(函数)。
CMake 命令
CMake命令类似于c++ /Java方法或函数,它们以列表的形式接受参数,并相应地执行某些任务。
CMake命令不区分大小写,具体的命令可以阅读CMake-Commands文档。
一些常用的CMake命令:
message
:打印信息cmake_minimum_required
:设置项目使用的CMake的最低版本add_executable
:添加可执行文件targetadd_library
:添加需要编译的target库add_subdirectory
:添加子目录
CMake编写规范:
缩进不是强制的,但为了方便阅读,请尽量合理使用缩进。
CMake不需要使用‘;’作为语句的结束。
所有条件语句需要以对应的结束命令结束。
CMake 环境变量
CMake的环境变量和普通变量相似,但有一下几点区别:
作用范围:CMake环境变量的作用范围为整个CMake进程,并且不会被缓存(也即构建之后无法再修改)。
引用形式:CMake环境变量可以通过
$ENV{<variable>}
的形式引用。初始化:CMake环境变量具有初始值,也可使用
set()
和unset()
修改,但只在CMake进程中生效,这点与系统环境变量不同。
cmake-env-variables列出了所有有特殊含义的CMake环境变量。
CMake 变量
CMake包含一系列预定义的变量用于定位源代码树和系统组分,与CMake命令不同,CMake变量区分大小写。
常用的CMake变量如下:
CMAKE_BINARY_DIR
, PROJECT_BINARY_DIR
:这两个变量内容一致,如果是内部编译,就指的是工程的顶级目录,如果是外部编译,指的就是工程编译发生的目录。 CMAKE_SOURCE_DIR
, PROJECT_SOURCE_DIR
:这两个变量内容一致,都指的是工程的顶级目录。 CMAKE_CURRENT_BINARY_DIR
:外部编译时,指的是target目录,内部编译时,指的是顶级目录 CMAKE_CURRENT_SOURCE_DIR
:CMakeList.txt所在的目录 CMAKE_CURRENT_LIST_DIR
:CMakeList.txt的完整路径 CMAKE_CURRENT_LIST_LINE
:当前所在的行 CMAKE_MODULE_PATH
:如果工程复杂,可能需要编写一些cmake模块,这里通过SET指定这个变量 LIBRARY_OUTPUT_DIR
, BINARY_OUTPUT_DIR
:库和可执行的最终存放目录 PROJECT_NAME
, CMAKE_PROJECT_NAME
:前者是当前CMakeList.txt里设置的project_name,后者是整个项目配置的project_name
cmake-variables给出了所有预定义的CMake变量列表。
开发者也可自定义变量:
CMake 列表
CMake中的所有值都存储为字符串,但在特定的上下文中,字符串可以被视为列表。
使用CMake构建C++代码
前面的段落介绍了CMake脚本的核心原理,接下来将从一个"Hello CMake!"程序出发,由浅到深讲解CMake脚本基本构成。
Hello CMake!
假设我们有这样一个main.cpp
:
那么在Linux平台下的编译指令为:
而CMakeLists.txt
则如下
假设是内部编译:
外部编译:
通常而言不建议使用内部编译,在OpenCV中就禁止了内部编译。
CMAKE_CXX_STANDARD
假如你的源代码中使用了更新的c++标准,那么你需要在CMakeLists.txt中指出。
系统检查
假如你想要为多个平台构建:
为不同平台单独生成可执行文件;
向指定系统的源代码传递宏定义等。
那么你需要在构建过程中检查当前的构建系统:
大型代码库的实现通常是与系统无关的,它们使用宏只对正确的系统使用特定的方法。
宏
宏帮助工程师有条件地构建代码,根据运行的系统配置放弃或包含某些方法。通过宏定义,开发者可以向源代码传递信息,进而实现不同的功能。
CMake的宏需要在宏的名字使用-D
标志来表明这是一个宏定义。
然后在代码中就可以根据定义的宏进行不同的操作,以下代码将打印CMAKEMACROSAMPL
这个宏的值:
CMake文件组织
CMake允许开发者在根目录下新建自己的构建目录,并在该目录下进行构建,以下为基本的CMake构建文件:
除了手动创建build
目录在进行构建,CMake还可以通过使用构建参数来完成同样的工作:
指定目标输出位置
通过设置
CMAKE_RUNTIME_OUTPUT_DIRECTORY
或EXECUTABLE_OUTPUT_PATH
变量的值,可以指定binary生成的位置。通过指定
CMAKE_LIBRARY_OUTPUT_DIRECTORY
或LIBRARY_OUTPUT_PATH
可以指定动态库生成的位置。通过指定
CMAKE_ARCHIVE_OUTPUT_DIRECTORY
或ARCHIVE_OUTPUT_PATH
可以指定静态库生成的位置。
通常来说指定单一路径即可,但对于大型工程项目而言,需要编写更复杂的CMake脚本来进行生成目标位置管理。
Debug和Release配置
CMake建议根据需要实现具有多种配置选项的CMakeLists.txt
,例如Debug和Release选项允许用户构建两周性能和使用场景不同的项目。CMake通过定义CMAKE_BUILD_TYPE
变量来完成构建版本控制:
需要为不同的版本创建不同的目录,这时CMakeLists.txt
就可以使用CMAKE_BUILD_TYPE
这个变量完成构建类型的检查:
[1] CMake不是一个跨平台软件,需要为不同的平台单独开发,这里与交叉编译无关。
Last updated