cmake怎么用,怎么运行cmake

对于C/C++的开发者而言,当涉及到复杂的第三方依赖时,工程的管理往往会变得十分棘手,尤其是还需要支持跨平台开发时。CMake做为跨平台的编译流程管理工具,为第三方依赖查找和引入,编译系统创建,程序测

对于 C/C++的开发者而言,当涉及到复杂的第三方依赖时,工程的管理往往会变得十分棘手,尤其是还需要支持跨平台开发时。 CMake 做为跨平台的编译流程管理工具,为第三方依赖查找和引入,编译系统创建,程序测试以及安装都提供了成熟的解决方案。 编写一次 CMakeLists.txt 文件,执行同样的命令,在不同系统上都可以完成可执行程序或者链接库的创建。 在熟悉 CMake 后,这种编译体验我认为勉强能赶上 Rust,Go 这些现代语言的一半,还有一半则是差在包管理上,这方面暂且不提。 当然,如果只是做做算法题,完全不需要用到 CMake 这样复杂的工具,简单使用 gcc,clang 就可以满足需求了。

CMake 和 C++一样,随着多年的发展,其设计也得到了许多改进,并且和旧版本相比产生了重要的差异,从而有了现代 CMake 的说法。 传统的 CMake 使用方式也没有什么问题,但就和现代 C++一样,现代的 CMake 使用方式在一些概念上更清晰,对开发者也更友好,更不容易出错。

34;main.cpp")target_link_library(MyEXE PRIVATE Poco::Net Poco::Util)target_compile_definition(MyEXE PRIVATE std_cxx_14)

Target 和围绕 Target 的配置

一个 C/C++工程通常都是为了生成可执行程序或者链接库,在现代 CMake 里他们被统称为target,创建命令分别是add_library()和add_executable()。 其中链接库的类型又分为很多种,最常用的就是SHARED以及STATIC,在命令中加入关键词进行

在CMakeLists.txt中可以有多个target,相关配置大多围绕这些 target 进行。比如指定target的源文件:

target_source(MyLib PRVIATE &34; &34;)

在 CMake 中,PRIVATE关键词用于描述参数的“应用范围”,此外还有INTERFACE和PUBLIC两种可能的值,在下一小节会对他们进行详细介绍,此处可以暂时无视。

将一个已有的项目改造为 CMake 工程时,通常会有较多的源文件,可以使用 CMake 的file命令进行遍历拿到全部的源文件:

file(GLOB_RECURSE SRCS ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)

target_source(MyLib PRIVATE ${SRCS})

除了源码,配置target时通常还需要指定头文件目录:

target_include_directories(MyLib PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/)

编译时需要的语言特性:

target_compile_features(MyLib PRIVATE std_cxx_14)

以及编译时的宏定义:

cmake怎么用

target_compile_definitions(MyLib PRIVATE LogLevel=3)

如果你有一些参数想直接传给底层的编译器(比如 gcc,clang,cl),可以使用

target_compile_options(MyLib PRIVATE -Werror -Wall -Wextra)

上面通过target_source这些target_*形式的命令进行的配置都是只对指定 target 有效的。 而在传统 CMake 中,这些配置通常都是以全局变量的形式定义,比如使用include_directories()、set_cxx_flags()等命令,传统方式的问题是灵活度低,当存在多个 target 时无法进行分别配置,导致某个 target 的属性意外遭到污染,因此现代 CMake 基于 target 的配置方式就和引入了 namespace 一样,管理起来更省心。

Build Specification 和 Usage Requirement

软件开发中依赖是十分常见的,C/C++通过 include 头文件的方式引入依赖,在动态或静态链接后可以调用依赖实现。 一个可执行程序可能会依赖链接库,链接库也同样可能依赖其他的链接库。 此时一个棘手的问题是,使用者如何知道使用这些外部依赖库需要什么条件? 比方说,其头文件的代码可能需要开启编译器 C++17 的支持、依赖存在许多动态链接库时可能只需要链接其中的一小部分、有哪些间接依赖需要安装、间接依赖的版本要求是什么……

对于这些问题,最简单粗暴的解决方案即文字说明,依赖库的作者可以在某个 README、网站、甚至在头文件里说明使用要求,但这种方式效率显然是很低下的。

