subdir函数是OpenWRT中使用得非常广泛的一个Makefile函数。该函数定义在include/subdir.mk中。该函数主要包括两个部分,第一个部分定义了一些Makefile的目标规则内容。第二部分定义了默认的依赖关系。我们先看第一部分。

去掉那些用于调试输出的warn函数不看。第一部分主体包括两个foreach遍历。

  1. 第一个foreach主要遍历*$(1)/builddirs*变量。该变量由调用环境定义,subdir.mk本身并没有定义该变量。枚举出来的内容依次赋给bd变量。
  2. 第二个foreach主要遍历SUBTARGETS变量。该变量主要包括一些常用的编译目标,比如clean、download、prepare、compile、install、update等等。该变量就定义在subdir.mk文件的头部。枚举出来的内容依次赋值给target变量。

两级遍历的得到的bdtarget内容,调用了四个指令集。分别如下:

  1. 第一个指令集,又是调用foreach去遍历buildtypes-$(bd)得到变量btype。我搜索了代码库,发现并没有任何地方定义过这个变量。所以这个指令集实际上什么都没干。
  2. 第二个指令集,主要定义了*$(1)/$(bd)/$(target)*目标。该目标的具体内容随后分析。
  3. 第三个指令集,主要是兼容性考虑,定义了*$(1)/$(bd)-$(target)*目标。该目标没有执行体,依赖于第二个指令集定义的目标。
  4. 第四个指令集,主要定义了可能的别名目标。本身没有意义。

所以,这两级遍历的主要内容,就是创建所有的*$(1)/$(bd)/$(target)*目标规则。该目标的执行体主要包括两部分:

  1. 如果定义了BUILD_LOG,则创建*$(BUILD_LOG_DIR)/$(1)/$(bd)*目录,用于记录编译日志。
  2. 进入到*$(1)/$(bd)子目录,执行make $(target)*

同时,如果调用环境中没有定义了QUILT变量,该目标会添加两个依赖*$(1)/$(bd)/$target$(call $(1)//$(target),$(1)/$(bd))*。

两级遍历定义完所有目录下的默认目标规则后,再次调用foreach。这次调用是将$(1)下SUBTARGETS定义到所有目标,都设置好依赖关系。这个依赖关系主要通过该文件中定义的subtarget函数创建的。简单的说,$1/$(target)依赖:

  1. 首先,该目标一定依赖$1/目录。
  2. 然后,检查调用环境是否存在$1/builddirs-$(target)变量。如果存在,则添加其内容作为依赖目标。
  3. 否则,检查调用环境中是否存在$1/builddirs-default变量。如果存在,则添加其内容作为依赖目标。
  4. 如果两者都不存在,则添加调用环境中的$1/builddirs变量作为依赖目标。