入门CMake

入门CMake

参考教程: YouTube-Simplified CMake Tutorial, Codevion vimwiki-Modern Simple CMake Tutorial,本笔记所用的全部代码整合GitHub - cmake_tutorial.7z

CMake可以完成复杂项目的编译任务,编译一个C++项目可能需要:

  1. 一个包含main()的主程序入口;
  2. 多个联合编译*.cpp文件(对头文件各种声明的实现);
  3. 多个头文件*.h
  4. 多个链接库*.so
  5. 编译指令-pthread, -O2, -O3等。
    下面我们将在VSCode上逐步完成这些功能,系统Ubuntu 22.04:

单个文件编译

首先我们创建一个空文件夹,里面写一个main.cpp文件:

#include <iostream>

int main() {
  std::cout << "Hello world\n";
  return 0;
}

最简单的编译方法是在终端中使用g++编译并运行起来

g++ main.cpp -o main && ./main

下面我们来用CMake实现这一操作,在和main.cpp的同级目录下创建CMakeLists.txt文件:

cmake_minimum_required(VERSION 3.10)  # CMAKE的最低版本要求
set(CMAKE_CXX_STANDARD 17)  # 使用的C++标准
set(CMAKE_CXX_STANDARD_REQUIRED ON)

project(hello VERSION 1.0)  # 项目名称 版本 版本号
add_executable(hi main.cpp)  # 可执行文件名称(目标) 联合编译的文件 [文件1, 文件2, ...]

真正有用的就是一句话,给出了编译出的可执行文件名称,以及联合编译的cpp文件

add_executable(hi main.cpp)  # 可执行文件名称(目标) 联合编译的文件 [文件1, 文件2, ...]

执行CMake编译方法:

  1. VSCode插件:在VSCode中安装CMake, CMake Tools, CMake Highlight插件,按ctrl+f5就会弹出对g++编译器的选择,选择一个编译器即可,再按ctrl+f5即可看到CMake创建了一个build文件夹,在其中已经编译出了hi可执行文件,下方打印出了我们代码的结果。(CMake会在右边打开Workbench side bar中显示输出信息,非常麻烦,我们可以把右侧边栏上方的输出图表拖到下方的panel面板中,这样就可以不用每次自动在右边显示啦)
  2. 终端:首先我们创建一个新文件夹mkdir my_build,进入该文件夹cd my_build,执行cmake ..即可看到创建了很多缓存文件,再执行make开始编译,完成后会产生可执行文件hi,运行./hi即可。
mkdir my_build
cd my_build
cmake .. && make && ./hi  # 创建CMake缓存 编译 执行

添加头文件

main.cpp同级目录下创建include/文件夹,里面创建include/bar.h头文件:

#pragma once
#include <iostream>

class Bar {
  public:
    inline void foo() {
      std::cout << "Foo!\n";
    }
};

我们在CMakeLists.txtadd_executable(hi main.cpp)下方加入

target_include_directories(hi PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)  # 目标 可见性 头文件目录

这里的PUBLIC表示当前编译的目标对于头文件中内容的可见程度,有如下三个选项(这个一般只有在多层调用时会用到,一般写PUBLIC就完了):

  1. PRIVATE:如果只有*.cpp文件用到头文件中的内容;
  2. INTERFACE:如果只有*.h文件用到头文件中的内容;
  3. PUBLIC:两者都用到。

修改完成CMakeLists.txt后就可以对main.cpp修改如下:

#include <iostream>
#include "bar.h"

int main() {
  std::cout << "Hello world\n";
  Bar().foo();
  return 0;
}

ctrl+f5编译运行了,但是我们发现vscode还是无法找到#include "bar.h"头文件位置,需要手动添加下路径,ctrl+shift+p输入c/c++ ui进入C/C++:编辑配置(UI),找到包含路径中发现已经添加了${workspaceFolder}/**,它就会自动递归寻找工作路径下的头文件了(如果不在本工作路径下的,需要手动添加哦)

添加库文件

手动创建库文件

我们新创建一个文件夹bar/,将刚才写的include/bar.h文件放到该文件夹下,并创建一个bar/bar.cpp文件用来定义其中声明的函数,文件结构如下:

.
├── bar
│   ├── bar.cpp
│   ├── CMakeLists.txt
│   └── include
│       └── bar.h
└── main.cpp

每个文件内容如下

我们需要将bar/文件夹下的内容作为一个整体编译成一个.so.a连接文件用于main.cpp的链接,所以在该目录下也需要一个bar/CMakeLists.txt

