Modern CMake 简明教程(1) - 初识 CMake

CMake 是一个开源、跨平台的构建系统生成器(Build-system Generator)。

CMake 是构建系统生成器,而不是构建系统,CMake 支持生成不同构建系统所支持的工程文件,如 Visual Studio,XCode,Makefile 等。

本教程作为 CMake 的简明教程,不会事无巨细的讲述 CMake 的每一个语法,而是以实用为目的,介绍 CMake 的基础语法和常用指令。

一、Modern CMake

CMake 距今已有 20 多年的历史,CMake 从 3.0 开始引入 Target 概念,有了 Target 和 Property 的定义,CMake 也就更加地现代化。

我们将引入 Target 概念之前(也就是 3.0 之前)的 CMake 称之为老式 CMake,之后的称之为现代 CMake(Modern CMake)。

现代 CMake 是围绕 Target 和 Property 来定义的,在现代 CMake 中不应该出现诸如下面的指令:

  • add_compile_options
  • include_directories
  • link_directories
  • link_libraries

因为这些指令都是目录级别的,在该目录(含子目录)上定义的所有目标都会继承这些属性,这样会导致出现很多隐藏依赖和多余属性的情况。

我们最好直接针对 Target 进行操作,如:

add_executable(hello main.cpp)

# 老式写法
include_directories(./include)

# 现代写法
target_include_directories(hello PRIVATE ./include)

本文讲述的知识点只适用于现代 CMake,让我们脱掉沉重的历史包袱,轻装上阵吧!

二、基础概念

所有的构建系统都需要通过某个入口点来定义项目(如 Visual Studio 的 .sln 文件),CMake 作为构建系统生成器也不例外,CMake 使用的是 CMakeLists.txt 的文件,该文件以 UTF-8 编码(也支持 UTF-8 BOM 文件头),其中存储了符合 CMake 语言规范的脚本代码。

2.1 项目结构

CMake 没有强制规定 CMakeLists.txt 文件的位置以及项目的目录结构,但目前大多数项目都会采用相似的目录结构。

如果项目名称为 my_project,且该项目包含一个名为 lib 的库和一个名为 app 的程序,则目录结构通常如下面所示:

- my_project
  - .gitignore
  - README.md
  - LICENSE.md
  - CMakeLists.txt
  - cmake
    - FindSomeLib.cmake
    - something_else.cmake
  - include
    - my_project
      - lib.h
  - src
    - CMakeLists.txt
    - lib.cpp
  - apps
    - CMakeLists.txt
    - app.cpp
  - tests
    - CMakeLists.txt
    - testlib.cpp
  - docs
    - CMakeLists.txt
  - extern
    - googletest
  - scripts
    - helper.py

当然,上面的名称并不是一成不变的,可以根据自己的喜好来定义,例如 my_project 可以是任意的项目名,如果不喜欢复数,可以将 tests 改成test,如果没有 python 代码,也可以移除 python 目录,cmake 目录则用于存放 CMake 辅助脚本。

从上面的目录结构可以看到,CMakeLists.txt 文件分散在各个子目录中,但在 include 目录中没有 CMakeList.txt 文件,这样是为了防止暴露不必要的文件给库的使用者,因为 include 目录中存放的是库的头文件,在安装时通常都会将该目录拷贝到指定位置(如Linux系统的 /usr/include)。

extern 目录用于存放第三方依赖库的源码,这些库可以通过 git submodule 的形式来管理,也可以直接将源码拷贝到此,并提交到项目 git 中。但无论使用哪种方式,依赖库最好能支持 CMake,这样可以方便的使用 add_subdirectory 命令将项目添加到工程中(add_subdirectory 可以添加任何包含 CMakeLists.txt 文件的目录到项目中)。

2.2 一个简单的示例

我们先从一个简单的示例开始,了解 CMake 的基本玩法。

该示例是只包含一个 main.cpp 文件,我们期望编译该文件能生成 hello_cmake 程序。

目录结构如下:

- hello_cmake
  - main.cpp
  - CMakeLists.txt

main.cpp 文件的内容非常简单:

#include <stdio.h>

int main() {
    printf("hello cmake");
    return 0;
}

CMakeLists.txt 内容如下:

# 设置 CMake 的最低版本
cmake_minimum_required(VERSION 3.16)

# 设置项目名称
project (hello_cmake)

