Skip to content

类 Unix 系统下 CMake 与使用第三方包的 C++ 环境的搭建#

Abstract

博主在使用 OpenCVC++ 版本配置环境时顺便学习了使用 CMake 配置的方法, 这里记录一下方便下次直接抄. 因为博主个人最喜欢使用的编辑器是 VSCode, 所以接下来的示例都以 VSCode 上整体配置过程效果为例进行说明.

1. 第三方包的下载与从源代码编译#


对于博主使用的 MacOS, 可以很方便地使用 Homebrew 安装一些第三方 C++ 包. 本次安装的 OpenCV 也在此列之中.

如果你没有安装 Homebrew, 直接执行下面的命令安装 Homebrew:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

有了 Homebrew, 直接执行下面的命令即可完成安装:

brew install opencv

这样下载得到的就是 C++ 版本的 OpenCV.

当然, 很多时候我们会遇到第三方包只有源代码而没有对应的二进制包, 或者编译好的第三方包版本过于老旧的问题. 这个时候的解决方法就是从源代码编译安装, 具体内容待博主在树莓派这一 Linux 平台上测试过后再续写.

2. CMake 简介#


首先理清一个概念, CMake 是生成编译脚本的工具, 本身不具备编译功能!!! 最终生成的编译脚本如 Makefile, ninja.build 等都需要对应的编译工具链执行. 一个比较直观的功能示意图如下所示

如图所示, CMake 的功能只是从 CMakeLists.txt 输出 Makefile (或者其他编译脚本), 并不直接参与最后的编译过程.

尽管已经有可以直接写的诸如 Makefile 等编译脚本, 但是 CMake 本身两个很大的优势让更多 C/C++ 开发者选择 CMake. 其一是跨平台性, 一份 CMake 脚本就可以生成大部分平台的编译脚本, 避免跨平台时重写脚本的时间开销; 其二是简洁性, 如果你使用过 Makefile, 应该知道在写复杂的工程项目时需要写的多复杂, 而 CMake 弥补了这一点.

3. CMake 基本脚本编写#


我们称 CMake 生成编译脚本的过程为 配置(Configure). 那么, 如何用 CMake 完成 C/C++ 工程的配置呢?

我们以实例说明. 假设我们正在写的是一个名为 OpenCVProject 的程序, 那么我们新建一个目录 OpenCVProject/. 接下里, 我们在 OpenCVProject/ 目录下新建一个文件, 名为 CMakeLists.txt.

我们写 CMakeLists.txt 的流程是, 首先指定使用的 CMake 版本:

cmake_minimum_required(VERSION 3.31) ## CMake 最低版本限制

接下来设定项目名称, 这一名称也是最终生成的二进制文件的名称:

1
2
3
cmake_minimum_required(VERSION 3.31) ## CMake 最低版本限制

project(OpenCVProject) ## 项目名称

然后指定使用的 C++ 标准, 保证编译链可以看懂对应的 C++ 代码:

1
2
3
4
5
cmake_minimum_required(VERSION 3.31) ## CMake 最低版本限制

project(OpenCVProject) ## 项目名称

set(CMAKE_CXX_STANDARD 11) ## 设置 C++ 编译标准: C++11

当然, 严格限制使用对应版本的 ISO C++ 标准. 可以添加下面的额外限制:

1
2
3
4
5
6
7
cmake_minimum_required(VERSION 3.31) ## CMake 最低版本限制

project(OpenCVProject) ## 项目名称

set(CMAKE_CXX_STANDARD 11) ## 设置 C++ 编译标准: C++11
set(CMAKE_CXX_STANDARD_REQUIRED ON) ## 在编译链没有 C++11 版本时不允许自动降级
set(CMAKE_CXX_EXTENSIONS OFF) ## 禁止编译器非 ISO 标准的编译功能

指定所有被编译的源代码. 因为是示例工程, 因此只有放在 src/ 子目录下的 main.cpp 这一个源代码文件:

cmake_minimum_required(VERSION 3.31) ## CMake 最低版本限制

project(OpenCVProject) ## 项目名称

set(CMAKE_CXX_STANDARD 11) ## 设置 C++ 编译标准: C++11
set(CMAKE_CXX_STANDARD_REQUIRED ON) ## 在编译链没有 C++11 版本时不允许自动降级
set(CMAKE_CXX_EXTENSIONS OFF) ## 禁止编译器非 ISO 标准的编译功能

## 编译对象(源代码)
add_executable(OpenCVProject src/main.cpp)

因为我们在源代码中用到了 C++ 标准库以外的第三方库 OpenCV, 因此需要额外指定包名, 库位置和 include 目录. 一般而言, 完成 OpenCV 的安装后会自动将编译产物加入到系统路径, 不需要手动查找操作, 因此只需要:

cmake_minimum_required(VERSION 3.31) ## CMake 最低版本限制

project(OpenCVProject) ## 项目名称

