
文章目录一、 为什么要用 eval二、 eval 的“两段式”展开逻辑脑细胞风暴预警三、 安卓实战动态生成多个模块的编译规则四、 安卓工程师的“避坑”金律五、 安卓工程师的记忆卡片一、 为什么要用 eval在普通的 Makefile 中规则是“死”的。比如你要编译 100 个类似的模块难道要手写 100 遍target: dependency吗普通函数 返回一个字符串如patsubst。eval函数 把一个字符串变成 Makefile 的代码。核心价值 它是“产生代码的代码”。它允许你在程序运行期间根据变量动态地“原地写出”新的规则、变量定义或指令。二、 eval 的“两段式”展开逻辑脑细胞风暴预警这是eval最难理解的地方它会对内容进行两次展开。第一次展开eval内部的变量如$(VAR)会被替换成具体的值形成一段临时的“纯文本”。第二次展开make解析器把这段“纯文本”当成正常的 Makefile 语法重新读一遍。专家提醒 因为有两次展开所以在eval内部定义规则时经常需要用到双美元符号$$。第一个$用来躲过第一次展开让第二个$在第二次展开时生效。三、 安卓实战动态生成多个模块的编译规则假设你要为 Android 系统编译三个不同的工具包ToolA, ToolB, ToolC它们的编译逻辑一模一样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 段手写的规则一样。四、 安卓工程师的“避坑”金律调试极其困难 因为eval生成的规则是动态的在文件中看不见。技巧 调试时把eval换成info如$(info $(call create-rule,$(t)))这样make会把即将生成的代码打印在屏幕上方便你检查语法。$$ 的玄学 如果在eval定义的命令里引用变量记得用 $$。$(VAR)→ \rightarrow→在eval执行时就被换掉了。\$\$(VAR)→ \rightarrow→只有在最终执行命令Shell 运行时才换掉。不要滥用 虽然eval很酷但它会显著增加 Makefile 的维护成本。只有在处理 AOSP 那种规模的重复逻辑时它才是最优解。五、 安卓工程师的记忆卡片eval是翻译官 它把字符串翻译成make能听懂的指令。它是 Android 的基石 Android 源码中的BUILD_PREBUILT、BUILD_EXECUTABLE等宏本质上都是复杂的eval模板。两遍扫描 永远记住它会“扫描两次”这能帮你解释 90% 关于eval的报错。【本篇自测】为什么在define模板中如果我们要写target: $$(DEP)必须要用两个$如果你在eval内部写了一个语法错误make报错的行号会指向哪里这是一个很有趣的调试问题