# 添加一个名为 hello_cmake 的目标
# 目标类型为可执行文件
# 使用 main.cpp 来编译生成 hello_cmake 可执行文件(如hello_cmake.exe)
add_executable(hello_cmake main.cpp)

完成上面步骤,我们就可以使用 CMake GUI 或命令行(当然你需要提前安装 CMake,这不在本文的介绍范围之内)就可以生成相应的工程了。

通过 CMake 命令行生成 Visual Studio 工程的命令如下:

cmake.exe -G "Visual Studio 15 2017" -S .\hello_cmake -B .\hello_cmake\build

2.3 源码外构建

我们通常会将构建目录指定到一个单独的子目录内,这个目录名称的通常是 build。如果不这样做,CMake 生成的工程文件和临时缓存文件会污染源码目录。这种方式有个学名叫“源码外构建” (out-of-source build)。

使用源码外构建时,我们通常还会将 build 目录添加到 .gitignore 文件中。

2.4 工作流程

编写 CMake 脚本的基本流程如下:

  1. 在脚本第一行使用 cmake_minimum_required 指定运行当前脚本所需的 CMake 最低版本。
  2. 使用 project 指定项目名称。
  3. 使用 add_executable 或 add_library 创建目标。
  4. 为目标设置包含目录、链接库等属性(可选)。
  5. 安装(可选)。

编写完 CMake 脚本以后,就可以使用 CMake GUI 或命令行来生成对应的工程文件了。以 Visual Studio 为例,对于有 my_lib 库 和 app 应用程序的项目,CMake 会生成如下图所示的 5 个项目:

下面介绍 CMake 自动生成的一些项目的作用:

  • 编译 ALL_BUILD 项目会自动编译除 INSTALL 项目外的所有项目。
  • 编译 INSTALL 项目会执行 CMake 脚本中指定的安装操作。
  • 编译 ZERO_CHECK 项目会再次执行 CMake 脚本,重新生成项目。因此若 CMake 脚本有更新,既可以使用 CMake 工具来重新生成项目,也可以是重新编译 ZERO_CHECK 项目。

2.5 注释

在 CMake 中使用 # 来声明单行注释,这是我们使用最多的注释方法。虽然也支持使用 #[[ ]] 来声明多行注释(也称块注释),但是使用的比较少,例如:

#[[
  这是多行注释也称块注释
  你明白了吗?
]]

2.6 CMake最低版本

cmake_minimum_required 是我们接触到第一个 CMake 指令,该指令用于指定编译该脚本所需的最低 CMake 版本。

在每个 CMakeList.txt 文件的第一行都会使用该指令。

cmake_minimum_required(VERSION <min>[...<policy_max>] [FATAL_ERROR])

如果运行 CMake 的版本低于要求的版本,则将停止处理该脚本并返回错误。

我们始终应该选择一个比编译器晚发布的 CMake 版本,因为只有这样,CMake 才能支持新的编译器选项。但最低版本不应低于 3.0,实际项目中通常最低版本不会低于 3.16(该版本于2020年09月15日发布),本教程也是以此为标准进行讲解的。

2.7 项目名称

使用 project 指定项目名称。

项目名称区别于目标(Target)名称,以 Visual Studio 为例,project 指定的名称对应“解决方案名称”,而 add_executable 或 add_library 等指定的名称才对应具体项目名和生成的“目标文件名”。

设置项目名称后,CMake 会自动定义一些变量(变量的具体用法会在稍后的“3.1 变量”小节进行介绍)。为了方便介绍各个变量的含义,假设我们是通过如下命令来运行 CMake 的:

cmake.exe -G "Visual Studio 15 2017" -S D:\hello_cmake -B D:\hello_cmake\build

下面列举了一些 CMake 自动定义的变量:

  • PROJECT_NAME
    项目名称,如 hello_cmake
  • CMAKE_PROJECT_NAME
    如果 CMakeLists.txt 位于项目的顶级目录,还会定义 CMAKE_PROJECT_NAME 变量,值与 PROJECT_NAME 一致。
  • PROJECT_SOURCE_DIR
    项目的根目录(绝对路径),即 -S 参数指定的目录,如 D:\hello_cmake
  • <PROJECT-NAME>_SOURCE_DIR
    值与 PROJECT_SOURCE_DIR 相同,只是变量名不同,如 hello_cmake_SOURCE_DIR
  • PROJECT_BINARY_DIR
    项目的构建目录(绝对路径),即 -B 参数指定的目录,如 D:\hello_cmake\build
  • <PROJECT-NAME>_BINARY_DIR
    值与 PROJECT_BINARY_DIR 相同,只是变量名不同,如 hello_cmake_BINARY_DIR

