一.为什么需要CMake,什么是CMake
1.由于各种make工具遵循不同的规范和标准,所执行的Makefile格式也不同,例如 GNU Make ,QT 的 qmake ,微软的 MS nmake,BSD Make(pmake),Makepp,等等。这些 Make 工具遵循着不同的规范和标准,所执行的 Makefile 格式也千差万别。
这样就带来了一个严峻的问题:如果软件想跨平台,必须要保证能够在不同平台编译。而如果使用上面的 Make 工具,就得为每一种标准写一次 Makefile ,这将是一件让人抓狂的工作。
CMake 就是针对上面问题所设计的工具:它首先允许开发者编写一种平台无关的 CMakeList.txt 文件来定制整个编译流程,然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。从而做到“Write once, run everywhere”。显然,CMake 是一个比上述几种 make 更高级的编译配置工具。让软件真正实现了跨平台。
一些使用 CMake 作为项目架构系统的知名开源项目有 VTK、ITK、KDE、OpenCV、OSG 等 。
2.官方文档网址:www.cmake.org
3.CMake的主要特点
跨平台:CMake可以在多种操作系统上运行,包括Windows、Linux、macOS等。
编译器无关:CMake支持多种编译器,如GCC、Clang、Microsoft Visual C++等。
自动化:CMake可以自动检测系统特性,如库、头文件和编译器特性,并根据这些信息生成构建文件。
可扩展性:CMake提供了模块和脚本机制,允许用户扩展其功能。
生成多种构建系统:CMake可以生成多种构建系统的构建文件,如Makefile、Ninja、Visual Studio工程文件等。
开发语言:CMake的开发语言是C和C++。
4.其他构建工具
除了CMake外还有很多其他构建工具:XMake,qmake,Scons, Ninja, Meson, Baze等
二.CMake怎么用
1.CMake与makefile关系图
2.CMake处理原理
CMake有两个主要的阶段。首先是"配置(configure)",在此阶段CMake处理所有的输入然后创建软件构建过程的内部表达。第二个阶段是"生成(generate)",负责创建出实际的构建文件。
(1)环境变量与缓存
对1999年甚至是今天的许多构建系统来说,生成工程时都要用到底层(shell级别)的环境变量。典型的情况是,用PROJECT_ROOT环境变量来指向源码树的根目录。环境变量还被用于指定可选软件包和外部软件包。但是使用环境变量的方法也有弊端,它需要每次构建时都重新设置环境变量。为解决这个问题,CMake使用缓存文件来存储生成过程中用到的所有变量。这些变量不再是环境变量,而是CMake变量。CMake针对某个特定构建树第一次运行时,会创建一个CMakeCache.txt文件,存储当前构建过程中需要用到的CMake变量。这个缓存文件属于构建树的一部分,所以在之后的每次针对该构建树的重新配置时, 这些变量都是可重用的。
(2)配置阶段
在配置阶段,CMake首先尝试读取CMakeCache.txt文件,该文件在第一次运行时生成。然后,读取源码树根目录下的CMakeLists.txt文件,并使用CMake词法分析器处理。CMakeLists.txt中的每条命令都由一个命令模式对象执行。通过include和add_subdirectory命令,更多的CMakeLists.txt得到执行。对于每条命令,CMake都有一个C++对象来处理,比如add_library, if, add_executable, add_subdirectory,include等。实际上,整个CMake语言就是以命令调用的方式实现的。词法分析器只不过将输入文件内容转化为命令和命令参数而已。
配置阶段主要是运行用户定义的CMake代码。等到执行完之后,以及所有缓存变量计算完成之后,CMake在内存中得到一个项目构建的内部表达。这个内存中的内部表达包括了所有的库文件,可执行文件,定制的命令,以及生成指定generator(指特定的编译环境)所需的其他必要信息。这时,CMakeCache.txt会被存储到磁盘上,供以后重新运行CMake时使用。
项目在内存中的表达实际上是一些待生成的目标的集合,包括基本的库文件和可执行文件。CMake还支持目标的定制,即用户可以定义输入和输出,并提供定制的可在构建过程中运行的可执行文件或脚本。CMake将每个目标存储在一个cmTarget对象中,然后多个cmTarget存储在一个cmMakefile对象中,cmMakefile对象实际上用来存储源码树中某个目录中的所有目标。最后得到的结果是一棵cmMakefile对象的树,树结点中存储cmTarget对象的映射。
(3)生成阶段
一旦配置(configure)阶段完成,生成(generator)阶段就可以开始了。生成阶段将生成用户指定类型(如Visual Studio或GNU/Linux GCC)的构建文件。这时,目标的内部表达(库,可执行文件,定制目标)转化为本地构建工具的输入文件,如Visual Studio或Makefile文件。CMake由配置阶段获得的内部表达要尽可能地抽象和通用,这样的数据结构才能被不同的本地构建工具所共享。
3.安装cmake
sudo apt-get install cmake
4.构建工程项目流程
(1).构建CMakeLists.txt
CMake 构建脚本是一个纯文本文件,您必须将其命名为 CMakeLists.txt,并在其中包含 CMake 构建您的 C/C++ 库时需要使用的命令。如果您的原生源代码文件还没有 CMake 构建脚本,您需要自行创建一个,并在其中包含适当的 CMake 命令。
(2).在包含CMakeLists.txt的目录下使用cmake
1)构建放在当前目录下(内部构建):cmake . 即在当前目录cmake,在当前目录build。
2)在当前目录下创建build文件夹存放构建文件(外部构建),build内输入cmake ..。即在上级目录cmake,在该目录下build。
结果:生成4个东西:CMakeFiles文件夹、cmake_install.cmake、CMakeCache.txt、Makefile
只要产生Makefile文件,就说明cmake成功了
(3).在包含Makefile的目录下使用make
直接在build出4个东西的目录下(命令针对的是Makefile)输入make
(4).生成可执行文件。 输入./文件名运行
5.举例
1)先编写main.cpp
#include <iostream>
using namespace std;
int main(){
cout<<"hello,world"<<endl;
return 0;
}
2)编写CMakeLists.txt
PROJECT(HELLO) #工程名为HEELO
SET(SRC_LIST main.cpp) #变量SRC_LIST包含main.cpp
MESSAGE(STATUS "This is BINARY dir " ${HELLO_BINARY_DIR})
MESSAGE(STATUS "This is SOURCE dir " ${HELLO_SOURCE_DIR})
ADD_EXECUTABLE(hello ${SRC_LIST}) #生成可执行程序文件名为hello,源文件读取变量SRC_LIST中内容
3)cmake
cmake .
生成了4个东西:CMakeFiles文件夹、cmake_install.cmake、CMakeCache.txt、Makefile
4)make
make
生成了可执行文件hello
5)执行
./hello
显示"hello,world"
三.如何编写CMakeLists.txt
1.CMakeLists.txt并不是顺序执行的,相当于一系列的声明
CMakeList.txt 的语法比较简单,由命令、注释和空格组成:
#后面内容为注释;
命令由命令名称、小括号和参数组成,参数之间使用空格进行间隔,其中命令是不区分大小写的。
2.语法详解
PROJECT关键字 ※
可以用来指定工程的名字和支持的语言,默认支持所有语言
PROJECT(HELLO) 指定了工程的名字为HELLO,且支持所有语言(建议这样做)
PROJECT(HELLO CXX)指定了工程的名字为HELLO,仅支持C++语言
PROJECT(HELLO C CXX)指定了工程的名字为HELLO,仅支持C和C++语言
该指定隐式地定义了两个CMake变了
<projectname>_BINARY_DIR
<projectname>_SOURCE_DIR
本例中是HELLO_BINARY_DIR、HELLO_SOURCE_DIR
MESSAGE关键字就可以直接使用这两个变量。当前都指向当前的工作目录
SET关键字
SET用来显式的指定变量,可以为多个
SET(SRC_LIST main.cpp) 意为SRC_LIST变量包含了main.cpp
SET(SRC_LIST main.cpp test1.cpp test2.cpp) 意为SRC_LIST变量包含了main.cpp、test1.cpp、test2.cpp
MESSAGE关键字
向终端输出用户自定义的信息,主要包含三种:
SEND_ERROR:产生错误,生成过程被跳过
STATUS:输出前缀为- -的信息
FATAL_ERROR:立即终止所有cmake过程
ADD_EXECUTABLE关键字 ※
生成可执行文件
ADD_EXECUTABLE(hello $(SRC_LIST)),生成的可执行文件名为hello,源文件读取变量SRC_LIST中的内容
也可直接写为 ADD_EXECUTABLE(hello main.cpp)
注意:工程名的HELLO和生成的可执行文件hello是没有任何关系的
所以,上例中的5行CMakeLists.txt可以简化为2行:
PROJECT(HELLO)
ADD_EXECUTABLE(hello main.cpp)
ADD_SUBDIRECTORY关键字
①有子目录时,要用ADD_SUBDIRECTORY关键字包含所有需要访问的子文件夹。除了工程目录外,每个被访问的子目录里也都需要有一个CMakeLists.txt说明
②将目标文件放入构建目录的bin子目录:ADD_SUBDIRECTOR(子目录名 bin):自动在子目录下新建一个名为bin的子目录,存放中间二进制文件、目标二进制文件、库文件。
注意:文件就是file。目录(文件夹)就是directory。
语法基本原则
(1).变量取值 ${ }
(1).分隔多个文件:空格或者分号
(3).大小写:指令大小写不敏感,参数和变量对大小写敏感
3.安装文件
INSTALL(FILES 文件名 DESTINATION 目标地址)
举例3:
工程目录下的CMakeLists.txt添加一行:
INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake) #会自动安装到/usr/local/share/doc/cmake下
DESTINATION后面:
(1).写绝对路径
(2).相对路径:
CMAKE_INSTALL_PREFIX 默认是在/usr/local/
若想要更改,可以手动设置
set(CMAKE_INSTALL_PREFIX "目标路径")
这时候再 DESTINATION ${CMAKE_INSTALL_PREFIX},就导出到目标路径上了。
4.可执行文件安装
在src的CMakeLists.txt里输入
INSTALL(TARGETS hello DESTINATION bin)
5.生成静态库和动态库
SET(变量 源文件名)
ADD_LIBRARY(库名 STATIC或SHARED ${变量})
构建动态库(命令用SHARED) (构建静态库,命令用STATIC)
举例:
SET(LIBHELLO_SRC hello.cpp)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC}) #生成动态库libhello.so
6.使用外部头文件和共享库
(1)添加头文件搜索路径:
INCLUDE_DIRECTORIES(路径)
(2)添加需要链接的共享库
TARGET_LINK_LIBRARIES(源文件名 库名)
TARGET_LINK_LIBRARIES(main libhello.so) #链接动态库
TARGET_LINK_LIBRARIES(main libhello.a) #链接静态库
四.QT中的CMake
1.QMake和CMake
qmake和cmake两者都用来构建系统,都生成一个Makefile,该文件由make读取以构建项目,告诉编译器和链接器该做什么,以创建可执行文件(或动态或静态库)。
qmake专注于使用Qt的项目,QtCreator可以轻松生成项目文件(适合初学者),并由QtCreator支持;CMake用于广泛的项目,支持多种平台和语言,受多个IDE支持:例如QtCreator,Visual Studio,
生成多个IDE的项目描述,包含简化Qt使用的命令(最重要:automoc)。如果项目使用Qt,则最好使用qmake。 CMake更通用,几乎适合任何类型的项目。
2.Qt公司自Qt 5.14版本开始推荐使用CMake作为Qt项目的构建系统,而不是qmake。
CMake是一个跨平台的构建系统,支持多种编译器和IDE,而qmake是Qt自带的构建工具,主要用于Qt项目。
Qt公司推荐使用CMake的原因包括:
跨平台支持:CMake支持多种操作系统和编译器,使得Qt项目可以在不同的平台上轻松构建。
社区支持:CMake拥有一个活跃的社区,提供了大量的模块和工具,可以帮助开发者更高效地构建和管理项目。
集成支持:CMake与许多IDE和构建系统紧密集成,如Visual Studio、Xcode、Make等。
模块化:CMake提供了模块化的构建脚本,使得项目可以更容易地管理和扩展。
性能:CMake的配置和生成过程通常比qmake更快,特别是在处理大型项目时。
3.qmake更适合构建小型Qt项目,简单易用;而cmake更适合构建复杂、跨平台的C++项目,功能更加强大和灵活。
如果没有什么跨平台需求的话,qmake也是没有问题的。