Openwrt main Makefile


OpenWrt是一个典型的嵌入式Linux工程,了解OpenWrt的Makefile的工作过程对提高嵌入式Linux工程的开发能力有极其重要意义。OpenWrt的主Makefile文件只有100行左右,可以简单分为三部分,前导部分、首次执行部分、再次执行部分。


下面就以openwrt-15.05版本的主Makefile为例进行讲解,该版本的主Makefile一共就只有91行,其中前导部分为9-20行、首次执行部分为22-31行、再次执行部分为32-91。(查看下文需要有一定的Makefile编程基础)

1.前导部分


前导部分的代码

TOPDIR:=${CURDIR}
LC_ALL:=C
LANG:=C
export TOPDIR LC_ALL LANG

empty:=
space:= $(empty) $(empty)
$(if $(findstring $(space),$(TOPDIR)),$(error ERROR: The path to the OpenWrt directory must not include any spaces))

world:

include $(TOPDIR)/include/host.mk

  • CURDIR为make默认变量,默认值为当前目录。也就是主Makefile的路径前导部分主要把变量TOPDIR赋值为当前目录,把变量LC_ALL、LANG赋值为C,并使用变量延伸指示符export,把上述三个变量延伸到下层Makefile。
  • 使用文件指示符include引入​​$(TOPDIR)/include/host.mk​​。在OpenWrt的主Makefile文件使用了多次include指示符,说明主Makefile文件被拆分成多个文件,被拆分的文件放在不同的目录。拆分的目的是明确各部分的功能,而且增加其灵活性。
  • 在前导部分比较费解的是使用world目标,Makefile规则由目标、依赖、命令三部分组成,在OpenWrt的主Makefile文件的第一个目标world没有依赖和命令,这个在Makefile里面被称作缺省规则吗,后面需要使用%::来重建规则,它主要起到指示当make命令不带目标时所要执行的目标。没有设定依赖和命令部分表明此目标在此后将会有其他依赖关系或命令,world目标的命令需要进一步参考​​$(TOPDIR)/include/toplevel.mk​​和主Makefile文件的再次执行部分。

2.首次执行部分


首次执行部分的代码

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

  • OPENWRT_BUILD是区分首次执行与再次执行的变量。在首次执行时使用强制赋值指示符override把OPENWRT_BUILD赋值为1,并使用变量延伸指示符export把OPENWRT_BUILD延伸。在OPENWRT_BUILD使用强制赋值指示符override意味着make命令行可能引入OPENWRT_BUILD参数。
  • 引入​​$(TOPDIR)/include/debug.mk、$(TOPDIR)/include/depends.mk、$(TOPDIR)/include/toplevel.mk​​三个文件,由于TOPDIR是固定的,所以三个文件也是固定的。其中​​$(TOPDIR)/include/toplevel.mk​​的173行%::进行重建规则,有效解释首次执行时world目标的规则。

3.再次执行部分


再次执行部分的代码较长,这边就不直接贴出,下面分析时进行查看

1.首先引入如下七个文件

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)

  • rules.mk没有目录名,即引入与主Makefile文件目录相同。
  • 在rules.mk定义了INCLUDE_DIR为​​$(TOPDIR)/include​​,所以​​$(INCLUDE_DIR)/depends.mk​​实际上与首次执行时引入的​​$(TOPDIR)/include/depends.mk​​是同一个文件。
  • 四个子目录下的Makefile实际上是不能独立执行。主要利用​​$(INCLUDE_DIR)/subdir.mk​​动态建立规则,诸如​​$(toolchain/stamp-install)​​目标是靠​​$(INCLUDE_DIR)/subdir.mk​​的stampfile函数动态建立。
  • 在package/Makefile动态建立了​​$(package/stamp-prereq)、$(package/stamp-cleanup)、$(package/ stamp-compile)、$(package/stamp-install)、$(package/ stamp-rootfs-prepare)​​目标。
  • 定义一些使用变量命名的目标,其变量的赋值位置在​​$(INCLUDE_DIR)/subdir.mk​​的stampfile函数中。目标只有依赖关系,可能说明其工作顺序,在​​$(INCLUDE_DIR)/subdir.mk​​的stampfile函数中有进一步说明其目标执行的命令,并为目标建立一个空文件,即使用变量命名的目标为真实的文件。

2.定义一些使用固定的目标规则

printdb:
@true

prepare: $(target/stamp-compile)

clean: FORCE
rm -rf $(BUILD_DIR) $(STAGING_DIR) $(BIN_DIR) $(BUILD_LOG_DIR)

