OpenWrt之跳过tools编译
OpenWrt之跳过tools编译
前言
OpenWrt在编译tools这块还是比较费时间, 于是本着缩短编译时间的目的, 想办法使用OpenWrt提供的SDK中已经编译好的tools使用
分析代码
在分析OpenWrt源码的时候, 越分析, 看的代码就越多, 需要懂的知识也就越多, 看得我头都大了, 下面会分成几个模块来发现是如何判断tools
是否应该rebuild
, 前面废话太多了, 不想看废话 直接快进到 硬改源码
SDK分析
在OpenWrt提供的SDK中, 本文主要用到
build_dir
staging_dir
这两个文件夹, 里面包含了文本需要用到的tools
和toolchain
顶层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
的最后, 这里调用了toolchain
与tools
的时间戳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
来决定是否重新编译tools
和toolchain
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
中有findmd5
和confvar
函数, 还有PKG_FILE_DEPENDS
和HOST_PREPARED_DEPENDS
变量, 还得搞懂是怎么生成.
- 在
rules.mk
中, 发现了confvar
函数定义
confvar=$(shell echo '$(foreach v,$(1),$(v)=$(subst ','\'',$($(v))))' | $(MKHASH) md5)
HOST_PREPARED_DEPENDS
这玩意好像没在其他地方出现过.PKG_FILE_DEPENDS
在package/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
-
次命令为
sort
与md5sum
(代替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_dir
和build_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 ~~