作为拥有 20 年发展历史的 CMake,肩负 Build-system Generate 的重任,被越来越多的开源库接纳,其自身理念也在不断推陈出现。到底 Modern CMake 和 Traditional CMake 有什么区别,这是个值得讨论的话题。

历史背景

CMake 是一个构建系统生成器(build-system generator)。常见的构建系统,有 Visual Studio,XCode,Make 等等。CMake 可以支持不同平台下构建系统的生成。

CMake 的出现已经有接近 20 年的历史,它的发展过程也初步经历了三个阶段。

  • ~2000 (~v2.x) ,刚刚启动,过程式描述为主。
  • 2000~2014 (v3.0~) ,引入 Target 概念。
  • 2014~now (~v3.15),有了 Target 和 Property 的定义,更现代化。

概 述

现代化的 CMake 是围绕 TargetProperty 来定义的,并且竭力避免出现变量 variable 的定义。Variable 横行是典型 CMake2.8 时期的风格。现代版的 CMake 更像是在遵循 OOP 的规则,通过 target 来约束 link、compile 等相关属性的作用域。如果把一个 Target 想象成一个对象(Object),会发现两者的组织方式非常相似:

  • 构造函数:addexecutableaddlibrary
  • 成员函数:gettargetproperty()settargetproperties()getproperty(TARGET)setproperty(TARGET)targetcompiledefinitions()targetcompilefeatures()targetcompileoptions()targetincludedirectories()targetlinklibraries()target_sources()
  • 成员变量Target properties(太多)

在 Target 中有两个概念非常重要:Build-Requirements 和 Usage-Requirements。这两个概念对于理解为什么现代 CMake 会如此设计提供了指导意义。

  • Build-Requirements: 包含了所有构建 Target必须的材料。如源代码,include 路径,预编译命令,链接依赖,编译/链接选项,编译/链接特性等。

  • Usage-Requirements:包含了所有使用 Target必须的材料。如源代码,include 路径,预编译命令,链接依赖,编译/链接选项,编译/链接特性等。这些往往是当另一个 Target 需要使用当前 target 时,必须包含的依赖。

    传统的 CMake 和现代化的 CMake 的主要区别(非语法层面)如下图所示。Traditioncal CMake 在设置 build-requirements 和 usage-requirements 上都依赖手动输入命令,并且人工维持其作用域(变量的作用域以目录为单位)。而 Modern CMake 在设置上述 requirement 均以 target 为单位,所以在传递 target 属性到其依赖的下游链条中更自动也更智能。

在 Moden CMake 中新增了不少关键字,其中最常见的是 PUBLIC、PRIVATE、INTERFACE。

  • PRIVATE/INTERFACE/PUBLIC:定义了 Target 属性的传递范围。
  • PRIVATE: 表示 Target 的属性只定义在当前 Target 中,任何依赖当前 Target 的 Target 不共享 PRIVATE 关键字下定义的属性。
  • INTERFACE:表示 Target 的属性不适用于其自身,而只适用于依赖其的 Target。
  • PUBLIC:表示 Target 的属性既是 build-requirements 也是 usage-requirements。凡是依赖。凡是依赖于当前 Target 的 Target 都会共享本属性。 -

解剖麻雀

我们来尝试写一个实例,看看在 CMake v3.13 及以后版本中的写法如何。

HelloWorld      |___ CMakeLists.txt      |___ hello-exe               |______ CMakeLists.txt               |______ main.cpp      |___ hello-lib               |______ CMakeLists.txt               |______ hello.hpp               |______ hello.cpp

以这样一个简单的 HelloWorld 开启有助于我们快速进入主题。这个项目结构很简单,包含两个子文件夹,hello-exe 生成 executable,hello-lib 生成链接库(动态)。

  • 我们先看下顶层 CMakeLists 的内容:
# HelloWorld/CMakeLists.txtcmake_minimum_required(VERSION 3.14)project(HelloWorld VERSION 1.0.0)add_subdirectory(hello-lib)add_subdirectory(hello-exe)

这里没有什么值得多讨论的,与传统 CMake 一样的写法,定义 project 名称,版本号,添加子文件夹。

  • 我们接着看 hello-lib。首先看源码。

