让程序编译更优雅的几个CMake命令

2023-05-16

简介

本文通过一个工程示例介绍了几个让程序编译更优雅的CMake命令。文末有完整下载地址。该工程示例首先生成一个动态库(libversion.dll:该库主要用于打印版本相关的信息),然后在一个可执行程序(cf_test)中使用该库。

测试环境

系统

Windows 10

编译环境

CLion:2020.1.1

编译工具

CMake:CLion内置

编译器

MinGW:gcc version 8.1.0 (x86_64-posix-seh-rev0, Built by MinGW-W64 project)

CMake命令

CMake命令没有很深奥的东西,基本上只要知道有这么个东西,然后搜下命令格式,照着写就行了。很多时候很多东西并不是学不会,而是不知道有这么个东西。本文粗略介绍几个可以让代码编译更自然的CMake命令。

其实不这么写,粗暴的使用脚本也行。不过CMake既然提供了类似的命令,使用的话,会让工程代码看起来更简洁优雅些。本人有代码洁癖,在写代码的时候会尽量使用设计者提供的方式完成想要的功能,其实完全没必要做的这么细,但还是做了。这也是为什么会有此文的原因。

下文介绍的几个命令都有很多参数,只介绍在工程示例中使用的参数。

示例工程CMakeLists相关目录结构如下。下文中使用1,2,3代指对应的CMakeLists文件

|-- cppFollowers
    |-- CMakeLists.txt            # 1
    |-- ...
    |-- test
    |   |-- CMakeLists.txt        # 2
    |   |-- ...
    |-- version
        |-- CMakeLists.txt        # 3
        |-- ...

其中,3会生成动态库libversion.dll,2会链接动态库libversion.dll生成可执行程序,用于测试。

add_subdirectory

当工程文件较多时,直写一个CMakeLists有时会显得有些乱,也不方便管理。使用add_subdirectory可以将指定文件下的CMakeLists添加到build任务列表中。通过该方式可以实现对CMakeList模块化拆分。

示例工程中,当CMake命令执行时会首先执行1。1使用如下命令:

add_subdirectory(version)     # 3对应的目录
add_subdirectory(test)        # 2对应的目录

当1执行到这两条语句时,就会分别执行3和2。

project

project用来设置与工程相关的变量。命令原型如下:

project(<PROJECT-NAME> [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]])

VERSION参数可以对版本信息相关的变量进行设置。

示例工程中,3使用如下命令:

project(version VERSION 0.0.0.1)

会将如下左边的变量设置为右边的值。左边是CMake内置的变量。

PROJECT_NAME version

PROJECT_VERSION 0.0.0.1

PROJECT_VERSION_MAJOR 0

PROJECT_VERSION_MINOR 0

PROJECT_VERSION_PATCH 0

PROJECT_VERSION_TWEAK 1

如果不习惯CMake的命名,可以使用如下命令设置自定义变量。

set(CF_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}")
set(CF_VERSION_MINOR "${PROJECT_VERSION_MINOR}")
set(CF_VERSION_PATCH "${PROJECT_VERSION_PATCH}")
set(CF_VERSION_TWEAK "${PROJECT_VERSION_TWEAK}")
set(CF_VERSION "${PROJECT_VERSION}")
set(CF_VERSION_SUFFIX "cppFollowers")

if (CF_VERSION_SUFFIX)
    set(CF_VERSION_FULL "${CF_VERSION}-${CF_VERSION_SUFFIX}")
else ()
    set(CF_VERSION_FULL "${CF_VERSION}")
endif ()

message("CF_VERSION = ${CF_VERSION}")
message("CF_VERSION_FULL = ${CF_VERSION_FULL}")

message命令用于CMake的输出。输出信息如下图所示:

configure_file

configure_file可以将源文件拷贝到另一个位置的同时将源文件中的变量替换为变量的值。若变量未定义,则替换为空。

示例工程中,3中使用如下命令。

configure_file(
        "${CMAKE_CURRENT_SOURCE_DIR}/config.h.in"
        "${CMAKE_CURRENT_SOURCE_DIR}/config.h"
)

将以下config.h.in文件。

#define CF_VERSION_MAJOR ${CF_VERSION_MAJOR}
#define CF_VERSION_MINOR ${CF_VERSION_MINOR}
#define CF_VERSION_PATCH ${CF_VERSION_PATCH}
#define CF_VERSION_TWEAK ${CF_VERSION_TWEAK}
#define CF_VERSION "${CF_VERSION}"
#define CF_VERSION_FULL "${CF_VERSION_FULL}"

拷贝替换成以下config.h。