CMake 提供的解决方案是,在对 target 进行配置时,可以规定配置的类型,分为 build specification 和 usage requirement 两类,会影响配置的应用范围。 Build specification 类型的配置仅在编译的时候需要满足,通过PRIVATE关键字

下面来看一个例子,我们编写了一个 library,在编译时静态链接了 Boost,在我们的实现文件中使用了 c++14 的特性,并用到了 Boost 的头文件和函数。 随后我们对外发布了这个库,其中有头文件和预编译好的动态链接库。 尽管我们的实现代码里用了 C++14,但在对外提供的头文件中只用到 C++03 的语法,也没有引入任何 Boost 的代码。 这种情况下,当其他工程在使用我们的 library 时,其使用的编译器不需要开启 C++14 的支持,开发环境下也不需要安装 Boost。我们 library 的 CMake 配置中可以这么写:

target_compile_features(MyLib PRIVATE cxx_std_14)target_link_libraries(MyLib PRIVATE Boost::Format)

此处用 PRIVATE 说明 c++14 的支持只在编译时需要用到,Boost 库的链接也仅在编译时需要。 但如果我们对外提供的头文件中也使用了 C++14,那么就需要使用 PUBLIC 修饰,改为:

target_compile_features(MyLib PUBLIC cxx_std_14)target_link_libraries(MyLib PRIVATE Boost::Format)

当 library 是 header-only 时,我们的工程是不需要单独编译的,因此也就没有 build specification,通过INTERFACE修饰配置即可

target_compile_features(MyLib INTERFACE cxx_std_14)

需要注意的是,Usage requirement 类型的配置,即通过INTERFACE或是PUBLIC修饰的配置是会传递的,比如 LibA 依赖 LibB 后,会继承 LibB 的 usage requirement,此后 LibC 依赖 LibB 时,LibA 和 libB 的 usage requirement 都会继承下来,这在存在多级依赖时是非常有用的。

现在的一个问题是,我们写好的这些 target,还有他们的PRIVATE,INTERFACE以及PUBLIC属性,使用者如何才能知道呢?

寻找和使用链接库

对于使用者而言,一大问题是如何找到依赖以及了解如何使用依赖。 C/C++标准没有规范库的安装位置和安装形式,通过 CMake 提供的方案寻找依赖,不光可以定位到头文件目录和链接库路径,还能够获取到库的 usage requirement。

在 CMake 中寻找第三方库的命令为find_package,其背后的工作方式有两种,一种基于 Config File 的查找,另一种则是基于 Find File 的查找。 在执行find_package时,实际上 CMake 都是在找这两类文件,找到后从中获取关于库的信息。

通过 Config file 找到依赖

Config File 是依赖的开发者提供的 cmake 脚本,通常会随预编译好的二进制一起发布,供下游的使用者使用。 在 Config file 里,会对库里包含的 target 进行描述,说明版本信息以及头文件路径、链接库路径、编译选项等 usage requirement。

CMake 对 Config file 的命名是有规定的,对于find_package(ABC)这样一条命令,CMake 只会去寻找ABCConfig.cmake或是abc-config.cmake。 CMake 默认寻找的路径和平台有关,在 Linux 下寻找路径包括usr/lib/cmake以及usr/lib/local/cmake,在这两个路径下可以发现大量的 Config File,一般在安装某个库时,其自带的 Config file 会被放到这里来。

在 Windows 下没有安装库的规范,也因此没有这样的目录,库可能被安装在各种奇奇怪怪的地方。 此外,在 Linux 下,库也可能没有被安装在上述这些默认位置,在这些情况下,CMake 也提供了解决方案,对于find_package(Abc)命令,如果 CMake 没有找到 Config file,使用者可以提供Abc_DIR变量,CMake 会到Abc_DIR指向的路径寻找 Config file。

通过 Find file 找到依赖

Config file 看似十分美好,由开发者编写 CMake 脚本,使用者只要能找到 Config file 即可获取到库的 usage requirement。 但现实是,并不是所有的开发者都使用 CMake,很多库并没有提供供 CMake 使用的 Config file,但此时我们还可以使用 Find file。