add_library(ba STATIC bar.cpp)  # (创建连接库) 目标(会生成一个libbar.a) 库类型(静态库STATIC) 源文件1 源文件2 ...
target_include_directories(ba PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)  # 所需的头文件

这样我们就可以在编译main.cpp时调用生成出来的libbar.a库文件了,修改CMakeLists.txt如下:

cmake_minimum_required(VERSION 3.10)  # CMAKE的最低版本要求
set(CMAKE_CXX_STANDARD 17)  # 使用的C++标准
set(CMAKE_CXX_STANDARD_REQUIRED ON)

project(hello VERSION 1.0)  # 项目名称 版本 版本号

add_subdirectory(bar)  # 编译子目录, 这里就是编译bar生成库文件

add_executable(hi main.cpp)  # 可执行文件名称(目标) 联合编译的文件 [文件1, 文件2, ...]
# 原来用的target_include_directories加的头文件,现在头文件直接编译成库文件了,直接调库文件即可
target_link_libraries(hi PUBLIC ba)  # (链接库文件) 目标 可见性 库名称1(注意: 库名称就是bar/CMakeLists.txt中的目标名称, 不是文件夹名称) 库名称2 ...

main.cpp下执行ctrl+f5即可完成编译运行了。

调用外部库

我们将分别调用SFML, 与Python相关的matplotlib-cpp, tensorboard_looger以及torch的原生C++库libtorch

SFML

以SFML可视化窗口库为例,安装SFML:

sudo apt install libsfml-dev

安装的SFML会在/usr/include/SFML/下创建所需的头文件,在/usr/lib/x86_64-linux-gnu/下创建链接所需的文件libsfml-*.so.x.x,在/usr/lib/x86_64-linux-gnu/cmake/SFML/创建CMake配置所需的SFMLConfig.cmake文件,用于find_package命令寻找包文件位置,在安装到/usr/lib中后就不用再执行find_package,链接库会自动查找文件位置,创建sfml.cpp和对应的CMakeLists.cpp如下

ctrl+f5执行后就会弹出一个可以通过上下左右移动的绿色长方形。

matplotlib

使用本用例需要我们先安装Python,并使用pip install matplotlib安装matplotlib,使用GitHub - matplotlib-cpp可以只用一个头文件matplotlibcpp.h直接通过C++调用Python接口,他需要python, numpy的头文件和python的链接库,按照如下步骤进行使用:

  1. 创建matplotlib.cpp源文件,下载matplotlibcpp.h,在cpp的同目录下创建一个include文件夹,将matplotlibcpp.h放进去;
  2. which python找到Python的可执行文件位置,例如我的在/home/wty/Programs/mambaforge/envs/yy/bin/python,那么相对可以找到如下位置:
  3. Python头文件:/home/wty/Programs/mambaforge/envs/yy/include/python3.11,记为PYTHON_INCLUDE_DIR
  4. Numpy头文件:/home/wty/Programs/mambaforge/envs/yy/lib/python3.11/site-packages/numpy/core/include,记为NUMPY_INCLUDE_DIR
  5. Python链接库:/home/wty/Programs/mambaforge/envs/yy/lib,记为PYTHON_LINK_DIR

为了支持输出中文以及公式,我修改了matplotlibcpp.h中的rcparams函数:

然后在rcparams函数的下方我加入了fontsize函数,可以更容易的调节字体大小,并保证上文的配置会随着该函数的调用而被配置:

inline void fontsize(const int &x) {
    rcparams(std::map<std::string, std::string>({{"font.size", std::to_string(x)}}));
}

文件架构如下

.
├── CMakeLists.txt
├── include
│   └── matplotlibcpp.h
└── matplotlib.cpp

分别编辑文件:

执行上述matplotlib.cpp文件会生成./build/love.png图像,绘制效果如下(和Python完全一致,就是调用Python嘛😂)

matplotlib.cpp love.png

tensorboard

这里我们使用GitHub - tensorboard_logger,这是一个独立的可执行文件,我们只需编译安装后就可以直接使用,步骤如下:

sudo apt install protobuf-compiler  # 安装protobuf
git clone https://github.com/RustingSword/tensorboard_logger.git
cd tensorboard_logger
mkdir build
cd build
cmake .. && make
sudo cmake --install  # 安装到根目录下

安装完成后,可以用官方仓库中给的测试用例test_tensorboard_logger.cc测试各种绘制方法(注意:测试图像时,需要将仓库中assets/文件夹拷贝到当前项目的./build/文件夹下,否则找不到文件),文件结构如下

