openwrt Makefile subdir.mk 详解
之前讲述了 openwrt Makefile 的整体框架,主要包括主 Makefile 的描述,,最后简单描述了 subdir.mk
, 本文就来详细剖析这个文件。
但是在解析 subdir.mk
之前,先来看两个 Makefile,
debug.mk
: 这也是主 Makefile 引入的第一个 .mk 文件,这里定义的调试函数在subdir.mk
中被调用target/Makefile
: 这是调用到subdir.mk
的首个 Makefile, 本文将以此为例进行说明
debug.mk
debug.mk
定义了几个调试函数。
debug
warn
debug_eval
warn_eval
define debug
$$(findstring $(2),$$(if $$(DEBUG_SCOPE_DIR),$$(if $$(filter $$(DEBUG_SCOPE_DIR)%,$(1)),$(build_debug)),$(build_debug)))
endef
define warn
$$(if $(call debug,$(1),$(2)),$$(warning $(3)))
endef
define debug_eval
$$(if $(call debug,$(1),$(2)),$(3))
endef
define warn_eval
$(call warn,$(1),$(2),$(3) $(4))
$(4)
endef
其中:
DEBUG_SCOPE_DIR
: 默认没有定义, 可以在执行make
时指定build_debug
: 对应的是$(DEBUG)
的值,是 "dltvr" 字符串的子集
要使这几个函数生效,需要定义变量 DEBUG
, 可以在执行 make 时定义.
make DEBUG=d V=s
# debug flags:
#
# d: show subdirectory tree
# t: show added targets
# l: show legacy targets
# r: show autorebuild messages
# v: verbose (no .SILENCE for common targets)
默认情况下,DEBUG
是没有定义的,通常不需要管,之所以在这拎出来,是因为 subdir.mk
有调用以上提及的调试函数。
target/Makefile
target/Makefile
与 tools
, toolchain
, package
目录的 Makefile
使用的是相同的结构,通过调用 subdir.mk
内定义的两个函数 stampfile
, subdir
动态生成编译目标规则。
curdir:=target
$(curdir)/subtargets:=install
$(curdir)/builddirs:=linux sdk imagebuilder toolchain
$(curdir)/builddirs-default:=linux
$(curdir)/builddirs-install:=linux $(if $(CONFIG_SDK),sdk) $(if $(CONFIG_IB),imagebuilder) $(if $(CONFIG_MAKE_TOOLCHAIN),toolchain)
$(curdir)/sdk/install:=$(curdir)/linux/install
$(curdir)/imagebuilder/install:=$(curdir)/linux/install
$(eval $(call stampfile,$(curdir),target,prereq,.config))
$(eval $(call stampfile,$(curdir),target,compile,$(TMP_DIR)/.build))
$(eval $(call stampfile,$(curdir),target,install,$(TMP_DIR)/.build))
$($(curdir)/stamp-install): $($(curdir)/stamp-compile)
$(eval $(call subdir,$(curdir)))
其中 curdir
代表当前目录. 该 Makefile
前半部分定义了一些变量,这些变量在后续的 stampfile
, subdir
函数中被调用。target
, package
等目录的 Makefile
都是先调用 stampfile
生成特定目标的依赖和指令,然后再调用 subdir
生成各个子目录下目标的编译规则。至于具体怎么生成,继续往后看。
subdir.mk
接下来看本文的主角 —— subdir.mk
, 这是编译项目过程中 非常重要 的文件,它可以动态定义和生成编译子目录相关目标的规则,包括 package
, target
, tools
, toolchain
等子目录相关目标的规则。 subdir.mk
也是编译生成 world
目标必不可少的文件,主 Makefile
在 include subdir.mk
之后会 include
以下 Makefile
:
- target/Makefile
- package/Makefile
- tools/Makefile
- toolchain/Makefile
这些子目录的 Makefile
会调用 subdir.mk
定义的以下两个函数:
- subdir
- stampfile
这两个函数是配合起来一起用的,先通过 stampfile
生成 stamp-$(target)
相关规则,然后通过 subddir
生成各个子目录相关目标的规则(如 target/linux/clean
)。
stampfile
先来看 stampfile
.
# Parameters: <subdir> <name> <target> <depends> <config options> <stampfile location>
define stampfile
$(1)/stamp-$(3):=$(if $(6),$(6),$(STAGING_DIR))/stamp/.$(2)_$(3)$(5)
$$($(1)/stamp-$(3)): $(TMP_DIR)/.build $(4)
@+$(SCRIPT_DIR)/timestamp.pl -n $$($(1)/stamp-$(3)) $(1) $(4) || \
$(MAKE) $(if $(QUIET),--no-print-directory) $$($(1)/flags-$(3)) $(1)/$(3)
@mkdir -p $$$$(dirname $$($(1)/stamp-$(3)))
@touch $$($(1)/stamp-$(3))
$$(if $(call debug,$(1),v),,.SILENT: $$($(1)/stamp-$(3)))
.PRECIOUS: $$($(1)/stamp-$(3)) # work around a make bug
$(1)//clean:=$(1)/stamp-$(3)/clean
$(1)/stamp-$(3)/clean: FORCE
@rm -f $$($(1)/stamp-$(3))
endef
首行注释了 stampfile
的参数列表,以 target/Makefile
首个 stampfile
调用为例。
# Parameters: <subdir> <name> <target> <depends> <config options> <stampfile location>
$(eval $(call stampfile,$(curdir),target,prereq,.config))
# subdir: $(curdir) - "target"
# name: "target"
# target: "prereq"
# depends: ".config"
# config options: ""
# stampfile location: ""
为了方便调试,可以在该行前添加一行, 将 eval
改成 warning
,这样就可以在编译 log 中看到解析后的编译规则啦。
# add below function call to get the details of stampfile
$(warning $(call stampfile,$(curdir),target,prereq,.config))
$(eval $(call stampfile,$(curdir),target,prereq,.config))
ok, 接下来清除 所有 编译结果,然后重新编译 defconfig
, 编译过程就会调用 target/Makefile
并执行以上语句。
make distclean
make -d DEBUG=vltdr defconfig > log 2>&1
截取 log 中打印以上 warning
信息的部分如下:
target/stamp-prereq:=/home/litreily/openwrt/staging_dir/target-_/stamp/.target_prereq
$(target/stamp-prereq): /home/litreily/openwrt/tmp/.build .config
@+/home/litreily/openwrt/scripts/timestamp.pl -n $(target/stamp-prereq) target .config || make $(target/flags-prereq) target/prereq
@mkdir -p $$(dirname $(target/stamp-prereq))
@touch $(target/stamp-prereq)
$(if $(findstring v,$(if $(DEBUG_SCOPE_DIR),$(if $(filter $(DEBUG_SCOPE_DIR)%,target),vltdr),vltdr)),,.SILENT: $(target/stamp-prereq))
.PRECIOUS: $(target/stamp-prereq) # work around a make bug
target//clean:=target/stamp-prereq/clean
target/stamp-prereq/clean: FORCE
@rm -f $(target/stamp-prereq)
由于默认没有 .config
文件,所以解析出来的 STAGING_DIR
为 target-_
, 如果是 .config
已存在的情况下,得到的结果更完整,当然这不是我们研究的重点。
从以上解析结果可以看出,stampfile
会生成 4 个目标规则:
$(target/stamp-prereq)
: 依赖tmp/.build
,.config
.SILENT
: 是在没有定义 DEBUG 参数时才会生成的目标,其依赖就是刚定义的$(target/stamp-prereq)
, 可以忽略.PRECIOUS
: 根据注释像是解决某个 bug 的 workaround, 可以忽略target/stamp-prereq/clean
: 用来删除目标$(target/stamp-prereq)
的伪目标
根据分析,以上结果可以进一步简化。
target/stamp-prereq:=/home/litreily/openwrt/staging_dir/target-_/stamp/.target_prereq
$(target/stamp-prereq): /home/litreily/openwrt/tmp/.build .config
@+/home/litreily/openwrt/scripts/timestamp.pl -n $(target/stamp-prereq) target .config || make $(target/flags-prereq) target/prereq
@mkdir -p $$(dirname $(target/stamp-prereq))
@touch $(target/stamp-prereq)
target//clean:=target/stamp-prereq/clean
target/stamp-prereq/clean: FORCE
@rm -f $(target/stamp-prereq)
同理也可以分析 target/Makefile
中剩余两个 stampfile
调用。
$(warning $(call stampfile,$(curdir),target,compile,$(TMP_DIR)/.build))
$(eval $(call stampfile,$(curdir),target,compile,$(TMP_DIR)/.build))
$(warning $(call stampfile,$(curdir),target,install,$(TMP_DIR)/.build))
$(eval $(call stampfile,$(curdir),target,install,$(TMP_DIR)/.build))
同样进行简化,得到编译目标及其规则:
# $(eval $(call stampfile,$(curdir),target,compile,$(TMP_DIR)/.build))
target/stamp-compile:=/home/litreily/openwrt/staging_dir/target-mips_24kc_musl/stamp/.target_compile
$(target/stamp-compile): /home/litreily/openwrt/tmp/.build /home/litreily/openwrt/tmp/.build
@+/home/litreily/openwrt/scripts/timestamp.pl -n $(target/stamp-compile) target /home/litreily/openwrt/tmp/.build || make $(target/flags-compile) target/compile
@mkdir -p $$(dirname $(target/stamp-compile))
@touch $(target/stamp-compile)
target//clean:=target/stamp-compile/clean
target/stamp-compile/clean: FORCE
@rm -f $(target/stamp-compile)
# $(eval $(call stampfile,$(curdir),target,install,$(TMP_DIR)/.build))
target/stamp-install:=/home/litreily/openwrt/staging_dir/target-mips_24kc_musl/stamp/.target_install
$(target/stamp-install): /home/litreily/openwrt/tmp/.build /home/litreily/openwrt/tmp/.build
@+/home/litreily/openwrt/scripts/timestamp.pl -n $(target/stamp-install) target /home/litreily/openwrt/tmp/.build || make $(target/flags-install) target/install
@mkdir -p $$(dirname $(target/stamp-install))
@touch $(target/stamp-install)
target//clean:=target/stamp-install/clean
target/stamp-install/clean: FORCE
注意:
$(target/stamp-compile)
和$(target/stamp-install)
的依赖包含两个重复的tmp/.build
, 看起来像是冗余的,或许可以将target/Makefile
优化下。
分析到这一步,stampfile
对 target 目录生成的目标已经都解析出来了,就是下面这些.
$(target/stamp-prereq):
target/stamp-prereq/clean:
$(target/stamp-compile):
target/stamp-compile/clean:
$(target/stamp-install):
target/stamp-install/clean:
这些目标在主 Makefile 是作为其它目标的依赖,也是目标 world
编译过程中的中间依赖目标。
注意: 每个
stampfile
除了生成目标规则都,都定义了一个target//clean
变量,这个在subdir
中会调用到。
ok, stampfile
分析完了,接下来看下 subdir
要完成哪些工作。
subdir
# Parameters: <subdir>
define subdir
$(call warn,$(1),d,D $(1))
$(foreach bd,$($(1)/builddirs),
$(call warn,$(1),d,BD $(1)/$(bd))
$(foreach target,$(SUBTARGETS) $($(1)/subtargets),
$(foreach btype,$(buildtypes-$(bd)),
$(call warn_eval,$(1)/$(bd),t,T,$(1)/$(bd)/$(btype)/$(target): $(if $(NO_DEPS)$(QUILT),,$($(1)/$(bd)/$(btype)/$(target)) $(call $(1)//$(btype)/$(target),$(1)/$(bd)/$(btype))))
$(call log_make,$(1)/$(bd),$(target),$(btype),$(filter-out __default,$(variant))) \
|| $(call ERROR,$(2), ERROR: $(1)/$(bd) [$(btype)] failed to build.,$(findstring $(bd),$($(1)/builddirs-ignore-$(btype)-$(target))))
$(if $(call diralias,$(bd)),$(call warn_eval,$(1)/$(bd),l,T,$(1)/$(call diralias,$(bd))/$(btype)/$(target): $(1)/$(bd)/$(btype)/$(target)))
)
$(call warn_eval,$(1)/$(bd),t,T,$(1)/$(bd)/$(target): $(if $(NO_DEPS)$(QUILT),,$($(1)/$(bd)/$(target)) $(call $(1)//$(target),$(1)/$(bd))))
$(foreach variant,$(if $(BUILD_VARIANT),$(BUILD_VARIANT),$(if $(strip $($(1)/$(bd)/variants)),$($(1)/$(bd)/variants),$(if $($(1)/$(bd)/default-variant),$($(1)/$(bd)/default-variant),__default))),
$(if $(BUILD_LOG),@mkdir -p $(BUILD_LOG_DIR)/$(1)/$(bd)/$(filter-out __default,$(variant)))
$(if $($(1)/autoremove),$(call rebuild_check,$(1)/$(bd),$(target),,$(filter-out __default,$(variant))))
$(call log_make,$(1)/$(bd),$(target),,$(filter-out __default,$(variant))) \
|| $(call ERROR,$(1), ERROR: $(1)/$(bd) failed to build$(if $(filter-out __default,$(variant)), (build variant: $(variant))).,$(findstring $(bd),$($(1)/builddirs-ignore-$(target))))
)
$(if $(PREREQ_ONLY)$(DUMP_TARGET_DB),,
# aliases
$(if $(call diralias,$(bd)),$(call warn_eval,$(1)/$(bd),l,T,$(1)/$(call diralias,$(bd))/$(target): $(1)/$(bd)/$(target)))
)
)
)
$(foreach target,$(SUBTARGETS) $($(1)/subtargets),$(call subtarget,$(1),$(target)))
endef
别看这么复杂,其中很大一部分是 debug 信息,只有在 DEBUG
值不为空的情况下才会打印,否则就是空值。在编译的时候加上 DEBUG=vltdr
, 可以看到以下详细的调试信息,也能方便理解。
target/Makefile:23: D target
target/Makefile:23: BD target/linux
target/Makefile:23: T target/linux/clean: target/stamp-install/clean
target/Makefile:23: T target/linux/download:
target/Makefile:23: T target/linux/prepare:
target/Makefile:23: T target/linux/compile:
target/Makefile:23: T target/linux/update:
target/Makefile:23: T target/linux/refresh:
target/Makefile:23: T target/linux/prereq:
target/Makefile:23: T target/linux/dist:
target/Makefile:23: T target/linux/distcheck:
target/Makefile:23: T target/linux/configure:
target/Makefile:23: T target/linux/check:
target/Makefile:23: T target/linux/check-depends:
target/Makefile:23: T target/linux/install:
target/Makefile:23: BD target/sdk
target/Makefile:23: T target/sdk/clean: target/stamp-install/clean
target/Makefile:23: T target/sdk/download:
target/Makefile:23: T target/sdk/prepare:
target/Makefile:23: T target/sdk/compile:
target/Makefile:23: T target/sdk/update:
target/Makefile:23: T target/sdk/refresh:
target/Makefile:23: T target/sdk/prereq:
target/Makefile:23: T target/sdk/dist:
target/Makefile:23: T target/sdk/distcheck:
target/Makefile:23: T target/sdk/configure:
target/Makefile:23: T target/sdk/check:
target/Makefile:23: T target/sdk/check-depends:
target/Makefile:23: T target/sdk/install: target/linux/install
target/Makefile:23: BD target/imagebuilder
target/Makefile:23: T target/imagebuilder/clean: target/stamp-install/clean
target/Makefile:23: T target/imagebuilder/download:
target/Makefile:23: T target/imagebuilder/prepare:
target/Makefile:23: T target/imagebuilder/compile:
target/Makefile:23: T target/imagebuilder/update:
target/Makefile:23: T target/imagebuilder/refresh:
target/Makefile:23: T target/imagebuilder/prereq:
target/Makefile:23: T target/imagebuilder/dist:
target/Makefile:23: T target/imagebuilder/distcheck:
target/Makefile:23: T target/imagebuilder/configure:
target/Makefile:23: T target/imagebuilder/check:
target/Makefile:23: T target/imagebuilder/check-depends:
target/Makefile:23: T target/imagebuilder/install: target/linux/install
target/Makefile:23: BD target/toolchain
target/Makefile:23: T target/toolchain/clean: target/stamp-install/clean
target/Makefile:23: T target/toolchain/download:
target/Makefile:23: T target/toolchain/prepare:
target/Makefile:23: T target/toolchain/compile:
target/Makefile:23: T target/toolchain/update:
target/Makefile:23: T target/toolchain/refresh:
target/Makefile:23: T target/toolchain/prereq:
target/Makefile:23: T target/toolchain/dist:
target/Makefile:23: T target/toolchain/distcheck:
target/Makefile:23: T target/toolchain/configure:
target/Makefile:23: T target/toolchain/check:
target/Makefile:23: T target/toolchain/check-depends:
target/Makefile:23: T target/toolchain/install:
target/Makefile:23: T target/clean: target/linux/clean
target/Makefile:23: T target/download: target/linux/download
target/Makefile:23: T target/prepare: target/linux/prepare
target/Makefile:23: T target/compile: target/linux/compile
target/Makefile:23: T target/update: target/linux/update
target/Makefile:23: T target/refresh: target/linux/refresh
target/Makefile:23: T target/prereq: target/linux/prereq
target/Makefile:23: T target/dist: target/linux/dist
target/Makefile:23: T target/distcheck: target/linux/distcheck
target/Makefile:23: T target/configure: target/linux/configure
target/Makefile:23: T target/check: target/linux/check
target/Makefile:23: T target/check-depends: target/linux/check-depends
target/Makefile:23: T target/install: target/linux/install
其中:
D
: Directory, 当前目录BD
: builddirs, 在target/Makefile
中定义的builddirs
变量T
: subtargets, 具体的目标
结合以上调试信息可以更容易分析 subdir
的执行流程,函数内部主要通过 3 层 foreach
循环逐层遍历以下信息:
$(call warn,$(1),d,D $(1))
# 1. builddirs: linux sdk imagebuilder toolchain`
$(foreach bd,$($(1)/builddirs),
$(call warn,$(1),d,BD $(1)/$(bd))
# 2. subtargets: clean download prepare compile update refresh prereq dist distcheck configure check check-depends install
$(foreach target,$(SUBTARGETS) $($(1)/subtargets),
# 3. buildtypes: ""
$(foreach btype,$(buildtypes-$(bd)),
$(call warn_eval,$(1)/$(bd),t,T,$(1)/$(bd)/$(target): $(if $(NO_DEPS)$(QUILT),,$($(1)/$(bd)/$(target)) $(call $(1)//$(target),$(1)/$(bd))))
# 3. variant: __default
$(foreach variant, ...
#...
$(foreach target,$(SUBTARGETS) $($(1)/subtargets),$(call subtarget,$(1),$(target)))
对其中的个别变量进行说明,$(bd)
对应 target
子目录 linux
, sdk
等;$(SUBTARGETS)
是在 rules.mk
中定义的。
# include/subdir.mk
SUBTARGETS:=$(DEFAULT_SUBDIR_TARGETS)
# rules.mk
DEFAULT_SUBDIR_TARGETS:=clean download prepare compile update refresh prereq dist distcheck configure check check-depends
遍历 target 的语句是 $(foreach target, $(SUBTARGETS) $(1)/subtargets)
, 就是说,除了上面的默认 targets 之外,还要加上 $(1)/subtargets
的值,对应 target/Makefile
中的值是 install
. 这与上面展示的 log 是相对应的。
整体结构清楚了,但是还是不好分析,那么先根据以下原则对 subdir
进行精简:
- 忽略调试信息
warn
- 解析变量
- 解析
if
条件判断,仅留下有效分支
举例说明,
subdir
中的buildtypes-$(bd)
为空,所以其对应foreach
可以直接删除;BUILD_VARIANT
未定义,对应if
判断结果为__default
;$(BUILD_LOG)
未定义,对应if
分支删除。$($(1)/autoremove)
未定义,对应if
分支删除。
根据这些规则后简化的 subdir
如下:
# Parameters: <subdir>
define subdir
$(foreach bd,$($(1)/builddirs),
$(foreach target,$(SUBTARGETS) $($(1)/subtargets),
$(call warn_eval,$(1)/$(bd),t,T,$(1)/$(bd)/$(target): $(call $(1)//$(target),$(1)/$(bd)))
$(foreach variant,__default,
$(call log_make,$(1)/$(bd),$(target),,$(filter-out __default,$(variant)))
)
# aliases
$(if $(call diralias,$(bd)),$(call warn_eval,$(1)/$(bd),l,T,$(1)/$(call diralias,$(bd))/$(target): $(1)/$(bd)/$(target)))
)
)
$(foreach target,$(SUBTARGETS) $($(1)/subtargets),$(call subtarget,$(1),$(target)))
endef
example
以遍历 $(bd)
值为 linux
说明以上精简后的操作。
$(call warn_eval,$(1)/$(bd),t,T,$(1)/$(bd)/$(target): $(call $(1)//$(target),$(1)/$(bd)))
在定义了 DEBUG=vltdr
的情况下,以上指令会打印 target 相关信息。
target/Makefile:23: T target/linux/clean: target/stamp-install/clean
target/Makefile:23: T target/linux/download:
target/Makefile:23: T target/linux/prepare:
target/Makefile:23: T target/linux/compile:
target/Makefile:23: T target/linux/update:
target/Makefile:23: T target/linux/refresh:
target/Makefile:23: T target/linux/prereq:
target/Makefile:23: T target/linux/dist:
target/Makefile:23: T target/linux/distcheck:
target/Makefile:23: T target/linux/configure:
target/Makefile:23: T target/linux/check:
target/Makefile:23: T target/linux/check-depends:
target/Makefile:23: T target/linux/install:
从前面 warn_eval
的定义可知,它除了打印 warning 信息外,还会将最后一个参数单独执行一遍。所以它包含两个功能,warn
和 eval
, 且 eval
更重要。它实际上也定义了一组编译目标。也就是 warn_eval
的第4个参数:
$(1)/$(bd)/$(target): $(call $(1)//$(target),$(1)/$(bd))
将变量依次替换得到:
target/linux/clean: $(call target//clean,target/linux)
$(target/linux)
: 变量没有定义$(target//clean)
: 前面stampfile
定义的变量,其值为target/stamp-install/clean
最终解析出来如下:
target/linux/clean: target/stamp-install/clean
该目标除了依赖之外,还有对应的指令,也就是紧随其后的 foreach
语句。
$(foreach variant,__default,
$(call log_make,$(1)/$(bd),$(target),,$(filter-out __default,$(variant)))
)
看到这应该明了了,就是这个 log_make
调用,顺藤摸瓜,来看下这个函数的定义。
subdir_make_opts = \
-r -C $(1) \
BUILD_SUBDIR="$(1)" \
BUILD_VARIANT="$(4)"
# 1: subdir
# 2: target
# 3: build type
# 4: build variant
log_make = \
$(if $(call debug,$(1),v),,@)+ \
$(if $(BUILD_LOG), \
set -o pipefail; \
mkdir -p $(BUILD_LOG_DIR)/$(1)$(if $(4),/$(4));) \
$(SCRIPT_DIR)/time.pl "time: $(1)$(if $(4),/$(4))/$(if $(3),$(3)-)$(2)" \
$$(SUBMAKE) $(subdir_make_opts) $(if $(3),$(3)-)$(2) \
$(if $(BUILD_LOG),SILENT= 2>&1 | tee $(BUILD_LOG_DIR)/$(1)$(if $(4),/$(4))/$(if $(3),$(3)-)$(2).txt)
同样,先进行简化,DEBUG
, BUILD_LOG
均未定义, 对应分支可删除;已知全局变量可以替换掉。
log_make = \
@+ \
scripts/time.pl "time: $(1)$(if $(4),/$(4))/$(if $(3),$(3)-)$(2)" \
$$(SUBMAKE) -r -C $(1) BUILD_SUBDIR="$(1)" BUILD_VARIANT="$(4)" \
$(if $(3),$(3)-)$(2)
接着回到 subdir
中的 log_make
的调用处:
$(call log_make,$(1)/$(bd),$(target),,$(filter-out __default,$(variant)))
按照简化后的 log_make
代入参数解析以上指令。
+ scripts/time.pl "time: target/linux/clean" make -w -r -C target/linux BUILD_SUBDIR="target/linux" BUILD_VARIANT="" clean
将目标、依赖及其指令组合起来就是这样的:
target/linux/clean: target/stamp-install/clean
+ scripts/time.pl "time: target/linux/clean" make -w -r -C target/linux BUILD_SUBDIR="target/linux" BUILD_VARIANT="" clean
看看这个目标定义,依赖项 target/stamp-install/clean
的规则在 stampfile
中定义好了;其指令是通过 perl 脚本 time.pl
执行 make
指令并记录时间信息。
到此就完成了对 subdir
遍历 subtargets
之一 target/linux/clean
的解析,其它 target
也是类似的。
# target/linux
target/linux/clean: target/stamp-install/clean
+ scripts/time.pl "time: target/linux/clean" make -w -r -C target/linux BUILD_SUBDIR="target/linux" BUILD_VARIANT="" clean
target/linux/download:
+ scripts/time.pl "time: target/linux/download" make -w -r -C target/linux BUILD_SUBDIR="target/linux" BUILD_VARIANT="" download
target/linux/prepare:
+ scripts/time.pl "time: target/linux/download" make -w -r -C target/linux BUILD_SUBDIR="target/linux" BUILD_VARIANT="" prepare
#...
target/linux/install:
+ scripts/time.pl "time: target/linux/install" make -w -r -C target/linux BUILD_SUBDIR="target/linux" BUILD_VARIANT="" install
# target/sdk
target/sdk/clean: target/stamp-install/clean
+ scripts/time.pl "time: target/sdk/clean" make -w -r -C target/sdk BUILD_SUBDIR="target/sdk" BUILD_VARIANT="" clean
target/sdk/download:
+ scripts/time.pl "time: target/sdk/download" make -w -r -C target/sdk BUILD_SUBDIR="target/sdk" BUILD_VARIANT="" download
#...
target/sdk/install:
+ scripts/time.pl "time: target/sdk/install" make -w -r -C target/sdk BUILD_SUBDIR="target/sdk" BUILD_VARIANT="" install
# target/imagebuilder
target/imagebuilder/clean: target/stamp-install/clean
+ scripts/time.pl "time: target/imagebuilder/clean" make -w -r -C target/imagebuilder BUILD_SUBDIR="target/imagebuilder" BUILD_VARIANT="" clean
target/imagebuilder/download:
+ scripts/time.pl "time: target/imagebuilder/download" make -w -r -C target/imagebuilder BUILD_SUBDIR="target/imagebuilder" BUILD_VARIANT="" download
#...
target/imagebuilder/install:
+ scripts/time.pl "time: target/imagebuilder/install" make -w -r -C target/imagebuilder BUILD_SUBDIR="target/imagebuilder" BUILD_VARIANT="" install
# target/toolchain
target/toolchain/clean: target/stamp-install/clean
+ scripts/time.pl "time: target/toolchain/clean" make -w -r -C target/toolchain BUILD_SUBDIR="target/toolchain" BUILD_VARIANT="" clean
target/toolchain/download:
+ scripts/time.pl "time: target/toolchain/download" make -w -r -C target/toolchain BUILD_SUBDIR="target/toolchain" BUILD_VARIANT="" download
#...
target/toolchain/install:
+ scripts/time.pl "time: target/toolchain/install" make -w -r -C target/toolchain BUILD_SUBDIR="target/toolchain" BUILD_VARIANT="" install
in the end
最后,还有一个小尾巴没讲到。注意看前面给出 log 的最后一部分。
target/Makefile:23: T target/clean: target/linux/clean
target/Makefile:23: T target/download: target/linux/download
target/Makefile:23: T target/prepare: target/linux/prepare
target/Makefile:23: T target/compile: target/linux/compile
target/Makefile:23: T target/update: target/linux/update
target/Makefile:23: T target/refresh: target/linux/refresh
target/Makefile:23: T target/prereq: target/linux/prereq
target/Makefile:23: T target/dist: target/linux/dist
target/Makefile:23: T target/distcheck: target/linux/distcheck
target/Makefile:23: T target/configure: target/linux/configure
target/Makefile:23: T target/check: target/linux/check
target/Makefile:23: T target/check-depends: target/linux/check-depends
target/Makefile:23: T target/install: target/linux/install
这里打印的信息是哪里来的呢?其实也是 subdir
函数,是该函数最后一个 foreach
循环遍历出来的。
$(foreach target,$(SUBTARGETS) $($(1)/subtargets),$(call subtarget,$(1),$(target)))
经过前面的推导,这部分简直是小 case, 无非是遍历所有 target
, 然后使用函数 subtarget
去生成规则。
subtarget-default = $(filter-out ., \
$(if $($(1)/builddirs-$(2)),$($(1)/builddirs-$(2)), \
$(if $($(1)/builddirs-default),$($(1)/builddirs-default), \
$($(1)/builddirs))))
define subtarget
$(call warn_eval,$(1),t,T,$(1)/$(2): $($(1)/) $(foreach bd,$(call subtarget-default,$(1),$(2)),$(1)/$(bd)/$(2)))
endef
同样对其进行简化。
# $(1): target
# $(2): $(target) -> "clean, download, ..., install"
# $(target/builddirs-clean): ""
# $(target/builddirs-default): linux # defined in target/Makefile
subtarget-default = $(filter-out ., $(if $($(1)/builddirs-$(2)),$($(1)/builddirs-$(2)), linux))
define subtarget
$(1)/$(2): $($(1)/) $(foreach bd,$(call subtarget-default,$(1),$(2)),$(1)/$(bd)/$(2))
endef
然后代入参数 $(1): target
, $(2): clean
, 得到:
subtarget-default = linux
define subtarget
target/clean: target/linux/clean
endef
这与 log 中的一致,其它 target
的规则也是类似,这里最主要的就是一个默认编译规则,相当于在不指定具体 target
哪个子目录时,根据默认值进行编译,target/Makefile
定义的 $(builddirs-default)
就是 linux
, 也就是说,执行 make target/clean
与 make target/linux/clean
是一样的。
除 target
目录外,package
, tools
, toolchain
目录也是类似的。
到这,最后的小尾巴也讲完啦。
Tips
使用 makefile 的 warning
函数打印信息可以快速梳理 Makefile 的执行流程。
$(warning info)
# Example
# target/Makefile
$(warning $(call subdir,$(curdir)))
配合 make -n V=s
能够打印指令信息, 如果加上 DEBUG
可以显示更加详细的调试信息。
make -d V=s DEBUG=dltvr
# -n to print command only
make -d -n V=s DEBUG=dltvr
Reference
版权声明:本博客所有文章除特殊声明外,均采用 CC BY-NC 4.0 许可协议。转载请注明出处 litreily的博客!