主CMakeLists.txt

主 CMakeLists.txt 即项目根目录下的 CMakeLists.txt 文件。可以通过检查 CMAKE_PROJECT_NAME 与 PROJECT_NAME 变量是否相同来判断当前的 CMakeLists.txt 文件是否为主 CMakeLists.txt。

if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)

endif()

2.8 目标类型

既然现代 CMake 是围绕目标(Target)工作的,Target 如此重要,那我们首先就需要创建一个 Target。

在 C/C++ 开发中,常见的 Target 类型有:可执行文件、静态库、动态库,CMake 还额外提供了一个 MODULE 类型。

下面列举了不同类型的目标的创建方式。

可执行文件

使用 add_executable 指令可以创建可执行文件类型的目标。

add_executable(my_exe main.cpp)

动态库和静态库

通过为 add_library 指令指定不同的参数,可以创建动态库和静态库。

add_library(<name> [<type>] [EXCLUDE_FROM_ALL] <sources>...)
# 动态库
add_library(my_lib SHARED main.cpp)

# 静态库
add_library(my_lib STATIC main.cpp)

我们也可以在 add_library 中不指定类型参数,改为通过设置 BUILD_SHARED_LIBS 变量来切换静态库和动态库。下面示例在脚本中设置了 BUILD_SHARED_LIBS 变量值为 ON (ON / OFF 对应 CMake 中的开/关):

cmake_minimum_required(VERSION 3.16)

project (hello_cmake)

set(BUILD_SHARED_LIBS ON)

add_library(hello_cmake main.cpp)

也可以通过命令行参数进行指定 BUILD_SHARED_LIBS 变量:

cmake.exe -G "Visual Studio 15 2017" -DBUILD_SHARED_LIBS=ON -S .\hello_cmake -B .\hello_cmake\build

亦可以在 GUI 界面上设置 BUILD_SHARED_LIBS 变量,如:

### 解决PyCharm无法加载Conda虚拟环境的方法 #### 配置设置 为了使 PyCharm 能够成功识别并使用 Conda 创建的虚拟环境,需确保 Anaconda 的路径已正确添加至系统的环境变量中[^1]。这一步骤至关重要,因为只有当 Python 解释器及其关联工具被加入 PATH 后,IDE 才能顺利找到它们。 对于 Windows 用户而言,在安装 Anaconda 时,默认情况下会询问是否将它添加到系统路径里;如果当时选择了否,则现在应该手动完成此操作。具体做法是在“高级系统设置”的“环境变量”选项内编辑 `Path` 变量,追加 Anaconda 安装目录下的 Scripts 文件夹位置。 另外,建议每次新建项目前都通过命令行先激活目标 conda env: ```bash conda activate myenvname ``` 接着再启动 IDE 进入工作区,这样有助于减少兼容性方面的问题发生概率。 #### 常见错误及修复方法 ##### 错误一:未发现任何解释器 症状表现为打开 PyCharm 新建工程向导页面找不到由 Conda 构建出来的 interpreter 列表项。此时应前往 Preferences/Settings -> Project:...->Python Interpreter 下方点击齿轮图标选择 Add...按钮来指定自定义的位置。按照提示浏览定位到对应版本 python.exe 的绝对地址即可解决问题。 ##### 错误二:权限不足导致 DLL 加载失败 有时即使指定了正确的解释器路径,仍可能遇到由于缺乏适当的操作系统级许可而引发的功能缺失现象。特别是涉及到调用某些特定类型的动态链接库 (Dynamic Link Library, .dll) 时尤为明显。因此拥有管理员身份执行相关动作显得尤为重要——无论是从终端还是图形界面触发创建新 venv 流程均如此处理能够有效规避此类隐患。 ##### 错误三:网络连接异常引起依赖下载超时 部分开发者反馈过因网速慢或者其他因素造成 pip install 操作中途断开进而影响整个项目的初始化进度条卡住的情况。对此可尝试调整镜像源加速获取速度或是离线模式预先准备好所需资源包后再继续后续步骤。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

china_jeffery

你的鼓励是我前进的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值