在前面两篇内容中,我们编写了很多示例程序,但这些示例程序都只有一个.c 源文件,非常简单。因此,编译这些示例代码其实都非常简单,直接使用 GCC 编译器编译即可,连 Makefile 都不需要。但是,在实际的项目中,并非如此简单,一个工程中可能包含几十、成百甚至上千个源文件,这些源文件按照其类型、功能、模块分别放置在不同的目录中;面对这样的一个工程,通常会使用 make 工具进行管理、编译,make 工具依赖于 Makefile 文件,通过 Makefile 文件来定义整个工程的编译规则,使用 make 工具来解析 Makefile 所定义的编译规则。
Makefile 带来的好处就是——“自动化编译”,一旦写好,只需要一个 make 命令,整个工程完全按照 Makefile 文件定义的编译规则进行自动编译,极大的提高了软件开发的效率。大都数的 IDE 都有这个工具, 譬如 Visual C++的 nmake、linux 下的 GNU make、Qt 的 qmake 等等,这些 make 工具遵循着不同的规范和标准,对应的 Makefile 文件其语法、格式也不相同,这样就带来了一个严峻的问题:如果软件想跨平台,必须要保证能够在不同平台下编译,而如果使用上面的 make 工具,就得为每一种标准写一次 Makefile,这将是一件让人抓狂的工作。
而 cmake 就是针对这个问题所诞生,允许开发者编写一种与平台无关的 CMakeLists.txt 文件来制定整个工程的编译流程,再根据具体的编译平台,生成本地化的 Makefile 和工程文件,最后执行 make 编译。
因此,对于大多数项目,我们应当考虑使用更自动化一些的 cmake 或者 autotools 来生成 Makefile,而不是直接动手编写 Makefile。
本专栏我们便来学习 cmake,也是作为我们项目编程的起点。
cmake 简介
cmake 是一个跨平台的自动构建工具,前面导语部分也已经给大家介绍了,cmake 的诞生主要是为了解决直接使用 make+Makefile 这种方式无法实现跨平台的问题,所以 cmake 是可以实现跨平台的编译工具, 这是它最大的特点,当然除了这个之外,cmake 还包含以下优点:
- 开放源代码 我们可以直接从 cmake 官网 https://cmake.org/下载到它的源代码;
- 跨平台 cmake 并不直接编译、构建出最终的可执行文件或库文件,它允许开发者编写一种与平台 无关的 CMakeLists.txt 文件来制定整个工程的编译流程,cmake 工具会解析 CMakeLists.txt 文件语法规则,再根据当前的编译平台,生成本地化的 Makefile 和工程文件,最后通过 make 工具来编译整个工程;所以由此可知,cmake 仅仅只是根据不同平台生成对应的 Makefile,最终还是通过 make 工具来编译工程源码,但是 cmake 却是跨平台的。
- 语法规则简单 Makefile 语法规则比较复杂,对于一个初学者来说,通常并不那么友好,并且 Makefile 语法规则在不同平台下往往是不一样的;而 cmake 依赖的是 CMakeLists.txt 文件,该文件的语法规则与平台无关,并且语法规则简单、容易理解!cmake 工具通过解析 CMakeLists.txt 自动帮我们生成 Makefile,这样就不需要我们自己手动编写 Makefile 了。
cmake 和 Makefile
直观上理解,cmake 就是用来产生 Makefile 的工具,解析 CMakeLists.txt 自动生成 Makefile:
除了 cmake 之外,还有一些其它的自动构建工具,常用的譬如 automake、autoconf 等,有兴趣的朋友可以自己了解下。
cmake 的使用方法
cmake 就是一个工具命令,在 Ubuntu 系统下通过 apt-get 命令可以在线安装,如下所示:
sudo apt-get install cmake
安装完成之后可以通过 cmake --version 命令查看 cmake 的版本号,如下所示:
由上图可知,当前系统安装的 cmake 对应的版本号为 3.5.1,cmake 工具版本更新也是比较快的,从官网CMake可知,cmake 最新版本为 3.22.0,不过这都没关系,其实使用哪个版本都是可以的,差别并不会太大,所以这个大家不用担心。
安装完 cmake 工具之后,接着我们就来学习如何去使用 cmake。cmake 官方也给大家提供相应教程,链接地址如下所示:
Documentation | CMake //文档总链接地址
CMake Tutorial — CMake 3.26.4 Documentation //培训教程
如果大家自学能力强,完全可以参考官方提供的培训教程学习 cmake;对于 cmake 的学习,笔者给大家两个建议:
- 从简单开始、再到复杂!
- 重点是自己动手多练习。
本文我们将从一个非常简单的示例开始向大家介绍如何使用 cmake,再从这个示例进一步扩展、提出更多需求,来看看 cmake 如何去满足这些需求。
示例:单个源文件编译
单个源文件的程序通常是最简单的,一个经典的 C 程序“Hello World”,如何用 cmake 来进行构建呢?
//main.c
#include <stdio.h>
int main(){
printf("Hello World!\n");
return 0;
}
现在我们需要新建一个CMakeLists.txt文件,CMakeLists.txt文件会被cmake工具解析,就好比是Makefile文件会被make工具解析一样;CMakeLists.txt创建完成后,在文件中写入如下内容:
project(HELLO)
add_executable(hello ./main.c)
写入完成之后,保存退出,当前工程目录结构如下所示:
├── CMakeLists.txt
└── main.c
在我们的工程目录下有两个文件,源文件 main.c 和 CMakeLists.txt,接着我们在工程目录下直接执行 cmake 命令,如下所示:
cmake ./
cmake 后面携带的路径指定了 CMakeLists.txt 文件的所在路径,执行结果如下所示:
执行完 cmake 之后,除了源文件 main.c 和 CMakeLists.txt 之外,可以看到当前目录下生成了很多其它的文件或文件夹,包括:CMakeCache.txt、CmakeFiles、cmake_install.cmake、Makefile,重点是生成了这个 Makefile 文件,有了 Makefile 之后,接着我们使用 make工具编译我们的工程,如下所示:
通过make编译之后得到了一个可执行文件 hello,这个名字是在 CMakeLists.txt 文件中指定的,稍后向大家介绍。通过 file 命令可以查看到 hello 是一个 x86-64 架构下的可执行文件,所以只能在我们的 Ubuntu PC 上运行:
为了验证 hello 可执行文件运行结果是否与源代码相同,我们直接在 Ubuntu 下运行即可,如下所示:
CMakeLists.txt 文件
上面我们通过了一个非常简单例子向大家演示了如何使用 cmake,重点在于去编写一个 CMakeLists.txt 文件,现在来看看 CMakeLists.txt 文件中写的都是什么意思。
⚫ 第一行 project(HELLO)
project 是一个命令,命令的使用方式有点类似于 C 语言中的函数,因为命令后面需要提供一对括号, 并且通常需要我们提供参数,多个参数使用空格分隔而不是逗号“,”。
project 命令用于设置工程的名称,括号中的参数 HELLO 便是我们要设置的工程名称;设置工程名称并不是强制性的,但是最好加上。
⚫ 第二行 add_executable(hello ./main.c)
add_executable 同样也是一个命令,用于生成一个可执行文件,在本例中传入了两个参数,第一个参数表示生成的可执行文件对应的文件名,第二个参数表示对应的源文件;所以 add_executable(hello ./main.c)表示需要生成一个名为 hello 的可执行文件,所需源文件为当前目录下的 main.c。
使用 out-of-source 方式构建
在上面的例子中,cmake 生成的文件以及最终的可执行文件 hello 与工程的源码文件 main.c 混在了一起,这使得工程看起来非常乱,当我们需要清理 cmake 产生的文件时将变得非常麻烦,这不是我们想看到的;我们需要将构建过程生成的文件与源文件分离开来,不让它们混杂在一起,也就是使用 out-of-source 方式构建。
将 cmake 编译生成的文件清理下,然后在工程目录下创建一个 build 目录,如下所示:
├── build
├── CMakeLists.txt
└── main.c
然后进入到 build 目录下执行 cmake:
cd build/
cmake ../
make
这样 cmake 生成的中间文件以及 make 编译生成的可执行文件就全部在 build 目录下了,如果要清理工程,直接删除 build 目录即可,这样就方便多了,如下图所示。