一. 前言
subdir严格来说是一个命令包,本文接下来都以函数称呼。openwrt的subdir函数非常重要,它位于openwrt/include/subdir.mk文件下,tools,toolchain,target和package模块的编译都使用到了subdir,如下:
openwrt/tools/Makefile:
curdir:=tools
......
$(eval $(call subdir,$(curdir)))
openwrt/toolchain/Makefile:
curdir:=toolchain
.....
$(eval $(call subdir,$(curdir)))
openwrt/package/Makefile和openwrt/target/Makefile也是一样的格式,就不举例了。接下来大致分析下subdir函数的工作原理。
二. subdir分析
subdir位于openwrt/include/subdir.mk里面,源码如下:
# 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))) \
$(if $(findstring $(bd),$($(1)/builddirs-ignore-$(btype)-$(target))), || $(call ERROR,$(1), ERROR: $(1)/$(bd) [$(btype)] failed to build.))
$(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))) \
$(if $(findstring $(bd),$($(1)/builddirs-ignore-$(target))), || $(call ERROR,$(1), ERROR: $(1)/$(bd) failed to build$(if $(filter-out __default,$(variant)), (build variant: $(variant))).))
)
$(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
分析之前,我们假设我们当前在编译tools,并以tools下的bison为例。
接下来,我将逐行分析源码:
$(call warn,$(1),d,D $(1))
该行调用了warn函数,warn函数在openwrt/include/debug.mk中,这句用于调试,可忽略。
$(foreach bd,$($(1)/builddirs),
......
)
该行foreach函数结束的括号为subdir函数的倒数第三行的括号。因为$(1)表示tools,所以展开为$(foreach bd,$(tools/builddirs)),从分析tools/Makefile可知,tools/builddirs的值如下:
gmp mpfr mpc libelf m4 libtool autoconf automake flex bison pkg-config sed mklibs sstrip make-ext4fs e2fsprogs mtd-utils mkimage firmware-utils patch-image patch quilt yaffs2 flock padjffs2 mm-macros missing-macros xz cmake scons bc findutils gengetopt patchelf lzma squashfs4 wrt350nv2-builder upslug2 upx qemu elftosb mtools dosfstools lzma-old squashfs b43-tools ppl cloog sparse
所以,bd依次为以上各个工具的名称。
$(call warn,$(1),d,BD $(1)/$(bd))
该行是调试信息,也可暂时跳过。
$(foreach target,$(SUBTARGETS) $($(1)/subtargets),
......
)
该行foreach函数结束的括号为subdir函数的倒数第四行的括号。由于SUBTARGETS:=$(DEFAULT_SUBDIR_TARGETS),而DEFAULT_SUBDIR_TARGETS的定义在openwrt/rules.mk里面,如下:
DEFAULT_SUBDIR_TARGETS:=clean download prepare compile update refresh prereq dist distcheck configure check check-depends
由于tools/subtargets没有定义,所以为空。所以该行代码展开如下:
$(foreach target,clean download prepare compile update refresh prereq dist distcheck configure check check-depends,
......
)
target依次为clean,download直到check-depends。
$(foreach btype,$(buildtypes-$(bd)),
......
)
此处,bd以bison为例,展开为$(foreach btype,$(buildtypes-bison)),由于buildtypes-bison未定义,所以此处foreach函数展开为空。
注意:在openwrt/tmp/.packageinfo文件中,拥有Build-Types的字段,该package的$(buildtype-$(package))不为空,具体机制后续分析。
$(call warn_eval,$(1)/$(bd),t,T,$(1)/$(bd)/$(target): $(if $(NO_DEPS)$(QUILT),,$($(1)/$(bd)/$(target)) $(call $(1)//$(target),$(1)/$(bd))))
warn_eval函数的定义在openwrt/include/debug.mk文件中,如下:
define warn_eval
$(call warn,$(1),$(2),$(3) $(4))
$(4)
endef
warn_eval函数是由warn函数和第四个参数组成,所以这里只需关心第四个参数即可。NO_DEPS和QUILT未定义,所以,整句代码展开为如下:
tools/bison/compile: $(tools/bison/compile) $(call tools//compile,tools/bison)
分析openwrt/tools/Makefile可知,tools/bison/compile=tools/flex/install tools/patch/install。tools//compile=$(STAGING_DIR)/.prepared $(STAGING_DIR_HOST)/.prepared。STAGING_DIR=openwrt/staging_dir/target-mipsel_24kc_musl/,STAGING_DIR_HOST=openwrt/staging_dir/host。注意,STAGING_DIR和STAGING_DIR_HOST都是绝对路径。所以,整句展开为,如下:
tools/bison/compile: tools/flex/install tools/patch/install openwrt/staging_dir/target-mipsel_24kc_musl openwrt/staging_dir/host
这里表示,在编译bison之前,需要先编译flex和patch,并且staging_dir/target_xxx和staging_dir/host存在。
$(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))),
......
)
此处,BUILD_VARIANT为空,所以此处展开为:
$(foreach variant, $(if $(strip $(tools/bison/variants)),$(tools/bison/variants),$(if tools/bison/default-variant),$(tools/bison/default-variant),default))
上面代码表示,如果tools/bison/variants有定义,则取tools/bison/variants变量的值,否则,查看tools/bison/default-variant变量是否有值,如果没有则取__default,对于bison,则取__default。
$(if $(BUILD_LOG),@mkdir -p $(BUILD_LOG_DIR)/$(1)/$(bd)/$(filter-out __default,$(variant)))
BUILD_LOG在rule.mk中定义,由CONFIG_BUILD_LOG变量控制,由于CONFIG_BUILD_LOG没有定义,则此行代码忽略。
$(if $($(1)/autoremove),$(call rebuild_check,$(1)/$(bd),$(target),,$(filter-out __default,$(variant))))
bison/autoremove未定义,则此行代码不执行。
$(call log_make,$(1)/$(bd),$(target),,$(filter-out __default,$(variant)))
展开为,如下:
$(call log_make,tools/bison,compile,,)
log_make定义如下:
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未定义,time.pl处理先不管。subdir_make_opts定义为
subdir_make_opts = \
-r -C $(1) \
BUILD_SUBDIR="$(1)" \
BUILD_VARIANT="$(4)"
所以,整行代码展开为:
make -r -C tools/bison compile
$(foreach target,$(SUBTARGETS) $($(1)/subtargets),$(call subtarget,$(1),$(target)))
这行代码是用于不指定目标,直接make的情况。subtarget的定义为:
define subtarget
$(call warn_eval,$(1),t,T,$(1)/$(2): $($(1)/) $(foreach bd,$(call subtarget-default,$(1),$(2)),$(1)/$(bd)/$(2)))
endef
subtarget-default的定义为:
subtarget-default = $(filter-out ., \
$(if $($(1)/builddirs-$(2)),$($(1)/builddirs-$(2)), \
$(if $($(1)/builddirs-default),$($(1)/builddirs-default), \
$($(1)/builddirs))))
该行表示,如果tools/builddirs-compile不为空,则subtarget-default的值为$(tools/builddirs-compile),否则看tools/builddirs-default是否为空,不为空,subtarget-default的值为$(tools/builddirs-default),否则为$(tools/builddirs)。
所以,整句代码展开为:
tools/clean: .config prereq tools/gmp/clean tools/mpfr/clean tools/mpc/clean tools/libelf/clean tools/expat/clean tools/m4/clean tools/libtool/clean tools/autoconf/clean tools/automake/clean tools/flex/clean tools/bison/clean tools/pkg-config/clean tools/mklibs/clean tools/zlib/clean tools/sstrip/clean tools/make-ext4fs/clean tools/e2fsprogs/clean tools/mtd-utils/clean tools/mkimage/clean tools/firmware-utils/clean tools/patch-image/clean tools/quilt/clean tools/padjffs2/clean tools/mm-macros/clean tools/missing-macros/clean tools/cmake/clean tools/scons/clean tools/bc/clean tools/findutils/clean tools/gengetopt/clean tools/patchelf/clean tools/mtools/clean tools/dosfstools/clean tools/libressl/clean tools/lzma/clean tools/squashfskit4/clean tools/zip/clean tools/tar/clean tools/xz/clean tools/patch/clean tools/flock/clean tools/sed/clean
tools/download: .config prereq tools/gmp/download tools/mpfr/download tools/mpc/download tools/libelf/download tools/expat/download tools/m4/download tools/libtool/download tools/autoconf/download tools/automake/download tools/flex/download tools/bison/download tools/pkg-config/download tools/mklibs/download tools/zlib/download tools/sstrip/download tools/make-ext4fs/download tools/e2fsprogs/download tools/mtd-utils/download tools/mkimage/download tools/firmware-utils/download tools/patch-image/download tools/quilt/download tools/padjffs2/download tools/mm-macros/download tools/missing-macros/download tools/cmake/download tools/scons/download tools/bc/download tools/findutils/download tools/gengetopt/download tools/patchelf/download tools/mtools/download tools/dosfstools/download tools/libressl/download tools/lzma/download tools/squashfskit4/download tools/zip/download tools/tar/download tools/xz/download tools/patch/download tools/flock/download tools/sed/download
tools/prepare: .config prereq tools/gmp/prepare tools/mpfr/prepare tools/mpc/prepare tools/libelf/prepare tools/expat/prepare tools/m4/prepare tools/libtool/prepare tools/autoconf/prepare tools/automake/prepare tools/flex/prepare tools/bison/prepare tools/pkg-config/prepare tools/mklibs/prepare tools/zlib/prepare tools/sstrip/prepare tools/make-ext4fs/prepare tools/e2fsprogs/prepare tools/mtd-utils/prepare tools/mkimage/prepare tools/firmware-utils/prepare tools/patch-image/prepare tools/quilt/prepare tools/padjffs2/prepare tools/mm-macros/prepare tools/missing-macros/prepare tools/cmake/prepare tools/scons/prepare tools/bc/prepare tools/findutils/prepare tools/gengetopt/prepare tools/patchelf/prepare tools/mtools/prepare tools/dosfstools/prepare tools/libressl/prepare tools/lzma/prepare tools/squashfskit4/prepare tools/zip/prepare tools/tar/prepare tools/xz/prepare tools/patch/prepare tools/flock/prepare tools/sed/prepare
tools/compile: .config prereq tools/gmp/compile tools/mpfr/compile tools/mpc/compile tools/libelf/compile tools/expat/compile tools/m4/compile tools/libtool/compile tools/autoconf/compile tools/automake/compile tools/flex/compile tools/bison/compile tools/pkg-config/compile tools/mklibs/compile tools/zlib/compile tools/sstrip/compile tools/make-ext4fs/compile tools/e2fsprogs/compile tools/mtd-utils/compile tools/mkimage/compile tools/firmware-utils/compile tools/patch-image/compile tools/quilt/compile tools/padjffs2/compile tools/mm-macros/compile tools/missing-macros/compile tools/cmake/compile tools/scons/compile tools/bc/compile tools/findutils/compile tools/gengetopt/compile tools/patchelf/compile tools/mtools/compile tools/dosfstools/compile tools/libressl/compile tools/lzma/compile tools/squashfskit4/compile tools/zip/compile tools/tar/compile tools/xz/compile tools/patch/compile tools/flock/compile tools/sed/compile
等等。
tools/$(target)是由stampfile函数里面调用的,如下:
......
$(MAKE) $(if $(QUIET),--no-print-directory) $$($(1)/flags-$(3)) $(1)/$(3)
......
当$(1)/$(3)是tools/compile时,就会调用到subdir的目标。
三. 总结
subdir总的来说就是使用了一个双层的foreach循环,将所有的bd都加上target的所有目标,为每一个不管是tools,toolchain,package还是target,都建立完整的目标和依赖关系。最终编译完整个工程。