源码比较简单,只是定义一个 hello_printer 类,并在其 cpp 中定义成员函数 print。请注意头文件中的预编译命令。这在 VS 中是非常常用的预编译命令,用于导出动态库的符号。而当该库被其他 Target 调用时,需要使用 dllimport 导入符号。注意这条预编译命令刚好符合 build-requirement 和 usage-requirement 的定义。对于 hello-lib 而言,定义 DLL_EXPORT 从而将 DLLAPI 定义为declspec(dllexport)是 build-requirement,而对于该 Target 的调用者,需要的是不定义 DLLEXPORT。因而需要在定义 compile_definitions 时将 DllEXPORT 放在 PRIVATE 关键词下。

当其他 Target 使用 hello-lib 的时候,还需要知道 hello.hpp 的路径。传统的 CMake 写法是通过在调用者的 CMakeLists.txt 中添加 includedirectory 来实现。但这种写法会依赖库之间的相对路径,一旦调整路径,所有的 CMakeLists 都将需要更新。在 Modern CMake 中不必如此,你只需要通过 targetincludedirectories 指定 hello.hpp 的路径,将之纳入 INTERFACE(当然 PUBLIC)也行。则调用者就可以得到该 include 路径。

CMakeLists.txt 全文如下:

set(target_name "hello-lib")add_library(${target_name}  SHARED        hello.cpp        hello.hpp    )target_include_directories(${target_name} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})target_compile_definitions(${target_name} PRIVATE Dll_EXPORT)
  • 最后看下 hello-exe。hello-exe 中的 CMakeLists.txt 就可以比较简单了:
add_executable(hello-exe main.cpp)target_link_libraries(hello-exe PUBLIC hello-lib)

补充

Modern CMake 中还有些有意思的知识点,这里没法一一覆盖,只能稍稍展开。最有意思的点是 generator-expression。在现代 IDE 中,Build-type 一般都不是在 CMake config 期间能确定的。如 VS,XCode 都支持 Multi-configuration,具体使用 Debug 还是 Release 是在编译时才确定,那如果 Target 的依赖路径或者依赖库需要区分 Configuration 来配置该怎么办呢?在传统 CMake 中是比较难办的,target_link_libraries 提供了一种手段,可以用 debug 和 optimized 来区分具体的库名,而其他的编译或链接设置则比较困难。在 Modern CMake 中,我们可以通过 generator-expression 来实现。

generator-expression 定义为$<...>的形式。该表达式的值有多种形式,而且支持嵌套使用:

  • 条件表达式
  • $\ 当条件为 1 时,表达式为 true_string,否则为空
  • $\ 当条件为 1 时,表达式为 true_string,否则为 false_string
  • 变量表达式
  • $ 当 target 存在为 1,否则为 0
  • $\ 当 config 为 cfg 时为 1,否则为 0。这是非常高频使用的一个表达式,可以通过它来区分 Debug/Release 等不同的 config。如下例所示,通过嵌套使用上述两个表达式,可以达到区分 CONFIG 来设置依赖库路径的目的。
target_link_directories(${PROJECT_NAME} PUBLIC                                                                                                                                                                        $<$<CONFIG:Debug>:${CONAN_LIB_DIRS_DEBUG}>                                                                                                                                                                          $<$<CONFIG:Release>:${CONAN_LIB_DIRS_RELEASE}>) 
  • ... 太多了,不一一列举。以上是 Modern CMake 中常用的内容,还有些如 IMPORTED,ALIAS 暂时还没用到,等用到再更新吧。

参考

  • https://crascit.com/2016/01/31/enhanced-source-file-handling-with-target_sources/#comment-414
  • https://github.com/onqtam/awesome-cmake/blob/master/README.md
  • https://www.youtube.com/watch?v=y7ndUhdQuU8

本文首发于 GitChat,未经授权不得转载,转载需与 GitChat 联系。

阅读全文: http://gitbook.cn/gitchat/activity/5d4cbaf5f84543415feac3ee

您还可以下载 CSDN 旗下精品原创内容社区 GitChat App , GitChat 专享技术内容哦。

