OpenWrt之跳过tools编译


OpenWrt之跳过tools编译


前言

OpenWrt在编译tools这块还是比较费时间, 于是本着缩短编译时间的目的, 想办法使用OpenWrt提供的SDK中已经编译好的tools使用


分析代码

在分析OpenWrt源码的时候, 越分析, 看的代码就越多, 需要懂的知识也就越多, 看得我头都大了, 下面会分成几个模块来发现是如何判断tools是否应该rebuild, 前面废话太多了, 不想看废话 直接快进到 硬改源码


SDK分析

在OpenWrt提供的SDK中, 本文主要用到

  • build_dir
  • staging_dir

这两个文件夹, 里面包含了文本需要用到的toolstoolchain


顶层Makefile分析

这里先讲两个重要的变量, 谨防有人不清楚

  • TOPDIR 这个是顶层路径, 也就是源代码的目录
  • CURDIR 这个是当前路径, 也就是当前文件所在的目录

export PATH:=$(TOPDIR)/staging_dir/host/bin:$(PATH)

下面就导入了一个PATH环境变量$(TOPDIR)/staging_dir/host/bin, 也就是tools的工具目录了


$(toolchain/stamp-compile): $(tools/stamp-compile) $(if $(CONFIG_BUILDBOT),toolchain_rebuild_check)
$(target/stamp-compile): $(toolchain/stamp-compile) $(tools/stamp-compile) $(BUILD_DIR)/.prepared

world的最后, 这里调用了toolchaintools的时间戳stamp文件生成有关


tools/Makefile分析

tools_enabled = $(foreach tool,$(sort $(tools-y) $(tools-)),$(if $(filter $(tool),$(tools-y)),y,n))
$(eval $(call stampfile,$(curdir),tools,compile,,_$(subst $(space),,$(tools_enabled)),$(STAGING_DIR_HOST)))
$(eval $(call stampfile,$(curdir),tools,check,$(TMP_DIR)/.build,,$(STAGING_DIR_HOST)))

这里也是调用了subdir.mk来生成stamp


subdir.mk分析

ifndef DUMP_TARGET_DB
# Parameters: <subdir> <name> <target> <depends> <config options> <stampfile location>
# 1: subdir
# 2: target
# 3: build type
# 4: build variant
# 5: all variants
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)lags-$(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
endif

这里还调用了timestamp.pl来生成stamp, 所以大概也了解到OpenWrt是依靠判断stamp来决定是否重新编译toolstoolchain


host-build.mk分析

HOST_STAMP_PREPARED=$(HOST_BUILD_DIR)/.prepared$(if $(HOST_QUILT)$(DUMP),,$(shell $(call find_md5,${CURDIR} $(PKG_FILE_DEPENDS),))_$(call confvar,CONFIG_AUTOREMOVE $(HOST_PREPARED_DEPENDS)))
HOST_STAMP_CONFIGURED:=$(HOST_BUILD_DIR)/.configured
HOST_STAMP_BUILT:=$(HOST_BUILD_DIR)/.built
HOST_BUILD_PREFIX?=$(if $(IS_PACKAGE_BUILD),$(STAGING_DIR_HOSTPKG),$(STAGING_DIR_HOST))
HOST_STAMP_INSTALLED:=$(HOST_BUILD_PREFIX)/stamp/.$(PKG_NAME)_installed

ifneq ($(if $(HOST_QUILT),,$(CONFIG_AUTOREBUILD)),)
  define HostHost/Autoclean
    $(call rdep,${CURDIR} $(PKG_FILE_DEPENDS),$(HOST_STAMP_PREPARED))
    $(if $(if $(Host/Compile),$(filter prepare,$(MAKECMDGOALS)),1),,$(call rdep,$(HOST_BUILD_DIR),$(HOST_STAMP_BUILT)))
  endef
endif
  • HOST_BUILD_DIR --> build_dir/host/

  • HOST_BUILD_PREFIX --> staging_dir/host/ , 这个在toolchain-build.mk中有定义

大胆猜则, 应该是通过几个隐藏文件来判断tools是否是编译安装好

  • .prepared${md5}_${confvar}
  • .configured
  • .built
  • .$(PKG_NAME)_installed

但是HOST_STAMP_PREPARED中有findmd5confvar函数, 还有PKG_FILE_DEPENDSHOST_PREPARED_DEPENDS变量, 还得搞懂是怎么生成.

  • rules.mk中, 发现了confvar函数定义
confvar=$(shell echo '$(foreach v,$(1),$(v)=$(subst ','\'',$($(v))))' | $(MKHASH) md5)
  • HOST_PREPARED_DEPENDS这玩意好像没在其他地方出现过.
  • PKG_FILE_DEPENDSpackage/base-files/Makefile中被定义
PKG_FILE_DEPENDS:=$(PLATFORM_DIR)/ $(GENERIC_PLATFORM_DIR)/base-files/
  • $(PLATFORM_DIR)GENERIC_PLATFORM_DIR include/target.mk
PLATFORM_DIR:=$(TOPDIR)/target/linux/$(BOARD)
GENERIC_PLATFORM_DIR := $(TOPDIR)/target/linux/generic
  • 传参总结

所以我们find_md5一共传入了三个参数, 分别是${CURDIR}, $(PLATFORM_DIR), $(GENERIC_PLATFORM_DIR)


depends.mk分析

# define a dependency on a subtree
# parameters:
#	1: directories/files
#	2: directory dependency
#	3: tempfile for file listings
#	4: find options

