CMake解析参数用法示例

cmake_parse_arguments 是 CMake 中用于解析函数或宏参数的工具,特别适合处理带有选项(OPTIONS)、单值参数(SINGLE_ARGS)和多值参数(MULTI_ARGS)的复杂参数列表。以下是用法说明和一个示例:


基本语法

cmake_parse_arguments(<PREFIX>          # 解析后变量的前缀"<OPTIONS>"       # 选项列表(如 --enable-foo)"<SINGLE_ARGS>"   # 单值参数列表(如 --key VALUE)"<MULTI_ARGS>"    # 多值参数列表(如 --files a.txt b.txt)${ARGN}           # 需要解析的参数(通常用 ${ARGN})

解析后,可以通过以下变量访问参数:

  • 选项: ${<PREFIX>_<OPTION_NAME>}(值为 TRUE/FALSE
  • 单值参数: ${<PREFIX>_<SINGLE_ARG_NAME>}
  • 多值参数: ${<PREFIX>_<MULTI_ARG_NAME>}

示例

假设我们需要定义一个宏 setup_project,支持以下参数:

  • 选项: ENABLE_TEST(是否启用测试)
  • 单值参数: VERSION(项目版本号)
  • 多值参数: SOURCES(源文件列表)
代码实现
# 定义宏
macro(setup_project)# 解析参数set(OPTIONS "ENABLE_TEST")        # 选项列表set(SINGLE_ARGS "VERSION")        # 单值参数列表set(MULTI_ARGS "SOURCES")         # 多值参数列表cmake_parse_arguments("ARG"                         # 前缀为 ARG"${OPTIONS}""${SINGLE_ARGS}""${MULTI_ARGS}"${ARGN}                       # 传入宏的参数)# 检查是否有未解析的参数(可选)if(ARG_UNPARSED_ARGUMENTS)message(WARNING "Unknown arguments: ${ARG_UNPARSED_ARGUMENTS}")endif()# 处理选项if(ARG_ENABLE_TEST)message(STATUS "Testing enabled")# 添加测试相关逻辑...endif()# 处理单值参数if(ARG_VERSION)message(STATUS "Project version: ${ARG_VERSION}")else()message(FATAL_ERROR "VERSION must be specified!")endif()# 处理多值参数if(ARG_SOURCES)message(STATUS "Source files: ${ARG_SOURCES}")# 添加编译目标...add_executable(my_app ${ARG_SOURCES})else()message(FATAL_ERROR "SOURCES must be specified!")endif()
endmacro()# 调用示例
setup_project(ENABLE_TESTVERSION 1.0.0SOURCES src/main.cpp src/utils.cpp
)

输出结果

-- Testing enabled
-- Project version: 1.0.0
-- Source files: src/main.cpp;src/utils.cpp

注意事项

  1. 变量作用域:如果使用 macro 而非 function,解析后的变量会影响父作用域。建议在函数中使用 function + PARENT_SCOPE 控制作用域。
  2. 参数顺序:CMake 不关心参数顺序,但需要确保参数名和值正确匹配。
  3. 未解析参数:通过 ${<PREFIX>_UNPARSED_ARGUMENTS} 可以捕获未知参数。

通过 cmake_parse_arguments,你可以更灵活地处理 CMake 函数/宏的复杂参数!


你提到的这一点非常重要!在 CMake 中,宏(macro)函数(function)的行为有本质区别(尤其是作用域规则不同)。以下我会详细解释它们的差异,并给出一个函数的例子,同时说明为何在之前的回答中选择了宏。


为什么先用了宏(macro)的例子?

  1. 简化示例
    在简单的示例中,宏的变量作用域规则(直接修改父作用域)可以让代码更简洁,不需要额外处理变量传递。对于新手来说,这可能更容易理解。

  2. 历史原因
    早期 CMake 版本中,cmake_parse_arguments 在函数中的行为需要更谨慎的作用域处理,而宏的行为更“直接”。

但实际项目中,**函数(function)**通常是更安全的选择,因为它能隔离作用域,避免变量污染。


函数(function)的示例

以下是将之前的 setup_project 从宏改为函数的实现,并修复作用域问题:

代码实现
# 定义函数
function(setup_project)# 解析参数set(OPTIONS "ENABLE_TEST")        # 选项列表set(SINGLE_ARGS "VERSION")        # 值参数列表set(MULTI_ARGS "SOURCES")         # 多值参数列表cmake_parse_arguments("ARG"                         # 前缀为 ARG"${OPTIONS}""${SINGLE_ARGS}""${MULTI_ARGS}"${ARGN}                       # 传入函数的参数)# 检查未解析的参数(可选)if(ARG_UNPARSED_ARGUMENTS)message(WARNING "Unknown arguments: ${ARG_UNPARSED_ARGUMENTS}")endif()# 处理选项if(ARG_ENABLE_TEST)message(STATUS "Testing enabled")# 启用测试逻辑(例如 include(CTest)endif()# 处理单值参数if(NOT ARG_VERSION)message(FATAL_ERROR "VERSION must be specified!")endif()message(STATUS "Project version: ${ARG_VERSION}")# 处理多值参数if(NOT ARG_SOURCES)message(FATAL_ERROR "SOURCES must be specified!")endif()message(STATUS "Source files: ${ARG_SOURCES}")# 关键点:将变量传递到父作用域(如果需要影响外部)# 例如,将源文件列表传递给父作用域:set(SOURCES ${ARG_SOURCES} PARENT_SCOPE)# 在函数内创建目标(变量直接可见)add_executable(my_app ${ARG_SOURCES})
endfunction()# 调用示例
setup_project(ENABLE_TESTVERSION 1.0.0SOURCES src/main.cpp src/utils.cpp
)

宏(macro) vs 函数(function)的关键区别

特性宏(macro)函数(function)
作用域直接修改父作用域的变量默认只在函数内部作用域有效
变量传递自动“泄漏”到父作用域需显式使用 set(... PARENT_SCOPE)
性能类似代码替换,可能更高效有独立作用域,稍复杂
安全性容易污染父作用域变量隔离作用域,更安全

为什么推荐使用函数?

  1. 避免变量污染
    函数不会意外覆盖父作用域的变量(例如,如果父作用域中已经有一个 VERSION 变量,宏可能会直接修改它,而函数不会)。

  2. 逻辑隔离
    函数内部的操作(如 add_executable)默认只在函数作用域内生效,除非显式传递变量到父作用域。

  3. 可维护性
    函数的隔离性使得代码行为更可预测,适合复杂项目。


何时使用宏?

  1. 需要直接修改父作用域
    例如,在顶层脚本中快速操作变量。
  2. 简单代码替换
    当逻辑非常简单且不需要隔离作用域时。

总结

  • 如果你的目标是封装一个可复用的逻辑模块,优先使用函数
  • 如果示例中使用了宏,可以补充一个函数的版本,明确说明作用域处理的差异。这样用户可以根据实际需求选择。

这段代码的目的是通过两次调用 cmake_parse_arguments 实现一种嵌套参数解析的机制。它的核心思路是:先解析外层的“元参数”(定义如何解析内层参数),再用这些元参数去解析实际传递的参数。以下是对代码的详细解释,以及这种设计的优缺点分析。


代码解释

function(nuttx_parse_function_args)cmake_parse_arguments(IN "" "FUNC""OPTIONS;ONE_VALUE;MULTI_VALUE;REQUIRED;ARGN" "${ARGN}")cmake_parse_arguments(OUT "${IN_OPTIONS}" "${IN_ONE_VALUE}""${IN_MULTI_VALUE}" "${IN_ARGN}")if(OUT_UNPARSED_ARGUMENTS)message(FATAL_ERROR "${IN_NAME}: unparsed ${OUT_UNPARSED_ARGUMENTS}")endif()foreach(arg ${IN_OPTIONS} ${IN_ONE_VALUE} ${IN_MULTI_VALUE})set(${arg}${OUT_${arg}}PARENT_SCOPE)endforeach()endfunction()
1. 函数定义
function(nuttx_parse_function_args)

定义了一个名为 nuttx_parse_function_args 的函数,目的是封装一个通用的参数解析工具。


2. 第一次解析:cmake_parse_arguments(IN ...)
cmake_parse_arguments(IN ""                   # 外层无选项(OPTIONS)"FUNC"               # 外层单值参数(SINGLE_ARGS)"OPTIONS;ONE_VALUE;MULTI_VALUE;REQUIRED;ARGN"  # 外层多值参数(MULTI_ARGS)"${ARGN}"            # 输入的参数列表
)
  • 目的:解析外层的“元参数”,这些参数定义了如何解析内层的实际参数。
  • 解析结果
    • IN_FUNC: 单值参数,表示实际要解析的函数名(可能用于错误提示)。
    • IN_OPTIONS: 多值参数,定义内层参数的选项(OPTIONS)。
    • IN_ONE_VALUE: 多值参数,定义内层参数的单值参数(SINGLE_ARGS)。
    • IN_MULTI_VALUE: 多值参数,定义内层参数的多值参数(MULTI_ARGS)。
    • IN_ARGN: 多值参数,保存实际需要解析的内层参数列表。

3. 第二次解析:cmake_parse_arguments(OUT ...)
cmake_parse_arguments(OUT "${IN_OPTIONS}"      # 内层选项(来自第一次解析的 IN_OPTIONS)"${IN_ONE_VALUE}"    # 内层单值参数(来自第一次解析的 IN_ONE_VALUE)"${IN_MULTI_VALUE}"  # 内层多值参数(来自第一次解析的 IN_MULTI_VALUE)"${IN_ARGN}"         # 实际需要解析的参数(来自第一次解析的 IN_ARGN)
)
  • 目的:用第一次解析得到的元参数(IN_OPTIONS, IN_ONE_VALUE, IN_MULTI_VALUE)去解析实际的内层参数(IN_ARGN)。
  • 解析结果
    • OUT_<OPTION>: 内层选项的值(TRUE/FALSE)。
    • OUT_<SINGLE_ARG>: 内层单值参数的值。
    • OUT_<MULTI_ARG>: 内层多值参数的值。

4. 错误检查
if(OUT_UNPARSED_ARGUMENTS)message(FATAL_ERROR "${IN_NAME}: unparsed ${OUT_UNPARSED_ARGUMENTS}")
endif()
  • 检查是否有未解析的参数,如果有则报错。

5. 将解析结果传递到父作用域
foreach(arg ${IN_OPTIONS} ${IN_ONE_VALUE} ${IN_MULTI_VALUE})set(${arg}${OUT_${arg}}PARENT_SCOPE)
endforeach()
  • 将内层解析得到的参数值(OUT_<arg>)传递到父作用域,使得调用者可以访问这些参数。

为什么使用两次解析?

这种设计的关键在于动态定义参数解析规则

  1. 外层解析:定义内层参数的解析规则(哪些是选项、单值参数、多值参数)。
  2. 内层解析:根据外层解析得到的规则,解析实际的参数列表。

例如,假设调用代码如下:

nuttx_parse_function_args(FUNC my_functionOPTIONS ENABLE_FOOONE_VALUE VERSIONMULTI_VALUE SOURCESARGN ENABLE_FOO VERSION 1.0.0 SOURCES a.cpp b.cpp
)
  • 外层解析会提取 OPTIONS ENABLE_FOO, ONE_VALUE VERSION, MULTI_VALUE SOURCES,并将 ENABLE_FOO VERSION 1.0.0 SOURCES a.cpp b.cpp 保存到 IN_ARGN
  • 内层解析会根据外层解析的规则,将 ENABLE_FOO 解析为选项,VERSION 解析为单值参数,SOURCES 解析为多值参数。

优点

  1. 灵活性
    允许动态指定参数解析规则,可以复用同一个函数解析不同结构的参数。
  2. 通用性
    适合需要为多个函数或宏统一封装参数解析逻辑的场景。
  3. 减少重复代码
    避免在每个函数中重复编写 cmake_parse_arguments

缺点

  1. 复杂度高
    嵌套解析逻辑难以理解,尤其是对 CMake 新手。
  2. 依赖外层参数的正确性
    如果外层参数(如 OPTIONS, ONE_VALUE)未正确传递,内层解析会失败。
  3. 错误处理困难
    嵌套解析可能导致错误信息不直观(例如,外层参数错误和内层参数错误混杂)。
  4. 变量作用域问题
    需要显式传递变量到父作用域,容易遗漏或出错。

改进建议

  1. 添加注释
    明确说明两次解析的目的,尤其是外层参数的含义。
  2. 严格的错误检查
    在外层解析后检查必要的元参数(如 FUNC, OPTIONS 是否存在)。
  3. 示例和文档
    提供调用示例和文档,说明如何正确传递外层和内层参数。
  4. 简化设计
    如果不需要动态定义解析规则,应优先使用单次 cmake_parse_arguments

总结

这段代码通过两次 cmake_parse_arguments 实现了一种动态参数解析机制,虽然灵活但复杂度较高。在实际项目中,应权衡灵活性和可维护性:如果不需要动态指定解析规则,应避免嵌套解析;若必须使用,需确保充分的错误处理和文档支持。

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

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

相关文章

自定义JackSon配置

避免前端&#xff08;JavaScript&#xff09;处理大数&#xff08;如 Long、BigInteger&#xff09;时发生精度丢失问题&#xff0c;所以引入了自定义 Jackson 配置。 先看代码&#xff1a; /** 根据id修改员工信息*/PutMappingpublic R<String> update(HttpServletRequ…

Qt入门——什么是Qt?

Qt背景介绍 什么是Qt? Qt 是⼀个 跨平台的 C 图形用户界面应用程序框架 。它为应用程序开发者提供了建立艺术级图形界面所需的所有功能。它是 完全面向对象 的&#xff0c;很容易扩展。Qt 为开发者提供了 ⼀种基于组件的开发模式 &#xff0c;开发者可以通过简单的拖拽和组合…

Linux CentOS 安装Python 3.8.0

在 CentOS 上升级 Python 3.6.8 到 3.8.0&#xff0c;可以按照以下步骤操作&#xff1a; 1. 安装依赖 sudo yum groupinstall -y "Development Tools" sudo yum install -y openssl-devel bzip2-devel libffi-devel zlib-devel wget 如果遇到报错“File "/bin…

Python爬虫(11)Python数据存储实战:深入解析NoSQL数据库的核心应用与实战

目录 引言一、背景&#xff1a;为什么选择NoSQL存储爬虫数据&#xff1f;1.1 爬虫数据的核心挑战1.2 NoSQL数据库的核心优势 二、NoSQL数据库在爬虫中的核心应用2.1 MongoDB&#xff1a;文档型数据库的王者2.2 Redis&#xff1a;内存数据库的极致性能 三、NoSQL选型与性能优化策…

PCB设计工艺规范(一)概述

PCB设计工艺规范&#xff08;一&#xff09; 1.概述2.关键词及引用标准3.PCB板材要求3.1 确定PCB使用板材以及TG值3.2 确定 PCB 的表面处理镀层 4.热设计要求5.器件库选项要求 资料来自网络&#xff0c;仅供学习使用。 1.概述 规范产品的 PCB 工艺设计&#xff0c;规定 PCB 工…

proxychains4系统代理for linux(加速国内github下载速度,pip安装)

1.proxychains4代理安装&#xff1a; sudo apt-get install proxychains42.找到配置文件/etc/proxychains4.conf在[ProxyList]后面添加以下内容&#xff1a; socks5 127.0.0.1 10808 配置如下&#xff1a; 3.使用proxychains4(git clone)&#xff1a; proxychains4 git c…

Seata客户端@GlobalTransactional核心源码解析

文章目录 前言一、GlobalTransactional1.1、wrapIfNecessary1.2、handleGlobalTransaction1.3、invoke 二、总结 前言 Seata是阿里开源的分布式事务解决方案。在Spring传统的事务中&#xff0c;开启事务&#xff0c;执行事务&#xff0c;回滚/提交事务&#xff0c;统一由Spring…

pytorch搭建并训练神经网络

#从小白开始学习人工智能# #学习笔记# 工具&#xff1a;pytorch 一、基础概念 1.神经网络是什么&#xff1f; 神经网络是人类受到生物神经细胞结构启发而研究出的算法体系。又称为人工神经网络&#xff08;Artificial neural network&#xff09; 最简版神经网络结构图&a…

JavaEE-多线程实战01

Java 多线程入门&#xff1a;第一个多线程程序 在 Java 中&#xff0c;多线程编程是非常重要的一部分。本篇文章将通过示例&#xff0c;带你快速了解如何创建第一个多线程程序&#xff0c;并深入分析其运行机制。 1. 创建一个线程类并继承 Thread 在 Java 中&#xff0c;我们…

Android Compose 无网络状态处理全指南:从基础到高级实践

Android Compose 无网络状态界面处理全方案 引言 在移动应用开发中&#xff0c;网络连接不稳定是常见场景。优雅地处理无网络状态能显著提升用户体验。Jetpack Compose 提供了强大的工具来实现各种网络状态下的界面展示。本文将全面介绍在 Compose 中处理无网络状态的多种方案…

Arduino项目实战与编程技术详解

一、智能避障小车:超声波传感器与PWM电机控制 1.1 硬件需求与工作原理 智能避障小车的核心在于超声波传感器与电机驱动模块的协同工作。超声波传感器(HC-SR04)通过发射高频声波并接收回波来测量距离,而L298N电机驱动模块则负责控制两个直流电机的转向与速度。 1.1.1 超声…

Java在云计算、大数据、云原生下的应用和优势 - 面试实战

Java在云计算、大数据、云原生下的应用和优势 - 面试实战 第一轮提问 面试官&#xff1a;马架构&#xff0c;请简单介绍一下Java在云计算中的主要应用场景有哪些&#xff1f; 马架构&#xff1a;Java在云计算中的主要应用场景包括微服务架构设计、容器化部署&#xff08;如D…

数据库与大数据技术教程资料

概述 无论你是刚入门的技术新人&#xff0c;还是寻求突破的资深工程师&#xff0c;这份精心整理的电子书合辑将为你打开系统性学习的大门&#xff01;所有资源支持多端阅读&#xff0c;助力技术成长每一步资料已经整理好&#xff0c;喜欢的朋友请自取&#xff1a;https://pan.…

【Spring Boot 注解】@ConfigurationProperties

文章目录 ConfigurationProperties注解一、简介二、依赖引入三、基本用法四、主要特性五、激活方式六&#xff0c;优点七、与 Value 对比 ConfigurationProperties注解 一、简介 ConfigurationProperties 是 Spring Boot 提供的一个强大注解&#xff0c;用于将外部配置&#…

C++(初阶)(十六)——set

set setset介绍set的构造和迭代器set的增删查findlower_boundmultiset和set的差异 题目[349. 两个数组的交集 - 力扣&#xff08;LeetCode&#xff09;](https://leetcode.cn/problems/intersection-of-two-arrays/description/)交集差集[142. 环形链表 II - 力扣&#xff08;L…

higress之:让流量通过gateway

本来想测跨域问题&#xff0c;结果参数配置过去之后一直没生效&#xff0c;经过了解说是gateway才是设置跨域参数的核心&#xff0c;所以需要让流量通过gateway&#xff0c;捣鼓了半天记录一下 第一步&#xff0c;测试服务是否正常 通过get svc、pod等&#xff0c;发现各pod都…

C盘哪些文件删除之后无影响,可以清理磁盘空间。

C盘是电脑的系统盘,存放了操作系统的重要文件和部分默认安装的软件。当C盘空间不足时,系统可能运行缓慢甚至卡顿,这时清理C盘是一个有效的解决方法。由于C盘包含许多关键数据,清理时需要格外谨慎,以免误删导致系统崩溃。将详细介绍C盘中可以安全删除的文件类型及清理方法,…

开源项目实战学习之YOLO11:ultralytics-cfg-models-fastsam(九)

&#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 文章大纲 1. __init__.py2. model.py3. predict.py4. utils.py5. val.py FastSAM 是一种目标检测和图像分割模型&#xff0c;Ultralytics 是一个在计算机视觉领域广泛使用的库&#x…

Windows11安装Docker

本次安装环境 Windows11&#xff08;23H2&#xff09;&#xff0c;CPU&#xff08;12代Intel&#xff09; 什么是Docker Docker 是一个软件平台&#xff0c;让您可以快速构建、测试和部署应用程序。Docker 将软件打包成名为容器的标准化单元&#xff0c;这些单元具有运行软件所…

C# 在VS2022中开发常用设置

一、基础环境配置 1. 安装必要组件 在 VS2022 安装时确保勾选以下工作负载&#xff1a; ​​使用 .NET 的桌面开发​​&#xff08;包含 WPF/WinForms&#xff09;​​ASP.NET 和 Web 开发​​​​.NET 跨平台开发​​​​Azure 开发​​​​数据存储和处理​​ 2. 主题与外…