(一)openwrt make menuconfig流程分析

(一)openwrt make menuconfig流程分析


最近探索了一下,在openwrt中,当我们输入make menuconfig命令时的,程序都做了哪些工作,相关的配置文件是如何生成的,文件之间有什么依赖关系,它的工作流程又是怎样的,它又是如何为下一步make命令做好准备工作的?带着种种疑问,开始了我的分析。我主要是按照目标之间的依赖关系,及规则的执行流程一步步进行分析的,下面是我总结的分析过程,希望与大家分享,共同学习,共同成长,其中可能会有一些理解不正确的地方,还望各位不吝指教,谢谢,^_^。


当我们在顶层目录输入make menuconfig时,由于指定的目标为menuconfig,所以make会去寻找文件中menuconfig所在的地方,然后去执行其相应规则。我们可以看到,顶层的makefile中包含了$(TOPDIR)/include/debug.mk、$(TOPDIR)/include/depends.mk、$(TOPDIR)/include/toplevel.mk,menuconfig目标就在include/toplevel.mk中,下面是它的依赖和规则。其中$(TOPDIR)就是顶层目录,定义在主Makefile中TOPDIR:=${CURDIR}。

menuconfig: scripts/config/mconf prepare-tmpinfo FORCE
	if [ \! -e .config -a -e $(HOME)/.openwrt/defconfig ]; then \
		cp $(HOME)/.openwrt/defconfig .config; \
	fi
	$< Config.in
接下来会依次分析menuconfig目标的依赖目标和规则。

1. 第一个依赖目标scripts/config/mconf

依赖目标scripts/config/mconf的定义如下:
scripts/config/mconf:
	@$(_SINGLE)$(SUBMAKE) -s -C scripts/config all CC="$(HOSTCC_WRAPPER)"
我们将其简化一下如下,即可以看到make会去执行scripts/config/Makefile文件,并指定该文件的目标为all:
scripts/config/mconf:
	@make -s -C scripts/config all CC="$(HOSTCC_WRAPPER)"
在scripts/config/Makefile文件中,目标all依赖如下:
all: conf mconf
依赖目标conf又依赖conf.c、zconf.tab.c等文件,依赖目标mconf依赖conf.c、zconf.tab.c等文件,其实这里就是要编译conf、mconf可执行文件。在后面讲到绘制图形配置菜单时,要用到mconf。


小结:scripts/config/mconf这个依赖目标作用就是生成conf、mconf可执行文件,供后面程序使用。

2. 第二个依赖目标prepare-tmpinfo

依赖目标prepare-tmpinfo的定义如下:

prepare-tmpinfo: FORCE
	mkdir -p tmp/info
	$(_SINGLE)$(NO_TRACE_MAKE) -j1 -r -s -f include/scan.mk SCAN_TARGET="packageinfo" SCAN_DIR="package" SCAN_NAME="package" SCAN_DEPS="$(TOPDIR)/include/package*.mk $(TOPDIR)/overlay/*/*.mk" SCAN_DEPTH=5 SCAN_EXTRA=""
	$(_SINGLE)$(NO_TRACE_MAKE) -j1 -r -s -f include/scan.mk SCAN_TARGET="targetinfo" SCAN_DIR="target/linux" SCAN_NAME="target" SCAN_DEPS="profiles/*.mk $(TOPDIR)/include/kernel*.mk $(TOPDIR)/include/target.mk" SCAN_DEPTH=2 SCAN_EXTRA="" SCAN_MAKEOPTS="TARGET_BUILD=1"
	for type in package target; do \
		f=tmp/.$ ${type}info; t=tmp/.config-$${type}.in; \
		[ "$$t" -nt "$$f" ] || ./scripts/metadata.pl $${type}_config "$$f" > "$$t" || { rm -f "$$t"; echo "Failed to build $$t"; false; break; }; \
	done
	[ tmp/.config-feeds.in -nt tmp/.packagefeeds ] || ./scripts/feeds feed_config > tmp/.config-feeds.in
	./scripts/metadata.pl package_mk tmp/.packageinfo > tmp/.packagedeps || { rm -f tmp/.packagedeps; false; }
	./scripts/metadata.pl package_feeds tmp/.packageinfo > tmp/.packagefeeds || { rm -f tmp/.packagefeeds; false; }
	touch $(TOPDIR)/tmp/.build

  1)第2行,规则首先创建一个tmp/info目录


  2)第3~4行,看着比较多,我们将其简化为如下,可以看到,就是去执行scan.mk这个makefile文件,SCAN_TARGET之后是传递给scan.mk的参数,我只列出了部分。

