【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 报错的行号会指向哪里?(这是一个很有趣的调试问题)
相关推荐
sbjdhjd21 分钟前
Docker | 核心概念科普 + 保姆级部署
linux·运维·服务器·docker·云原生·面试·eureka
charlie11451419121 分钟前
嵌入式C++教程实战之Linux下的单片机编程(9):HAL时钟使能 —— 不开时钟,外设就是一坨睡死的硅
linux·开发语言·c++·单片机·嵌入式硬件·c
以太浮标23 分钟前
华为eNSP模拟器综合实验之- DHCP、DNS、HTTP和FTP服务器配置案例Client-Server
linux·服务器·windows·http·华为·信息与通信
摇滚侠32 分钟前
Vmvare 虚拟机安装 Linux CentOS 7 操作系统 一键安装 Docker 1Panel 一键安装 MySQL Redis OpenClaw
linux·docker·centos
路溪非溪38 分钟前
如何使用procfs来排查问题
linux·arm开发·驱动开发
帕里亚1 小时前
ubuntu18.04 APT升级 glibc2.28 (Jetson)
linux·运维·windows
好运的阿财1 小时前
“锟斤拷”问题——程序中用powershell执行命令出现中文乱码的解决办法
linux·前端·人工智能·机器学习·架构·编辑器·vim
cyber_两只龙宝1 小时前
【Nginx】Nginx实现FastCGI详解
linux·运维·nginx·云原生·php·memcached·fastcgi
砖头拍死你1 小时前
Powershell使用vim修改文件保存后文件名自动全变小写
linux·编辑器·vim
wang09071 小时前
linux的中断分析(硬中断和软中断)
linux·运维·服务器