.
├── build
│   └── assets  # 把官方仓库中的/asssets/拷贝过来
├── CMakeLists.txt
├── tensorboard.cpp
└── test_tensorboard_logger.cc  # 官方仓库中的/tests/文件夹下

编辑文件如下:

通过修改CMakeLists.txt可以分别对tensorboard.cpptest_tensorboard_logger.cc进行编译&执行,在./build/demo/文件夹下创建日志文件,我们在Python中安装pip install tensorboard,执行tensorboard --logdir ./build/demo进入localhost:6006即可看到绘制的日志内容:

tensorboard中的scalars

libtorch

最后我们来尝试下libtorch的效果,这就是PyTorch的底层库,有两种安装方法(这里先以CPU版本为例,后续添加CUDA版本):

  1. Python安装:conda install pytorch(如果是用conda安装的,否则用pip安装),进入环境终端里面执行python -c 'import torch; print(torch.utils.cmake_prefix_path)',即可看到输出的cmake路径,例如我的是/home/wty/Programs/mambaforge/envs/jax/lib/python3.11/site-packages/torch/share/cmake
  2. 直接下载PyTorch官网中选择LibTorch以及对应的cudacpu版本,下载完成后找到.../libtorch/对应的目录即可。

把包含TorchConfig.cmake的路径记录下来称为TORCH_PATH,只需包含两个文件troch_tensor.cppCMakeLists.txt,编辑文件如下:

相应的如果VsCode没有找到libtorch相关的头文件位置,我们只需在C/C++:编辑配置(UI)中包含路径里面加入如下两个即可(相对你的libtorch文件夹,肯定也能找到的):

/home/wty/Programs/mambaforge/envs/jax/lib/python3.11/site-packages/torch/include/torch/csrc/api/include
/home/wty/Programs/mambaforge/envs/jax/lib/python3.11/site-packages/torch/include

上面跑的torch_tensor.cpp是一段测速代码1024×40961024\times40964096×10244096\times 1024矩阵乘法计算100次所需的平均时间(我的CPU为4800U):

  1. libtorch(C++): 用时44.7ms44.7ms
  2. pytorch(Python): 用时84.29ms84.29ms
  3. numpy(Python): 用时134.83ms134.83ms

GPU测速待补充

可以看出C++不是一般的快,Python所用的测速代码如下:

多个项目编译

在上文中我们一共创建了5个不同的*.cpp, *.h文件以及对应的CMakeLists.txt文件,我们可以通过一个CMakeLists.txt文件对他们一起编译,并选择其中某一个运行,文件结构如下:

.
├── CMakeLists.txt
├── libtorch
│   ├── CMakeLists.txt
│   ├── numpy_tensor.py
│   ├── torch_tensor.cpp
│   └── torch_tensor.py
├── main
│   ├── bar
│   │   ├── bar.cpp
│   │   ├── CMakeLists.txt
│   │   └── include
│   ├── CMakeLists.txt
│   └── main.cpp
├── matplotlib
│   ├── CMakeLists.txt
│   ├── include
│   │   └── matplotlibcpp.h
│   └── matplotlib.cpp
├── sfml
│   ├── CMakeLists.txt
│   └── sfml.cpp
└── tensorboard
    ├── CMakeLists.txt
    ├── tensorboard.cpp
    └── test_tensorboard_logger.cc

其中./CMakeLists.txt文件中只需要将每个项目通过add_subdirectory(...)加入到编译路径中即可:

add_subdirectory(main)
add_subdirectory(sfml)
add_subdirectory(matplotlib)
add_subdirectory(tensorboard)
add_subdirectory(libtorch)

运行的方法有两种:

  1. VSCode:在左边栏找到CMake图标,在左侧的项目状态中,可以进行如下设置:
  2. 设置生成目标:可以选择ALL编译全部文件,也可选择某个项目仅对其编译;
  3. 设置启动/调试目标:在5个项目中选择一个你当下想要启动的,按ctrl+f5即可启动程序。
  4. 终端:
# 进入到根目录下
mkdir my_build  # 创建文件夹
cmake -B my_build  # 创建cmake缓存
# 可选--target编译目标, 如果没有指定则是全部编译
# 注意: --target后面是最后编译出的目标(可执行文件)名称
cmake --build my_build --target [main|sfml|matplotlib|tensorboard|torch_tensor]
# 以运行torch_tensor为例
./my_build/libtorch/torch_tensor

全部用例代码整合GitHub - cmake_tutorial.7z


入门CMake
https://wty-yy.github.io/posts/50507/
作者
wty
发布于
2024年11月4日
许可协议