对于find_package(ABC)命令,如果 CMake 没有找到 Config file,他还会去试着寻找FindABC.cmake。Find file 在功能上和 Config file 相同,区别在于 Find file 是由其他人编写的,而非库的开发者。 如果你使用的某个库没有提供 Config file,你可以去网上搜搜 Find file 或者自己写一个,然后加入到你的 CMake 工程中。

一个好消息是 CMake 官方为我们写好了很多 Find file,在CMake Documentation这一页面可以看到,OpenGL,OpenMP,SDL 这些知名的库官方都为我们写好了 Find 脚本,因此直接调用 find_package 命令即可。 但由于库的安装位置并不是固定的,这些 Find 脚本不一定能找到库,此时根据 CMake 报错的提示设置对应变量即可,通常是需要提供安装路径,这样就可以通过 Find file 获取到库的 usage requirement。 不论是 Config file 还是 Find file,其目的都不只是找到库这么简单,而是告诉 CMake 如何使用这个库。

坏消息是有更大部分库 CMake 官方也没有提供 Find file,这时候就要自己写了或者靠搜索了,写好后放到本项目的目录下,修改CMAKE_MODULE_PATH这个 CMAKE 变量:

list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_SOURCE_DIR}/cmake)

这样${CMAKE_SOURCE_DIR}/cmake目录下的 Find file 就可以被 CMake 找到了。

不过一个新的问题是,Config file 以及 Find file 究竟要怎么写?

cmake怎么用

Imported Target

在 C/C++工程里,对于依赖,我们最基本的要求就是知道他们的链接库路径和头文件目录,通过 CMake 的find_library和find_path两个命令就可以完成任务:

find_library(MPI_LIBRARYNAMES mpiHINTS &34; ${MPI_LIB_PATH}34;${CMAKE_PREFIX_PATH}/include& 如果默认路径没找到mpi.h,还会去MPI_INCLUDE_PATH找,下游使用者可以设置这个变量值)

于是在早期 CMake 时代,依赖的开发者在 cmake 脚本里通过全局变量来

在现代 CMake 中,cmake 脚本提供一个 target 显然会更好,因为 target 具备属性,我们不光是要找到库,还需要了解库的使用方式,使用 target 除了头文件目录和链接库路径,我们还可以拿到更多关于库的信息。

因此现代 CMake 提供了一种特别的 target,Imported Target,创建命令为add_library(Abc STATIC IMPORTRED),用于表示在项目外部已经存在、无需编译的依赖,命令的第二个参数用于说明类型,比如是静态库或动态库等。 对于 Imported Target 的名字,似乎开发者们都喜欢使用 namespace 的方式,比如Boost::Format、Boost::Asio等。 同样的,对于一个 CMake 脚本,可以有多个 Imported Target。

我们可以像对待普通 target 一样,对 Imported Target 调用target_link_libraries等命令来说明他的 usage requirement。 但其实还有另一种配置方式,上文提到过可以通过PRIVATE,INTERFACE,PUBLIC用于修饰 target 属性,这实际上可看作是一种语法糖。 在 CMake 中,target 的大多属性都有对应的 private 以及 interface 两个版本的变量。 比如通过target_include_directories命令配置头文件目录时,当使用PRIVATE修饰时,值被写入 target 的 INCLUDE_DIRECTORIES变量;使用INTERFACE修饰时,值写入INTERFACE_INCLUDE_DIRECTORIES变量;而使用PUBLIC时,则会写入两个变量。 在 CMake 中,我们可以不使用 target 命令,而是直接使用set_target_properties修改这些值的变量。

对于 Imported Target,当库已经事先编译好时,我们需要通过一个特殊的变量,IMPORTED_LOCATION,来指明动态链接库的具体位置。 这个变量就可以通过set_target_properties进行设置,在实际生产环境下,由于存在 Release 以及 Debug 环境的区别,IMPORTED_LOCATION实际上也存在多个版本,比如IMPORTED_LOCATION_RELEASE以及IMPORTED_LOCATION_DEBUG,都进行设置后,在对应的环境下,CMake 会根据这些变量为下游使用者选择正确的链接库。