#define CF_VERSION_MAJOR 0
#define CF_VERSION_MINOR 0
#define CF_VERSION_PATCH 0
#define CF_VERSION_TWEAK 1
#define CF_VERSION "0.0.0.1"
#define CF_VERSION_FULL "0.0.0.1-cppFollowers"

也许你可能觉得这个命令有些多余,直接定义这个版本信息相关的宏不就行了么?或许你不知道发布版本的版本号呢?又或许发布版本的人不能改源码呢?又或许你想获取CMakeLists文件中别的变量的值呢?这个时候就需要这个命令了。

add_custom_command

add_custom_command可以将自定义的规则添加到目标构建的过程中。

示例工程中,3使用如下命令:

# PRE_BUILD PRE_LINK POST_BUILD all after Linking
# different from what I expected, why??????
add_custom_command(TARGET ${PROJECT_NAME} PRE_BUILD
        COMMENT "pre build..." # show before the commands are executed
        VERBATIM # recommended as it enables correct behavior.
        )

add_custom_command(TARGET ${PROJECT_NAME} PRE_LINK
        COMMAND ${CMAKE_COMMAND} -E echo "pre link..."
        VERBATIM # recommended as it enables correct behavior.
        )

# copy version.h libversion after build target
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E echo "copy version.h $<TARGET_FILE_NAME:${PROJECT_NAME}>..."
        COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/version.h ${CMAKE_CURRENT_SOURCE_DIR}/../include
        COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/../lib/$<TARGET_FILE_NAME:${PROJECT_NAME}> ${CMAKE_CURRENT_SOURCE_DIR}/../bin
        VERBATIM
        )

分别在TARGET构建前PRE_BUILD、链接前PRE_LINK和构建后POST_BUILD执行COMMAND中的指令。PRE_BUILDPRE_LINK只是测试,POST_BUILD将动态库的头文件和库文件分别拷贝到include和bin目录中。

注意,PRE_BUILDPRE_LINKPOST_BUILD都是针对TARGET的构建,并不是针对编译的。测试环境中执行时顺序如下图所示:

可以看到在PRE_LINK对应的命令执行的时候源文件已经编译完成了。

set_directory_properties

上面使用add_custom_command可以在目标构建后拷贝了头文件和库文件,你可能想在目标clean的时候将这两个文件clean掉。set_directory_properties可以实现这个需求。

示例工程中,3使用如下命令:

# rm version.h libversion when make clean
set(SRCS ${CMAKE_CURRENT_SOURCE_DIR}/../include/version.h
        ${CMAKE_CURRENT_SOURCE_DIR}/../bin/$<TARGET_FILE_NAME:${PROJECT_NAME}>
        )
set_directory_properties(PROPERTIES
        ADDITIONAL_MAKE_CLEAN_FILES "${SRCS}"
        )

在执行clean时会将上文使用add_custom_command拷贝的头文件和库文件删除。

install

install命令可以实现对工程的安装。实际上就是指定一些文件,在执行install命令的时候将其拷贝到对应的目录。这个命令可以很方便的将工程编译后的目标文件汇总将其提供给他人。

示例工程中,1使用如下命令:

#install dir prefix
set(CMAKE_INSTALL_PREFIX ${CMAKE_CURRENT_SOURCE_DIR}/install)
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DESTINATION include FILES_MATCHING PATTERN "*.h")
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/lib/ DESTINATION lib)
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bin/ DESTINATION bin)

在执行install命令时会将工程的头文件,库文件和可执行文件拷贝到install文件中。所有安装的文件都被记录在install_manifest.txt文件中。

遇到的坑

1、CLion的Reload CMake Project有时候Reload不彻底。

当发现CMakelists修改以后不生效时,可以试着将cmake-build-debug文件下的那一坨文件删掉再试下。

2、在windows下使用gcc编译动态库时,对于库中的函数有以下两种说法。其实都是不全面的。

2.1、windows下默认隐藏。实测并不是这样的,实测中发现当有一个函数export后,别的不export的函数才会隐藏。若完全不使用export,所有的函数都是导出的。不知道为什么这么设计,这个问题花费了我五个小时左右时间呀。

2.2、可以使用如下类似命令隐藏函数。在测试环境下貌似并没有什么用,在linux环境下应该有用,家里没有linux环境,没试。

set(CMAKE_C_VISIBILITY_PRESET hidden)
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
set(CMAKE_C_FLAGS$ "${CMAKE_C_FLAGS} -fvisibility = hidden")
set(CMAKE_CXX_FLAGS$ "${CMAKE_CXX_FLAGS} -fvisibility = hidden")

