三、cmake语法-提高篇
目录
上一章节
一、前言
二、库文件的制作
1、静态库
2、动态库
3、指定库文件输出路径
三、库文件的使用
1、静态库的使用
1.1、指定库文件路径
2、动态库的使用
四、日志打印
1、消息等级:
五、变量操作
1、字符串的拼接
(1)set命令
(2)list 命令
2、字符列表中剔除字符串
3、List 命令介绍
(1)LENGTH 获取字符串长度
(2)GET 列表通过索引获取字符串
(3)JOIN指定连接符将列表中字符串连接起来
(4)FIND在列表中查找字符串,并返回索引
(5)INSERT在列表指定位置后插入若干元素
(6)PREPEND在列表最前面插入元素
(7)POP_BACK移除列表最后一个元素
(8)POP_FRONT移除列表第一个元素
(9)REMOVE_AT移除列表指定位置元素
(10)ROMOVE_DUPLICATES移除重复元素
(11)REVERSE列表翻转
(12)SORT列表排序
六、自定义宏
1、通过gcc, 命令编译指定宏
2、通过CMakeLists.txt中指定
七、总结
上一章节
https://blog.csdn.net/weixin_36323170/article/details/151336498?spm=1001.2014.3001.5501
https://blog.csdn.net/weixin_36323170/article/details/151336498?spm=1001.2014.3001.5501
一、前言
开启本文之前先做个知识点的梳理,这里主要介绍库文件的制作与使用、日志打印、变量操作、宏定义等

二、库文件的制作
前面介绍了主要是生成可执行文件,但是我们在实际工作中会遇到,给别人提供第三方库的场景,这里就需要将开发的源代码编译成库文件,这里介绍在cmake下如何制作库文件
1、静态库
制作静态库命令如下:
add_library(库名称 STATIC 源文件1 [源文件2] ...)
编译生成的库文件常见名称构成如下:
linux下:lib+库名称+.a
windows下:lib+库名称+.lib,这里必须是window自带的msvc原生工具链,cl.exe编译生成的;
如果使用mingw在windows系统下编译生成的依然是.a结尾的库,这里使用仍然要在相同工具链下引用才可以,否则会出现二进制不兼容。
例如:
cmake_minimum_required(VERSION 3.10)# 项目名称和支持的语言(显式指定C语言)
project(CMTEST LANGUAGES C)# 设置C标准版本(根据需要调整,例如C99、C11)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(LIBRARY_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib) # 指定库文件输出目录
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LST)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)add_library(calc STATIC ${SRC_LST})
生成静态库:

2、动态库
动态库制作命令
add_library(库名称 SHARED 源文件1 [源文件2] ...)
Linux下: lib+库名称+.so
Windows下:lib+库文件+.dll
例如:
cmake_minimum_required(VERSION 3.10)# 项目名称和支持的语言(显式指定C语言)
project(CMTEST LANGUAGES C)# 设置C标准版本(根据需要调整,例如C99、C11)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(LIBRARY_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib) # 指定库文件输出目录
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LST) # 将源文件指定给变量SRC_LST
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)add_library(calc SHARED ${SRC_LST}) # 制作动态库并指定名称为calc
生成库文件:

3、指定库文件输出路径
LIBRARY_OUTPUT_PATH:该关键字用于指定库文件的输出路径;
三、库文件的使用
库文件使用必须有两个部分,首先是头文件(获取到库文件中所包含的函数声明,以及类对象等信息), 再一个就是库文件。
例如下目录结构:

1、静态库的使用
链接静态库命令如下:
link_libraries(<static lib> [<static lib>...])
这里指定链接库,此处参数可做如下的要求:
参数1:指定链接库的名字,(1)、可以是全名例如 libcalc.a; (2)、也可以是掐头去尾,留下calc
参数2:缺省,可以在一条命令中指定多个静态库;
如果该库是自定义的,或者第三方提供的,这里不是系统库,有可能存在找不到的情况;这里有两个解决办法
(1)、使用环境变量,将该库的存放文件夹路径放到环境路径下;
(2)、自己在CMakeLists.txt中添加该库存放路径,最好是相对当前项目的相对路径,这样保证在与他人合作时。路径统一,推荐此种方法;
1.1、指定库文件路径
link_directories(<lib path>) # 可以一次性指定多个静态库/动态库的路径
例如下CMakeLists.txt
cmake_minimum_required(VERSION 3.10)# 项目名称和支持的语言(显式指定C语言)
project(CMTEST LANGUAGES C)# 设置C标准版本(根据需要调整,例如C99、C11)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.c) # 获取当前目录下的所有C源文件
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)link_directories(${CMAKE_CURRENT_SOURCE_DIR}/lib) # 设置静态库目录
link_libraries(calc)# 链接静态库add_executable(testapp ${SRC})
编译-》链接 生成可执行文件

