• 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)
    
    • 1

    下面就导入了一个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
    
    • 1
    • 2

    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)))
    
    • 1
    • 2
    • 3

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


    subdir.mk分析

    ifndef DUMP_TARGET_DB
    # Parameters:      
    # 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    这里还调用了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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 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)
    
    • 1
    • HOST_PREPARED_DEPENDS这玩意好像没在其他地方出现过.
    • PKG_FILE_DEPENDSpackage/base-files/Makefile中被定义
    PKG_FILE_DEPENDS:=$(PLATFORM_DIR)/ $(GENERIC_PLATFORM_DIR)/base-files/
    
    • 1
    • $(PLATFORM_DIR)GENERIC_PLATFORM_DIR include/target.mk
    PLATFORM_DIR:=$(TOPDIR)/target/linux/$(BOARD)
    GENERIC_PLATFORM_DIR := $(TOPDIR)/target/linux/generic
    
    • 1
    • 2
    • 传参总结

    所以我们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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 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}'
    
    • 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)))
    
    • 1
    • 2

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

    $(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)
    
    • 1
    • 2

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

    硬改源码

    既然他是用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)))
    
    • 1

    改动之后:

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

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


    后话

    我们把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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

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


    Enjoy it ~~

  • 相关阅读:
    Django DetailView视图
    Java设计模式_适配器模式
    创建飞书自定义机器人发送 Gerrit 消息提醒
    【Eclipse】Project interpreter not specified 新建项目时,错误提示,已解决
    Objective-C——基础知识2(协议)
    [JS高级程序设计] ES6 新增的集合对象总结
    我如何编码8个小时而不会感到疲倦。
    java计算机毕业设计基于ssm的农业信息管理系统(源代码+数据库+Lw文档)
    模型 金字塔原理
    【设计模式】六、建造者模式
  • 原文地址:https://blog.csdn.net/a924282761/article/details/126199198