3、这个坑比较有意思。在dev分支开发完了需要merge到master分支,由于命令比较多。所以写了一个脚本。

@echo off
:: switched to dev
git checkout dev || GOTO label
:: pull from remote dev
git pull || GOTO label
:: switched to master
git checkout master || GOTO label
:: pull from remote master
git pull || GOTO label
:: merge dev to master
git merge dev || GOTO label
:: push to remote master
git push -u origin master || GOTO label
:: switched to dev
git checkout dev 
:label
pause
exit

第一次使用这个脚本合并分支的时候,执行到中途时,dos窗口提示“批处理文件不存在”。我发现这个脚本确实不在了,很诧异。其实是因为当脚本执行到第7行的时候分支切换到了master,master分支本身没有这个脚本,所以执行出错了。相当于脚本文件将自己删除了,但是系统还在执行脚本文件,然后报错了。

当时在想如果这个执行脚本的这个程序要是我写的,肯定就崩了,因为我根本不会考虑到这种情况。很多时候程序员会觉得用户傻X,比如这种情况,就不是故意的。

于是我手动将dev分支merge到master分支。不过很奇怪,当master分支有这个脚本以后按理说是两个不同的文件,切过去以后脚本竟然还能接着执行。既然能用,就不深究脚本执行的原理了。

源码地址

在公众号后台回复[cppFollowers]获取完整源码地址。为什么我不写一个简单回复,比如[c],就可以获取源码路径呢。其实是有小情绪的,因为整理这篇文章含源码陆续的花费了两周时间。这不是为了CMake优雅么,大部分时间花费在查找测试CMake命令上。

下载完成后建议直接使用默认的master分支。dev分支用于测试新的代码可能会有瑕疵。

本人公众号链接原文:让程序编译更优雅的几个CMake命令

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

