Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >CMake使用教程和原理

CMake使用教程和原理

原创
作者头像
mariolu
修改于 2019-12-28 09:23:34
修改于 2019-12-28 09:23:34
14.4K02
代码可运行
举报
运行总次数:2
代码可运行

一、什么是CMake

CMake是一个主要用于CPP的构建工具。CMake语言是平台无关的中间编译工具。同一个CMake编译规则在不同系统平台构建出不同的可执行构建文件。在Linux产生MakeFile,在Windows平台产生Visual Studio工程等。CMake旨在解决各平台的不同Make工具的产生的差异(比如GNU Make, QT的qmake,微软的nmake, BSD的pmake)。

其实除了CMake构建系统之外,CMake已经发展出一系列开发工具:CMake,CTest,CPack和CDash。

- CMake是负责构建软件的构建工具。

- CTest是一个测试驱动程序工具,用于运行回归测试

- CPack是一种打包工具,用于为使用CMake构建的软件创建特定于平台的安装程序。

- CDash是一个Web应用程序,用于显示测试结果并执行连续的集成测试

- 其他还有Doxygen和BullseyeCoverage

1.1 CMake的前世今生

项目的通常做法是为Unix平台提供配置脚本和Makefile,为Windows提供Visual Studio项目文件。autoconf / libtool构建软件的方法不能满足跨平台的要求。

历史上曾经出现的1999年的VTK构建系统。该系统由Unix的配置脚本和pcmaker Windows 的可执行文件组成。pcmaker是一个C程序,可以读取Unix Makefile文件并为Windows创建NMake文件。

另一种是是gmake针对Sun工作站上C ++计算机视觉环境。Sun工作站使用该imake系统创建Makefile。但是,有时需要Windows端口时,gmake才创建了系统。Unix编译器和Windows编译器均可与此gmake基于此的系统一起使用。

这两个系统都存在严重缺陷:它们迫使Windows开发人员使用命令行。有经验的Windows开发人员更喜欢使用集成开发环境IDE)。

1.2 Cmake的使命

  • 创建和源代码库隔离的构建目录,分离开发和构建目录。易于进行源代码版本控制。
  • CMake是具有管理依赖项,依赖之间的关系。如果变更了源文件,必须重新构建所有依赖该源文件的脚本。
  • 并且要求高效的依赖关系解析是耗时短的。
  • CMake提供一些易于操作的API,向开发人员屏蔽平台细节。

二、CMake怎么解决问题

CMake有两个阶段,配置和生成阶段。

图1、CMake配置和生成阶段
图1、CMake配置和生成阶段

2.1 配置阶段

配置阶段解析所有的输入变量,并存储在CMakeCache.txt这个文件。这个阶段解决了用户构建一个项目需要依赖的各种输入参数。

在项目的构建过程中都使用shell级别的环境变量。通常,项目具有指向根目录位置的PROJECT_ROOT环境变量。还有配置可选或外部程序包。要使构建正常进行,每次执行构建时都需要设置所有这些外部变量。所有CMakeFile在配置阶段解决了这个问题。

先来窥探下CMakeCache.txt的构成,CmakeCache.txt由两部分构成:External Cache Entries和Internal Cache Entries。而CMakeCache.txt是由解析器Parser生成。解析器的匹配器找到各种token。CMakeLists也可以解析外部的CMake语法,他是由“include” 或者“add_subdirectory”包含进来,两者的区别后面会说到。

解析完这些变量,cmake在内存中有了项目(可执行程序、库、用户自定义Command)的构建表达方法。在代码中一个target用cmTarget对象表示,所有的cmTarget构成了cmMakefile对象。

图2、CMakeCache.txt的 外部输入变量
图2、CMakeCache.txt的 外部输入变量
图3、CMakeCache.txt的内部输入变量
图3、CMakeCache.txt的内部输入变量

2.2 生成阶段

在生成阶段,cmake使用了一套语法解析系统,关键的类图如下。cmMakefile对象存错了CMakeLists.txt的所有输入变量。解析器使用了lex/yacc语法解析器,执行构建动作。cmCommand定义了命令的执行动作,并且该动作的注释在代码也有注释。这些关键类 是抽象类,CMake的跨平台实现依赖于这些类的平台实现类。

