【CMake】15分钟带你入门CMake

news2024/12/24 3:29:58

博主未授权任何人或组织机构转载博主任何原创文章,感谢各位对原创的支持!
博主链接

本人就职于国际知名终端厂商,负责modem芯片研发。
在5G早期负责终端数据业务层、核心网相关的开发工作,目前牵头6G算力网络技术标准研究。


博客内容主要围绕:
       5G/6G协议讲解
       算力网络讲解(云计算,边缘计算,端计算)
       高级C语言讲解
       Rust语言讲解



文章目录

  • 一、CMake的工作原理
  • 二、CMake语法入门
    • 2.1 基本的CMakeLists.txt结构
    • 2.2 变量和列表
      • 2.2.1 变量
      • 2.2.2 列表
    • 2.3 条件判断和循环
      • 2.3.1 条件判断
      • 2.3.2 循环
    • 2.4 函数与宏
      • 2.4.1 函数
      • 2.4.2 宏
    • 2.5 执行外部程序
      • 2.5.1 在配置阶段执行命令
      • 2.5.2 在配置阶段执行命令
    • 2.6 查找外部库
    • 2.7 静态、动态和可执行文件
      • 2.7.1 静态库
      • 2.7.2 动态库
      • 2.7.3 可执行文件
  • 三、总结

在这里插入图片描述

一、CMake的工作原理

分隔符

在上图所示的配置过程中,CMake可以生成多种构建工具对应的构建文件,默认情况下会生成“Unix Makefiles”构建文件。我们可以使用-G命令来设置CMake在配置过程中生成对应的构建文件,下图所示是CMake(version 3.22.1)支持的构建工具:

在这里插入图片描述
完成配置和构建之后,会在指定的build文件夹中生成很多文件,其中值得注意的是一个名为CMakeCache.txt的文件,如下图所示:
在这里插入图片描述
这里缓存了整个项目在配置过程中检测到的所有信息,例如工具链配置、自定义的带有cache属性的宏、系统环境等,其部分内容如下所示:

在这里插入图片描述
CMakeCache.txt文件存在的意义是提升之后的构建速度。第一次构建项目时会经历上述“配置+构建”流程,如果在上次构建项目后没有修改CMakeLists.txt或者与CMake相关的其它文件(例如,CMakePresets.txt、CMakeCache.txt等),则会直接进入构建流程,缩短项目整体的构建时间。


二、CMake语法入门

2.1 基本的CMakeLists.txt结构

# 定义要求的最小CMake版本
cmake_minimum_required(VERSION 3.26)

# 定义项目名称和使用的语言
project(cmake_share C)

set(CMAKE_C_STANDARD 11)

# 定义一个可执行文件cmake_share,并声明其依赖的源文件
add_executable(cmake_share main.c)

2.2 变量和列表

2.2.1 变量

CMake中可以使用set()定义和unset()删除一个变量,并使用${<variable-name>}访问一个变量的值。注意,在CMake中是区分大小写的。

下面是一个定义和删除变量的示例:

# 定义变量CSRS
set(CSRS "congshanruoshui")

# 输出变量CSRS的值
message(STATUS "The content of CSRS is ${CSRS}")

# 删除变量CSRS
unset(CSRS)

# 输出变量CSRS的值,此时为空
message(STATUS "The content of CSRS is ${CSRS}")

输出如下:

在这里插入图片描述

2.2.2 列表

使用set() 命令可以定义一个列表,其中所有的列表元素可以通过双引号括起并使用分号隔离,也可以不使用双引号括起,直接使用空格隔离即可,例如下面的代码所示:

set(CSRS this is a good blogger)
message(STATUS "The content of CSRS is ${CSRS}")

set(CSRS "this;is;a;good;blogger")
message(STATUS "The content of CSRS is ${CSRS}")

输出内容如下:
在这里插入图片描述

如果要在列表中追加、查找、获取一个元素,需要使用list()命令。下面是一个示例:

# 使用关键词APPEND向列表尾部追加元素
list(APPEND CSRS "!")
message(STATUS "The content of CSRS is ${CSRS}")

list(FIND CSRS "blogger" BLOGGER_INDEX)

# 如果没有查找到,则BLOGGER_INDEX的值为-1
if(${BLOGGER_INDEX} LESS 0)
    message(STATUS "Not find \"blogger\" in CSRS")
else()
    message(STATUS "Find \"blogger\" in CSRS at index ${BLOGGER_INDEX}")
endif()

# 根据索引值获取列表中的对应元素
list(GET CSRS ${BLOGGER_INDEX} BLOGGER)
message(STATUS "The word in CSRS at index ${BLOGGER_INDEX} is ${BLOGGER}")