让程序编译更优雅的几个CMake命令 的相关文章

  • CLion - 命令行程序参数

    当我分配给 运行 调试配置 程序参数 之类的 aaa bbb 然后打印它时 任何人都可以告诉我 JetBrains CLion 有什么问题吗 printf s n argv 1 我刚刚得到 aaa 而它必须是 aaa bbb 因为它们用双引
  • Cmake:将子项目目标导出到主项目

    我目前有一个项目叫做LIBS具有这样的结构 Lib1 CMakeLists txt lib1 class cpp lib1 class h lib2 CMakeLists txt lib2 class cpp lib2 class h cm
  • 在 CMake 中,CHECK_INCLUDE_FILE_CXX 如何工作?

    以下代码不打印任何内容 CHECK INCLUDE FILE CXX glog logging h GLOG INCLUDE IF GLOG INCLUDE MESSAGE YY ENDIF GLOG INCLUDE 但我设置了以下环境变量
  • 如何使我的单元测试适应 cmake 和 ctest?

    到目前为止 我已经使用了一个临时的单元测试程序 基本上是由批处理文件自动运行的全部单元测试程序 尽管其中很多都明确地检查了他们的结果 但还有更多的作弊行为 他们将结果转储到版本控制的文本文件中 测试结果中的任何更改都会被颠覆标记 我可以轻松
  • CMake 创建可执行文件时未定义的引用

    我是 CMake 新手 我正在尝试编译我的项目 该项目创建了一些静态库和一些可执行文件 下面是我拥有的文件结构的示例 PROJECT SRC 子项目 1该文件夹的 cpp 所有源文件 和CMakeLists txt 1 创建静态库 子项目
  • 如何在使用 Cmake 构建期间编译 HLSL 着色器?

    我正在开发 d3d 应用程序 我想在使用 cmake 构建期间编译我的 hlsl 着色器 我不知道从哪里开始 这是我当前的 CMakeLists txt cmake minimum required VERSION 3 20 project
  • CMake 找不到请求的 Boost 库

    既然我已经浏览了其他人的解决方案几个小时 但找不到适合我的问题的正确答案 我想将我的具体问题带给您 我正在尝试使用 CMake 构建 vsomeip 为此 我之前构建了 boost 1 55 但是 我在 CMake 中收到以下错误 The
  • 使用 CMake 和 clang 在 Windows 上构建简单的 C++ 项目

    我正在尝试在 Windows 10 上构建一个简单的 Hello World 程序 最好使用 CMake 和 clang 如果我使用 MinGW 的 g 编译器 我可以成功编译 链接和运行同一个项目 但当我尝试使用 clang 时会遇到问题
  • 如何在Linux上构建GLFW3项目?

    我已经使用 cmake 和 make 编译了 glfw3 和包含的示例 没有出现任何问题 开始编写我的第一个项目 作为 opengl 和 glfw 的新手 并且对 C 和 CMake 没有经验 我正在努力理解示例构建文件 甚至要链接哪些库和
  • 如何告诉 CMake 将构建文件放在哪里?

    我想告诉 CMake 将文件和文件夹输出到不同的文件夹而不是当前文件夹 我在下面讨论的是 CMake 生成的文件 文件 CMakeCache txt 目录 CMakeFiles 文件 生成文件 目录 bin 文件 cmake install
  • 带包装的 CMAKE RPATH

    我正在使用 cmake 创建包 我有以下结构 bin bin1 lib lib1 lib2 其中lib1和lib2是外部动态库 如何设置 RPATH 以便它自动与 lib1 和 lib2 链接 我也在这方面对 cmake 很感兴趣 Cmak
  • CMake - 未定义参考

    我正在尝试将 gtest 包含到我的项目中 问题是我在 GTest 中收到未定义的引用错误 我正在尝试在 Gtest 中测试 Node 类 在节点的构造函数中 我使用类记录器 尽管我已将库记录器添加到 gtest target 中 但我仍然
  • 如何在cmake中将构建类型更改为Release模式?

    我正在尝试以发布模式构建一个项目 默认情况下它是在调试模式下构建的 我正在设置变量CMAKE BUILD TYPE到 释放 CMakeLists txt 但它仍在调试模式下构建项目 当我在 CMake 命令中传递 Release 作为构建类
  • CMake:如何更改单个目标的编译器

    我有使用交叉编译器的嵌入式项目 我想介绍一下Google测试 用原生GCC编译器编译 另外使用 CTC 编译器构建一些单元测试目标 Briefly 我有 3 个不同的目标并用 3 个不同的编译器编译它们 如何表达它CMakeLists tx
  • Opencv - 找不到头文件

    我正在尝试使用 opencv 开始开发 问题是 到目前为止我几乎无法设置 opencv 因为我找不到它的头文件 我对此主题进行了一些研究 但没有一个真正有帮助 下面是一些链接 opencv2 包含文件在哪里 https stackoverf
  • CMake 添加对安装目标的依赖

    我在使用cmake时遇到以下问题 我使用 UseDoxygen 来自http tobias rautenkranz ch cmake doxygen http tobias rautenkranz ch cmake doxygen 为我的图
  • CMake、Exe找不到DLL

    所以我尝试在 Windows 上使用 cmake 设置一个项目 这就是我的项目结构 游戏引擎 git build include source testing CMakeLists txt 这个想法是 source 目录包含 GameEng
  • 是否可以让 cmake 构建文件(CMakeLists.txt)不在 CLion 的根目录中

    是否可以让 cmake 构建文件 CMakeLists txt 不在 CLion 的根目录中 我目前正在开发的项目中 cmake 构建文件不在 CLion 项目的根目录中 在 out Debug 目录中 我希望 CLion 打开该项目的根目
  • 奇怪的函数参数名称行为

    我问了一个关于cmake和传递变量的问题here https stackoverflow com questions 14375519 cmake how to write a nice function that passes varia
  • 为什么我会收到此 Android Studio 错误:“使用 -fPIC 重新编译”?

    我正在使用 NDK 18 并使用 x86 64 NDK 独立工具链单独编译静态库 我可以成功链接它 但是当我尝试以一种不平凡的方式访问该库时 我在构建时遇到了许多错误 例如 requires dynamic R X86 64 PC32 re