DEP_FINDPARAMS := -x "*/.svn*" -x ".*" -x "*:*" -x "*\!*" -x "* *" -x "*\\\#*" -x "*/.*_check" -x "*/.*.swp" -x "*/.pkgdir*"

find_md5=find $(wildcard $(1)) -type f $(patsubst -x,-and -not -path,$(DEP_FINDPARAMS) $(2)) -printf "%p%T@\n" | sort | $(MKHASH) md5

define rdep
  .PRECIOUS: $(2)
  .SILENT: $(2)_check

  $(2): $(2)_check
  check-depends: $(2)_check

ifneq ($(wildcard $(2)),)
  $(2)_check::
	$(if $(3), \
		$(call find_md5,$(1),$(4)) > $(3).1; \
		{ [ \! -f "$(3)" ] || diff $(3) $(3).1 >/dev/null; } && \
	) \
	{ \
		[ -f "$(2)_check.1" ] && mv "$(2)_check.1"; \
	    $(TOPDIR)/scripts/timestamp.pl $(DEP_FINDPARAMS) $(4) -n $(2) $(1) && { \
			$(call debug_eval,$(SUBDIR),r,echo "No need to rebuild $(2)";) \
			touch -r "$(2)" "$(2)_check"; \
		} \
	} || { \
		$(call debug_eval,$(SUBDIR),r,echo "Need to rebuild $(2)";) \
		touch "$(2)_check"; \
	}
	$(if $(3), mv $(3).1 $(3))
else
  $(2)_check::
	$(if $(3), rm -f $(3) $(3).1)
	$(call debug_eval,$(SUBDIR),r,echo "Target $(2) not built")
endif

endef
  • wildcard:扩展通配符, 这里是直接传入三个路径, 不做处理

  • patsubst:替换通配符, 把DEP_FINDPARAMS-x替换成-and -not -path

  • $(1): 这个传入的是上面三个路径

  • $(2): 空

  • 拆解命令参数

  • 主命令为find

  • 次命令为sortmd5sum(代替scripts/mkhash.c), 计算出find分类之后md5值

find {path1, path2, path3} -type f -and -not -path "*/.svn*" -and -not -path ".*" -and -not -path "*:*" -and -not -path "*\!*" -and -not -path "* *" -and -not -path "*\\\#*" -and -not -path "*/.*_check" -and -not -path "*/.*.swp" -and -not -path "*/.pkgdir" -printf "%p%T@\n" | sort | md5sum | awk '{print $1}'

$(call rdep,${CURDIR} $(PKG_FILE_DEPENDS),$(HOST_STAMP_PREPARED))
$(if $(if $(Host/Compile),$(filter prepare,$(MAKECMDGOALS)),1),,$(call rdep,$(HOST_BUILD_DIR),$(HOST_STAMP_BUILT)))

上面的语句,对应着下面的结果

$(call rdep,tools/flock,build_dir/host/flock-2.18/.prepared${find_md5}_${confvar}
$(call rdep,build_dir/host/flock-2.18,build_dir/host/flock-2.18/.built)

分析得头都大了,, 搞不下去了, 只能应该源码达到适配效果了

硬改源码

既然他是用md5检测的话, 我也改成md5检测, 就根据tools/xxx文件夹的改动时间取md5做对照就好了, 果断改host-build.mk

源代码:

HOST_STAMP_PREPARED=$(HOST_BUILD_DIR)/.prepared$(if $(HOST_QUILT)$(DUMP),,$(shell $(call find_md5,${CURDIR} $(PKG_FILE_DEPENDS),))_$(call confvar,CONFIG_AUTOREMOVE $(HOST_PREPARED_DEPENDS)))

改动之后:

HOST_STAMP_PREPARED=$(HOST_BUILD_DIR)/.prepared$(shell stat -c %Y ${CURDIR} | $(MKHASH) md5)

这样, 校验方式就变成了我们想要的了


后话

我们把SDK中的staging_dirbuild_dir移动到源码目录之后

mkdir -p staging_dir/host/stamp
tools_name="$(ls -F ./tools/ | grep "/$")"
for a in ${tools_name}; do
    if [ "${a}" != 'include/' ]; then
    PKG_NAME=$(cat "./tools/${a}Makefile" | grep '^PKG_NAME' | cut -d '=' -f 2 | sed 's/^[ \t]*//g')
    PKG_VERSION=$(cat "./tools/${a}Makefile" | grep '^PKG_VERSION' | cut -d '=' -f 2 | sed 's/^[ \t]*//g')

    touch staging_dir/host/stamp/.${PKG_NAME}_installed

    mkdir -p build_dir/host/${PKG_NAME}-${PKG_VERSION}

    prepared_md5=$(stat -c %Y "./tools/${a}" | md5sum | cut -d ' ' -f 1)

    touch build_dir/host/${PKG_NAME}-${PKG_VERSION}/.prepared${prepared_md5}
    touch build_dir/host/${PKG_NAME}-${PKG_VERSION}/.configured
    touch build_dir/host/${PKG_NAME}-${PKG_VERSION}/.built
    
    echo ${PKG_VERSION} >build_dir/host/${PKG_NAME}-${PKG_VERSION}/.version
    echo ${PKG_VERSION} >build_dir/host/${PKG_NAME}-${PKG_VERSION}/.tarball-version

    fi
done

这样就可以跳过tools的编译了, 最简单的方法还是是把所有tools/Makefile里面的tools清空掉


Enjoy it ~~

posted @ 2022-08-06 18:30  虐心i  阅读(678)  评论(0编辑  收藏  举报