输出结果如下:
在这里插入图片描述

列表中元素的索引值是从0开始的。

2.3 条件判断和循环

2.3.1 条件判断

CMake条件判断的基本结构如下所示:

if(<condition>)
  <commands>
elseif(<condition>) # optional block, can be repeated
  <commands>
else()              # optional block
  <commands>
endif()

其中condition是由各种关键字组成的,包括:

  • 常用一元运算符:
    • COMMAND:检查提供的值是否为一个命令,如果是则为true;
    • EXISTS: 检查一个文件或者路径是否存在;
    • DEFINED: 检查提供的值是否被定义,如果定义则为 true;
    • IS_DRRECTORY: 检查提供的路径是否为一个日录,如果是则为 true;
    • IS_ABSOLUTE: 检查提供的路径是否为一个绝对路径,如果是则为 true;
  • 常用二元运算符:
    • LESS、 GREATER、 EQUAL、 LESS EQUAL 和 GREATER EQUAL:用于数值之间的比
      较;
    • STRLESS、 STREQUAL、 STRGREATER、 STRLESS EQUAL 和 STRGREATER EQUAL:
      用字符串之间的比较;
    • MATCHES:用于匹配一个正则表达式;
  • 常用逻辑运算符:
    • AND、 OR:逻辑与和或;
    • NOT:逻辑非

下面是一个示例:

set(TEST_IF ON)
if(DEFINED TEST_IF)
    message(STATUS "TEST_IF is ON")
endif()

set(MONTH 12)
if(${MONTH} EQUAL 12)
    message(STATUS "MONTH is 12")
endif()

set(CSRS "CongShanRuoShui")
if(${CSRS} STREQUAL "CongShanRuoShui")
    message(STATUS "CSRS is CongShanRuoShui")
endif()

if((${MONTH} EQUAL 12) AND (${CSRS} STREQUAL "CongShanRuoShui"))
    message(STATUS "MONTH is 12")
    message(STATUS "CSRS is CongShanRuoShui")
endif()

输出结果如下:
在这里插入图片描述

2.3.2 循环

循环结构有两种,分别while和foreach,它们的结构如下所示。

while(<condition>)
  <commands>
endwhile()

对于while循环来说,其condition与if中的condition是一样的,例如下面的代码示例:

set(test_while 5)
while(${test_while} GREATER 0)
    message(STATUS "test_while is ${test_while}")
    math(EXPR test_while "${test_while}-1")
endwhile()

输出结果如下:
在这里插入图片描述

foreach(<loop_var> <items>)
  <commands>
endforeach()

对于foreach循环来说,items可以是一组元素、一个列表或者一个数值范围,例如下面的代码所示:

set(test_while 5)
while(${test_while} GREATER 0)
    message(STATUS "test_while is ${test_while}")
    math(EXPR test_while "${test_while}-1")
endwhile()

set(test_foreach a b c d e)
foreach(item IN LISTS test_foreach)
    message(STATUS "item is ${item}")
endforeach()

foreach(item IN ITEMS a b c d e)
    message(STATUS "item is ${item}")
endforeach()

foreach(month RANGE 1 12)
    message(STATUS "month is ${month}")
endforeach()

输出结果如下:
在这里插入图片描述

2.4 函数与宏

2.4.1 函数

CMake的函数定义,是通过关键字function()endfunction()完成的,其结构如下所示:

function(<name> [<arg1> ...])
  <commands>
endfunction()

注意,CMake中的变量也是有生命周期的,函数内定义的变量只能在函数范围内访问(除非定义了PARENT_SCOPE属性)。下面是一个函数的例子:

function(test_function name year)
    message(STATUS "${name} is ${year} years old")
endfunction()

test_function(CSRS 18)

其运行结果如下:
在这里插入图片描述

2.4.2 宏

宏定义的结构如下所示:

macro(<name> [<arg1> ...])
  <commands>
endmacro()

宏的概念和函数类似,但是不同的是宏不会创建一个新的作用域,而且宏的参数必须使用花括号访问,下面是一个示例代码:

macro(test_macro name year)
    message(STATUS "${name} is ${year} years old")
endmacro()
test_macro(CSRS 18)

其运行结果如下:
在这里插入图片描述

2.5 执行外部程序

有些时候我们需要执行一些外部指令,例如编译外部项目、获取项目git信息等,可以通过下面的几个命令来实现。

2.5.1 在配置阶段执行命令

execute_process()命令会在项目的配置阶段执行,例如可以检查项目依赖的子项目是否已经下载、当前的git分支是否正确等。 execute_process()的常用结构如下所示:

execute_process(
	# 要执行的命令
	COMMAND <custom command>
	# 执行命令的目录
	WORKING_DIRECTORY <dir>
	# 命令结果输出
	OUTPUT_VARIABLE var
	# 剔除输出尾部的空格
	OUTPUT_STRIP_TRAILING_WHITESPACE
	# 发送错误时停止执行
	ERROR_QUIET
)

下面是一个例子:

execute_process(
        COMMAND ls -al
        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
        OUTPUT_VARIABLE result
        OUTPUT_STRIP_TRAILING_WHITESPACE
        ERROR_QUIET
)
message(STATUS "result is ${result}")

输出结果如下:
在这里插入图片描述

2.5.2 在配置阶段执行命令

add_custom_command()命令可以在配置阶段执行客制化的命令,配置阶段又细分为编译前(PRE_BUILD)、链接前(PRE_LINK)和编译后(POST_BUILD),其通用结构如下所示:

add_custom_command(
	TARGET <target>
	PRE_BUILD | PRE_LINK | POST_BUILD
	COMMAND command1 [ARGS] [args1...]
)

大部分情况都是在编译后执行客制化的命令,例如检查编译生成的文件是否正确等。下面是一个例子,将编译后的可执行文件改名:

add_executable(cmake_share main.c)


add_custom_command(
        TARGET cmake_share
        POST_BUILD
        COMMAND mv cmake_share cmake_share_test
)

结果如下所示:
在这里插入图片描述

2.6 查找外部库

有些时候我们需要使用外部库,这种情况尤其发生在交叉编译时,可以使用下面的命令查找一个库:

pkg_search_module(<prefix>
				[REQUIED] [QUIET]
				<moduleSpec> [<moduleSpec>...])

pkg_search_module()函数会查找满足<moduleSpec>的库文件,并将查找结果保存到以<prefix>开头的关键字中,例如下面的代码:

include(FindPkgConfig)
find_package(PkgConfig REQUIRED)

pkg_search_module(OPENSSL REQUIRED openssl)

if(OPENSSL_FOUND)
    message(STATUS "openssl is found")
endif()

输出结果如下:
在这里插入图片描述

ubuntu可以通过apt命令安装 libssl-dev

上面的代码首先include对应的库文件FindPkgConfig,然后检查pkg-config程序是否存在,因为pkg_search_module函数依赖pkg-config程序,所以使用前建议使用上面的命令检查本地是否安装了pkg-config程序。

上面代码表示在搜索路径中查找名字为"openssl"的库,如果不存在则终止配置过程,如果存在则将查找结果保存到以"OPENSSL_"开头的关键字中。下面介绍几个常用的关键字:

  • <xxx>_FOUND:如果当前的库找到了,则为1;
  • <xxx>_LIBRARIES:查找到的库的名称(没有-l)(小写L);
  • <xxx>_LINK_LIBRARIES:查找到的库的名称,带有绝对路径;
  • <xxx>_LIBRARY_DIRS:查找到的库所在的目录(没有-L);
  • <xxx>_LDFLAGS:查找到的库相关的依赖选项;
  • <xxx>_INCLUDE_DIRS:查找到的库对应的头文件所在的目录(没有I)(大写i);
  • <xxx>_CFLAGS:查找到的库对应的cflags;

对于交叉编译来说,可能需要将对应的库所在的路径添加到CMake变量CMAKE_PREFIX_PATH或者环境变量PKG_CONFIG_PATH中。

2.7 静态、动态和可执行文件

通常一个项目包含一些子模块,为这些子模块编写单独的CMakeLists.txt并将其编译成库(静态、动态)有助于项目的管理和维护。

我们在项目里添加一个utils目录,并将目录中的文件编译成一个utils库,目录结构如下:
在这里插入图片描述

2.7.1 静态库

下面我们编译一个示例,将utils编译成静态库:

# 加入utils目录,这样可以找到头文件
include_directories("utils")

# add_library()在默认情况下会将库编译成静态库
add_library(utils
        utils/test_library.c
)

编译的静态库utils如下:
在这里插入图片描述

2.7.2 动态库

下面的代码将上述utils库编译成动态库:

include_directories("utils")
add_library(utils SHARED
        utils/test_library.c
)

# 对于SHARED和MODULE来说,CMake会自动开启PIC选项
# set_property(TARGET utils PROPERTY POSITION_INDEPENDENT_CODE ON)

编译的动态库utils如下:
在这里插入图片描述

2.7.3 可执行文件

编译一个可执行程序的命令是add_executable(),其结构如下所示:

add_executable(<name>
				[source1]
				[source2...])

下面是一个例子:

add_executable(cmake_share main.c)

# 声明cmake_share依赖的库
target_link_libraries(cmake_share utils)

无论我们编译的是静态库、动态库还是可执行程序,有时我们都需依赖一些外部库,此时可以使用下面的命令target_link_libraries(),其结构如下所示:

target_link_libraries(<target>
					<item>... ...)

其中<target>是库或者可执行程序的名称,<item>是依赖库。

三、总结

上面介绍了CMake的工作原理,以及一些基本的语法操作,掌握上述语法之后,可以简单的阅读一些项目的CMakeLists.txt。下面给出了上述例子的项目地址,感兴趣的童鞋可以下载运行一下。

cmake-share



在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1174337.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[黑马程序员SpringBoot2]——运维实用篇

目录&#xff1a; 工程打包与运行打包插件Boot工程快速启动&#xff08;Linux版本&#xff09;临时属性配置文件4级分类自定义配置文件多环境开发(yaml版)多环境开发多文件版&#xff08;yaml版&#xff09;多环境开发多文件版&#xff08;properties版&#xff09;多环境分组…

基于AOSP源码Android-10.0.0_r41分支编译,framework开发,修改系统默认字体大小

文章目录 基于AOSP源码Android-10.0.0_r41分支编译&#xff0c;framework开发&#xff0c;修改系统默认字体大小 基于AOSP源码Android-10.0.0_r41分支编译&#xff0c;framework开发&#xff0c;修改系统默认字体大小 主要修改一个地方就行 代码源码路径 frameworks/base/co…

直流无刷电机(BLDC)六步换相驱动

直流无刷电机&#xff08;BLDC&#xff09;六步换相驱动 文章目录 直流无刷电机&#xff08;BLDC&#xff09;六步换相驱动1. 前言2. 六步换相原理3. 电角度与机械角度4. 动手实践4.1 霍尔输出表测量4.2 换向控制4.3 代码编写 5. 总结 1. 前言 直流无刷电机相对直流有刷电机具…

AOE性能调优问题案例

AOE&#xff08;Ascend Optimization Engine&#xff09;是一款自动性能调优工具&#xff0c;目的是为了充分利用有限的硬件资源&#xff0c;满足算子和整网的性能要求。 本期就分享几个关于AOE性能调优问题的典型案例&#xff0c;并给出原因分析及解决方法。 调优过程中进程…

Mysql之多表查询上篇

Mysql之多表查询上篇 多表查询什么是多表查询笛卡尔积(交叉连接)产生笛卡尔积的条件避免笛卡尔积的方法 多表查询的分类1.等值连接 VS 非等值连接等值连接非等值连接扩展1表的别名扩展2&#xff1a;连接多个表 2.自连接与非自连接扩展3&#xff1a;SQL语法标准 内连接SQL92语法…

【大模型应用开发教程】04_大模型开发整体流程 基于个人知识库的问答助手 项目流程架构解析

大模型开发整体流程 & 基于个人知识库的问答助手 项目流程架构解析 一、大模型开发整体流程1. 何为大模型开发定义核心点核心能力 2. 大模型开发的整体流程1. 设计2. 架构搭建3. Prompt Engineering4. 验证迭代5. 前后端搭建 二、项目流程简析步骤一&#xff1a;项目规划与…

TensorRT量化实战课YOLOv7量化:YOLOv7-PTQ量化(二)

目录 前言1. YOLOv7-PTQ量化流程2. 模型标定3. 敏感层分析 前言 手写 AI 推出的全新 TensorRT 模型量化实战课程&#xff0c;链接。记录下个人学习笔记&#xff0c;仅供自己参考。 该实战课程主要基于手写 AI 的 Latte 老师所出的 TensorRT下的模型量化&#xff0c;在其课程的基…

el-tree中展示项换行展示

文章目录 效果如下所示&#xff1a;没有换行展示的效果修改样式换行之后的展示效果 想要了解el-tree使用的详情往下看代码和数据如下所示Vue代码中可能使用到的数据如下Vue的代码如下&#xff1a;没有换行展示的效果换行之后的展示效果样式调试 效果如下所示&#xff1a; 没有…

仅以此文,纪念毕业一年后的日子

22年6月份从华农毕业了之后&#xff0c;拿到了好几份不错的offer&#xff0c;最后我进入了我学生时代十分憧憬的一家公司&#xff08;腾讯&#xff09;工作&#xff0c;加上实习的时间&#xff0c;已经在腾讯差不多工作了两年了。 从一开始实习的时候的懵懂学生气到现在的清醒…