make V=s -j1 -r -s -f include/scan.mk SCAN_TARGET="packageinfo" SCAN_DIR="package"...
make V=s -j1 -r -s -f include/scan.mk SCAN_TARGET="targetinfo" SCAN_DIR="target/linux"...

    上面两条命令几乎是一样的,我们分析其中第一条,来看一下scan.mk文件,该文件最终目标为all。

    2.1)在分析all目标前,我们首先看一下该文件中的如下代码,因为这部分代码会在执行all目标规则之前执行。

$(FILELIST):
	rm -f $(TMP_DIR)/info/.files-$(SCAN_TARGET)-*
	$(call FIND_L, $(SCAN_DIR)) $(SCAN_EXTRA) -mindepth 1 $(if $(SCAN_DEPTH),-maxdepth $(SCAN_DEPTH)) -name Makefile | xargs grep -HE 'call (Build/DefaultTargets|Build(Package|Target)|.+Package)' | sed -e 's#^$(SCAN_DIR)/##' -e 's#/Makefile:.*##' | uniq > $@

$(TMP_DIR)/info/.files-$(SCAN_TARGET).mk: $(FILELIST)
	( \
		cat $< | awk '{print "$(SCAN_DIR)/" $$0 "/Makefile" }' | xargs grep -HE '^ *SCAN_DEPS *= *' | awk -F: '{ gsub(/^.*DEPS *= */, "", $ $2); print "DEPS_" $ $1 "=" $$2 }'; \
		awk -v deps="$$DEPS" '{ \
			info=$$0; \
			gsub(/\//, "_", info); \
			print "$ $(eval $$(call PackageDir," info "," $$0 "))"; \
		} ' < $<; \
		true; \
	) > $@

-include $(TMP_DIR)/info/.files-$(SCAN_TARGET).mk
      2.1.1)第16行,首先make会执行这行命令,所以我们先看下它,简化后如下:
-include $(TOPDIR)/tmp/info/.files-targetinfo.mk
        这行意思比较清楚,就是读取.files-targetinfo.mk这个Makefile文件,如果该文件存在,那么就读取成功,否则会执行第5行的目标。
      2.1.2)第1~3行,刚才说了,如果没读取到.files-targetinfo.mk文件,就会执行第5行的目标,我们先看一下它的依赖目标$(FILELIST),简化如下:

$(TOPDIR)/tmp/info/.files-targetinfo-$(SCAN_COOKIE):
	rm -f $(TOPDIR)/tmp/info/.files-targetinfo-*
	$(call FIND_L, package)  -mindepth 1 -maxdepth 5 -name Makefile | xargs grep -HE 'call (Build/DefaultTargets|Build(Package|Target)|.+Package)' | sed -e 's#^package/##' -e 's#/Makefile:.*##' | uniq > $@

        其中,SCAN_COOKIE?=$(shell echo $ $ $ $)定义在include/toplevel.mk中,$ $ $ $表示进程号。FIND_L=/usr/bin/find -L $(1)定义在tmp/.host.mk中。

        这里的规则就是先删除$(TOPDIR)/tmp/info目录中以.files-targetinfo-开头的文件,然后用find搜索package目录下的Makefile文件,搜索深度1~5,再经过一系列正则式子处理后,重定向到$(TOPDIR)/tmp/info/.files-targetinfo-$(SCAN_COOKIE)目标文件中,生成的文件内容如下:

network/config/ltq-adsl-app
network/config/qos-scripts
...
system/procd
kernel/brcm2708-gpu-fw
kernel/om-watchdog
...
      2.1.3)第5~14行,经过正则式子处理后,会生成目标文件$(TOPDIR)/tmp/info/.files-targetinfo.mk,内容如下:
DEPS_package/kernel/linux/Makefile=modules/*.mk $(TOPDIR)/target/linux/*/modules.mk
$(eval $(call PackageDir,network_config_ltq-adsl-app,network/config/ltq-adsl-app))
$(eval $(call PackageDir,network_config_qos-scripts,network/config/qos-scripts))
$(eval $(call PackageDir,network_config_firewall,network/config/firewall))
$(eval $(call PackageDir,network_config_gre,network/config/gre))
...
        此时,还生成了以下这些文件,我们在后面会讲到,它们会被merge到一个叫$(TOPDIR)/tmp/.packageinfo文件中。
.packageinfo-network_config_firewall
.packageinfo-network_config_gre
.packageinfo-network_config_ltq-adsl-app
...

    2.2)all目标的分析

      all目标依赖关系如下:

all: $(TMP_DIR)/.$(SCAN_TARGET)

      其依赖目标展开如下:

