
author: hjjdebugdate: 2026年 06月 12日 星期五 21:57:59 CSTdescrip: buildroot Makefile include *.mk 的玄机.文章目录1. 文件结构2. Makefile3. Makefile.in4. demo.mk5. foo.mk6 测试结果7. buildroot 高仿项目读buildroot 中Makefile, 在每个package目录的.mk文件中,都有一句$(eval $(host-autotools-package));我知道这是Makefile 语句的动态展开. 进一步分析一下宏命令 host-autotools-package它在 ./package/pkg-autotools.mk 中定义319:host-autotools-package ( c a l l i n n e r − a u t o t o o l s − p a c k a g e , h o s t − (call inner-autotools-package,host-(callinner−autotools−package,host−(pkgname),( c a l l U P P E R C A S E , h o s t − (call UPPERCASE,host-(callUPPERCASE,host−(pkgname)),( c a l l U P P E R C A S E , (call UPPERCASE,(callUPPERCASE,(pkgname)),host)那每个目录下的.mk又是怎样被包含的呢?顶层Makefile 543 行有包含543 include $(sort $(wildcard package//.mk))我当时有点犯迷糊, 它这一股脑的把各个.mk 文件包含进来, pkgname 怎么来设置呢? 在哪里设置的?带着这个疑问,我进行了简化实验, 终于得出了结论.原来pkgname 并不需要我们自己去设置, 它是一个变量,通过一定的计算方法得到.而这个方法就借助了make 每include 一个.mk 文件, 它会把.mk这个文件名加入到MAKEFILE_LIST变量中,用(lastword $(MAKEFILE_LIST)) 可以取到文件路径其精妙处在与,你include 一个.mk 文件, pkgname会被即时计算(这就是变量延时展开),从文件路径中导出包名.可见,include *.mk, 与把一大堆.mk 合并成一个大的.mk文件再include 进来是不一样的.前者借助与make变量MAKEFILE_LIST 可以得到包含的文件名称,从而可以进行一堆makefile 扩展下面用一个简单的例子,几十行代码,来演示一下这种技巧.1. 文件结构$ tree.├── Makefile└── package├── demo│ └── demo.mk├── foo│ └── foo.mk└── Makefile.in2. Makefilehjjhjj-laptop:~/test/make3$ cat Makefile #1.先引入 package/Makefile.in定义延迟展开的 pkgname include package/Makefile.in # 让all 成为第一个目标,默认目标 PHONY:all all:#2.关键行原封不动展开并 include 所有 package/*/*.mk include $(sort $(wildcard package/*/*.mk))#3.给all 设定依赖,两处声明all 规则会合并.all:host-foo host-demo echo 全部规则加载完成 3. Makefile.inhjjhjj-laptop:~/test/make3/package$ cat Makefile.in # 用定义延迟展开的 pkgname每次使用时才重新计算 pkgname$(basename $(notdir $(lastword $(MAKEFILE_LIST))))4. demo.mkhjjhjj-laptop:~/test/make3/package/demo$ cat demo.mk DEMO_VER:1.0.0define PKG_TPL # 每次宏展开时都会打印当前 pkgname证明它是正确的 $(info[DEBUG]包含文件:$(lastword $(MAKEFILE_LIST)))$(info[DEBUG]当前 pkgname$(pkgname))host-$(pkgname):echo 运行目标 host-$(pkgname) , 版本: $(DEMO_VER)endef $(eval $(PKG_TPL))5. foo.mkhjjhjj-laptop:~/test/make3/package/foo$ cat foo.mk FOO_VER:2.0.0define PKG_TPL $(info[DEBUG]包含文件:$(lastword $(MAKEFILE_LIST)))$(info[DEBUG]当前 pkgname$(pkgname))host-$(pkgname):echo 运行目标 host-$(pkgname) , 版本: $(FOO_VER)endef $(eval $(PKG_TPL))6 测试结果hjjhjj-laptop:~/test/make3$ make[DEBUG]包含文件:package/demo/demo.mk[DEBUG]当前 pkgnamedemo[DEBUG]包含文件:package/foo/foo.mk[DEBUG]当前 pkgnamefoo运行目标 host-foo,版本:2.0.0运行目标 host-demo,版本:1.0.0全部规则加载完成hjjhjj-laptop:~/test/make3$ make host-demo[DEBUG]包含文件:package/demo/demo.mk[DEBUG]当前 pkgnamedemo[DEBUG]包含文件:package/foo/foo.mk[DEBUG]当前 pkgnamefoo运行目标 host-demo,版本:1.0.0hjjhjj-laptop:~/test/make3$ make host-foo[DEBUG]包含文件:package/demo/demo.mk[DEBUG]当前 pkgnamedemo[DEBUG]包含文件:package/foo/foo.mk[DEBUG]当前 pkgnamefoo运行目标 host-foo,版本:2.0.0这里makefile 相当于脚本语言,make相当于解释器. pkgname变量相当于一个函数返回值(字符串型的)include xxx.mk 文件, 在xxx.mk 文件中,可以利用make的MAKEFILE_LIST 变量获取到文件名称.这是自动获取,就不用你手工设置相关变量了.有了这点自动化,差点没把我搞蒙.再浏览代码,发现还可以简化, 就是现在demo,foo中都定义了PKG_TPL, 形成了一种函数覆盖. 这是不好的,应该把它放到Makefile.in 中定义, 求同存异,把版本号作为函数的参数传进来. 这样就只有一处PKG_TPL, demo.mk,fool.mk进一步简化. 经实验也同样符合要求.代码完美.自己练一下吧,这里就不给代码了.7. buildroot 高仿项目送礼要送大礼,先看看结果吧.就跟真的一样. 流程都到了.每个包都有 download → extract → configure → build → install 的完整依赖$ make[DEBUG]包含文件:package/demo/demo.mk[DEBUG]处理包:demo,版本:1.0.0[DEBUG]包含文件:package/foo/foo.mk[DEBUG]处理包:foo,版本:2.0.0下载 foo:https://example.com/foo/foo-2.0.0.tar.gz解压 foo:foo-2.0.0.tar.gz配置 foo:./configure--prefix/usr编译 foo:make-j4安装 foo:make install ✅ foo 构建完成下载 demo:https://example.com/demo/demo-1.0.0.tar.gz解压 demo:demo-1.0.0.tar.gz配置 demo:./configure--prefix/usr编译 demo:make-j4安装 demo:make install ✅ demo 构建完成全部规则加载完成而这一些,仅仅有这么多代码:$ find . -name “.mk -o -name Makefile”|xargs wc -l4 ./package/foo/foo.mk7 ./package/demo/demo.mk42 ./package/Makefile.in13 ./Makefile66 总用量Makefile.in, 调试,书写它花了我一点点时间, 值的. 由此对make有了进一步理解.希望你由此爱上模板编程.它能够根据模板,生成实际的Makefile 中的目标规则.更多知识自己体会.下面把整个工程倾囊相送,放到一起了,由名称可以区分开hjjhjj-laptop:~/test/make3$ cat./package/foo/foo.mk FOO_VER:2.0.0FOO_SITE:https://example.com/fooFOO_SOURCE:foo-$(FOO_VER).tar.gz $(eval $(PKG_BUILD_TPL))hjjhjj-laptop:~/test/make3$ cat./package/demo/demo.mk # 包自定义变量 DEMO_VER:1.0.0DEMO_SITE:https://example.com/demoDEMO_SOURCE:demo-$(DEMO_VER).tar.gz # 执行宏生成所有目标 $(eval $(PKG_BUILD_TPL))hjjhjj-laptop:~/test/make3$ cat./package/Makefile.in # 用定义延迟展开的 pkgname每次使用时才重新计算 pkgname$(basename $(notdir $(lastword $(MAKEFILE_LIST))))# 当Makefile 没有uppercase 内置函数时,自己写一个 define uppercase $(shell echo $(1)|tr[:lower:][:upper:])endef VER$($(call uppercase,$(pkgname))_VER)SITE$($(call uppercase,$(pkgname))_SITE)SOURCE$($(call uppercase,$(pkgname))_SOURCE)# 定义构建流程宏依赖链download → extract → configure → build → install define PKG_BUILD_TPL # 调试信息 $(info[DEBUG]包含文件:$(lastword $(MAKEFILE_LIST)))$(info[DEBUG]处理包:$(pkgname),版本:$(VER))#1.download 目标 host-$(pkgname)-download:echo 下载 $(pkgname): $(SITE)/$(SOURCE)#2.extract 目标依赖 download host-$(pkgname)-extract:host-$(pkgname)-download echo 解压 $(pkgname): $(SOURCE)#3.configure 目标依赖 extract host-$(pkgname)-configure:host-$(pkgname)-extract echo 配置 $(pkgname): ./configure --prefix/usr#4.build 目标依赖 configure host-$(pkgname)-build:host-$(pkgname)-configure echo 编译 $(pkgname): make -j4#5.install 目标依赖 build host-$(pkgname)-install:host-$(pkgname)-build echo 安装 $(pkgname): make install#6.主目标依赖 install host-$(pkgname):host-$(pkgname)-install echo✅ $(pkgname) 构建完成endef hjjhjj-laptop:~/test/make3$ cat./Makefile #1.先引入 package/Makefile.in定义延迟展开的 pkgname include package/Makefile.in # 让all 成为第一个目标,默认目标 PHONY:all all:#2.关键行原封不动展开并 include 所有 package/*/*.mk include $(sort $(wildcard package/*/*.mk))#3.给all 设定依赖,两处声明all 规则会合并.all:host-foo host-demo echo 全部规则加载完成