一、前言
最近一段时间在用 c++ 写一个项目,因此学了学 cmake。说实话,cmake 奇怪的语法在一开始实在容易让人望而生畏。但是上手使用的话就会发现平常会用到的不过是其中的一小部分,并且通常有规律可循。掌握这一部分的内容,大概率就可以组织起一个规模较大的项目了。因此本文也就旨在讲述 cmake 的这一部分的内容。
当然,阅读本教程之前需要了解代码编译、链接的相关知识。关于编译相关的命令,可见我的文章系统编程之命令行编译。
cmake 下载方式如下(apt)
sudo apt-get install build-essential
sudo apt-get install cmake
本文配套代码 practical-cmake,欢迎 star 😃
更多内容请看博客
二、第一步
说到第一个程序,那当然要请出经典的 hello, world
了。
#include <stdio.h>
int main()
{
printf("hello, world\n");
return 0;
}
在本文中我会首先给出使用 gcc 编译的命令,之后再使用 cmake 做同样的事情。那么对于第一步,我们的 gcc 命令如下
gcc main.cpp -o main
当然很简单,而对于 cmake 也类似。要使用 cmake,我们需要添加一个配置文件 CMakeLists.txt,其中包含要执行的操作。本小节中,CMakeLists.txt 的内容是
cmake_minimum_required(VERSION 3.10)
project(main)
add_executable(main main.cpp)
其中第一条指定了 cmake 版本要求,第二条指定了当前项目名,而第三条 add_executable
则实现了和 gcc 命令相同的操作:指定源文件 main.cpp
和输出文件名 main
,生成一个可执行文件。
要进行编译,需要首先使用 cmake
命令生成构建文件,这会在当前目录下生成包含 Makefile 在内的许多配置文件。之后再通过 make
指令即可完成编译。
为了不影响源文件的结构,我们可以选择新建一个文件夹执行 cmake
命令。假设现在位于项目根目录,那么执行如下命令
mkdir build
cd build
cmake ..
即可生成构建文件。
随后执行 make
命令完成编译并运行。
make
./main
仅就这一节看来,cmake 和 gcc 并没有什么差距,反而步骤更加繁琐,但是随着项目结构越发复杂,cmake 的优势就会越发明显。
三、头文件的引入
大部分实际的项目都不会只有一个文件。如果有多个源文件,编译器可以将其编译成同一个 .o 文件,源文件间的符号通过头文件共享。下面我们举如下的项目为例。
.
|-- CMakeLists.txt
|-- include
| `-- add.h
`-- src
|-- add.cpp
`-- main.cpp
add.h 中声明了函数 int add(int a, int b)
,并在 add.cpp 中实现,而 main.cpp 则调用了该函数,因此需要引入头文件 add.h 获得该函数的声明。
如果使用 gcc,可以使用如下命令编译。注意此处需要通过命令行参数 -I
设定头文件所在路径。
gcc src/add.cpp src/main.cpp -I ./include -o main
对于 cmake 来说,配置如下
cmake_minimum_required(VERSION 3.10)
project(main)
add_executable(main src/main.cpp src/add.cpp)
target_include_directories(main PRIVATE include)
可以看出 add_executable
指令之外,我们新增加了一条指令 target_include_directories
,这条命令将路径 ./include 添加到生成目标文件 main 所需的头文件路径中。
注意
target_include_directories
中除了目标和 include 路径外,还有一个符号PRIVATE
,该符号指定了头文件的作用范围,PRIVATE
表示头文件只用于当前的目标,而不用于链接当前目标的文件,类似还有PUBLIC
,将头文件向外暴露,和INTERFACE
,目标不使用,但向外暴露。当然现在不理解没有关系,在之后我们还会遇到。
四、库的编译和链接
有时候可能需要将一个模块编译成一个库,以便用于不同的程序。我们还以前一小节所用的代码为例,但这次我们重新组织代码结构。因为 main 模块调用了 add 模块提供的函数,所以我们可以直接把 add 模块编译成一个库,再将该库与 main 链接。
因为要将 add 作为一个库,所以这里将 add 移到了一个文件夹中