all: $(TOPDIR)/tmp/.packageinfo

      2.2.1)$(TOPDIR)/tmp/.packageinfo又依赖下面两个目标
$(TMP_DIR)/.$(SCAN_TARGET): $(TARGET_STAMP) $(SCAN_STAMP)
	$(call progress,Collecting $(SCAN_NAME) info: merging...)
	-cat $(FILELIST) | awk '{gsub(/\//, "_", $ $0);print "$(TMP_DIR)/info/.$(SCAN_TARGET)-" $$0}' | xargs cat > $@ 2>/dev/null
	$(call progress,Collecting $(SCAN_NAME) info: done)
	echo
        2.2.1.1)第一个依赖目标$(TARGET_STAMP)在scan.mk文件开始处定义的
TARGET_STAMP:=$(TMP_DIR)/info/.files-$(SCAN_TARGET).stamp
          将其简化为:
TARGET_STAMP:=$(TOPDIR)/tmp/info/.files-packageinfo.stamp

          该目标的依赖如下:

$(TARGET_STAMP)::
	+( \
		$(NO_TRACE_MAKE) $(FILELIST); \
		MD5SUM=$$(cat $(FILELIST) | (md5sum || md5) 2>/dev/null | awk '{print $$1}'); \
		[ -f "$@.$$MD5SUM" ] || { \
			rm -f $@.*; \
			touch $@.$$MD5SUM; \
			touch $@; \
		} \
	)
          上面双冒号表示,对于一个没有依赖而只有规则的目标时,即$(TARGET_STAMP)目标,当引用此目标时,规则的命令将会被无条件执行。而普通单冒号规则,当规则的目标文件存在时,此规则的命令永远不会被执行(目标文件永远是最新的)。换言之,对双冒号来说,不管$(TARGET_STAMP)文件是否存在,其规则都会被执行;而单冒号来说,$(TARGET_STAMP)文件若存在,其规则就不会被执行。
          第3行,该行的规则,我的理解是,若$(FILELIST)文件存在,就什么都不做,若不存在,就会执行$(FILELIST)目标的规则生成$(FILELIST)文件。不过这个地方还是有些明白。

          第4行,根据$(FILELIST)文件计算报文摘要值MD5SUM,例如得到的值:3996604f33f70af7d720ab4a5f9fe1f8。

          第5~8行,表示若$(TOPDIR)/tmp/info/.files-packageinfo.stamp文件不存在,就创建它,以及$(TOPDIR)/tmp/info/.files-packageinfo.stamp.3996604f33f70af7d720ab4a5f9fe1f8文件。

        2.2.1.2)第二个依赖目标$(SCAN_STAMP)我没有找到它的定义,它应该是一个空的值。

        2.2.1.3)$(TOPDIR)/tmp/.packageinfo目标的规则中,

          第2、4行,会调用process变量打印Collecting package info: merging...、Collectingpackage info: done信息。

          第3行,将 2.1.3)中得到的.packageinfo-network_config_firewall等文件内容,依次取出并重定向到$(TOPDIR)/tmp/.packageinfo目标文件中,生成的目标文件内容如下:

Source-Makefile: package/network/config/ltq-adsl-app/Makefile
Package: ltq-adsl-app
Menu: 1
Version: 3.24.4.4-2
...
John Crispin 
@@


Source-Makefile: package/network/config/qos-scripts/Makefile
Package: qos-scripts
Version: 1.2.1-7
...


  3)scan.mk文件执行完后,接下来继续执行第5~8行的for语句中的命令,我们简化一下当type变量为package时要执行的命令:

f=tmp/.packageinfo; t=tmp/.config-package.in;
[ tmp/.config-package.in -nt tmp/.packageinfo ] || ./scripts/metadata.pl package_config tmp/.packageinfo > tmp/.config-package.in || { rm -f "$$t"; echo "Failed to build $$t"; false; break; };

    先给变量f、t分别赋值,然后再判断tmp/.config-package.in文件是否比tmp/.packageinfo文件修改时间更加新,若不是则由scripts/metadata.pl脚本根据刚才2.2.1.3)中得到的tmp/.packageinfo生成tmp/.config-package.in,内容如下:

menuconfig IMAGEOPT
	bool "Image configuration"
	default n
source "package/*/image-config.in"
menu "Base system"

	config PACKAGE_base-files
		tristate "base-files................................... Base filesystem for OpenWrt"
		default y if DEFAULT_base-files
		default m if ALL
		select PACKAGE_libc
		select PACKAGE_netifd
		select PACKAGE_procd
		select PACKAGE_jsonfilter
		select PACKAGE_librt if USE_EGLIBC
		select PACKAGE_libpthread if USE_EGLIBC
		help
		 This package contains a base filesystem and system scripts for OpenWrt.
		 http://openwrt.org/
		 

	config PACKAGE_block-mount