Azure 机器学习 - 使用 ONNX 对来自 AutoML 的计算机视觉模型进行预测

目录 一、环境准备二、下载 ONNX 模型文件2.1 Azure 机器学习工作室2.2 Azure 机器学习 Python SDK2.3 生成模型进行批量评分多类图像分类 三、加载标签和 ONNX 模型文件四、获取 ONNX 模型的预期输入和输出详细信息ONNX 模型的预期输入和输出格式多类图像分类 多类图像分类输入…

【Liunx基础】之指令(一)

【Liunx基础】之指令&#xff08;一&#xff09; 1.ls指令2.pwd命令3.cd指令4.touch指令5.mkdir指令(重要)6.rmdir指令与rm指令&#xff08;重要&#xff09;7.man指令&#xff08;重要&#xff09;8.cp指令&#xff08;重要&#xff09; &#x1f4c3;博客主页&#xff1a; 小…

核电堆芯组件动态特性试验研究

u 核电试验概述 反应堆是核电事业的核心组成部分之一&#xff0c;堆内构件、堆芯燃料组件等部件在冷却剂流动冲击下&#xff0c;会诱发剧烈振动&#xff0c;导致堆芯内试验件流道不稳定。为了保障反应堆的安全运行&#xff0c;根据国家核安全法规规定&#xff0c;有必要对受冷…

单链表的应用(2)

环形链表的约瑟夫问题 编号为 1 到 n 的 n 个人围成一圈。从编号为 1 的人开始报数&#xff0c;报到 m 的人离开。 下一个人继续从 1 开始报数。 n-1 轮结束以后&#xff0c;只剩下一个人&#xff0c;问最后留下的这个人编号是多少&#xff1f; 利用链表实现 思路&#xff1…

基础Redis-结构与命令

结构与命令 1.基础-Redisa.Redis数据结构介绍b.Redis通用命令c.key的结构d.String类型e.Hash类型f.List类型g.Set类型h.SortedSet类型 1.基础-Redis a.Redis数据结构介绍 Redis是一个key-value的数据库&#xff0c;key一般是String类型&#xff0c;不过value的类型多种多样&a…

idea中配置spring boot单项目多端口启动

参照文章 https://zhuanlan.zhihu.com/p/610767685 项目配置如下 下面为 idea 2023&#xff0c;不同版本的设置有区别&#xff0c;但是没那么大&#xff0c;idea 2023默认使用新布局&#xff0c;切换为经典布局即可。 在项目根目录的.idea/workspace.xml文件里添加如下配置 &l…

Java用log4j写日志

日志可以方便追踪和调试问题&#xff0c;以前用log4net写日志&#xff0c;换Java了改用log4j写日志&#xff0c;用法和log4net差不多。 到apache包下载下载log4j的包&#xff0c;解压后把下图两个jar包引入工程 先到网站根下加一个log4j2.xml的配置文件来配置日志的格式和参…

测试开发面试宝典,涨价倒计时

大家好&#xff0c;我是洋子&#xff0c;相信在面试软件测试、测试开发岗位的小伙伴都深有体会&#xff0c;考察的知识点越来越多 不仅会考察到软件测试的理论&#xff0c;让你对某种功能进行测试用例的设计&#xff0c;更难一点会给出一个测试场景进行测试方案的设计&#xf…

C++二分算法:平衡子序列的最大和

涉及知识点 二分 动态规划 #题目 给你一个下标从 0 开始的整数数组 nums 。 nums 一个长度为 k 的 子序列 指的是选出 k 个 下标 i0 < i1 < … < ik-1 &#xff0c;如果这个子序列满足以下条件&#xff0c;我们说它是 平衡的 &#xff1a; 对于范围 [1, k - 1] 内的所…

openGauss学习笔记-115 openGauss 数据库管理-设置安全策略-设置密码安全策略

文章目录 openGauss学习笔记-115 openGauss 数据库管理-设置安全策略-设置密码安全策略115.1 操作步骤 openGauss学习笔记-115 openGauss 数据库管理-设置安全策略-设置密码安全策略 115.1 操作步骤 用户密码存储在系统表pg_authid中&#xff0c;为防止用户密码泄露&#xff…

2023-2024-1高级语言程序设计-一维数组

7-1 逆序输出数组元素的值 从键盘输入n个整数存入一维数组中&#xff0c;然后将数组元素的值逆序输出。 输入格式: 第一行输入整数个数n&#xff1b; 第二行输入n 个整数&#xff0c;数据之间以空格隔开。 输出格式: 逆序输出数组元素的值&#xff0c;每个数据之后跟一个空…