dirclean: clean
rm -rf $(STAGING_DIR_HOST) $(TOOLCHAIN_DIR) $(BUILD_DIR_HOST) $(BUILD_DIR_TOOLCHAIN)
rm -rf $(TMP_DIR)

  • clean是清除编译结果的目标,清除​​$(BUILD_DIR) $(STAGING_DIR) $(BIN_DIR) $(BUILD_LOG_DIR)​​目录的用意是十分明确。
  • dirclean是删除所有编译过程产生的目录和文件的目标,执行dirclean目标依赖于clean,因此将执行clean目标所执行的命令,然后删除​​$(STAGING_DIR_HOST) $(TOOLCHAIN_DIR) $(BUILD_DIR_HOST) $(BUILD_DIR_TOOLCHAIN)​​目录,以及删除$(TMP_DIR)目录。

3.预处理

ifndef DUMP_TARGET_DB
$(BUILD_DIR)/.prepared: Makefile
@mkdir -p $$(dirname $@)
@touch $@

tmp/.prereq_packages: .config
unset ERROR; \
for package in $(sort $(prereq-y) $(prereq-m)); do \
$(_SINGLE)$(NO_TRACE_MAKE) -s -r -C package/$$package prereq || ERROR=1; \
done; \
if [ -n "$$ERROR" ]; then \
echo "Package prerequisite check failed."; \
false; \
fi
touch $@
endif
  • tmp/.prereq_packages目标是对所需软件包的预处理。目标依赖于.config,即执行​​make menuconfig​​后将会进行一次所需软件包的预处理。不知什么原因在编译前删除tmp目录,执行时无法建立tmp/.prereq_packages文件。

4.目标处理

# check prerequisites before starting to build
prereq: $(target/stamp-prereq) tmp/.prereq_packages
@if [ ! -f "$(INCLUDE_DIR)/site/$(ARCH)" ]; then \
echo 'ERROR: Missing site config for architecture "$(ARCH)" !'; \
echo ' The missing file will cause configure scripts to fail during compilation.'; \
echo ' Please provide a "$(INCLUDE_DIR)/site/$(ARCH)" file and restart the build.'; \
exit 1; \
fi

prepare: .config $(tools/stamp-install) $(toolchain/stamp-install)
world: prepare $(target/stamp-compile) $(package/stamp-compile) $(package/stamp-install) $(target/stamp-install) FORCE
$(_SINGLE)$(SUBMAKE) -r package/index

.PHONY: clean dirclean prereq prepare world package/symlinks package/symlinks-install package/symlinks-clean

endif

  • prereq是预请求目标,在OpenWrt执行Makefile时好像都要先执行prereq目标。
  • prepare是准备目标,是world依赖的一个伪目标。依赖于文件.config和​​$(tools/stamp-install)$(toolchain/stamp-install)​​目标。
  • world是编译的目标。依赖于prepare为目标和前面提到的变量命名目标。采用取消隐含规则方式执行package/index目标。package/index目标在package/Makefile的92行定义。
  • package/symlinks和package/symlinks-install是更新或安装软件包来源的目标,使用$(SCRIPT_DIR)/feeds脚本文件完成。
  • package/symlinks-clean是清除软件包来源的目标,也是使用$(SCRIPT_DIR)/feeds脚本文件完成。
  • 最后使用伪目标.PHONY说明clean dirclean prereq prepareworld package/symlinks package/symlinks-installpackage/symlinks-clean属于伪目标。通过伪目标说明可以知道可以执行的目标。

5.其他说明

1.在主Makefile中我们就看到clean/disclean这样的目标规则,那我们每次编译用的​​make V=99​​​、​​make menuconfig​​呢?

这个指令其实在其他Makefile下,主Makefile会包含toplevel.mk,toplevel.mk里面就包含了​​make menuconfig​​​、​​make oldconfig​​​等等,toplevel.mk又会包含verbose.mk,​​make V=99​​​、​​make V=s​​就在这个Makefile下面定义了.

2.我们会发现在14.07版本的主Makefile里面比15.05版本的多了有十行左右,其实是多了如下内容:

# update all feeds, re-create index files, install symlinks
package/symlinks:
./scripts/feeds update -a
./scripts/feeds install -a

# re-create index files, install symlinks
package/symlinks-install:
./scripts/feeds update -i
./scripts/feeds install -a

# remove all symlinks, don't touch ./feeds
package/symlinks-clean:
./scripts/feeds uninstall -a

其实在15.05版本只不过把这一段移动到toplevel.mk里面了,让主Makefile看起来更简洁点,所以还是可以使用​​make package/symlinks​​来更新feeds的。

Openwrt main Makefile的分析就到这边,有感悟时会持续会更新。


注:以上内容都是本人在学习过程积累的一些心得,难免会有参考到其他文章的一些知识,如有侵权,请及时通知我,我将及时删除或标注内容出处,如有错误之处也请指出,进行探讨学习。文章只是起一个引导作用,详细的数据解析内容还请查看Openwrt相关教程,感谢您的查阅。