...


  4)第9行,判断tmp/.config-feeds.in文件是否比tmp/.packagefeeds文件修改时间更加新,若不是则由scripts/feeds脚本

生成tmp/.config-feeds.in。


  5)第10行,由scripts/metadata.pl脚本根据tmp/.packageinfo生成tmp/.packagedeps,内容如下:

package-$(CONFIG_PACKAGE_6in4) += network/ipv6/6in4
package-$(CONFIG_PACKAGE_6rd) += network/ipv6/6rd
package-$(CONFIG_PACKAGE_6to4) += network/ipv6/6to4
package-$(CONFIG_PACKAGE_8021xd) += ramips/applications/8021xd
...
    这个文件最终被包含在package/Makefile中,当我们配置了对应选项后,在编译的时候,选项后对应的目录中makefile就会被执行。比如CONFIG_PACKAGE_8021xd选项被配置后,package/ramips/applications/8021xd目录中Makefile被执行,去编译该目录下的文件,生成8021xd工具。
-include $(TMP_DIR)/.packagedeps


  6)第11行,由scripts/metadata.pl脚本根据tmp/.packageinfo生成tmp/.packagefeeds,这是一个空文件,用于第9行中与tmp/.config-feeds.in文件做修改时间比较的。


小结:

(1)prepare-tmpinfo这个依赖目标作用就是先生成tmp/.packageinfo、tmp/.targetinfo文件,再根据它们得到tmp/.config-package.in、tmp/.config-target.in这两个config文件,tmp/.config-feeds.in config文件,这些文件最终会由主目录中的Config.in文件读取,供mconf程序使用。

tmp/.config-package.in由主目录下onfig.in读取:source "tmp/.config-package.in"

tmp/.config-target.in由target/Config.in读取:source "tmp/.config-target.in"

tmp/.config-feeds.in由package/base-files/image-config.in读取:source "tmp/.config-feeds.in"

(2)prepare-tmpinfo这个依赖目标作用还生成了相应的makefile文件tmp/.packagedeps。


3. 第三个依赖目标FORCE

依赖目标FORCE的定义如下:

FORCE:
.PHONY: FORCE

可以看到这个依赖目标是一个伪目标,它没有依赖和规则,根据规则,该目标总会被认为是最新的,所以依赖它的目标的规则总会被执行。也就是说依赖FORCE的menuconfig目标的规则总会被执行。


小结:FORCE这个依赖目标作用就是要让依赖它的目标总会被执行。

4. menuconfig的规则

1)依赖目标分析完之后,我们再来看一看规则部分,首先是第2~4行,这个较简单,if成立的条件是,顶层目录下的.config文件不存在,且$(HOME)/.openwrt/defconfig存在,就用defconfig覆盖.config。

2)第5行,$<表示第一个依赖目标,即scripts/config/mconf,表示如下:
scripts/config/mconf Config.in
  所以该行表示执行mconf可执行文件,并传入Config.in参数,mconf是在第一个依赖目标scripts/config/mconf中生成的。mconf会解析Config.in文件,以及顶层目录下的.config,并绘制图形界面供我们进行相关选项的配置。然后在我们保存退出时,.config就会被及时的更新,供我们接下来make编译时使用。

小结:menuconfig的规则作用主要是通过mconf读取Config.in、.config文件,生成配置界面,与用户交互,然后更新.config,为下一步make编译做准备。

5. 总结

  1)要想读懂Makefile,我们可以根据最终目标的依赖关系一步步去分析它的依赖目标,及其对应的规则,比如刚才我们分析的menuconfig这个目标,它的依赖关系如下,我们需要分析它的3个依赖目标和规则。
menuconfig
  |--scripts/config/mconf
  |--prepare-tmpinfo
  |--FORCE
  2)建议在读openwrt Makefile时,最好有一定Makefile和shell的基础,不然我认为很难的看懂。给大家推荐2本我觉得不错的资料,《GNU make中文手册》、《 Linux命令行与shell脚本编程大全》,网上有它们的pdf文档,建议大家可以看看。
  3)文中有个别地方,我目前理解的可能不太准确,希望有人能给予指出。或我知道了之后,再来更新,谢谢。
  4)由于显示格式的问题,文中连续出现2个或4个“$”符号的地方,我在它们之间加了一个空格,希望不要影响到各位的阅读。

你可能感兴趣的:(Openwrt,develop)