【CMake】15分钟带你入门CMake

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

本人就职于国际知名终端厂商,负责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】15分钟带你入门CMake_第1张图片

一、CMake的工作原理

【CMake】15分钟带你入门CMake_第2张图片

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

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

【CMake】15分钟带你入门CMake_第5张图片
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()删除一个变量,并使用${}访问一个变量的值。注意,在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}")

输出如下:

【CMake】15分钟带你入门CMake_第6张图片

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}")

输出内容如下:
【CMake】15分钟带你入门CMake_第7张图片

如果要在列表中追加、查找、获取一个元素,需要使用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}")

输出结果如下:
【CMake】15分钟带你入门CMake_第8张图片

列表中元素的索引值是从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()

输出结果如下:
【CMake】15分钟带你入门CMake_第9张图片

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()

输出结果如下:
【CMake】15分钟带你入门CMake_第10张图片

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()

输出结果如下:
【CMake】15分钟带你入门CMake_第11张图片

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}")

输出结果如下:
【CMake】15分钟带你入门CMake_第12张图片

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
)

结果如下所示:
【CMake】15分钟带你入门CMake_第13张图片

2.6 查找外部库

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

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

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

include(FindPkgConfig)
find_package(PkgConfig REQUIRED)

pkg_search_module(OPENSSL REQUIRED openssl)

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

输出结果如下:
【CMake】15分钟带你入门CMake_第14张图片

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

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

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

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

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

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

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

我们在项目里添加一个utils目录,并将目录中的文件编译成一个utils库,目录结构如下:
【CMake】15分钟带你入门CMake_第15张图片

2.7.1 静态库

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

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

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

编译的静态库utils如下:
【CMake】15分钟带你入门CMake_第16张图片

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如下:
【CMake】15分钟带你入门CMake_第17张图片

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>... ...)

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

三、总结

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

cmake-share



在这里插入图片描述

你可能感兴趣的:(cmake)