OpenWRT subdir.mk
回顾
之前讲述了OpenWRT的Makefile有一个遗留问题如下:
prepare: .config $(tools/stamp-install) $(toolchain/stamp-install) world: prepare $(target/stamp-compile) $(package/stamp-compile) $(package/stamp-install) $(target/stamp-install) FORCE
对于world终极目标$(tools/stamp-install)的依赖是怎么来的,在分析的过程中由于toolchain和tools通过Makefile手动写了依赖,package的Makefile无关信息较多,因此我们选用target/Makefile进行分
那么$(target/stamp-install)到底是怎么进行中展开的我们来一探究竟。
在主Makefile里有如下:
ifneq ($(OPENWRT_BUILD),1)
_SINGLE=export MAKEFLAGS=$(space);
override OPENWRT_BUILD=1
export OPENWRT_BUILD
GREP_OPTIONS=
export GREP_OPTIONS
include $(TOPDIR)/include/debug.mk
include $(TOPDIR)/include/depends.mk
include $(TOPDIR)/include/toplevel.mk
else
include rules.mk
include $(INCLUDE_DIR)/depends.mk
include $(INCLUDE_DIR)/subdir.mk
include target/Makefile
include package/Makefile
include tools/Makefile
include toolchain/Makefile
$(toolchain/stamp-install): $(tools/stamp-install)
$(target/stamp-compile): $(toolchain/stamp-install) $(tools/stamp-install) $(BUILD_DIR)/.prepared
$(package/stamp-compile): $(target/stamp-compile) $(package/stamp-cleanup)
$(package/stamp-install): $(package/stamp-compile)
$(target/stamp-install): $(package/stamp-compile) $(package/stamp-install)
target/Makefile
在主Makefile里通过include target/Makefile进行展开,target/Makefile如下:
# # Copyright (C) 2007 OpenWrt.org # # This is free software, licensed under the GNU General Public License v2. # See /LICENSE for more information. # curdir:=target $(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)/imagebuilder/install:=$(curdir)/linux/install
# stampfile在subdir.mk中定义,其参数如下
#Parameters: <subdir> <name> <target> <depends> <config options> <stampfile location>
# 通过调用$(info ...)打印出展开式 $(eval $(call stampfile,$(curdir),target,prereq,.config)) $(info $(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)))
stampfile
在讲解stampfile之前,我们先回顾上层是如何调用stampfile的,如下:
$(eval $(call stampfile,$(curdir),target,prereq,.config))
根据上面的参数展开
# stampfile subdir name target depends config-options stampfile location
$(eval $(call stampfile,target,target,prereq,.config)
在subdir.mk里定义的stampfile如下:
# Parameters: <subdir> <name> <target> <depends> <config options> <stampfile location>
# 根据上面的信息,我们将其展开如下:
# 参数为$(1) target $(2)target $(3)prereq $(4).config $(5)null $(6)null
# STAGING_DIR在TOPDIR下的rule.mk中定义
# 一下通过call展开
target/stamp-prereq:=staging_dir/target-arm_cortex-a7_uClibc-1.0.14_eabi/stamp/.target_prereq
$(target/stamp-prereq): tmp/.build .config
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)
.PRECIOUS: $(target/stamp-prereq) #防止Makefile只更新文件的时间戳
target/clean := target/stamp-prepre/clean
target/stamp-prereq/clean: FORCE
rm -rf $(target/stamp-prereq)
$(target/stamp-prereq)在主Makefile中有定义如下:
prereq: $(target/stamp-prereq) tmp/.prereq_packages,在Makefile中也可以知道许目标以来prereq因此prereq会被先执行
#在通过eval展开如下,$(target/flags-prereq)没有定义为空:
target/stamp-prereq:=staging_dir/target-arm_cortex-a7_uClibc-1.0.14_eabi/stamp/.target_prereq
staging_dir/target-arm_cortex-a7_uClibc-1.0.14_eabi/stamp/.target_prereq: tmp/.build .config
scripts/timestamp.pl -n $(target/stamp-prereq) target .config || make target/prereq
mkdir -p $(dirname staging_dir/target-arm_cortex-a7_uClibc-1.0.14_eabi/stamp/.target_prereq)
touch staging_dir/target-arm_cortex-a7_uClibc-1.0.14_eabi/stamp/.target_prereq
.PRECIOUS: staging_dir/target-arm_cortex-a7_uClibc-1.0.14_eabi/stamp/.target_prereq #防止Makefile只更新文件的时间戳
target/clean := target/stamp-prepre/clean
target/stamp-prereq/clean: FORCE
rm -rf staging_dir/target-arm_cortex-a7_uClibc-1.0.14_eabi/stamp/.target_prereq
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/stamp-prereq): tmp/.build .config
make $(target/flags-prereq) target/prereq
那么make target/prereq是怎么来的的,这个就是我们要将的另外一个函数subdi
subdir
subdir这个函数比较复杂,我们可以这样做,我们只需知道总结框架,有时候太过深入细节反而让自己迷失
curdir:=target
$(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)/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)
$(info $(call subdir,$(curdir))) #在此处加入info函数,我们看看subdir到底做了什么
$(eval $(call subdir,$(curdir)))
我们执行Make > gg我们将信息存放在当前目录下的gg文件里,如下的gg是我去掉空行生成的
target/linux/clean: target/stamp-install/clean @+ $(SUBMAKE) -r -C target/linux clean BUILD_VARIANT="" # aliases target/linux/download: @+ $(SUBMAKE) -r -C target/linux download BUILD_VARIANT="" # aliases target/linux/prepare: @+ $(SUBMAKE) -r -C target/linux prepare BUILD_VARIANT="" # aliases target/linux/compile: @+ $(SUBMAKE) -r -C target/linux compile BUILD_VARIANT="" # aliases target/linux/install: @+ $(SUBMAKE) -r -C target/linux install BUILD_VARIANT="" # aliases target/linux/update: @+ $(SUBMAKE) -r -C target/linux update BUILD_VARIANT="" # aliases target/linux/refresh: @+ $(SUBMAKE) -r -C target/linux refresh BUILD_VARIANT="" # aliases target/linux/prereq: @+ $(SUBMAKE) -r -C target/linux prereq BUILD_VARIANT="" # aliases target/linux/dist: @+ $(SUBMAKE) -r -C target/linux dist BUILD_VARIANT="" # aliases target/linux/distcheck: @+ $(SUBMAKE) -r -C target/linux distcheck BUILD_VARIANT="" # aliases target/linux/configure: @+ $(SUBMAKE) -r -C target/linux configure BUILD_VARIANT="" # aliases target/sdk/clean: target/stamp-install/clean @+ $(SUBMAKE) -r -C target/sdk clean BUILD_VARIANT="" # aliases target/sdk/download: @+ $(SUBMAKE) -r -C target/sdk download BUILD_VARIANT="" # aliases target/sdk/prepare: @+ $(SUBMAKE) -r -C target/sdk prepare BUILD_VARIANT="" # aliases target/sdk/compile: @+ $(SUBMAKE) -r -C target/sdk compile BUILD_VARIANT="" # aliases target/sdk/install: @+ $(SUBMAKE) -r -C target/sdk install BUILD_VARIANT="" # aliases target/sdk/update: @+ $(SUBMAKE) -r -C target/sdk update BUILD_VARIANT="" # aliases target/sdk/refresh: @+ $(SUBMAKE) -r -C target/sdk refresh BUILD_VARIANT="" # aliases target/sdk/prereq: @+ $(SUBMAKE) -r -C target/sdk prereq BUILD_VARIANT="" # aliases target/sdk/dist: @+ $(SUBMAKE) -r -C target/sdk dist BUILD_VARIANT="" # aliases target/sdk/distcheck: @+ $(SUBMAKE) -r -C target/sdk distcheck BUILD_VARIANT="" # aliases target/sdk/configure: @+ $(SUBMAKE) -r -C target/sdk configure BUILD_VARIANT="" # aliases target/imagebuilder/clean: target/stamp-install/clean @+ $(SUBMAKE) -r -C target/imagebuilder clean BUILD_VARIANT="" # aliases target/imagebuilder/download: @+ $(SUBMAKE) -r -C target/imagebuilder download BUILD_VARIANT="" # aliases target/imagebuilder/prepare: @+ $(SUBMAKE) -r -C target/imagebuilder prepare BUILD_VARIANT="" # aliases target/imagebuilder/compile: @+ $(SUBMAKE) -r -C target/imagebuilder compile BUILD_VARIANT="" # aliases target/imagebuilder/install: target/linux/install @+ $(SUBMAKE) -r -C target/imagebuilder install BUILD_VARIANT="" # aliases target/imagebuilder/update: @+ $(SUBMAKE) -r -C target/imagebuilder update BUILD_VARIANT="" # aliases target/imagebuilder/refresh: @+ $(SUBMAKE) -r -C target/imagebuilder refresh BUILD_VARIANT="" # aliases target/imagebuilder/prereq: @+ $(SUBMAKE) -r -C target/imagebuilder prereq BUILD_VARIANT="" # aliases target/imagebuilder/dist: @+ $(SUBMAKE) -r -C target/imagebuilder dist BUILD_VARIANT="" # aliases target/imagebuilder/distcheck: @+ $(SUBMAKE) -r -C target/imagebuilder distcheck BUILD_VARIANT="" # aliases target/imagebuilder/configure: @+ $(SUBMAKE) -r -C target/imagebuilder configure BUILD_VARIANT="" # aliases target/toolchain/clean: target/stamp-install/clean @+ $(SUBMAKE) -r -C target/toolchain clean BUILD_VARIANT="" # aliases target/toolchain/download: @+ $(SUBMAKE) -r -C target/toolchain download BUILD_VARIANT="" # aliases target/toolchain/prepare: @+ $(SUBMAKE) -r -C target/toolchain prepare BUILD_VARIANT="" # aliases target/toolchain/compile: @+ $(SUBMAKE) -r -C target/toolchain compile BUILD_VARIANT="" # aliases target/toolchain/install: @+ $(SUBMAKE) -r -C target/toolchain install BUILD_VARIANT="" # aliases target/toolchain/update: @+ $(SUBMAKE) -r -C target/toolchain update BUILD_VARIANT="" # aliases target/toolchain/refresh: @+ $(SUBMAKE) -r -C target/toolchain refresh BUILD_VARIANT="" # aliases target/toolchain/prereq: @+ $(SUBMAKE) -r -C target/toolchain prereq BUILD_VARIANT="" # aliases target/toolchain/dist: @+ $(SUBMAKE) -r -C target/toolchain dist BUILD_VARIANT="" # aliases target/toolchain/distcheck: @+ $(SUBMAKE) -r -C target/toolchain distcheck BUILD_VARIANT="" # aliases target/toolchain/configure: @+ $(SUBMAKE) -r -C target/toolchain configure BUILD_VARIANT="" # aliases target/clean: target/linux/clean target/download: target/linux/download target/prepare: target/linux/prepare target/compile: target/linux/compile target/install: target/linux/install target/update: target/linux/update target/refresh: target/linux/refresh target/prereq: target/linux/prereq target/dist: target/linux/dist target/distcheck: target/linux/distcheck target/configure: target/linux/configure
通过stampfile函数我们分析了prereq如下
$(target/stamp-prereq): tmp/.build .config
make $(target/flags-prereq) target/prereq
我们将其中的prereq替换为compile如下
$(target/stamp-compile): tmp/.build .config
make $(target/flags-compile) target/compile
我们可以得出结论当我们make时最终调用,假设$(target/flags-compile)为空,则会执行命令make target/compile,
而target/compile依赖target/linux/compile,我们继续根据发现target/linux/compile执行如下:
@+ $(SUBMAKE) -r -C target/linux compile BUILD_VARIANT=""
也就是说切入到target/linux 执行compile目标
有了以上的结论,我们再反过来分析subdir就简单了,我们把代码贴上来
define subdir $(call warn,$(1),d,D $(1)) $(foreach bd,$($(1)/builddirs), $(call warn,$(1),d,BD $(1)/$(bd)) $(foreach target,$(SUBTARGETS), $(foreach btype,$(buildtypes-$(bd)), $(call warn_eval,$(1)/$(bd),t,T,$(1)/$(bd)/$(btype)/$(target): $(if $(QUILT),,$($(1)/$(bd)/$(btype)/$(target)) $(call $(1)//$(btype)/$(target),$(1)/$(bd)/$(btype)))) $(if $(call debug,$(1)/$(bd),v),,@)+$$(SUBMAKE) -r -C $(1)/$(bd) $(btype)-$(target) $(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 $(QUILT),,$($(1)/$(bd)/$(target)) $(call $(1)//$(target),$(1)/$(bd)))) $(if $(BUILD_LOG),@mkdir -p $(BUILD_LOG_DIR)/$(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 $(call debug,$(1)/$(bd),v),,@)+$(if $(BUILD_LOG),set -o pipefail;) $$(SUBMAKE) -r -C $(1)/$(bd) $(target) BUILD_VARIANT="$(filter-out __default,$(variant))" $(if $(BUILD_LOG),SILENT= 2>&1 | tee $(BUILD_LOG_DIR)/$(1)/$(bd)/$(target).txt) $(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),$(call subtarget,$(1),$(target))) endef
代码里面所有的warn相关的都可以忽略,但是warn_eval的不能,根据忽略warn的原则上面的展开如下,我们传递$1为target,$(buildtypes-$(bd))这个变量也没有值,其所在的foreach可以忽略
$(foreach bd, $(target/builddirs),
$(foreach target, $(SUBTARGET),
target/linux/compile: $(target/linux/compile)#这句话非常重要,主要用于产生编译依赖,详细请看下面蓝色字体使用bison的举例
make -r -C target/linux BUILD_VARIANT= compile
)
)
我们再以tools为例,编译bison包则会产生如下规则
tools/bison/compile: $(tools/bison/compile)
make -r -C tools/bison compile BUILD_VARIANT=
在tools的Makefile有如下定义:
$(curdir)/bison/compile := $(curdir)/flex/install,这里的curdir为tools,则扩展为tools/bison/compile:=tools/flex/install
替换规则$(tools/bison/compile)产生如下规则
tools/bison/compile: tools/flex/install
make -r -C tools/bison compile BUILD_VARIANT=
通过这样的方法就产生了包和包之间编译的先后顺序
相关变量定义如下
$(curdir):=target
SUBTARGETS:=clean download prepare compile install update refresh prereq dist distcheck configure
$(curdir)/builddirs:=linux sdk imagebuilder toolchain
=>
SUBTARGETS:=clean download prepare compile install update refresh prereq dist distcheck configure
target/builddirs:=linux sdk imagebuilder toolchain
我们选取SUBTARGET为compile,target/builddirs为linux进行分析
我们对上面进行替换如下
target/linux/compile:
make -r -C target/linux compile
#aliases
target/compile: target/linux/compil
到此就基本分析完成
本文原创,转载请注明出处!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律