34;CXX&34;${_IMPORT_PREFIX}/lib/spdlog/spdlog.lib")

find_package(spdlog REQUIRED)add_executable(MyEXE)target_source(MyExe &34;)target_link_libraries(MyExe SPDLog::spdlog)

无需target_include_directories,spdlog 的头文件目录自动会加进来。

通过编写CMakeLists.txt,可以控制生成的Makefile,从而控制编译过程。CMake自动生成的Makefile不仅可以通过make命令构建项目生成目标文件,还支持安装(make install)、测试安装的程序是否能正确执行(make test,或者ctest)、生。

find_package 的处理

CMake 可以编译源代码、制作程式库、产生适配器(wrapper)、还可以用任意的顺序建构执行档。CMake 支持 in-place 建构(二进档和源代码在同一个目录树中)和 out-of-place 建构(二进档在别的目录里),因此可以很容易。

回到find_package这个命令,这个命令可以指定很多参数,比如指定版本,指定具体的模块等等。 以 SFML 多媒体库为例,其包含了 network 模块,audio 模块,graphic 模块等等,但我很多时候只用到 graphic 模块,那么其他的模块对应的链接库不需要被链接,于是 CMake 脚本可以这么写

SFML提供的target名字为sfml-graphicstarget_link_libraries(MyEXE PRIVATE sfml-graphics)

对于find_package命令,这些版本、模块等参数在 Config file 或是 Find file 中显然是需要处理的,在版本不匹配,模块不存在的情况下应该对下游使用者进行提示。 这一方面 CMake 官方也为依赖开发者做了考虑,提供了FindPackageHandleStandardArgs这个模块,在 CMake 脚本中 include 此模块后,就可以使用find_package_handle_standard_args命令,来告知 CMake 如何获取当前 package 的版本变量,如何知道是否找到了库,比如下面针对 RapidJSON 的 cmake 脚本:

include(FindPackageHandleStandardArgs)find_package_handle_standard_args(RapidJSONREQUIRED_VARS RapidJSON_INCLUDE_DIRVERSION_VAR RapidJSON_VERSION)

一般用法是,新建一个文件夹,一般命名为 build ,在终端进入该文件夹,然后调用 cmake ../ , cmake 会在找到上级目录找到 CMakeLists.txt ,生成 makefile 和一些其它文件。在 makefile 所在目录,调用 make 命令,会。

这段脚本

使用 CMake 来编译

CMake 生成好编译环境后,底层的 make,ninja,MSBuild 编译命令都是不一样的,但 CMake 提供了一个统一的方法进行编译:

cmake --build .

cmake怎么用

使用--buildflag,CMake 就会调用底层的编译命令,在跨平台时十分方便。

对于 Visual Studio,其 Debug 和 Release 环境是基于 configuration 的,因此CMAKE_BUILD_TYPE变量无效,需要在 build 时指定:

cmake --build . --config Release

CMake 的缺陷

CMake 的缺陷是很明显的,入门成本很高,其语法的设计也很糟糕,find_package这些函数不会返回结果,而是对全局变量或是 target 产生副作用,函数的行为不查阅文档是很难预测的。 并且在 CMake 中,变量,target,字符串的区分不明确,很容易让人感到迷惑,不知道什么时候应该使用${}去读取值。

此外,官方网站上的教程也十分落后,尽管可用,但并没有使用现代 CMake 方式创建工程。 推荐看本文最后给的资料而不是官网上的 Tutorial。

之后有空了再介绍 Config file 的具体创建方式、库的 install 还有基于 ctest 的测试,不过希望在我更新之前就能有更好的替代工具诞生吧。

cmake-buildsystem

cmake-packages

It's Time To Do CMake Right

上一篇 2023年02月07 22:46
下一篇 2023年02月03 21:08

