尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

RIOT 构建系统工具函数测试解析:makefiles/utils 的验证与实现原理

RIOT 构建系统工具函数测试解析:makefiles/utils 的验证与实现原理 RIOT 构建系统工具函数测试解析makefiles/utils 的验证与实现原理【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT导读本篇文章以 tests/build_system/utils/README.md 为骨架深入剖析 RIOT 操作系统中内建构建系统GNU Make工具函数makefiles/utils目录下的checks.mk、strings.mk、variables.mk及其自动化测试工程。读完本文你将掌握ensure_value、memoized、target-export-variables、字符串大小写转换、版本号比较、max_number等 Make 函数的用法、边界行为与测试验证方式并能在自己的 RIOT 应用或 Makefile 中直接复用这些技巧。测试工程概述tests/build_system/utils是 RIOT 中专门用于验证构建系统工具函数的测试工程。其 README 明确说明了测试范围This test checks the build system utils functions declared inmakefiles/utils.也就是说被测对象并非 C 代码而是makefiles/utils目录下声明的一组 GNU Make 函数。该目录在仓库中的实际布局如下makefiles/utils/ ├── ansi.mk # ANSI 转义序列相关工具 ├── ansi_special.mk ├── checks.mk # ensure_value 等错误处理工具 ├── strings.mk # 大小写转换、版本比较、max_number ├── test-checks.mk # checks.mk 的独立测试 ├── test-strings.mk # strings.mk 的独立测试 ├── test-variables.mk # variables.mk 的独立测试 └── variables.mk # memoized、target-export-variables 等测试用例覆盖checks.mk、strings.mk、variables.mk三个核心文件中的全部公开函数ansi*.mk不在本次测试范围。每个函数既有正向用例应当成功也有负向用例应当失败这正是保证构建系统健壮性的关键。成功即静默测试的判定约定README 特别强调了一个重要的判定约定The test output says nothing in case of success.即测试成功时终端不输出任何内容。这是 Unix 工具哲学在测试中的体现——只有失败时才输出错误信息便于 CI 日志的干净与机器可读。这一约定在测试 Makefile 的两个辅助宏中落实# tests/build_system/utils/Makefile define command_should_fail $1 2/dev/null { echo Command $1 should have failed but did not 2; $1; exit 1; } || true endef define command_should_succeed $1 || { echo Command $1 failed 2; $1; exit 1; } endefcommand_should_succeed执行命令若失败则打印错误并exit 1command_should_fail执行命令并丢弃标准错误2/dev/null若意外成功则报错退出——用于验证负向用例确实会失败。测试目标清单九组用例测试通过COMPILE_TESTS变量注册了全部目标tests/build_system/utils/MakefileCOMPILE_TESTS test-ensure_value test-ensure_value-negative COMPILE_TESTS test-exported-variables COMPILE_TESTS test-memoized-variables COMPILE_TESTS test-lowercase COMPILE_TESTS test-uppercase COMPILE_TESTS test-uppercase_and_underscore COMPILE_TESTS test-version_is_greater COMPILE_TESTS test-version_is_greater_or_equal COMPILE_TESTS test-max_number这些目标通过all: build-system-utils-tests汇总触发并且注释明确说明Tests will be run both in the host machine and indocker即同一套用例会在宿主机与 Docker 两种环境中执行确保工具函数在不同 GNU Make / shell 环境下行为一致。每个目标实际调用makefiles/utils下对应的test-*.mk测试文件test-lowercase: $(Q)$(call command_should_succeed,$(MAKE) -C $(MAKEFILES_UTILS) -f test-strings.mk test-lowercase) test-ensure_value-negative: $(Q)$(call command_should_fail,$(MAKE) -C $(MAKEFILES_UTILS) -f test-checks.mk test-ensure_value-negative)其中MAKEFILES_UTILS $(RIOTMAKE)/utils指向makefiles/utils目录-C切换工作目录-f显式指定测试文件而$(Q)是 RIOT 构建系统的静默前缀不打印命令本身。关于“需要编译”的说明README 的 Note 部分解释了为什么一个纯 Make 测试工程仍保留了 C 代码与编译流程It should not be necessary to compile but this simplifies the integration in murdock for the moment. Also there will be other tests that may need to check the result of the compilation.原因有二简化 murdockRIOT 的 CI集成所有测试统一走“编译 执行”的标准流水线无需为纯 Make 测试单独定制流程未来扩展性后续可能会有需要检查编译结果的测试用例加入。因此 main.c 是一个刻意保持为空的主函数文件其注释/* The important rules are in the Makefile */直接点明了“真正的测试逻辑在 Makefile 里”这一设计。测试工程声明支持native32与native64两块板卡BOARDS_SUPPORTED native32 native64并继承 Makefile.build_system_common 与RIOTBASE/Makefile.include的标准测试框架。ensure_valueMake 内联错误检查ensure_value定义在 makefiles/utils/checks.mk用于在 Make 解析阶段直接产生友好错误# 返回第一个参数若非空若为空则立即抛出 error 并显示第二个参数的消息 ensure_value $(if $(1),$(1),$(error $(2)))行为传入的值非空则原样返回为空则触发$(error ...)使 make 立即终止并打印自定义错误信息。测试文件 test-checks.mk 分别验证了正负两个方向test-ensure_value: test $ $(call ensure_value,$,This should not fail) test-ensure_value-negative: echo $(call ensure_value,$^,This should fail)正向用例$目标名test-ensure_value非空ensure_value应原样返回test比较相等即通过负向用例$^依赖列表此处为空会触发$(error)因此整个子 make 调用必然失败外层command_should_fail即判定通过。memoized惰性求值的变量缓存memoized定义在 makefiles/utils/variables.mk解决 GNU Make 中延迟求值与:立即求值之间的经典取舍# 变量只在第一次使用时求值之后的引用等价于立即求值: # 目标用于包裹 shell 命令避免重复执行 memoized $2$(eval $1:$2)原理$2$(eval $1:$2)先展开$2产生实际值再通过$(eval $1:$2)把变量改写为立即求值。此后该变量不再重新计算。在 test-variables.mk 中用纳秒时间戳通过python3获取注释解释了 macOS 的date不支持%s%N的原因验证其语义MEMOIZED_CURRENT_TIME $(call memoized,MEMOIZED_CURRENT_TIME,$(call date_nanoseconds)) PRE_MEMOIZED_TIME : $(call date_nanoseconds) REF_CURRENT_TIME_1 : $(MEMOIZED_CURRENT_TIME) REF_CURRENT_TIME_2 : $(strip $(MEMOIZED_CURRENT_TIME))断言逻辑PRE_MEMOIZED_TIME REF_CURRENT_TIME_1——值确实是在首次使用时才求值求值发生在PRE_MEMOIZED_TIME之后REF_CURRENT_TIME_1 REF_CURRENT_TIME_2——两次引用返回同一时间戳已被缓存且strip后无多余空白验证函数不引入副作用字符PARSE_TIME MEMOIZED_CURRENT_TIME_2——第二个 memoized 变量直到测试目标执行时才被求值。target-export-variables目标级环境变量导出同文件中的target-export-variables提供“仅对特定目标导出环境变量”的能力替代全局export与立即求值target-export-variables $(foreach var,$(2),$(call _target-export-variable,$1,$(var))) _target-export-variable $(eval $1: export $2?)关键实现细节$1: export $2?通过?将变量声明为运行时求值避免在 make 解析期就展开$(shell ...)一类的命令。测试通过导出两个变量验证静态值MY_VARIABLE与延迟求值的CURRENT_TIME$(call target-export-variables,test-exported-variables,$(EXPORTED_VARIABLES)) test-exported-variables: $(Q)test $(MY_VARIABLE) $${MY_VARIABLE} || ... $(Q)test $(PARSE_TIME) -lt $${CURRENT_TIME} || ...$${MY_VARIABLE}在 shell 层读取环境变量验证其确实被子进程继承$${CURRENT_TIME}应晚于PARSE_TIME解析期时间戳证明导出发生在目标执行阶段而非解析阶段。真实使用示例见 makefiles/tools/serial.inc.mk$(call target-export-variables,term cleanterm,OPENOCD_DBG_EXTRA_CMD)——仅在term/cleanterm目标中导出 OpenOCD 调试参数避免污染全局环境。字符串工具纯 Make 的大小写转换makefiles/utils/strings.mk 实现了纯 Make 版本的大小写转换注释明确指出了动机与性能依据This replaces the pattern of using : $(shell echo $(var) | tr a-z- A-Z_) On local tests the make version was ~100 times faster than the shell one即用嵌套$(subst ...)链替代shell tr管道本机测试中 make 版本比 shell 版本快约 100 倍。实现方式lowercase $(subst A,a,$(subst B,b,... $(subst Z,z,$1)...)) uppercase $(subst a,A,$(subst b,B,... $(subst z,Z,$1)...)) uppercase_and_underscore $(call uppercase,$(subst -,_,$1))uppercase_and_underscore先把连字符-替换为下划线_再整体转大写——这正是 C 语言宏名如MY_MODULE_FEATURE的标准生成方式。test-strings.mk 用三组基准字符串验证含数字与连字符覆盖非字母字符的透传STRING_LOWER abcdefghijklmnopqrstuvwxyz-123456789 STRING_UPPER ABCDEFGHIJKLMNOPQRSTUVWXYZ-123456789 STRING_MACRO ABCDEFGHIJKLMNOPQRSTUVWXYZ_123456789 test-lowercase: $(Q)test $(STRING_LOWER) $(call lowercase,$(STRING_UPPER)) || ... test-uppercase: $(Q)test $(STRING_UPPER) $(call uppercase,$(STRING_LOWER)) || ... test-uppercase_and_underscore: $(Q)test $(STRING_MACRO) $(call uppercase_and_underscore,$(STRING_LOWER)) || ...版本比较语义化版本号的 Make 实现同一文件还实现了语义化版本号比较这是构建系统判断“工具链/依赖是否满足最低版本”的常用需求。核心思路是先补齐、后字典序比较_version index version按.拆分版本串取出 major/minor/patch_pad_number用printf %0Nd将各段补齐为 3 位数字空值按 0 处理_padded_version重组为004.002.001形式的定长串_is_greater利用 make 内置$(sort)的字典序排序判断大小。注释特别警告待比较串必须补齐到相同长度否则sort会认为2大于19字典序陷阱。对外暴露的两个公开函数version_is_greater $(call _is_greater,$(call _padded_version,$1),$(call _padded_version,$2)) version_is_greater_or_equal $(or \ $(call _is_greater,$(call _padded_version,$1),$(call _padded_version,$2)),\ $(call _is_equal,$(call _padded_version,$1),$(call _padded_version,$2)))version_is_greater_or_equal通过$(or ...)组合“大于”与“相等”两个条件_is_equal用双向$(findstring)判断相等。测试数据覆盖了多种边界test-strings.mkTEST_VERSION_1 4.1 TEST_VERSION_2 3.10 TEST_VERSION_3 4.2.3 TEST_VERSION_4 4.19.3 TEST_VERSION_5 4.1.0关键断言示例version_is_greater 4.1 3.10→ 14.1大于3.10验证补齐后 major 段004 003避开1与10的字典序陷阱version_is_greater 4.19.3 4.2.3→ 1patch 段补齐为019 002version_is_greater 4.2.3 4.19.3→ 空反向不成立version_is_greater_or_equal 4.1.0 4.1→ 1补齐后004.001.000 004.001.000验证空段按 0 补齐相等版本4.1vs4.1、4.2.3vs4.2.3→ 1。max_number自然数列表求最大max_number是这批工具中唯一依赖外部命令awk的函数# $1: 用空白分隔的自然数列表 max_number $(shell echo $1 | awk BEGIN{max0} {for(i1;iNF;i){x$$i; if (x max){max x}}} END{print max})awk 逐字段比较求最大值。测试覆盖了排序无关性、最大值位于首/中/尾、全零、单元素、空列表返回0等边界test-strings.mk。如何运行与扩展这套测试在仓库根目录执行make -C tests/build_system/utils all成功时无任何输出失败时输出形如Command ... failed或Command ... should have failed but did not的错误信息并以非零状态退出。该测试同时接入 CImurdock并可在宿主机与 Docker 两种环境中运行。若你希望在自己的 Makefile 中复用这些工具只需include $(RIOTMAKE)/utils/checks.mk include $(RIOTMAKE)/utils/strings.mk include $(RIOTMAKE)/utils/variables.mk然后即可直接调用例如# 校验必填参数为空立即报错 FOO $(call ensure_value,$(MY_REQUIRED_VAR),MY_REQUIRED_VAR must be set) # 缓存一次 shell 调用结果 TOOLCHAIN_PATH $(call memoized,TOOLCHAIN_PATH,$(shell which gcc)) # 判断工具链版本是否达标 ifeq (1,$(call version_is_greater_or_equal,$(GCC_VER),8.0.0)) ... endif新增测试用例时在COMPILE_TESTS中追加目标名并在makefiles/utils/test-*.mk中定义同名目标即可正负向用例分别套用command_should_succeed与command_should_fail两个辅助宏。总结tests/build_system/utils虽然是一份极简的测试工程成功即静默、main.c 为空其背后却是一组精心设计、被 RIOT 构建系统广泛复用的 GNU Make 工具函数ensure_value负责解析期错误检查memoized与target-export-variables优化了变量求值时机与环境变量导出纯 make 的大小写转换与补齐后的版本号比较则兼顾了性能与正确性。理解这套测试等于同时掌握了 Make 函数式编程的常用范式与 RIOT 构建系统的内部约定。【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表