buildroot Makefile include *.mk 的玄机.


author: hjjdebug

date: 2026年 06月 12日 星期五 21:57:59 CST

descrip: buildroot Makefile include *.mk 的玄机.


文章目录

读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.in

2. Makefile

cpp 复制代码
hjj@hjj-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.in

cpp 复制代码
hjj@hjj-laptop:~/test/make3/package$ cat Makefile.in
# 用 = 定义延迟展开的 pkgname,每次使用时才重新计算
pkgname = $(basename $(notdir $(lastword $(MAKEFILE_LIST))))

4. demo.mk

cpp 复制代码
hjj@hjj-laptop:~/test/make3/package/demo$ cat demo.mk
DEMO_VER := 1.0.0

define 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.mk

cpp 复制代码
hjj@hjj-laptop:~/test/make3/package/foo$ cat foo.mk
FOO_VER := 2.0.0

define PKG_TPL
$(info [DEBUG] 包含文件: $(lastword $(MAKEFILE_LIST)))
$(info [DEBUG] 当前 pkgname = $(pkgname))

host-$(pkgname):
	@echo ">>> 运行目标 host-$(pkgname) , 版本: $(FOO_VER)"
endef

$(eval $(PKG_TPL))

6 测试结果

cpp 复制代码
hjj@hjj-laptop:~/test/make3$ make
[DEBUG] 包含文件: package/demo/demo.mk
[DEBUG] 当前 pkgname = demo
[DEBUG] 包含文件: package/foo/foo.mk
[DEBUG] 当前 pkgname = foo
>>> 运行目标 host-foo , 版本: 2.0.0
>>> 运行目标 host-demo , 版本: 1.0.0
===== 全部规则加载完成 =====
hjj@hjj-laptop:~/test/make3$ make host-demo
[DEBUG] 包含文件: package/demo/demo.mk
[DEBUG] 当前 pkgname = demo
[DEBUG] 包含文件: package/foo/foo.mk
[DEBUG] 当前 pkgname = foo
>>> 运行目标 host-demo , 版本: 1.0.0
hjj@hjj-laptop:~/test/make3$ make host-foo
[DEBUG] 包含文件: package/demo/demo.mk
[DEBUG] 当前 pkgname = demo
[DEBUG] 包含文件: package/foo/foo.mk
[DEBUG] 当前 pkgname = foo
>>> 运行目标 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 的完整依赖

cpp 复制代码
$ 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 -l

4 ./package/foo/foo.mk

7 ./package/demo/demo.mk

42 ./package/Makefile.in

13 ./Makefile

66 总用量

Makefile.in, 调试,书写它花了我一点点时间, 值的. 由此对make有了进一步理解.

希望你由此爱上模板编程.

它能够根据模板,生成实际的Makefile 中的目标规则.

更多知识自己体会.

下面把整个工程倾囊相送,放到一起了,由名称可以区分开

cpp 复制代码
hjj@hjj-laptop:~/test/make3$ cat ./package/foo/foo.mk 
FOO_VER := 2.0.0
FOO_SITE := https://example.com/foo
FOO_SOURCE := foo-$(FOO_VER).tar.gz
$(eval $(PKG_BUILD_TPL))
hjj@hjj-laptop:~/test/make3$ cat ./package/demo/demo.mk 
# 包自定义变量
DEMO_VER := 1.0.0
DEMO_SITE := https://example.com/demo
DEMO_SOURCE := demo-$(DEMO_VER).tar.gz

# 执行宏,生成所有目标
$(eval $(PKG_BUILD_TPL))
hjj@hjj-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

hjj@hjj-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 "===== 全部规则加载完成 ====="
相关推荐
ScilogyHunter2 天前
Buildroot完全指南:从入门到实战
linux·嵌入式·buildroot
secret_to_me5 天前
buildRoot编译rootfs实战
linux·c语言·c++·ubuntu·电脑·buildroot
Zevalin爱灰灰5 天前
makefile从入门到实战 第一章 认识makefile(二)
makefile
Zevalin爱灰灰6 天前
makefile从入门到实战 第一章 认识makefile(一)
linux·makefile
A_humble_scholar6 天前
Linux(三)深入理解 Makefile:自动变量、增量编译原理与文件时间属性
linux·服务器·c++·makefile
sulikey11 天前
个人Linux操作系统学习笔记4 - makefile
linux·makefile·make·构建
Irissgwe20 天前
二、Linux基础开发工具(2)
linux·makefile·gcc·g++·
量子炒饭大师24 天前
【Linux系统编程】——【自动化构建-make/Makefile】拒绝手动编译!构建你的赛博代码加工厂,重塑逻辑矩阵效率极限
linux·运维·自动化·makefile·make·自动化构建
yuanyuan2o21 个月前
从最小项目开始的 CMake 教程
c语言·开发语言·arm开发·c++·makefile·make·cmake