博主未授权任何人或组织机构转载博主任何原创文章,感谢各位对原创的支持!
博主链接
本人就职于国际知名终端厂商,负责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:用于匹配一个正则表达式;
- LESS、 GREATER、 EQUAL、 LESS EQUAL 和 GREATER EQUAL:用于数值之间的比
- 常用逻辑运算符:
- 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