【Makefile 专家之路 | 函数篇】11. 终极奥义:eval 函数——动态生成规则的“核武器”

文章目录

  • [一、 为什么要用 eval?](#一、 为什么要用 eval?)
  • [二、 eval 的"两段式"展开逻辑(脑细胞风暴预警)](#二、 eval 的“两段式”展开逻辑(脑细胞风暴预警))
  • [三、 安卓实战:动态生成多个模块的编译规则](#三、 安卓实战:动态生成多个模块的编译规则)
  • [四、 💡 安卓工程师的"避坑"金律](#四、 💡 安卓工程师的“避坑”金律)
  • [五、 💡 安卓工程师的记忆卡片](#五、 💡 安卓工程师的记忆卡片)

一、 为什么要用 eval?

在普通的 Makefile 中,规则是"死"的。比如你要编译 100 个类似的模块,难道要手写 100 遍
target: dependency 吗?

  • 普通函数 : 返回一个字符串(如 patsubst)。
  • eval 函数 : 把一个字符串变成 Makefile 的代码
  • 核心价值: 它是"产生代码的代码"。它允许你在程序运行期间,根据变量动态地"原地写出"新的规则、变量定义或指令。

二、 eval 的"两段式"展开逻辑(脑细胞风暴预警)

这是 eval 最难理解的地方:它会对内容进行两次展开。

  1. 第一次展开eval 内部的变量(如 $(VAR))会被替换成具体的值,形成一段临时的"纯文本"。
  2. 第二次展开make 解析器把这段"纯文本"当成正常的 Makefile 语法重新读一遍。
  3. 专家提醒 : 因为有两次展开,所以在 eval 内部定义规则时,经常需要用到双美元符号 $$。第一个 $ 用来躲过第一次展开,让第二个 $ 在第二次展开时生效。

三、 安卓实战:动态生成多个模块的编译规则

假设你要为 Android 系统编译三个不同的工具包(ToolA, ToolB, ToolC),它们的编译逻辑一模一样:

bash 复制代码
TOOLS := ToolA ToolB ToolC

# 定义一个生成规则的模板
define create-rule
$(1): $(1).c
	gcc -o $(1) $(1).c
	@echo "编译模块 $(1) 完成"
endef

# 使用 eval 和 foreach 批量"炸"出规则
$(foreach t,$(TOOLS),$(eval $(call create-rule,$(t))))

发生了什么?

  • foreach 循环三次。
  • 第一次循环,call 生成了字符串:ToolA: ToolA.c ...
  • eval 拿过这个字符串,直接拍在 Makefile 里。
  • 最终,你的 Makefile 就像瞬间多出了 3 段手写的规则一样。

四、 💡 安卓工程师的"避坑"金律

  1. 调试极其困难 : 因为 eval 生成的规则是动态的,在文件中看不见。

    • 技巧 : 调试时,把 eval 换成 info(如 $(info $(call create-rule,$(t)))),这样 make 会把即将生成的代码打印在屏幕上,方便你检查语法。
  2. ** 的玄学** : 如果在 `eval` 定义的命令里引用变量,记得用

    • $(VAR) → \rightarrow → 在 eval 执行时就被换掉了。
    • \$\$(VAR) → \rightarrow → 只有在最终执行命令(Shell 运行)时才换掉。
  3. 不要滥用 : 虽然 eval 很酷,但它会显著增加 Makefile 的维护成本。只有在处理 AOSP 那种规模的重复逻辑时,它才是最优解。


五、 💡 安卓工程师的记忆卡片

  1. eval 是翻译官 : 它把字符串翻译成 make 能听懂的指令。
  2. 它是 Android 的基石 : Android 源码中的 BUILD_PREBUILTBUILD_EXECUTABLE 等宏,本质上都是复杂的 eval 模板。
  3. 两遍扫描 : 永远记住它会"扫描两次",这能帮你解释 90% 关于 eval 的报错。

【本篇自测】

  1. 为什么在 define 模板中,如果我们要写 target: $$(DEP),必须要用两个 $
  2. 如果你在 eval 内部写了一个语法错误,make 报错的行号会指向哪里?(这是一个很有趣的调试问题)
相关推荐
Cx330❀44 分钟前
【MySQL基础】一文吃透“表的约束”:从 Null/Default 到主外键的终极安全法则
linux·服务器·数据库·c++·mysql·安全
程序员JerrySUN1 小时前
Jetson 刷机深度解析:flash.sh vs l4t_initrd_flash.sh(含安全与磁盘加密对比)
linux·网络·arm开发·安全·系统安全
六点_dn2 小时前
Linux学习笔记-printf命令
linux·运维·算法
蜡台2 小时前
Linux Python 安装使用
linux·运维·服务器
欧神附体1232 小时前
在虚拟机中添加网卡,修改网卡名并关闭SElinux
linux·服务器·网络
brave_zhao4 小时前
openEuler下安装tar包解压工具
linux·服务器·数据库
小宏运维有点菜5 小时前
JumpServer
linux·运维
是个西兰花5 小时前
Linux:死锁与生产者消费者模型解析
linux·运维·服务器·c++·死锁·生产者消费者模型
RisunJan5 小时前
Linux命令-rsync(远程/本地文件同步 —— 增量传输的备份与镜像神器)
linux·运维·服务器
爆浇牛肉面6 小时前
Linux进阶命令:测开必学的curl和jq
linux