CMake 从零开始快速上手
CMake 是一个跨平台的构建、测试、打包工具,被广泛使用于 C++ 开源项目中
本文将介绍 CMake 在 C++ 项目中的通常用法
本文的例子使用 CMake 常用的规范项目结构,因此可以直接当作 C++ 项目的脚手架
环境配置
macOS
首先我们需要 C++ 编译器和调试器,于是我们可以输入以下命令直接安装 Xcode 的命令行工具来使用它自带的 Clang
xcode-select --install
然后使用 brew
安装 CMake 就行了
brew install cmake
Windows
推荐使用 MSYS2 直接安装集成到 mingw-w64 上的 CMake 和 Clang,打开 MSYS2 Shell 输入以下命令,当然你最好要把放着 CMake 和 Clang 的 bin
文件夹扔到 PATH
环境变量里
pacman -S mingw-w64-x86_64-cmake
pacman -S mingw-w64-x86_64-clang
当然也可以直接到 CMake 官网下载安装包,再去装 mingw-w64 的编译器集成,甚至可以装个 Visual Studio 来使用微软提供的 C++ 编译器,但是很麻烦
Helllo World
项目的目录结构
创建一个文件夹命名为 HelloWorld
,在其中创建一个 CMakeLists.txt
文件,build
和 src
这两个文件夹。
把 Hello.cpp
放 src
文件夹里,项目结构大概长这样
- HelloWorld
- build
- src
- Hello.cpp
- CMakeLists.txt
HelloWorld/CMakeLists.txt
这个是 CMake 项目的配置文件,用来描述项目中包含哪些目标,有什么源文件、头文件等等
它会被 CMake 读取,用于配置 HelloWorld
项目
看下面它的代码,显然就是字面意义上的限定 CMake 最低版本的要求,然后项目名叫做 HelloWorld
,添加一个名为 Hello
的可执行目标,它包括了 src
文件夹里的 Hello.cpp
这个源文件
最后编译出来就是一个二进制可执行文件,并以 src/Hello.cpp
里的 main
函数作为入口点
cmake_minimum_required(VERSION 3.0.0)
project(HelloWorld)
add_executable(Hello src/Hello.cpp)
HelloWorld/src/Hello.cpp
这是即将被编译的源文件,包含 main
函数,输出一个字符串
#include <iostream>
int main(int, char**) {
std::cout << "Hello, world!\n";
return 0;
}
项目构建
于是我们就可以开始编译了,CMake 不会直接构建项目,它会生成构建所需的与平台有关的中间文件,这是 CMake 跨平台的实现方法,这样对于同一个项目,我们在不同的平台上都能生成对应的中间文件来进行构建
比如在 Windows 上可以生成 Visual Studio 项目文件,然后用 VS 打开它来编译;类 UNIX 上可以生成 Makefile
;此外也可以选择生成跨平台的 build.ninja、macOS 上的 Xcode 项目文件等等
但是 CMake 生成的这些中间文件很混乱,因此习惯上我们会新建一个 build
文件夹来存放它们,也就是上面项目目录结构里的那个 build
文件夹
于是在终端中切到 HelloWorld/build
目录,使用生成器生成中间文件,依据操作系统可能需要选择不同的生成器
Linux 和 macOS
类 UNIX 系统上默认会生成 Makefile
,于是使用 cmake
命令之后再用 make
命令就能编译出可执行文件,直接运行就可以了
cd build
cmake ..
make
./Hello
Windows
Windows 上不知道默认会生成什么,CMake 文档中没说,Windows 也不会自带 VS 或者 mingw32-make 这样的东西,而且不同 VS 版本的项目还不怎么兼容,因此我们最好使用 -G
参数来手动指定,比如在 PowerShell 中输入下面的命令会在 build
目录下生成 VS 2019 项目
cd build
cmake.exe .. -G "Visual Studio 16 2019"
build
目录下应该会有一个 HelloWorld.sln
,接下来就直接用 VS 2019 打开它,正常构建就行了
你也可以安装一个 mingw32-make,然后选择对应的生成器来生成中间文件,这样就能用类似 make
的方式来构建项目
在 MSYS2 Shell 中安装 mingw32-make
pacman -S mingw-w64-x86_64-make
PowerShell 切到 HelloWorld
目录之后
cd build
cmake.exe .. -G "MinGW Makefiles"
mingw32-make.exe
.\Hello.exe
具体能生成什么可以输入下面的命令来查询 CMake 支持的所有生成器
cmake --help
库的配置与链接
刚刚我们构建了一个可执行目标 Hello
,但是我们可能需要引入外部的开源库或者项目本身的库,这时我们就需要把 Hello
跟这堆库链接起来,并且把库的头文件暴露给 Hello
假设有一个外部库 HelloExternalLib
,一般习惯把外部库单独放在一个文件夹里,于是我们创建一个 external
文件夹把它扔进去
项目结构长这样
- HelloWorld
- build
- external
- HelloExternalLib
- include
- HelloExternalLib
- HelloExternalLib.h
- src
- HelloExternalLib.cpp
- CMakeLists.txt
- CMakeLists.txt
- src
- Hello.cpp
- CMakeLists.txt
HelloWorld/external/HelloExternalLib/include/HelloExternalLib/HelloExternalLib.h
是这个库的一个头文件,随便声明一个方法好了
#ifndef HELLO_EXTERNAL_LIB_H
#define HELLO_EXTERNAL_LIB_H
void sayHello();
#endif // HELLO_EXTERNAL_LIB_H
HelloWorld/external/HelloExternalLib/src/HelloExternalLib.cpp
在这个文件中定义那个方法
#include <HelloExternalLib/HelloExternalLib.h>
#include <iostream>
void sayHello() {
std::cout << "Hello, world!\n";
}
HelloWorld/external/HelloExternalLib/CMakeLists.txt
这个文件用于构建 HelloExternalLib
这个库本身,添加了 HelloExternalLib
的库目标,包括 HelloExternalLib.cpp
这个源文件
其中的 target_include_directories
用于指定库的头文件路径,PUBLIC
代表如果库被链接这些头文件对外部是可见的,改成 PRIVATE
的话就是对外部不可见,只有库本身的源文件能够找到这些头文件,比如 Hello
目标链接了 HelloExternalLib
,include
文件夹中的头文件都是对 Hello
可见的
cmake_minimum_required(VERSION 3.0.0)
project(HelloExternalLib)
add_library(HelloExternalLib src/HelloExternalLib.cpp)
target_include_directories(HelloExternalLib PUBLIC include)
HelloWorld/external/CMakeLists.txt
这个文件仅仅是通过 add_subdirectory
把子文件夹添加到 CMake 项目中,也就是把 HelloExternalLib
这个文件夹加进来,这样 CMake 才会读取子文件夹中的 CMakeLists.txt
文件并进行相应的构建工作
注意子文件夹可以有多级目录嵌套,并且是递归进行的,当然需要在每一级目录下都编写 CMakeList.txt
然后加上 add_subdirectory
add_subdirectory(HelloExternalLib)
HelloWorld/CMakeLists.txt
这个文件就是刚刚用来配置 HelloWorld
项目的
我们加一行 add_subdirectory
来让它添加 external
子目录
并且使用 target_link_libraries
把 Hello
这个可执行目标跟 HelloExternalLib
这个库目标链接起来,并使用 PRIVATE
的链接方式
PRIVATE
这种链接方式表示 HelloExternalLib
不会被暴露给链接了 Hello
的其他目标,如果使用 PUBLIC
链接方式,其他的目标链接 Hello
时也会自动链接 HelloExternalLib
,尽可能使用 PRIVATE
,这样可以避免很多潜在的问题
而对于可执行目标来说,它不会被其它的目标链接,所以可以全部使用 PRIVATE
cmake_minimum_required(VERSION 3.0.0)
project(HelloWorld)
add_subdirectory(external)
add_executable(Hello src/Hello.cpp)
target_link_libraries(Hello PRIVATE HelloExternalLib)
HelloWorld/src/Hello.cpp
这时我们的源文件中就可以包含外部库的头文件并调用里面声明的方法了
#include <HelloExternalLib/HelloExternalLib.h>
int main(int, char**) {
sayHello();
}
然后生成中间文件
macOS 和 Linux 下可以这样,Windows 下就根据上文添加 -G
参数并选择合适的生成器
cd build
cmake ..
最后使用 make
或者 Windows 下的其他工具构建就行了