- 2025-02-28
-
回复了主题帖:
[树莓派Pico 2 RP2350开发板] pwm的学习
树莓派RP2350和老版本的树莓派PR2040有什么大的区别呢?
-
回复了主题帖:
【树莓派Pico 2 RP2350开发板】 测评 【一】 开箱 + 编译uf2 固件 + blinkLed
详细的教程!什么时候树莓派官方这种板子的接口可以换成Tpye-C
- 2025-02-27
-
回复了主题帖:
自制 AirTag,支持安卓/鸿蒙/PC/Home Assistant,无需拥有 iPhone
设备附件使用的苹果设备多,应该信号就好吧,到荒无人烟的地方估计就找不着了
-
回复了主题帖:
《CMake构建实战》第十章-策略和向后兼容
Jacktang 发表于 2025-2-27 07:29
渐进式重构 CMake 程序,在用的时候会提示警告,让使用者及时偿还技术债务来更好过度到新版本。
怎么理 ...
"及时偿还技术债务"这个概念源自于软件开发领域,是当项目随着时间推移积累了技术上的“债务”。这些“债务”可以是代码质量不高、设计缺陷、未完成的特性或者使用了过时的技术等。如果不加以处理,这些问题可能会导致后续开发效率降低、维护成本增加、出现更多bug等
- 2025-02-26
-
回复了主题帖:
《CMake构建实战》- CMake在HarmomyOS中的实际使用
蛋黄派派主 发表于 2025-2-25 21:50
感谢分享
-
回复了主题帖:
《CMake构建实战》- CMake在HarmomyOS中的实际使用
craigtao 发表于 2025-2-25 10:11
不错,CMake现在是主流,不管是纯应用开发还是嵌入式系统开发,cmake都占有一席之地,期待楼主可以多分享一 ...
是的对于嵌入式开发,一般厂家或者IDE那边都会提供对应的模板,对于开发使用有很大的便利
-
回复了主题帖:
【NUCLEO-WB09KE测评】五、蓝牙数据收发实现
板子可以接收到APP发送的数据,但为啥发送会失败呢?
Fail : aci_gatt_srv_notify TESTRESPONSE command, error code: 0x C
- 2025-02-25
-
发表了主题帖:
《CMake构建实战》- CMake在HarmomyOS中的实际使用
本帖最后由 eew_Eu6WaC 于 2025-2-25 09:17 编辑
《CMake构建实战》- CMake在HarmomyOS中的实际使用
本章只注重CMake在HarmonyOS中NDK的应用,NDK其实就是 Napi 的跨语言的调用,就先看看里面是如何使用CMake构建这一部分,有兴趣可以深入讲讲鸿蒙里面跨语言的用法和机制
下面是DevEco Studio中创建的Native C++中CMake相关的代码
可以看到在最顶层有一个CMakeLists.txt文件,这是顶层文件,下面分析一下代码
# the minimum version of CMake.
cmake_minimum_required(VERSION 3.5.0)
project(NDKDemo)
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
if(DEFINED PACKAGE_FIND_FILE)
include(${PACKAGE_FIND_FILE})
endif()
include_directories(${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/include)
add_library(entry SHARED napi_init.cpp)
add_subdirectory(demo)
target_link_libraries(entry PUBLIC libace_napi.z.so calc)
cmake_minimum_required 设置了最低版本的要求为3.5.0
project 定义了项目名称为NDKDemo
设置了一个变量:NATIVERENDER_ROOT_PATH,为当前源目录的路径
条件判断,如果定义了PACKAGE_FIND_FILE,就包含这个路径
include_directories 添加到编译器的头文件所搜索路径中去
add_subdirectory 创建了一个共享库entry,是由源码napi_init.cpp编译而成
target_link_libraries 将库libace_napi.z.so和calc链接到目标entry
demo这个文件是自己创建的,在其中也添加了CMakeLists.txt,编译器也会找到这里进行处理,原理非常简单,要将添加的4个文件也一起打包成库以便使用
include_directories(${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/include)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/lib)
add_library(calc SHARED ${SRC_LIST})
include_directories 将当前目录和当前目录下的include包含进去,这样就将head.h添加到了编译器搜索路径中,在head.h中申明了src中所使用到的所有函数
file 可以生成一个源文件列表,在当前目录下的scr目录中下
link_directories 链接可执行文件或库时,链接器会在这个目录下查找所需的库文件
add_library 创建了一个名为calc的共享库,也就是在顶层文件中链接的
编译过后,就可以得到需要的共享库了
-
回复了主题帖:
这个新功能蛮不错
哈哈哈哈哈哈这个英文界面切换功能强的,专业英语可不好翻译的
- 2025-02-24
-
发表了主题帖:
《CMake构建实战》第十章-策略和向后兼容
《CMake构建实战》第十章-策略和向后兼容
CMake非常重视向后兼容性,其中策略机制是解决该问题的关键。它既能确保旧代码在新版本 CMake 中配置生成,又能针对弃用特性给出警告,还会适时清理旧特性,推动自身发展。所以本文会介绍一下相关概念、命令及程序重构方法。
CMake策略(以 CMP0115 为例)
CMake 策略名称以 CMP 加四位整数命名,如 CMP0115。每个策略对应新旧两种行为,具体可以查阅官方文档进行了解详情。在官网中会依次列出CMake从新到旧的各个版本进入的策略。比如CMP0115的详情页中可以看到,在CMake 3.19及以前的版本中允许在add_executable等命令中省略源文件扩展名,而新版本则要求必须显式指定。
那么CMake文件是如何发现版本升级了呢?那就要和cmake_minimum_required命令有关了,见下小节。
指定CMake最低版本要求:cmake_minimum_required
这个命令是用于指定运行当前 CMake 程序所需的最低版本,若当前版本低于要求则报错!
这个命令不仅适用于目录程序,在CMake脚本程序、模块程序中都可以调用该命令来设置CMake的最低版本要求。
使用非常的简单:cmake_minimum_required(VERSION <最低版本>)
可以看到上面的程序和运行结果,当我把最小版本的设置改成大于当前安装的版本时,运行就会报错终止。
管理策略行为:cmake_policy
cmake_policy命令中有多个子命令,可以分别用于获取和显示指定策略的行为:
按策略名称设置策略行为:cmake_policy(SET <策略名称> NEW|OLD) - 可手动设置指定策略的行为为 NEW 或 OLD,方便在代码重构后或不想看到警告时进行调整。
获取策略行为:cmake_policy(GET <策略名称> <结果变量>) - 用于获取指定策略的行为并存储到结果变量中,若策略未设置,变量为空值,否则为 NEW 或 OLD。
按 CMake 版本设置策略行为:cmake_policy(VERSION <最低版本>[...<策略兼容的最高版本>]) - 依据指定的版本范围设置相关策略行为,cmake_minimum_required命令也支持类似设置。
管理 CMake 策略栈:子目录和模块程序会创建新的策略作用域并加入策略栈,cmake_policy操作的是栈顶策略。cmake_policy(PUSH) 和 cmake_policy(POP) 分别用于策略行为的入栈和出栈,便于程序重构升级时管理策略。sudo apt-get install openssh-client
渐进式重构 CMake 程序
策略可以很好的让CMake兼容古老的代码,同时在使用的时候会提示警告,让使用者及时偿还技术债务来更好过度到新版本。
局部代码重构并启用新行为:对部分代码重构后,借助策略栈实现细粒度的策略行为设置与恢复,若某个子目录重构完成,可直接对该目录采用指定策略的 NEW 行为。
禁用警告信息:将策略行为设为 OLD 可禁用警告,局部设置可通过策略栈完成,适用于暂时不想重构但不想受警告干扰的情况。
同时兼容旧版 CMake:利用if(POLICY)判断策略是否存在,根据 CMake 版本进行不同的项目配置,以兼容新旧版本,重构后可只保留新行为。
为全部策略采用新行为:重构完成后,调整cmake_minimum_required命令的<策略兼容的最高版本>参数,使程序在不同版本下按相应设置运行,同时清理冗余代码。
完全切换到新版 CMake:修改cmake_minimum_required命令的<最低版本>参数,强制用户使用新版 CMake,删除if(POLICY)条件分支,优化代码
好的,CMake相关的基础知识到这里结束,下面会去看下鸿蒙在添加编译NAPI应用的时候如何使用CMake的
-
回复了主题帖:
《CMake构建实战》第八章-生成器表达式
Jacktang 发表于 2025-2-22 15:04
虽然是两类表达式,布尔型生成器表达式 和 字符串生成器表达式,细分也不少
是的呢要掌握的东西确实不少,先大致了解用的时候查漏补缺就快啦
- 2025-02-22
-
回复了主题帖:
【Raspberry Pi 5测评】树莓派5学习笔记08(部署YOLOv5进行目标检测)
树莓派还是挺好用的,YOLOv5模型的识别的准确度咋样呢?
-
回复了主题帖:
【正点原子i.MX93开发板】测评 + 二、环境搭建 + 开发板连接上位机
正点原子的这块板子对应的教程和资料丰富吗?
-
发表了主题帖:
《CMake构建实战》第九章-模块
《CMake构建实战》第九章-模块
这一章围绕CMake模块展开,介绍了其功能模块、查找模块的使用方法、自定义查找模块的编写步骤。并且有着大量案例,对每个用法展示运行效果。
引用功能模块
CMake 模块程序是代码复用单元,像类库一样被引用。其预置模块丰富,位于安装目录的share/…/Modules子目录,可通过include命令引用。
常用的预置功能模块
1. 调试模块:CMakePrintHelpers 的 cmake_print_properties 和 cmake_print_variables 命令分别用于输出属性和变量值。
cmake_minimum_required(VERSION 3.20)
project(print-system-info)
include(CMakePrintSystemInformation)
cmake_minimum_required(VERSION 3.20)
project(print-helpers VERSION 1.0)
include(CMakePrintHelpers)
cmake_print_properties(DIRECTORIES .
PROPERTIES
BINARY_DIR
SOURCE_DIR
)
2. 环境检查模块:有多个检查功能的模块,CheckSourceCompiles用于检查源程序能否编译、CheckSymbolExists用于检查符号是否存在,CheckStructHasMember
用于检查结构体/类是否存在指定的成员变量
cmake_minimum_required(VERSION 3.20)
project(check-source-compiles)
include(CheckSourceCompiles)
check_source_compiles(C "int main() { return 0; }" res)
message("${res}") # 输出:1
set(CMAKE_REQUIRED_QUIET True)
check_source_compiles(C "invalid code" res2)
message("${res2}") # 输出空值
cmake_minimum_required(VERSION 3.20)
project(check-symbol-exists)
include(CheckSymbolExists)
check_symbol_exists(printf "stdio.h" res)
message("${res}") # 输出:1
cmake_minimum_required(VERSION 3.20)
project(check-struct-member)
include(CheckStructHasMember)
check_struct_has_member("std::pair<int,int>" "first" "utility" res
LANGUAGE CXX)
3. 生成导出头文件模块:GenerateExportHeader模块用于生成导出头文件,定义多种宏,用于解决库开发中接口头文件编写的复杂问题
痛点:
各个编译器对导出接口所需的属性不同,隐藏符号的编译选项也不同;另外,有时需要更灵活地配置接⼝,使其能够同时⽤于静态库和动态库;⼜或者需要通过⼀些宏来标记某些过时的接⼝为弃⽤状态…… 通常会编写⼀个头⽂件,⽤宏来判断不同的编译器,再定义⼀些宏来对应不同的属性设置等,以此将不同编译器的差异隐藏起来。每次开发⼀个库时,恐怕都会这样“重复造轮⼦”。
解决:
CMake提供了GenerateExportHeader模块。
//CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(export-api)
include(GenerateExportHeader)
add_library(print SHARED print.c)
generate_export_header(print
# DEFINE_NO_DEPRECATED # 取消注释以定义忽略弃用接口宏
) # 导出头文件会被生成到二进制目录中
# 将当前二进制目录加到头文件搜索目录中
target_include_directories(print PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
# 定义print_EXPORTS宏,表明正在构建该库(若不定义,则表示使用该库)
target_compile_definitions(print PRIVATE print_EXPORTS)
# 设置目标属性,默认隐藏符号和内联函数
set_target_properties(print PROPERTIES
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN 1
)
# 也可以通过下面两个变量,设置上述两个目标属性的默认值
# set(CMAKE_CXX_VISIBILITY_PRESET hidden)
# set(CMAKE_VISIBILITY_INLINES_HIDDEN 1)
# 主程序
add_executable(main main.c)
target_link_libraries(main PRIVATE print)
if(MSVC)
# 为MSVC编译器启用级别3的警告
target_compile_options(main PRIVATE /W3)
endif()
# 静态库
add_library(print_static STATIC print.c)
target_include_directories(print_static PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
target_compile_definitions(print_static PUBLIC PRINT_STATIC_DEFINE)
//main.c
#include "print.h"
int main() {
print();
old_print();
return 0;
}
//print.c
#include "print.h"
#include <stdio.h>
void print() { printf("Hello\n"); }
void _internal() {}
#ifndef PRINT_NO_DEPRECATED
void old_print() { printf("Hi\n"); }
#endif // PRINT_NO_DEPRECATED
//print.h
#ifndef PRINT_H
#define PRINT_H
#include "print_export.h"
PRINT_EXPORT void print();
PRINT_NO_EXPORT void _internal();
#ifndef PRINT_NO_DEPRECATED
PRINT_DEPRECATED_EXPORT void old_print();
#endif // PRINT_NO_DEPRECATED
#endif // PRINT_H
查找模块
查找模块(find module)是⼀系列用于搜索第三⽅依赖软件包(包括库或可执⾏⽂件)的模块。对查找模块的引用⼀般不使用include命令,⽽是使用find_package命令。
查找软件包命令(模块模式)
find_package命令的模块模式用于搜索第三方依赖软件包,调用 “Find < 软件包名>.cmake” 模块,可设置多种参数,执行后会定义相关变量。
格式:
find_package(<软件包名> [<版本号>] [EXACT] [QUIET] [MODULE]
[REQUIRED] [[COMPONENTS] [<⼦组件>...]]
[OPTIONAL_COMPONENTS <可选⼦组件>...]
[NO_POLICY_SCOPE])
用点:在开发应用的时候,经过会碰到 undefined reference to `xxxxx'。以`pthread_create'为例,就是:undefined reference to `pthread_create。可以在编译的时候添加一个编译选项来链接这个库。
难点:如何在CMake目录程序中设置链接呢?
解决:1. 可以通过判断当前的操作系统,来链接上对应的编译选项。2、可以使用CMake查找线程的模块:FindThreads。(可以针对不同的平台找到对应的线程酷)
//CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(find-threads)
# 调用FindThreads模块,它会创建一个导入目标Threads::Threads
find_package(Threads)
add_executable(main main.cpp)
# 如果不链接Threads::Threads,在Linux环境中构建会出错:
# undefined reference to `pthread_create'
target_link_libraries(main PRIVATE Threads::Threads)
#include <cstdio>
#include <thread>
void worker(int i) { printf("worker%d\n", i); }
int main() {
std::thread th(worker, 0);
th.join();
return 0;
}
- 2025-02-21
-
回复了主题帖:
颁奖:验证并选择心仪MOSFET,探寻选型奥秘!注册、体验双重好礼等你拿~
确认个人信息无误
-
发表了主题帖:
《CMake构建实战》第八章-生成器表达式
本帖最后由 eew_Eu6WaC 于 2025-2-21 17:33 编辑
《CMake构建实战》第八章-生成器表达式
本章主要介绍CMake中的生成器表达式。CMake在生成阶段,能够根据具体选用的构建系统生成器生成特定的配置,常常用于获取与构建系统相关的信息。
这是CMake比较重要的特性,可以减少构建系统差异性带来的复杂度,让CMake程序更简洁易读
生成器表达式大体分为两类:布尔型生成器表达式 和 字符串生成器表达式
在介绍生成器表达式之前,先看看哪些命令支持它
创建构建目标的命令:add_executable和add_library命令的<源文件>参数可使用生成器表达式,实现根据不同构建模式选择不同源文件
属性相关命令:多数设置目录和目标属性的命令支持生成器表达式,可针对不同构建模式设置属性值
自定义构建规则和目标:add_custom_command和add_custom_target命令部分参数支持生成器表达式,还可用于调试生成器表达式
字符串生成器表达式
顾名思义,字符串生成器表达式在生成阶段会被解析为一段字符串,可以嵌套使用在其他表达式的参数中。
1. 字符转义:对构成生成器表达式语法结构的字符进行转义,如:">" "," ";"
$<ANGLE-R> # 转义为">"
$<COMMA> # 转义为","
$<SEMICOLON> # 转义为";"
2. 条件表达式:根据条件值解析为不同字符串,有完整和简化两种形式。格式:$<IF:{条件},{字符串1},{字符串2}>
cmake_minimum_required(VERSION 3.20)
project(condition)
add_custom_target(debug-gen-exp ALL
COMMAND ${CMAKE_COMMAND} -E echo "Is Debug: <$<IF:$<CONFIG:Debug>,Yes,No>$<ANGLE-R>>"
VERBATIM
)
3. 字符串变换
有分隔符连接、大小写转换、移除重复元素、列表筛选等多种字符串操作表达式
分隔符连接:JOIN:$<JOIN:{列表字符串},{分隔符}>
⼤⼩写转换:LOWER_CASE和UPPER_CASE:$<LOWER_CASE:{字符串}> # 转⼩写 $<UPPER_CASE:{字符串}> # 转⼤写
移除重复元素:REMOVE_DUPLICATES: $<REMOVE_DUPLICATES:{列表字符串}>
列表筛选:FILTER:$<FILTER:{列表字符串},INCLUDE|EXCLUDE,{正则表达式}
⽣成C标识符:MAKE_C_IDENTIFIER:$<MAKE_C_IDENTIFIER:{字符串}>
转换为Shell路径:SHELL_PATH:$<SHELL_PATH:{绝对路径列表字符串}>
4. ⽬标相关表达式
用于查询构建目标的名称、二进制文件路径、链接文件路径等多种属性信息
⽬标名称:TARGET_NAME_IF_EXISTS:$<TARGET_NAME_IF_EXISTS:{⽬标}>
⽬标的⼆进制⽂件:TARGET_FILE:$<TARGET_FILE:{⽬标}>
⽬标的链接⽂件:TARGET_LINKER_FILE:$<TARGET_LINKER_FILE:{⽬标}>
⽬标的带SONAME的⽂件:TARGET_SONAME_FILE:$<TARGET_SONAME_FILE:{⽬标}>
⽬标的PDB⽂件:TARGET_PDB_FILE:$<TARGET_PDB_FILE:{⽬标}>
⽬标的BUNDLE⽬录:TARGET_BUNDLE_DIR:$<TARGET_BUNDLE_DIR:{⽬标}>
⽬标属性:TARGET_PROPERTY:$<TARGET_PROPERTY:{⽬标},{⽬标属性}>
⽬标的⽬标⽂件:TARGET_OBJECTS:$<TARGET_OBJECTS:{⽬标}
5. 解析生成器表达式
这是一个比较特殊的,其值是将参数作为生成器表达式解析后的结果
解析⼀般表达式:GENEX_EVAL:$<GENEX_EVAL:{⽣成器表达式}>
解析⽬标相关表达式:TARGET_GENEX_EVAL:$<TARGET_GENEX_EVAL:{构建⽬标},{⽣成器表达式}>
-
发表了主题帖:
《CMake构建实战》第七章-构建目标和属性(下)
本帖最后由 eew_Eu6WaC 于 2025-2-21 15:44 编辑
《CMake构建实战》第七章-构建目标和属性(下)
属性相关的命令
在CMake中为了简化常用属性的操作,其提供了专门的配置命令,这些属性可以针对宏定义、编译选项等各方面进行配置,下面罗列常用的命令:
设置目标链接库:target_link_libraries:用于将库目标链接到当前目标,通过PRIVATE、INTERFACE、PUBLIC参数决定链接是作为构建要求还是使用要求。
PUBLIC、INTERFACE、PRIVATE与传递性:这三个参数决定了库目标使用要求的传递方式,影响构建目标的构建和使用要求。
设置宏定义:add_compile_definitions:对当前目录及其子目录构建目标生效,用于设置编译源文件的宏定义,不支持带参数的宏,CMake会自动转义特殊字符。
设置目标宏定义:target_compile_definitions:用于设置指定构建目标源文件的宏定义,会影响COMPILE_DEFINITIONS和INTERFACE_COMPILE_DEFINITIONS属性。
设置编译参数:add_compile_options:对当前目录及其子目录构建目标生效,用于设置编译源文件的参数,会影响目录和目标的COMPILE_OPTIONS属性。
设置目标编译参数:target_compile_options:用于设置指定构建目标源文件的编译参数,会影响COMPILE_OPTIONS和INTERFACE_COMPILE_OPTIONS属性。
设置目标编译特性:target_compile_features:用于设置指定构建目标源文件所需的编译特性,会影响COMPILE_FEATURES和INTERFACE_COMPILE_FEATURES属性,编译特性需在指定变量中存在。
设置头文件目录:include_directories:对当前目录及其子目录构建目标生效,用于设置头文件搜索目录,可控制目录添加位置,还可指定系统头文件目录。
设置目标头文件目录:target_include_directories:用于将目录加入指定构建目标的头文件搜索目录,会影响INCLUDE_DIRECTORIES和INTERFACE_INCLUDE_DIRECTORIES属性,还涉及SYSTEM参数的作用。
设置链接库:link_libraries:对当前目录及其子目录构建目标生效,用于链接库文件或库目标,但不推荐使用,建议用target_link_libraries命令。
设置链接目录:link_directories:对当前目录及其子目录构建目标生效,用于设置链接库搜索目录,可控制目录添加位置。
设置目标链接目录:target_link_directories:用于设置指定构建目标的链接库搜索目录,会影响LINK_DIRECTORIES和INTERFACE_LINK_DIRECTORIES属性,可控制目录添加位置。
设置链接参数:add_link_options:对当前目录及其子目录构建目标生效,用于设置链接构建目标的参数,会影响目录和目标的LINK_OPTIONS属性。
设置目标链接参数:target_link_options:用于设置指定构建目标的链接参数,会影响LINK_OPTIONS和INTERFACE_LINK_OPTIONS属性,可控制参数添加位置。
设置目标源文件:target_sources:用于设置指定构建目标所需的源文件,可在创建目标后设置源文件,会影响SOURCES和INTERFACE_SOURCES属性。
无须递归传递的例程:用CMake重新组织相关例程,展示了在库依赖但不暴露接口时,使用PRIVATE参数设置链接库,使库的使用要求不递归传递的方法。
存在间接引用的例程:用CMake重新组织存在间接引用的例程,展示了在库暴露接口时,使用PUBLIC参数设置链接库,使库的使用要求递归传递的方法。
自定义构建规则
上面所展示CMake定义的各类构建目标,它们的生成过程和属性过程都是由CMake掌握的,现在介绍自定义的一些规则:add_custom_command
只需要定义以下这样的构建目标,然后调用add_custom_command即可
custom_rule: <依赖⽬标>
<调⽤命令⾏⼯具>...
对构建过程的控制中,add_custom_command分别有生成文件和响应构建事件这两种形式。
1. 生成文件:通过指定输出文件、命令、依赖文件等参数定义生成文件的规则,文件不会立即生成,只有在构建过程中被引用时才执行生成命令
add_custom_command(OUTPUT <⽣成⽂件1> [<⽣成⽂件2>...]
COMMAND <命令1> [ARGS] [<命令⾏参数>...]
[COMMAND <命令2> [ARGS] [<命令⾏参数>...] ...]
[BYPRODUCTS [<副产品⽂件>...]]
[MAIN_DEPENDENCY <主依赖⽂件>]
[DEPENDS [<依赖⽂件>...]]
[IMPLICIT_DEPENDS <编程语⾔1> <隐式依赖⽂件>
[<编程语⾔2> <隐式依赖⽂件>] ...]
[DEPFILE <依赖清单⽂件>]
[WORKING_DIRECTORY <⼯作⽬录>]
[COMMENT <注释>]
[VERBATIM] [APPEND]
[COMMAND_EXPAND_LISTS]
[JOB_POOL <Ninja构建⼯具的Job Pool>]
[USES_TERMINAL]
)
2. 响应构建事件:用于声明当构建目标的指定构建事件(PRE_BUILD、PRE_LINK、POST_BUILD)触发时执行的自定义构建规则。
add_custom_command(TARGET <构建⽬标>
PRE_BUILD | PRE_LINK | POST_BUILD
COMMAND <命令1> [ARGS] [<命令⾏参数>...]
[COMMAND <命令2> [ARGS] [<命令⾏参数>...] ...]
[BYPRODUCTS [<副产品⽂件>...]]
[WORKING_DIRECTORY <⼯作⽬录>]
[COMMENT <注释>]
[VERBATIM] [COMMAND_EXPAND_LISTS]
[USES_TERMINAL]
)
自定义构建目标
可以创建一个真正的构建目标,其构建规则为指定的一系列命令,默认不在构建全部时构建,可通过`ALL`参数指定是否包含在`all`目标内,还可指定源文件便于在IDE中使用。
命令形式:
add_custom_target(<构建⽬标名称> [ALL]
[<命令1> [<命令⾏参数>...]]
[COMMAND <命令2> [<命令⾏参数>...] ...]
[DEPENDS [<依赖⽂件>...]]
[BYPRODUCTS [<副产品⽂件>...]]
[WORKING_DIRECTORY <⼯作⽬录>]
[COMMENT <注释>]
[JOB_POOL <Ninja构建⼯具的Job Pool>]
[VERBATIM] [COMMAND_EXPAND_LISTS]
[USES_TERMINAL]
[SOURCES <源⽂件>...]
)
使用:
设置依赖关系
用于显式指定构建目标间的依赖关系,使一个构建目标在另一个构建目标之后构建。与自定义构建规则及目标中的 DEPENDS 参数作用不同, DEPENDS 参数主要用于设置对文件的依赖关系。
命令格式:
add_dependencies(<构建⽬标> [<依赖的构建⽬标>]...)
第7章上和下小结
因为这张内容比较重要而且知识点多,所以分成两个章节,在其中涵盖创建构建目标、添加子目录、声明项目的方法,介绍了常用属性及相关命令,强调了面向目标的属性设置在现代CMake中的重要性,还介绍了自定义构建规则和目标,展示了CMake在组织项目结构方面的功能特性及优势。
- 2025-02-19
-
回复了主题帖:
【deepseek】01预热贴:gpt回帖,得积分小礼
通义千文
-
回复了主题帖:
DigiKey应用探索站重磅上线!潮流应用,硬核技术探秘,N多干货,一站get!
DigiKey应用探索站干货满满当当啊,学习潮流技术
- 2025-02-17
-
发表了主题帖:
《CMake构建实战》第七章-构建目标和属性(上)
本帖最后由 eew_Eu6WaC 于 2025-2-19 14:17 编辑
《CMake构建实战》第七章-构建目标和属性(上)
“毫不夸张地说,如果学习CMake的目标就是组织简单的C和C++小项目的构建流程,那么阅读掌握本章的内容就足够了”,那我应该在本书的开头提示一下,看完第一章介绍就可以直接跳到本章了哈哈。在本章中和第一章呼应,介绍在CMake的目录程序中定义各种类型的构建目标,包括可执行文件、静态库、动态库、接口库和目标等等。
二进制构建目标
定义:是构建过程中生成的可执行文件、库文件或目标文件,并且全局可见
下面介绍一些二进制构建目标的定义方法:
可执行文件目标:使用add_executable命令创建,参数包括目标名称、源文件等,还有WIN32、MACOSX_BUNDLE、EXCLUDE_FROM_ALL等特殊的参数
格式如下:
add_executable(<目标名称>
[WIN32][MACOSX_BUNDLE][EXCLUDE_FROM_ALL]
[<源文件>……]
)
cmake_minimum_required(VERSION 3.20)
project(myProgram)
add_executable(myProgram main.c)
add_executable(myProgramExcludedFromAll
EXCLUDE_FROM_ALL
main.c
)
#include <stdio.h>
int main() { printf("Hello\n"); }
一般库目标:用add_library命令创建,库类型有STATIC、SHARED、MODULE,可通过BUILD_SHARED_LIBS变量决定库类型
格式:
add_library(<目标名称><库类型>
[EXCLUDE_FROM_ALL]
[<源文件>]
)
cmake_minimum_required(VERSION 3.20)
project(myLib)
add_library(myLib lib.c)
int add(int a, int b) { return a + b; }
目标文件库目标:用add_library命令创建目标文件集合,可在其他构建目标中通过特定表达式链接其目标文件
格式:
add_library(<⽬标名称> OBJECT
[<源⽂件>...]
)
cmake_minimum_required(VERSION 3.20)
project(myObjLib)
add_library(myObjLib OBJECT a.c b.c)
add_executable(main main.c $<TARGET_OBJECTS:myObjLib>)
int fa(int a, int b) { return a + b; }
int fb(int a, int b) { return a - b; }
#include <stdio.h>
int fa(int a, int b);
int fb(int a, int b);
int main() {
printf("%d %d\n", fa(1, 2), fb(3, 1));
return 0;
}
指定源文件的方式:简答省事的方式-使用手动罗列比较好哈哈,但是当文件数量较多那怎么办?可以使用开源项目或者用file和aux_source_directory命令来获取源文件,当然它们存在一定的缺点,比如CMake如何知道构建目标添加了新的源文件呢??有一条命令:aux_source_directory(<⽬录> <结果变量>) 可以遍历目录中的源文件
伪构建目标
定义:那些不会被构建二进制文件的目标,通常用于表明仅具有使用要求的可链接的对象。
分为以下三种类型:
接口库目标
导入目标
别名目标
接口库目标:用add_library命令创建。作用:头文件库的抽象
格式:
add_library(<⽬标名称> INTERFACE)
cmake_minimum_required(VERSION 3.20)
project(myInterfaceLib)
add_library(myInterfaceLib INTERFACE)
# 声明myInterfaceLib的使用要求:头文件搜索目录应为include
target_include_directories(myInterfaceLib INTERFACE include)
add_executable(main main.c)
# 声明main的构建要求:链接myInterfaceLib库,也就是说
# 将myInterfaceLib接口库的使用要求作为main的构建要求
target_link_libraries(main myInterfaceLib)
#include <a.h>
#include <stdio.h>
int main() {
printf("%d\n", add(1, 2));
return 0;
}
static int add(int a, int b) { return a + b; }
导入目标:包括可执行文件导入目标和库导入目标,但不会构建它,可使用add_executable和add_library命令创建
格式:
add_executable(<⽬标名称> IMPORTED [GLOBAL])
因为要运行一个可执行文件,所以就自己新建一个main.c通过gcc编译出可执行文件,代码如下:
cmake_minimum_required(VERSION 3.20)
project(import-main)
add_executable(main main.c)
add_custom_target(run-main
COMMAND ./main
DEPENDS main
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
#include <stdio.h>
void main(void){
printf("Hello CMake\r\n");
}
别名目标:是另一个构建目标的别名,使用add_executable或add_library命令创建
格式:
add_executable(<⽬标名称> ALIAS <指向的实际⽬标名称>)
add_library(<⽬标名称> ALIAS <指向的实际⽬标名称>)
创建一个静态库 my_liba,其源文件为 a.c。
根据 USE_EXTERNAL_LIBA 的值进行判断:
若 USE_EXTERNAL_LIBA 为真,引入一个外部的静态库 liba,并将其位置设置为 liba.lib。
若 USE_EXTERNAL_LIBA 为假,创建一个别名目标 liba,它指向之前创建的 my_liba 库。
cmake_minimum_required(VERSION 3.20)
project(alias-target)
add_library(my_liba STATIC "a.c")
if(USE_EXTERNAL_LIBA)
add_library(liba STATIC IMPORTED)
set_target_properties(liba PROPERTIES IMPORTED_LOCATION "liba.lib")
else()
add_library(liba ALIAS my_liba)
endif()
add_executable(main "main.c")
target_link_libraries(main liba)
#include <stdio.h>
void liba_function() {
printf("This is a function from liba.\n");
}
extern void liba_function();
int main() {
liba_function();
return 0;
}
子目录
CMak可以为每一个目录设置独立的目录属性,这样可以方便地针对不同子目录分别进行构建配置。
用处:使一些子目录结构更加清晰
使用:调用add_subdirectory命令将子目录加入项目,子目录需要包含CMakeLists.txt文件
格式:
add_subdirectory(< 源⽂件⽬录 > [< ⼆进制⽬录 >] [EXCLUDE_FROM_ALL])
项目:project
项目使CMake中组织项目的一个逻辑概念,在CMake目录程序中,可以有若干个项目,顶层目录程序中的第一个我呢见叫-顶层项目。
可声明项目名称、编程语言、版本号等属性,通过变量可获取项目相关属性。并且支持代码注入,通过设置特定变量可在项目定义前后注入代码。
project(< 项⽬名称 > [< 编程语⾔ >...])
project(< 项⽬名称 >
[VERSION < 主版本号 >[.< 次版本号 >[.< 补丁版本号 >[.< 修订版本号 >]]]]
[DESCRIPTION < 项⽬描述 >]
[HOMEPAGE_URL < 项⽬主⻚ URL>]
[LANGUAGES < 编程语⾔ >...])
属性
在CMake中属性根据作用域可以分为以下7中类型:
全局属性:CMake进程所具有的属性,通常用于获取一些全局状态。get_cmake_property和get_property命令来获取状态,set_property命令来设置状态
cmake_minimum_required(VERSION 3.20)
project(global_property)
get_cmake_property(res GENERATOR_IS_MULTI_CONFIG)
message("GENERATOR_IS_MULTI_CONFIG: ${res}")
get_property(res GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG SET)
message("GENERATOR_IS_MULTI_CONFIG is SET: ${res}")
目录属性:作用于目录(包括子目录),通常用于为目录中的构建目标统一设置编译选项或获取目录信息。get_directory_property和get_property命令来获取,set_directory_properties和set_property命令来设置
cmake_minimum_required(VERSION 3.20)
project(directory_property)
add_subdirectory(a)
add_subdirectory(b)
# 获取目录属性SUBDIRECTORIES的值到res
get_directory_property(res SUBDIRECTORIES)
message("SUBDIRECTORIES: ${res}")
#include <stdio.h>
int main() {
printf("DIR: %s\n", DIR);
return 0;
}
# 设置目录属性COMPILE_DEFINITIONS的值为DIR="a"
set_directory_properties(PROPERTIES COMPILE_DEFINITIONS DIR="a")
add_executable(a ../main.c)
# 设置目录属性COMPILE_DEFINITIONS的值为DIR="b"
set_directory_properties(PROPERTIES COMPILE_DEFINITIONS DIR="b")
add_executable(b ../main.c)
目标属性:用于构建目标、设置构建和使用要求,通过get_target_property和get_property命令来获取,set_target_properties和set_property命令来设置
//CmakeLists.tet
cmake_minimum_required(VERSION 3.20)
project(target_property)
add_executable(main main.c)
# 设置main的目标属性
set_target_properties(main PROPERTIES
# 构建要求:将include加入头文件搜索目录
INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/include
# 构建要求:链接a库(即传递a库的使用要求到main中作为main的构建要求)
LINK_LIBRARIES a
)
add_subdirectory(liba)
//main.c
#include <f.h>
#include <liba.h>
#include <stdio.h>
int main() {
printf("fa: %s\n", fa());
printf("f: %s\n", f());
return 0;
}
//include.f.h
const char *f() { return "main"; }
源文件属性:作用于源文件,用于配置源文件构建和获取信息,通过get_source_file_property和get_property命令来获取,set_source_files_properties和set_property命令来设置
//CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(source_file_property)
add_subdirectory(a)
add_subdirectory(b)
set_source_files_properties(main.c DIRECTORY a
PROPERTIES COMPILE_DEFINITIONS VERSION="0.1")
set_source_files_properties(main.c DIRECTORY b
PROPERTIES COMPILE_DEFINITIONS VERSION="0.2")
//main.c
#include <stdio.h>
int main() {
printf("VERSION: %s\n", VERSION);
return 0;
}
//b/CMakeLists.txt
add_executable(b ../main.c)
//a/CMakeLists.txt
add_executable(a ../main.c)
缓存变量属性:作用于缓存变量,用于获取相关信息,通过get_property命令来获取,set_property命令来设置
自定义属性(define_property):使用define_property命令可以自定义属性并添加文档说明,并且指定属性作用域和是否继承上层作用域属性值