图4、生成阶段的关键类
图4、生成阶段的关键类

2.3 依赖管理和更新构建

CMake在使用IDE的平台不生成依赖,这些依赖由IDE自己完成。在Unix系统,CMake做了依赖管理,并把这些信息写在depend.make,flags.make, build.make,DependInfo.cake。当这些文件有变化,都会从cmake的重新构建。

图5, 构建目标的文件夹结构
图5, 构建目标的文件夹结构

depend.make和DependInfo.make:所有object的依赖关系。DependInfo.cmake保存了语言和对象文件的关系。

图6 depend.cmake文件内容
图6 depend.cmake文件内容
图7 DependInfo.make的文件内容
图7 DependInfo.make的文件内容

flags.make保存了编译选项,如果编译选项改变了,也会触发重建构建

图8、flags.make的文件内容
图8、flags.make的文件内容

最后这些信息都会汇总成build.make

图9、build.make的文件内容
图9、build.make的文件内容

三、Cmake怎么使用

CMakeLists.txt定义了所有编译规则的入口。CMakeLists的常用编译指令按照目的分类有:

我们联想从最简单的编译规则说起:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
gcc -Wall -std=c++11 -DMY_MACRO -I/home/lib [-Ldir] -llibname main.c -o main 

比如gcc 这里的-Wall是编译选项,-DMY_MACRO定义了MY_MACRO宏,-L指库的搜索路径,-l指链接libname库,源文件是main.c,最终生成的二进制可执行文件是main 

那么怎么用CMake表示这个规则。

3.1 定义编译选项(或者编译特征)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
target_compile_features(target PRIVATE|PUBLIC|INTERFACE feature1 [feature2 ...])

PRIVATE的意思是这个target的编译选项只对该target有效,如果需要对引用该target的上级target也有效,那么这里需要用PUBLIC。

样例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
target_compile_features(main PRIVATE-Wall”)
set_target_properties(main PROPERITES
    COMPILE_FLAGS "-Wall"
)
target_compile_features(mylib PUBLIC cxx_std_11)

还有个target_compile_option()是什么区别

另外提一下,这里C++在这里是CXX? 因为涉及到不同平台下C++程序的后缀名不一样,在Windows下我们常用的就是一个.cpp扩展名,还有gcc一般用c.cc.cxx 等等都是C++文件的扩展名。

有些c++就是直接用语言的名字命名的扩展名,但有些系统可能不支持在文件名里放入加号"+",或许这里用cxx的x有点像+,当时设计意图可能是这边吧。

编译命令可以归结为以下3个大类:

  • 编译最低要求:版本号什么的
  • 编译选项:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
SET(CMAKE_CXX_STANDARD 14):为什么是CXX
  • 条件编译:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
如果开启了CXX_VARIADIC_TEMPLATES
#if Foo_COMPILER_CXX_VARIADIC_TEMPLATES
#else
#endif

3.2 找到编译头文件

让CMake找到我的头文件, include_directories(/home/include)。常见的也有这样写,把工程的include文件夹加到包含路径。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
include_directories(${CMAKE_CURRENT_LIST_DIR}/include)

CMAKE_CURRENT_LIST_DIR这个变量,它表示当前CMakeLists所在的路径.或者PROJECT_SOURCE_DIR,这个命令的原型是

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
命令: include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])

作用是把dir1, [dir2 …]这(些)个路径添加到当前CMakeLists及其子CMakeLists的头文件包含路径中;

AFTER 或者 BEFORE 指定了要添加的路径是添加到原有包含列表之前或之后

若指定 SYSTEM 参数,则把被包含的路径当做系统包含路径来处理

如果需要递归include文件夹及子文件夹的所有目录,用

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
add_subdirectory()

那target_inlucde_directories()是指什么,库的所有者都可以使用

外部的target

#include(TARGET),它会去子文件夹cmake/TARGET文件夹,搜索TARGET.cmake的文件。

3.3、找到源文件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
aux_source_directory(./src ${hello_src})

作用: 把当前路径下src目录下的所有源文件路径放到变量hello_src中

命令:aux_source_directory(<dir> <variable>)

作用:查找dir路径下的所有源文件,保存到variable变量中.