Modern CMake 简介相关推荐

  1. 【Android NDK 开发】Android Studio 使用 CMake 导入静态库 ( CMake 简介 | 构建脚本路径配置 | 引入静态库 | 指定静态库路径 | 链接动态库 )

    文章目录 I . CMake 简介 II . Android Studio 中 CMake 引入静态库流程 III . 指定 CMake 最小版本号 IV . 导入函数库 ( 静态库 / 动态库 ) ...

  2. 【转载】CMake 简介和 CMake 模板

    转载自我的博客: CMake 简介和 CMake 模板 . 如果你用 Linux 操作系统,使用 cmake 会简单很多,可以参考一个很好的教程: CMake 入门实战 | HaHack .如果你用 ...

  3. CMake快速入门01:CMake简介与安装

    目录 1 CMake简介 2 CMake安装 3 CMake基本使用 3.1 实验源文件 3.2 实验CMakeLists.txt 3.2.1 CMakeList.txt命令概述 3.2.2 add_ ...

  4. CMake简介及使用实例

    1.CMake简介 CMake是一个跨平台的建构系统的工具,可以用简单的语句来描述所有平台的安装(编译过程).他能够输出各种各样的构建文档makefile或者project文件,描述系统建构的过程 ...

  5. Modern CMake 翻译 2.1 变量和缓存

    <<Modern CMake>> 翻译 2.1 变量和缓存 局部变量 我们首先来看变量.局部变量通常这样设置: set(MY_VARIABLE "value" ...

  6. modern cmake的概念剖析

    modern cmake的概念剖析 这篇文章主要介绍一些相关的概念,具体的语法建议去github上看相关教程. 从c++的依赖关系说起 如果要学习modern cmake,最基本的是要搞清楚文件之间以 ...

  7. Android NDK开发一 NDK环境搭建及cmake简介

    1 前言 关于NDK的介绍可以查看官方的介绍: https://developer.android.com/ndk/guides/index.html 一句话总结NDK:NDK(Native Deve ...

  8. cmake 简介-初识

    Table of Contents CMake 使用方法 /src/main.c: /src/a.c /include/a.h 补充: CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述 ...

  9. cmake语法【一】

    一.Cmake 简介 cmake 是一个跨平台.开源的构建系统.它是一个集软件构建.测试.打包于一身的软件.它使用与平台和编译器独立的配置文件来对软件编译过程进行控制. 二.常用命令 指定 cmake ...

  10. CMake 条件判断

    CMake简介 CMake 是做什么的? CMake是一套类似于automake的跨平台辅助项目编译的工具. 我觉得语法更加简单易用. CMake的工作流程 CMake处理顶级目录的CMakeList ...

最新文章

  1. 《用Python进行自然语言处理》第3章 加工原料文本
  2. AlphaGo Zero的伟大与局限——ResNet作者、旷视研究院院长孙剑深度评述
  3. 深入思考编译原理之 理解执行过程和编译过程
  4. Linux修改终端显示前缀及环境变量
  5. python多线程详解_python基础:python多线程详解
  6. 自考计算机专业,自考计算机
  7. feign直接走熔断_121 SpringCloud之服务熔断、隔离、Hystrix、 Dashboard和turbine
  8. linux服务器跟踪命令,Linux下使用strace命令来跟踪.htaccess的使用
  9. 半监督学习(semi-supervised learning)
  10. Sql Plus 操作
  11. xcode4 引入poco库
  12. “飞跃四十载 同发展·共繁荣”巡回展在江苏举行
  13. 在Cesium中实现与CAD的DWG图叠加显示分析
  14. python3 调用http接口例子
  15. 人到底是为了什么活着?
  16. 互联网公司和外包公司有什么区别?为什么有些程序员不想进外包公司?
  17. IAR分析内存重要的神器 - map文件全解析
  18. PS保留渐变进行换色
  19. 解决win10一开机内存(8G)就占用70%多,查看任务管理器并没有占用内存很高的进程的问题
  20. BJDCTF on buuoj

热门文章

  1. 灵活操作MS SQL 2005 中的数据库 - 分离、附加、离线、在线、日志截断
  2. SpringBoot 3.0最低版本要求的JDK 17,这几个新特性不能不知道
  3. 安装错误 Package requirements (json-c) were not met: No package ‘json-c‘ found
  4. 安卓动态调试七种武器之离别钩 – Hooking(上)
  5. oceancolor数据批量下载
  6. 冬瓜哥的PC机上唯独它9年没换!
  7. flutter A problem occurred configuring project ‘:shared_preferences_linux‘.
  8. OpenCV动作识别
  9. 2018世界互联网大会首日,丁磊马化腾雷军等都说了啥?
  10. 智慧云教育平台实战项目笔记