相关推荐

  • nfc怎么用,手机怎么使用nfc刷门禁卡

    NFC功能相信大家并不陌生,现在的智能手机几乎都会搭载,但是NFC功能有没有用,到底怎么用很多人不知道。其实NFC功能在日常生活中非常实用,并且非常的便利,如果不知道真的浪费了,手机怎么使用nfc刷门

    2023年01月09 231
  • 怎么隐藏app

    相信每个人的手机里都会藏有自己的小秘密,当然,苹果单独隐藏一个app,我们也不希望自己的App在使用的时候被发现,那么我们如何将自己的不想被别人看到的App隐藏起来呢?今天就教大家如何用三星手机隐藏自

    2023年02月03 292
  • 微信好友删除怎么恢复,微信好友删除恢复方式

    微信好友删除后,有以下六种方式可以尝试找回:看点赞或评论通过微信朋友圈点赞或评论找回删除的微信好友,该方法适用于单删对方,如果已经互删则该方法不管用。因为对方也已经删除你的话,微信好友删除恢复方式,那

    2023年01月16 281
  • 怎么搜图片,百度图片识别在线识图

    今天小编要和大家分享一个有意思的操作,都知道vlookup是用于文本的查找操作,那图片呢,图片该如何查找呢1.为了完成这样的操作,我们可以选择方方格子的图片工具按钮1、用文字搜图片打开百度图片页面网页

    2023年02月09 294
  • 一件代发怎么操作

    首先申明,做一件代发确实很难暴富。而做一件代发成本虽然赚得少,但风险也小,即使不做了,也没多大损失。如果运气好,能选到好的产品,收益也会不错。这个卖家去年做一件代发新店,双十一单天访客就过万,半个月时

    2023年01月22 237
  • pr怎么导出mp4格式,为什么pr导出没有mp4格式

    哈喽大家好,我是小朋。下面我们继续给大家分享一些关于剪辑软件premiere的实用教程。刚接触到pr这个软件的小伙伴们肯定会发现我们在做完一系列视频剪辑后,保存文件导出文件时,可能会遇到想把视频保存为

    2023年01月23 277
  • 怎么测试下载速度

    怎么测试网络下载速度,据indiatoday1月26日报道,预计5G将于2022年底在印度推出。但是,根据印度电信部(DoT)的公告,将首先在13个都会城市发布。RelianceJio近日宣布,已完成

    2023年01月14 288
  • 怎么解压文件

    工具/原料系统版本:windows7系统品牌型号:宏碁传奇X方法1、鼠标右键点击需要解压的文件,可以选择“解压到”、“解压到当前文件夹”等。2、第一种可以自行选择文件的存放位置,第二种解压文件会存放到

    2023年01月09 229
  • 抖音怎么发视频

    在4月份上旬,抖音还只能上传15秒的短视频,抖音怎么发相册里的视频,并且粉丝数量需要达到1000以上才能开通一分钟长视频,这让许多需要用长视频才能完整表达内容的用户,被挡在了长视频门外....而在4月

    2023年02月03 207
  • gmail怎么注册,国内手机怎么注册gmail邮箱

    最近很多朋友问我该如何注册一个谷歌账号我那也是尝试了很多方法,最终找到了一个最快捷有效的方法分享给大家。先说一下,看到网上有很多小伙伴在注册账号的时候遇到的一些问题,然后把注册方法放到最后。1、谷歌网

    2023年02月05 282
  • 微信置顶文字怎么设置,2022最火微信状态短句

    微信置顶文字是什么鬼?很多小伙伴居然不知道。顾名思义,就是在微信的顶部设置你喜欢的一句话,看图:对了,不论你的手机是安卓还是苹果系统,都可以制作微信置顶文字。只要打开微信,点击“我”—“收藏”—“加号

    2023年01月09 293
  • 微信怎么赚钱,5~10元微信红包扫雷群

    学生挣钱最快的游戏?我记得当初还是学生那会,5~10元微信红包扫雷群,最喜欢的就是跑到网吧里玩游戏,我想现在的学生应该也是差不多,不过,我看到目前大部分的学生玩游戏都是在花钱。可你知道吗?其实,我们也

    2023年01月15 276
  • 怎么挂代理,手机如何挂代理

    代理商在线上售卖酒店,其道理跟天猫或淘宝一样,都是一种线上销售行为,手机如何挂代理,一台电脑足不出户就可以在线上平台上售卖酒店。只不过这个行业目前很冷门,知道的人不多,圈内同行也少之又少,知道了解的人

    2023年01月17 280
关注微信