上面的例子中,hello_src是一个自定义变量,在执行了aux_source_directory(./src ${hello_src})之后,我就可以像这样来添加一个可执行文件:add_executable(hello ${hello_src}), 意思是用hello_src里面的所有源文件来构建hello可执行程序, 不用手动列出src目录下的所有源文件了。

值得注意的是:aux_source_directory 不会递归包含子目录,仅包含指定的dir目录

CMake官方不建议用aux_source_directory及类似命令(file(GLOB_RECURSE …))搜索源文件。因为这样子文件夹的变化不容易被感知到,从而无法触发重新构建。比如被搜索的路径下添加源文件,此时没有修改CMakeLists脚本,但是CMakeLists并不需要(没有)变化,构建系统无法察觉到新加的文件,除非手动重新运行cmake,否则新添加的文件就不会被编译到项目结果中。

3.4 找到库文件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
link_directories(${CMAKE_CURRENT_LIST_DIR}/lib)

link_directories(directory1 directory2 ...)和include_directories()类似他,添加库包含路径。

3.5 链接库文件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
target_link_libraries(${PROJECT_NAME} util)
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
命令:target_link_libraries(<target> [item1 [item2 [...]]] [[debug|optimized|general] <item>] ...)

这个target需要链接util这个库,会优先搜索libutil.a(windows上就是util.lib), 如果没有就搜索libutil.so(util.dll, util.dylib)’

类似于与pkg-config去文件夹找*.pc,cmake也提供了find_package(),它会去cmake安装目录module文件夹执行Find<Package>.cmake

3.6生成target

Target包括3种: executable、 library、自定义command

指令分别为

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
add_custom_command()
add_library(archive archive.cpp zip.cpp lzma.cpp)
add_executable(zipapp zipapp.cpp)

链接库和最终target:target_link_libraries(zipapp archive)

3.7 其他命令等

3.7.1、打印调试日志消息

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
message(STATUS “my custom debug info”)

3.7.2、操作文件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
FILE()

3.7.3、循环控制

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
foreach()
endforeach()

3.7.4、定义宏

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
macro()
endmacro()

3.7.5、设置cmake最低版本

设置要求版本>=3.5:CMAKE_MINIMUM_REQUIRED(VERSION 3.5)

CMAKE_MODULE_PATH:

什么是工程MODULE,多个工程连接

编译选项:

SET(CMAKE_CXX_STANDARD 14):为什么是CXX

3.7.6、包含外部子target

#include(TARGET),它会去子文件夹cmake/搜索TARGET.cmake的文件。也可能去cmake的安装目录下搜索。

3.7.8、工程包名字

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
PROJECT(output_binary_name CXX)

四、高级特性 - 在线下载编译工程

ExternalProject在构建时从另一个项目填充内容。这意味着在构建主项目之前,本地没有其他项目的库。首先需要add_dependencies()声明,ExternalProject才会下载,配置或构建。最主要外部下载引用是 ExternalProject_Add,功能很强大,支持不同的地址去获取依赖,可以是打包文件的 URL,比如 github 上的某个项目的 tag,或者像 boost 这种,在官网提供的下载链接,也可以直接是 GIT_REPOSITORY,一般建议直接使用打包的 tag,因为比较快,而且有固定的 tag,比较好做版本管理,但是有些项目引用了外部项目需要执行 git submodule update --init,这种就比较适合用 git 地址,会自动下载依赖模块