静态库编译后会直接编译到可执行文件中,因此在使用可执行文件时,不依赖静态库;
2、动态库的使用
在正常软件开发过程中,需要多人协作,有时候会将各个功能模块分开,这里各自维护各自的动态库,再最终软件运行时,将这些动态库串联起来,这里就要提到动态库的使用。
target_link_libraries: 链接动态库/静态库,用于指定编译一个源文件/库文件/可执行文件时需要链接的动态库
target_link_libraries(<target> <PRIVATE|PUBLIC|INTERFACE> <item>... [<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
(1)、target: 指定要加载库文件的名字
这里可以是一个源文件/动态库/静态库/可执行文件。
(2)、PRIVATE|PUBLIC|INTERFACE:动态库访问权限,默认PUBLIC
若动态库之间没有相互依赖关联,则默认用PUBLIC
PUBLIC:在public后面的库会被Link到前面的target中,并且里面的符号也会被导出,提供给第三方使用,可以理解为当前目标 “使用” 了该库,并且把该库的接口等资源“暴露” 给了依赖自己的目标(即其他目标依赖当前目标时,相当于间接依赖了这个库) ;
PRIVATE:在private后面的库仅被link到前面的target中,并且终结掉,第三方不能感知你调了啥库,对于第三方使用当前目标库依赖时,屏蔽了目标库依赖的库;
INTERFACE:在interface后面引入的库不会被链接到前面的target中,只会导出符号。可以理解为当前目标本身不用这个库,但要求所有依赖自己的目标必须依赖这个库(相当于 “转发” 依赖);
(3)、动态库的链接具有传递性,如果动态库 A 链接了动态库B、C,动态库D链接了动态库A,此时动态库D相当于也链接了动态库B、C,并可以使用动态库B、C中定义的方法。
target_link_libraries(A B C)
target_link_libraries(D A)
# 这里D相当于链接了BC
(4)、编译可执行文件链接动态库时,链接要写在可执行文件后面,指定动态库时需要掐头去尾,去除开头的lib跟结尾的.dll/.so
cmake_minimum_required(VERSION 3.10)# 项目名称和支持的语言(显式指定C语言)
project(CMTEST LANGUAGES C)# 设置C标准版本(根据需要调整,例如C99、C11)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.c) # 获取当前目录下的所有C源文件
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)link_directories(${CMAKE_CURRENT_SOURCE_DIR}/bin) # 设置动态库目录add_executable(testapp ${SRC}) target_link_libraries(testapp PUBLIC calc) # 链接动态库
编译生成的可执行文件,运行时需要将动态库放置到同级路径下,否则会报如下错误:

(5)、动态库跟静态库差异,动态库不会编译到目标文件中,但是静态库会直接编译进去,因此最终生成目标文件,静态库编译方式生成的文件较大,动态库链接方式生成的较小;

四、日志打印
命令:message
message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "message to display" ...)
1、消息等级:
(无) :重要消息
STATUS :非重要消息
WARNING:CMake 警告, 会继续执行
AUTHOR_WARNING:CMake 警告 (dev), 会继续执行
SEND_ERROR:CMake 错误, 继续执行,但是会跳过生成的步骤
FATAL_ERROR:CMake 错误, 终止所有处理过程
# 输出一般日志信息
message(STATUS "source path: ${PROJECT_SOURCE_DIR}")
# 输出警告信息
message(WARNING "source path: ${PROJECT_SOURCE_DIR}")
# 输出错误信息
message(FATAL_ERROR "source path: ${PROJECT_SOURCE_DIR}")
五、变量操作
1、字符串的拼接
(1)set命令
set(变量名 ${参数1} ${参数2} ...) # 将后面的参数1/2拼接成一个字符串,赋值给变量
这里如果原来变量有值会被覆盖, 可以是常量字符串,也可以是变量字符串
set(temp hello world)
set(temp1 ${temp} gg)
message(${temp})
message(${temp1})
(2)list 命令
list(APPEND <list> [<element> ... ])
指定字符串list, 采用追加发方式,将后续成员字符追加到list后面;
list在管理字符串时,采用链表的方式存储,字符串之间用‘,’分隔,但是在message命令打印时,整体是一个字符串
2、字符列表中剔除字符串
使用List 命令
list(REMOVE_ITEM <list> <value> [<value> ...])
在字符串list,中删除后续的value值的字符串,可以是一个也可以是多个;
3、List 命令介绍
(1)LENGTH 获取字符串长度
list(LENGTH <list> <output variable>) # list 当前操作列表, <output variable>创建新的变量,用于存储长度
(2)GET 列表通过索引获取字符串
list(GET <list> <element index> [<element index> ...] <output variable>)
<list>:当前操作列表
<element index>:列表元素的索引, 索引0开始编号,0表示第一元素;负数表示从最后一个元素开始,-1表示最后一个元素,以此类推;索引不管正负不能超过实际长度,否则运行报错;
<output variable>:新创建的变量,存储指定索引元素的返回结果,也是一个列表
(3)JOIN指定连接符将列表中字符串连接起来
list (JOIN <list> <glue> <output variable>)
<list>:当前操作列表
<glue>:指定连接符
<output variable>:创建新变量,存储返回的字符串
(4)FIND在列表中查找字符串,并返回索引
list(FIND <list> <value> <output variable>)
<value>:列表中搜索的字符串
<output variable>:新创建的变量,若搜索到字符串,则返回该字符串的索引,否则返回-1
(5)INSERT在列表指定位置后插入若干元素
list(INSERT <list> <element_index> <element> [<element> ...])
(6)PREPEND在列表最前面插入元素
list (PREPEND <list> [<element> ...])
(7)POP_BACK移除列表最后一个元素
list (POP_BACK <list> [<out-var>...])
(8)POP_FRONT移除列表第一个元素
list (POP_FRONT <list> [<out-var>...])
(9)REMOVE_AT移除列表指定位置元素
list (REMOVE_AT <list> <index> [<index> ...])
(10)ROMOVE_DUPLICATES移除重复元素
list (REMOVE_DUPLICATES <list>)
(11)REVERSE列表翻转
list(REVERSE <list>)
(12)SORT列表排序
list (SORT <list> [COMPARE <compare>] [CASE <case>] [ORDER <order>])
COMPARE:指定排序方法。有如下几种值可选:
STRING:按照字母顺序进行排序,为默认的排序方法
FILE_BASENAME:如果是一系列路径名,会使用basename进行排序
NATURAL:使用自然数顺序排序
CASE:指明是否大小写敏感。有如下几种值可选:
SENSITIVE: 按照大小写敏感的方式进行排序,为默认值
INSENSITIVE:按照大小写不敏感方式进行排序
ORDER:指明排序的顺序。有如下几种值可选:
ASCENDING:按照升序排列,为默认值
DESCENDING:按照降序排列
六、自定义宏
我们在代码运行时添加一些宏,从而控制代码内部的执行逻辑,可以灵活控制编译过程中的执行逻辑,是项目管理和功能开关的常用手段。
例如:
#include <stdio.h>
#define NUMBER 3int main()
{int a = 10;
#ifdef DEBUG // 这里对该宏进行判断,如果被定义了,这执行这部分代码,没定义,相当于此处为注释掉的代码printf("这里是编译调试...\n");
#endiffor(int i=0; i<NUMBER; ++i){printf("hello, word!!!\n");}return 0;
}
针对这个宏,可以采用如下两个方式定义:
1、通过gcc, 命令编译指定宏
gcc hello.c -DDEBUG -o app
2、通过CMakeLists.txt中指定
add_definitions
add_definitions(-D宏名称)
七、总结
至此,关于 CMake 语法提高篇的核心内容已梳理完毕。从库文件的构建到实际应用,再到变量、宏定义等高级特性的灵活运用,我们能清晰感受到 CMake 作为跨平台构建工具的强大与便捷 —— 它不仅能通过统一的语法解决不同系统下库文件格式差异的问题,还能借助路径管理、条件判断等功能,让复杂项目的构建流程更具逻辑性和可维护性。
