目录
一、cmake 简介
在实际的项目中, 一个工程中可能包含几十、成百甚至上千个源文件, 这些源文件按照其类型、功能、模块分别放置在不同的目录中; 面对这样的一个工程,通常会使用 make 工具进行管理、编译, make 工具依赖于 Makefile 文件,通过 Makefile 文件来定义整个工程的编译规则,使用 make 工具来解析 Makefile 所定义的编译规则。
Makefile 带来的好处就是——“自动化编译”,一旦写好,只需要一个 make 命令,整个工程完全按照Makefile 文件定义的编译规则进行自动编译,极大的提高了软件开发的效率。但是各种软件下的make工具遵循着不同的规范和标准, 对应的 Makefile 文件其语法、 格式也不相同, 这样就带来了一个严峻的问题:如果软件想跨平台,必须要保证能够在不同平台下编译, 而如果使用上面的 make 工具,就得为每一种标准写一次 Makefile,这将是一件让人抓狂的工作。
cmake 就是针对这个问题所诞生,它是跨平台的。 cmake 并不直接编译、构建出最终的可执行文件或库文件, 它允许开发者编写一种与平台无关的 CMakeLists.txt 文件来制定整个工程的编译流程, cmake 工具会解析 CMakeLists.txt 文件语法规则,再根据当前的编译平台,生成本地化的 Makefile 和工程文件,最后通过 make 工具来编译整个工程;所以由此可知, cmake 仅仅只是根据不同平台生成对应的 Makefile,最终还是通过 make工具来编译工程源码,但是 cmake 却是跨平台的。
cmake 还包含优点:开放源代码;语法规则简单;
cmake 和 Makefile
cmake 就是用来产生 Makefile 的工具,解析 CMakeLists.txt 自动生成 Makefile
当然,除了 cmake 之外, 还有一些其它的自动构建工具,常用的 automake、 autoconf 等
二、cmake使用
官方教程
cmake 官方也给大家提供相应教程:
Documentation | CMake文档总链接地址:Documentation | CMake
最新培训教程:CMake 教程 — CMake 3.26.1 文档
1、安装
cmake 就是一个工具命令,在 Ubuntu 系统下通过 apt-get 命令可以在线安装
sudo apt-get install cmake
安装完成之后查看版本
2、单个源文件
单个源文件的程序通常是最简单的,用 cmake 来进行构建,创建一个hello.c文件
现在我们需要新建一个CMakeLists.txt文件, CMakeLists.txt文件会被cmake工具解析,CMakeLists.txt 创建完成之后,在文件中写入如下内容:
project(HELLO)
add_executable(hello ./hello.c)
在我们的工程目录下有两个文件,源文件 main.c 和 CMakeLists.txt,接着我们在工程目录下直接执行cmake 命令
执行完 cmake 之后,除了源文件 main.c 和 CMakeLists.txt 之外,可以看到当前目录下生成了很多其它的文件或文件夹,包括: CMakeCache.txt、 CmakeFiles、 cmake_install.cmake、 Makefile,重点是生成了这个Makefile 文件, 有了 Makefile 之后,接着我们使用 make 工具编译我们的工程,如下
可以看到,执行make命令之后,可执行hello就编译出来了
分析
总体: 首先是有需要编译的一个文件,然后创建CMakeLists.txt文件,填写内容之后,用cmake命令生成Makefile文件,最后用Makefile的命令编译文件
CMakeLists.txt 文件内容
project(HELLO):project 是一个命令, 命令的使用方式有点类似于 C 语言中的函数,因为命令后面需要提供一对括号,并且通常需要我们提供参数,多个参数使用空格分隔;
project 命令用于设置工程的名称, 括号中的参数 HELLO 便是我们要设置的工程名称;设置工程名称并不是强制性的,但是最好加上
add_executable(hello ./hello.c):add_executable 同样也是一个命令,用于生成一个可执行文件,在这里传入了两个参数,第一个参数表示生成的可执行文件对应的文件名,第二个参数表示对应的源文件,意思是需要生成一个名为 hello 的可执行文件,所需源文件为当前目录下的 main.c。
3、使用 out-of-source 方式构建
在上面的例子中, cmake 生成的文件以及最终的可执行文件 hello 与工程的源码文件 main.c 混在了一起,这使得工程看起来非常乱,如下图,当我们需要清理 cmake 产生的文件时将变得非常麻烦,这不是我们想看到的;我们需要将构建过程生成的文件与源文件分离开来, 不让它们混杂在一起,也就是使用 out-of-source 方式构建。
把保留hello.c文件和CMakeLists.txt文件,把其余文件删除,创建一个build目录
进入到 build 目录下执行 cmake:
要注意,这次编译的文件路径在上一个目录中,所以用"../"
可以看到编译出来的文件全在build目录下了,所以也需要在该目录下执行Makeflel命令
只需要在Makefile文件的目录下执行make命令即可,不需要加路径,编译出来的执行文件也在build目录下,如果要清理工程,直接删除 build 目录即可,这样就方便多了
4、多个源文件
添加一个 hello.h 头文件和 main.c 源文件。在 hello.c 文件中定义了一个函数 hello,然后在 main.c 源文件中将会调用该函数:
hello.h
hello.c
main.c
CMakeLists.txt 文件
编译
方法都一样,先创建build目录,进入该目录下使用cmake命令生成Makefile文件,再用make命令编译出hello可执行目录
CMakeLists.txt 文件中使用到了 set 命令, set 命令用于设置变量,如果变量不存在则创建
该变量并设置它;在这里中,定义了一个 SRC_LIST 变量, SRC_LIST 变量是一个源文件列表, 记录生成可执行文件 hello 所需的源文件 main.c 和 hello.c,而在 add_executable 命令引用了该变量; 当然也可以不去定义 SRC_LIST 变量,直接将源文件列表写在 add_executable 命令中,如下
add_executable(hello main.c hello.c)
5、生成库文件
除了生成可执行文件 hello 之外,我们还需要将 hello.c 编译为静态库文件或者动态库文件,
在上面的基础上对 CMakeLists.txt 文件进行修改,如下所示:
使用到了 add_library 命令和 target_link_libraries 命令
add_library 命令用于生成库文件,在这里我们传入了两个参数,第一个参数表示生成库文件的名字,需要注意的是,这个名字是不包含前缀和后缀的名字; 在 Linux 系统中,库文件的前缀是 lib,动态库文件的后缀是.so,而静态库文件的后缀是.a; 所以,意味着最终生成的库文件对应的名字会自动添加上前缀和后缀。 第二个参数表示库文件对应的源文件。
target_link_libraries 命令为目标指定依赖库,在这里中, hello.c 被编译为库文件, 并将其链接进 hello 程序。
编译
6、将源文件组织到不同的目录
这些源文件都是放在同一个目录下,这样还是不太正规,我们应该将这些源文件按照类型、功能、模块给它们放置到不同的目录下
创建了build、src 和 libhello 目录,build目录下有bin和lib字目录,并将 hello.c 和 hello.h 文件移动到 libhello 目录下,将main.c 文件移动到 src 目录下,并且在顶层目录、 libhello 目录以及 src 目录下都有一个 CMakeLists.txt 文件,每一个 CMakeLists.txt 文件的内容如下
顶层 CMakeLists.txt
cmake_minimum_required 命令,该命令用于设置当前工程的 cmake 最低版本号要求,如果当前系统的 CMake 版本低于指定的版本,那么会发生错误。当然这个并不是强制性的,但是最好还是加上
project(HELLO)指定项目名称,本例中是 HELLO
add_subdirectory (libhello)命令,告诉 cmake 去子目录libhello中查找 CMakeLists.txt 文件并执行其中的指令,将生成的库添加到工程中
第三句同上理,在 src 目录下查找 CMakeLists.txt 文件并执行其中的指令,将生成的可执行文件添加到工程中
src 目录下的 CMakeLists.txt
include_directories 添加一个头文件搜索路径,将 PROJECTSOURCEDIR/libhello添加到了头文件搜索路径中。其中,${PROJECT_SOURCE_DIR} 表示项目源代码目录的路径。在该项目中,libhello 目录下包含了所需的头文件,因此这条命令的作用是让编译器在编译时能够正确地找到这些头文件,以便顺利地编译生成可执行文件。
set设置了可执行文件的输出路径为项目源代码目录下的 bin 目录。也就是说,编译生成的可执行文件将会被放置在 bin 目录下。EXECUTABLE_OUTPUT_PATH
是 CMake 中的一个预定义变量,它用于指定可执行文件的输出路径。简单来说,在编译某个可执行文件的时候,如果没有通过其他方式指定可执行文件的输出路径,那么 CMake 就会将其输出到 EXECUTABLE_OUTPUT_PATH
所指定的路径中,${CMAKE_CURRENT_SOURCE_DIR}
表示当前正在处理的 CMakeLists.txt 所在的目录的完整路径
第三行,用于编译生成一个名为 hello 的可执行文件,并将 main.c 源文件加入到编译队列中。在编译过程中,编译器将对 main.c 进行编译,生成相应的目标文件,并将这些目标文件链接成可执行文件 hello
第四行,用于将可执行文件 hello 链接到名为 libhello 的库上
libhello 目录下的 CMakeLists.txt
第一行同上理,设置输出文件路径
第二行,用于将 hello.c 文件编译成静态库,默认生成的库文件名是 libhello.a。这里我们没有指定库类型,即默认生成静态库
第三行,该命令将生成的库文件名修改为 hello。这里我们将 libhello.a 改名为 hello.a。
通过 CMake 的命令来修改库名具有以下优点:
-
自动化:使用命令可以将库文件名的修改自动化,避免手动修改带来的繁琐和出错的可能性
-
跨平台:在不同的平台、不同的编译器下,库文件的命名规则可能会有所不同。使用 CMake 命令来修改库文件名可以提高跨平台编译的可移植性,更好地支持多平台的开发需求。
-
易于维护:通过 CMake 命令修改库文件名可以使得项目结构更加清晰,并且便于将来的维护和管理。比如,在后期需要修改库名时,只需要修改一行代码即可完成
换句话说,如果将生成的 libhello.a
文件重命名为 hello.a
,那么在使用该库时,链接器需要使用 -lhello_new
参数来指定库名。否则,在链接期间系统会找不到 libhello.a
库文件,导致链接失败。
编译
进入子目录build目录下,执行“cmake ../" ,再执行”make“就可以生成文件在对应的目录