一个ExternalProject_ADD的例子如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ExternalProject_ADD(
  #--External-project-name------
  antlr4cpp
  #--Depend-on-antrl-tool-----------
  # DEPENDS antlrtool
  #--Core-directories-----------
 # PREFIX             ${ANTLR4CPP_EXTERNAL_ROOT}
  PREFIX             ${ANTLR4CPP_LOCAL_ROOT}
  #--Download step--------------
 # GIT_REPOSITORY     ${ANTLR4CPP_EXTERNAL_REPO}
  URL                ${ANTLR4CPP_LOCAL_REPO}
  # GIT_TAG          ${ANTLR4CPP_EXTERNAL_TAG}
  TIMEOUT            10
  LOG_DOWNLOAD       ON
  #--Update step----------
  UPDATE_COMMAND     ${GIT_EXECUTABLE} pull
  #--Patch step----------
  # PATCH_COMMAND sh -c "cp <SOURCE_DIR>/scripts/CMakeLists.txt <SOURCE_DIR>"
  #--Configure step-------------
  CONFIGURE_COMMAND  ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Release -DANTLR4CPP_JAR_LOCATION=${ANTLR4CPP_JAR_LOCATION} -DBUILD_SHARED_LIBS=ON -BUILD_TESTS=OFF -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DCMAKE_SOURCE_DIR:PATH=<SOURCE_DIR>/runtime/Cpp <SOURCE_DIR>/runtime/Cpp
  LOG_CONFIGURE ON
  #--Build step-----------------
  # BUILD_COMMAND ${CMAKE_MAKE_PROGRAM}
  LOG_BUILD ON
  #--Install step---------------
  # INSTALL_COMMAND    ""
  # INSTALL_DIR ${CMAKE_BINARY_DIR}/
  #--Install step---------------
  # INSTALL_COMMAND    ""

下载完之后编译这个过程,基本不需要额外的配置,会自动编译,也许会按照个人习惯设置一个编译后的 install 目录,可以通过 CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${DMP_CLIENT_SOURCE_DIR}/third/gtest/build 设置 cmake 的参数来实现。

  • ExternalProject_Get_Property()是获取工程的一些属性。
  • add_dependencies增加依赖编译项目

五、总结

这些变量和指令不好记,怎么快速记忆。

  • 全为大写
  • 大小写混用
  • 规则指令add_xxxxxx等
  • token之间没有逗号,用空格隔断两个token

5.1 cmake开启详细信息调试模式

--trace-expand

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
cmake 简介2021-10-03
CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake。
用户3519280
2023/07/08
4140
CMake简易指南
CMake 详细说明参考官方文档 https://cmake.org/cmake/help/latest/index.html,其中latest为最新版本版本,不同 CMake 版本,API 有差异,请根据当前项目设置的最低版本来参考,高版本 API 在低版本无法使用。3.20之后的文档会标记该 API 的生效版本
Huahua
2023/09/06
9550
CMake简易指南
Cmake的使用
有了Cmake以后,只需要编写一个CmakeLists文件就可以对应将一个C++工程不通操作系统
全栈程序员站长
2022/09/22
1.5K0
Cmake的使用
CMake使用总结
CMake意为cross-platform make,可用于管理c/c++工程。CMake解析配置文件CMakeLists.txt生成Makefile,相比直接用Makefile管理工程,CMake更灵活和简单。
bear_fish
2018/09/19
1.8K0
C/C++生态工具链——编译构建工具CMake/CMakeList初探
CMake的全称是Cross-platform Make。我第一次参与Linux C++开发时使用的工具是Make,而后开始切换到CMake,一开始以为CMake是和C语言有关,原来开头的C表示它可以跨平台。
Coder-ZZ
2023/02/23
2.9K0
C/C++生态工具链——编译构建工具CMake/CMakeList初探
cmake快速入门「建议收藏」
cmake是kitware公司以及一些开源开发者在开发几个工具套件(VTK)的过程中所产生的衍生品。后来经过发展,最终形成体系,在2001年成为一个独立的开放源代码项目。其官方网站是www.cmake.org,可以通过访问官方网站来获得更多关于cmake的信息,而且目前官方的英文文档比以前有了很大的改进,可以作为实践中的参考手册。
全栈程序员站长
2022/09/13
1.2K0
cmake快速入门「建议收藏」
CMake 基础学习
使用 {}进行变量的引用。例如:message({Hello_VERSION}), Hello为工程名。CMake提供了很多有用的变量。以下仅列举常用的变量:
艳龙
2021/12/16
1.8K0
CMake学习笔记(二)——CMake语法
剑影啸清寒
2018/01/02
5.9K0
Cmake学习总结(三)
大家好,在上一篇文Cmake文章里面,我们同样在文章的最后面留了一个问题实现,就是把源文件放到src目录下,把头文件放到include目录下去,这样也比较符合别人和自己日后去配置工程(一看到这两个目就能知道啥意思了,清晰明了),同时在linux环境下生成的elf文件放到bin目录下;不过在文章发出去了几天,后面有网友又有提出了一些新的需求:
用户6280468
2022/03/21
5080
Cmake学习总结(三)
CMake 秘籍(五)
每个项目都必须处理依赖关系,而 CMake 使得在配置项目的系统上查找这些依赖关系变得相对容易。第三章,检测外部库和程序,展示了如何在系统上找到已安装的依赖项,并且到目前为止我们一直使用相同的模式。然而,如果依赖关系未得到满足,我们最多只能导致配置失败并告知用户失败的原因。但是,使用 CMake,我们可以组织项目,以便在系统上找不到依赖项时自动获取和构建它们。本章将介绍和分析ExternalProject.cmake和FetchContent.cmake标准模块以及它们在超级构建模式中的使用。前者允许我们在构建时间获取项目的依赖项,并且长期以来一直是 CMake 的一部分。后者模块是在 CMake 3.11 版本中添加的,允许我们在配置时间获取依赖项。通过超级构建模式,我们可以有效地利用 CMake 作为高级包管理器:在您的项目中,您将以相同的方式处理依赖项,无论它们是否已经在系统上可用,或者它们是否需要从头开始构建。接下来的五个示例将引导您了解该模式,并展示如何使用它来获取和构建几乎任何依赖项。
ApacheCN_飞龙
2024/05/16
9610
CMAKE使用手记
首先感谢那位叫“任麒麟”的网友整理的PDF,有心了。 我也忘了哪里下载的,不过确实挺全的。
看、未来
2021/10/09
5530
CMAKE入门实战
最近做的项目使用CLION构建,而这个采用CMakeLists.txt管理,因此为了更好的学习,故找到了一篇大牛级别的入门文章,有文章有代码,本文是花了一点时间把这篇文章学习后的重要点记录吧,原作者github地址:https://github.com/wzpan/cmake-demo。
公众号guangcity
2019/09/20
1.5K0
CMAKE入门实战
Cmake学习总结(二)
大家好,上次给大家分享了第一篇 cmake 文章:cmake学习总结(一),今天继续给大家分享cmake。那么废话就不多说,开始内容分享。
用户6280468
2022/03/21
2810
cmake使用
CMake是一个跨平台的安装编译工具,可以用简单的语句来描述所有平台的安装编译过程。
李小白是一只喵
2020/10/28
2K0
cmake使用
CMake学习笔记
当多个人用不同的语言或者编译器开发一个项目,最终要输出一个可执行文件或者共享库(dll,so等等)这时候神器就出现了—–CMake!
CtrlX
2023/02/06
2.1K0
CMake,大型项目采用的构建工具
本篇文章主要描述CMake的基本用法。在之前的文件中我对Makefile,Autotools这两个构建工具。相关文章如下:
Rice加饭
2022/05/10
1.2K0
跨平台编译工具-CMake的语法特性与常用变量
最近在学习 Linux 下的 C+ 开发的编译知识,总结出该系列笔记,这是第五篇,可以通过以下链接阅读之前的笔记:
极客开发者
2022/01/18
1.2K0
CMake 使用学习
CMake 工具能够自动生成 Makefile 文件,减轻手写 Makefile 文件的工作量,同时减少书写 Makefile 文件产生的错误。
hotarugali
2022/03/13
2.6K0
CMake简介及使用实例
CMake是一个跨平台的建构系统的工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的构建文档makefile或者project文件,描述系统建构的过程。还能测试编译器所支持的C++特性,类似UNIX下的automake。只是 CMake 的组态档取名为 CmakeLists.txt。CMake并不直接建构出最终的软件,而是产生标准的建构档(如 Unix的 Makefile或 Windows Visual C++的 projects/workspaces),然后再依一般的构建方式使用。
恋喵大鲤鱼
2018/08/03
2.7K0
使用Cmake生成跨平台项目编译解决方案
    项目最近有需求在windows下面运行,我花了几周时间将linux的服务器移植到windows下面,目前已经能够正常运行服务器,目前又有了新需求,两边的代码结构和组织是分开的,因此为了两边能够同步维护,需要一个能够跨平台的项目编译解决方案,经过调研之后,选择了使用cmake这个工具,本文主要讲述,使用cmake的生产项目的一些基础知识。
帘卷西风
2018/08/03
5.6K0
使用Cmake生成跨平台项目编译解决方案
相关推荐
cmake 简介2021-10-03
更多 >
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验