随机推荐

  • typedef的用法——c语言

    一 ypedef 1 1 xff1a typedef的用法 xff08 如上图所示 xff09 typedef 用法一句话总结 把定义的类型改名 举个例子 xff1a 我们熟悉的int类型 定义一个变量 xff0c 如int a xff1b
  • shell的测试语句

    一 shell的条件测试语句 在写shell脚本时 xff0c 经常遇到的问题就是判断字符串是否相等 xff0c 可能还要检查文件状态或进 行数字测试 xff0c 只有这些测试完成才能做下一步动作 1 1 shell脚本中的条件测试如下 x
  • 在miivii域控制器(基于Xavier)上复现Ultra-Fast-Lane-Detection源论文项目

    源项目链接 一 安装 参考源项目安装教程 xff0c 个别步骤有改动 xff1a 1 我没有安装Anaconda xff0c 所以跳过步骤2 2 步骤3中安装pythorch torchvision的血泪史 xff1a 网上看了很多下载py
  • shell的控制语句(3)

    shell脚本与我们所用的c语言控制语句 xff0c 是存在一些差异的 xff0c 因此 xff0c 我们需要重新认识并且学会它们 因此 xff0c 接下来 xff0c 我会介绍常用的流程控制语句 如 xff1a if for while
  • shell的函数

    一 shell函数 有些脚本段间互相重复 xff0c 如果能只写一次代码块而在任何地方都能引用那就提高了代码的可重用性 shell 允许将一组命令集或语句形成一个可用块 xff0c 这些块称为 shell 函数 二 shell函数的格式 2
  • Visual Studio 2015(C#)编写实现TCP调试助手(服务端+客户端一体)-新手

    近期在做项目的时候运用到了WIFI模块 xff0c 想着自己捣鼓捣鼓弄个上位机调试调试 初次接触 xff0c 长达3天的修修改改终于完成 xff0c 实现代码比较杂乱 xff0c 但是可以正常使用 不足之处 xff0c 还望指正 xff01
  • IDEA This is not a valid Java qualified name问题解决

    今天在创建类的时候突然出现这样的一个错误 This is not a valid Java qualified name 出现这个错误的主要原因是因为类名出现了空格 我是在类名前有一个空格
  • 双系统ubuntu18.04如何更新到22.04

    将双系统中的Ubuntu 18 04更新到22 04 xff0c 按照以下步骤操作 xff1a 1 打开终端并更新系统 xff0c 使用以下命令 xff1a span class token function sudo span span
  • 如何查看自己的ubuntu系统的镜像源,并且换源

    1 查看自己的Ubuntu系统当前使用的镜像源 xff1a 1 打开终端 xff1a 按下Ctrl 43 Alt 43 T xff0c 或者在菜单中搜索 终端 2 输入以下命令并按Enter键 xff1a span class token
  • QT项目2048游戏(C++)(附流程图、源码)

    在学校学完C语言后用easyX图形库写了一个2048游戏 xff0c 在大概学完C 43 43 后用QT改进了一下的2048游戏 游戏框图 流程图 开始界面 游戏中的界面 游戏结束界面 改进之处 1 添加了背景音乐 2 添加了结束游戏后重新
  • 用 Saleae Logic 16 示波器测量并分析 I2C、SPI、串口的信号

    文章目录 一 安装Saleae Logic 16软件二 Saleae Logic 16简单介绍1 Saleae Logic 16 逻辑分析仪2 Saleae Logic 16软件 三 三个实例1 I2C信号2 SPI信号3 串口信号 四 总
  • TCP通信—客户端与客户端的双向通信

    功能 xff1a 1 实现客户端与客户端之间的TCP双向通信 xff1b 2 服务器记录客户端实名连接 xff0c 并显示客户端数据记录 xff1b 3 客户端退出 xff0c 服务器和另一客户端显示相应提示 xff1b 服务器代码思路 x
  • xshell登录wsl

    配置ssh server 卸载 span class token function sudo span span class token function apt get span remove openssh server 安装 span
  • 获取form表单中的数据对象集合

    form表单中直接获取所有数据的对象集合 form表单中的数据获取方法 我们在开发中如果出现form表单 xff0c 那么肯定我们是要获取用户在表单中填写的数据 xff0c 当然如果一个个去那大每一个输入框中的数据 xff0c 也是可取的
  • JavaScript中的window.location的使用

    window location对象可以用于获取当前页面地址 xff08 url xff09 并把浏览器重定向到新页面 location对象的属性与对应的属性值 xff1a window location href 返回当前页面的href u
  • js原生实现本地图片转base64上传服务器(js,jq,html)

    这里写自定义目录标题 上传过程 xff1a htmljs 上传过程 xff1a 1 xff0c 使用input xff1a type 61 file读取本地图片 xff1b 2 xff0c 使用new FileReader 将 图片转化为b
  • AJAX异步请求原理与过程;

    AJAX异步请求原理和过程 1 AJAX创建异步对象XMLHttpRequest xff1a AJAX 的要点是 XMLHttpRequest 对象 不同的浏览器创建 XMLHttpRequest 对象的方法是有差异的 IE浏览器使用 Ac
  • vue生命周期图解(带注释)

    vue生命周期图解 xff08 带注释 xff09
  • c++ class和struct的区别是什么?

    c 43 43 class和struct的区别是什么 在c 43 43 中使用struct和class xff0c 定义类的唯一区别就是默认的访问权限 c 43 43 primer第五版 没错 c 43 43 中class和struct几乎
  • 让程序编译更优雅的几个CMake命令

    简介 本文通过一个工程示例介绍了几个让程序编译更优雅的CMake命令 文末有完整下载地址 该工程示例首先生成一个动态库 xff08 libversion dll xff1a 该库主要用于打印版本相关的信息 xff09 xff0c 然后在一个