set(CMAKE_CXX_STANDARD 11) ## 设置 C++ 编译标准: C++11
set(CMAKE_CXX_STANDARD_REQUIRED ON) ## 在编译链没有 C++11 版本时不允许自动降级
set(CMAKE_CXX_EXTENSIONS OFF) ## 禁止编译器非 ISO 标准的编译功能

## 编译对象(源代码)
add_executable(OpenCVProject src/main.cpp)

## 必要的第三方库: OpenCV
find_package(OpenCV REQUIRED) ## 包名: OpenCV, 通过查找 OpenCVconfig.cmake
target_link_libraries(OpenCVProject PRIVATE ${OpenCV_LIBS}) ## OpenCV 的 CMake 配置中自带的变量名 OpenCV_LIBS
target_include_directories(OpenCVProject PRIVATE ${OpenCV_INCLUDE_DIRS}) ## 类似于上一条

Note

如果你也使用 VSCode, 当 OpenCV 安装完毕后, 重启 VSCode 才能刷新刷出 OpenCV 包. 大部分时候 VSCode 自动执行 CMake 报错没有此包而已经安装了对应的包都是此类问题.

这里必须要注意 , 可以直接指定 find_package 寻找 OpenCV 这个包是因为这个包本来就是这个名字(一般而言是因为安装的 OpenCVC++ 第三方库自带 CMake 配置文件 OpenCVConfig.cmake(其他的也差不多叫 XXXConfig.cmake), CMake 可以直接从系统路径查找符合这一文件名的 CMake 配置文件, 查找完成之后会将内部的内容导入到目前的 CMake 配置文件中. 后面的 ${OpenCV_LIBS}${OpenCV_INCLUDE_DIRS} 就是导入的 CMake 配置文件 OpenCVConfig.cmake 中定义的常量, 因此才不需要我们手动指定).

为了方便大家检验 CMake 是否正常运行, 我们再在 OpenCVProject/ 目录下新建目录 src/, 进入 src/ 目录, 创建源代码文件 main.cpp:

#include <iostream>
#include <opencv2/highgui.hpp> // 在正常的编辑器中(比如 VSCode), 使用 CMake 完成配置之后会自动生静态检查器的解析路径, 正常而言是不会报错的.
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main() {
    Mat p1 = imread("..."); // ... 替换为图片路径. 建议使用绝对路径

    if (!p1.data) {
        cout << "Couldn't find the image..." << endl;
        return 1;
    }

    namedWindow("Hollow's Avatar", WINDOW_AUTOSIZE);
    imshow("Hollow's Avatar", p1);

    waitKey(0);
    return 0;
}

4. 调用命令让 CMake 生成配置文件#


部分时候不是使用如 VSCode 等有自动配置功能的编辑器, 这时候就需要手动在终端执行命令. 由于习惯上将 CMake 的配置产物放在 build 子目录下, 我们可以执行配置生成命令:

cmake -S . -B build

其中的 -S 表示源代码(项目)根目录, 也就是 CMakeLists.txt 所在的目录; -B 表示构建配置生成的位置, 一般而言指定为 build, 生成位置是项目根目录所在目录的子目录.

接下来是根据 cmake 中指定的编译链完成编译(如果没有指定, 也会自动查找和调用编译工具链. 当然, VSCode 只会生成配置, 不会自动编译):

cmake --build build

这样编译产生的二进制文件在 build/ 目录下, 有同学可能会觉得不太方便和混乱. 我们是可以指定生成位置的, 简单来说, 我想要指定生成的二进制文件到 bin/ 目录下, 只需要在原有的 CMakeLists.txt 添加:

cmake_minimum_required(VERSION 3.31) ## CMake 最低版本限制

project(OpenCVProject) ## 项目名称

## 推荐指定目录的命令放在 project 后面, 注意不能在 target 设置之后
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/bin")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_SOURCE_DIR}/bin")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_SOURCE_DIR}/bin")

set(CMAKE_CXX_STANDARD 11) ## 设置 C++ 编译标准: C++11
set(CMAKE_CXX_STANDARD_REQUIRED ON) ## 在编译链没有 C++11 版本时不允许自动降级
set(CMAKE_CXX_EXTENSIONS OFF) ## 禁止编译器非 ISO 标准的编译功能

## 编译对象(源代码)
add_executable(OpenCVProject src/main.cpp)

## 必要的第三方库: OpenCV
find_package(OpenCV REQUIRED) ## 包名: OpenCV, 通过查找 OpenCVconfig.cmake
target_link_libraries(OpenCVProject PRIVATE ${OpenCV_LIBS}) ## OpenCV 的 CMake 配置中自带的变量名 OpenCV_LIBS
target_include_directories(OpenCVProject PRIVATE ${OpenCV_INCLUDE_DIRS}) ## 类似于上一条

这样, 一个 C/C++ 的工程配置工作就完成了.

Comments