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

到此就基本分析完成

本文原创,转载请注明出处!

posted on 2022-03-04 15:07  sudochen  阅读(480)  评论(0编辑  收藏  举报

导航