zl程序教程

您现在的位置是:首页 >  后端

当前栏目

OpenWRT开发之——BuildPackage剖析

开发 剖析 openwrt
2023-09-14 08:58:20 时间

但是有个问题博主始终没有弄明白。为什么我们 make 一下,管理器就为我们从网上仓库下载软件源码,并编译打包。这个过程是怎么回事儿?还有,为什么我们在 package/ 包名 / 下的Makefile文件下的最后一行是:


$(eval $(call BuildPackage,xxx))

这句话的语意是什么?BuildPackage是什么?

带着这个问题,我们今天来研究一下 BuildPackage 以及展开后的Makefile全貌。

如果对Makefile的格式不太熟悉的同学,请访问我之前的博文:[Makefile学习笔记],了解Makefile的基本语法。


我们还是来看看一个完整的Makefile内貌,如cpp11-demo的Makefile:


include $(TOPDIR)/rules.mk

PKG_NAME:=cpp11-demo

PKG_RELEASE:=1

PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)

include $(INCLUDE_DIR)/package.mk

define Package/cpp11-demo

 SECTION:=utils

 CATEGORY:=Utilites

 TITLE:=$(PKG_NAME)

 DEPENDS:=+libstdcpp

endef

define Build/Prepare

 $(MKDIR) -p $(PKG_BUILD_DIR)

 $(CP) ./src/* $(PKG_BUILD_DIR)

endef

define Package/cpp11-demo/install

 $(INSTALL_DIR) $(1)/usr/bin

 $(INSTALL_BIN) $(PKG_BUILD_DIR)/$(PKG_NAME) $(1)/usr/bin

endef

$(eval $(call BuildPackage,cpp11-demo))

知道Makefile基本语法的同学们都知道 define xxxx ... endef 其实就是给 xxxx 变量赋值,只不过是多行文本而已。

依此,可以将上面的简化为:


include $(TOPDIR)/rules.mk

PKG_NAME:=cpp11-demo

PKG_RELEASE:=1

PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)

include $(INCLUDE_DIR)/package.mk

Package/cpp11-demo=XXXXXX

Build/Prepare=YYYYYY

Package/cpp11-demo/install=XXXXX

$(eval $(call BuildPackage,cpp11-demo))
从上面,没有看到一个目标的定义,如下面这样:


ALL:$(target)

objects:=main.o

$(target):$(objects)

 $(CC) $(CFLAGS) -o $@ $^

.PHONY clean

 -$(RM) $(objects)

那,这些目标在哪里?

玄机就在最后一行的BuildPackage,它根据上面定义的变量生成了所有上面的一切。

$(eval text ),是将 text 作为Makefile文件中的一部分。

$(eval $(call BuildPackage,cpp11-demo)),是引用BuildPackage变量中的内容,并将内部中的$(1)替换为cpp11-demo,然后将处理后的内部作为Makefile的一部分。


1. 研究BuildPackage

BuildPackage 变量定义在 include/package.mk 文件中。


define BuildPackage

 $(Build/IncludeOverlay)

 $(eval $(Package/Default))

 $(eval $(Package/$(1)))

ifdef DESCRIPTION

$$(error DESCRIPTION:= is obsolete, use Package/PKG_NAME/description)

endif

ifndef Package/$(1)/description

define Package/$(1)/description

 $(TITLE)

endef

endif

 BUILD_PACKAGES += $(1)

 $(STAMP_PREPARED): $$(if $(QUILT)$(DUMP),,$(call find_library_dependencies,$(DEPENDS)))

 $(foreach FIELD, TITLE CATEGORY SECTION VERSION,

 ifeq ($($(FIELD)),)

 $$(error Package/$(1) is missing the $(FIELD) field)

 endif

 $(if $(DUMP), \

 $(Dumpinfo/Package), \

 $(foreach target, \

 $(if $(Package/$(1)/targets),$(Package/$(1)/targets), \

 $(if $(PKG_TARGETS),$(PKG_TARGETS), ipkg) \

 ), $(BuildTarget/$(target)) \

 $(if $(PKG_HOST_ONLY)$(DUMP),,$(call Build/DefaultTargets,$(1)))

endef


tip1

BuidPackage的第一行就是:$(Build/IncludeOverlay),其定义:


define Build/IncludeOverlay

 $(eval -include $(wildcard $(TOPDIR)/overlay/*/$(PKG_NAME).mk))

 define Build/IncludeOverlay

 endef

endef

Build/IncludeOverlay作用就是 inlcude $(TOPDIR)/overlay/*/$(PKG_NAME).mk 文件。

比如我要更改lua这个包里面的某个变量,如果我们直接修改 package/xx/lua/Makefile 文件,总有不妥之处。因为这个文件是从网上源码上同步下来的。我们最好不要去直修改它们。若需要用户自定义某些内容,不防使用overlay。就是在 trunk/overlay/ 路径下的任意子目录下建立与包名对应的 .mk 文件,比如lua,那么就建 lua.mk。
我们在 trunk/overlay/xxx/lua.mk 文件里重新定义我们的变量或操作。


tip2

其中第9~13行:


ifndef Package/$(1)/description

define Package/$(1)/description

 $(TITLE)

endef

endif

如果没有定义Package/$(1)/description,那么就以$(TITLE)来定义一个默认的。这里$(1)为 "cpp11-demo"。

tip3


 BUILD_PACKAGES += $(1)

将cpp11-demo追加到变量 BUILD_PACKAGES 后面,可能是将来编译的时候需要。

tip4


 $(STAMP_PREPARED): $$(if $(QUILT)$(DUMP),,$(call find_library_dependencies,$(DEPENDS)))

如果 $(QUILT)$(DUMP) 都没有定义,那么以$(DEPENDS)为参引用 find_library_dependencies 变量。

其中 find_library_dependencies 定义如下:


find_library_dependencies = $(wildcard $(patsubst %,$(STAGING_DIR)/pkginfo/%.version, \

 $(filter-out $(BUILD_PACKAGES),$(foreach dep, \

 $(filter-out @%, $(patsubst +%,%,$(1))), \

 $(if $(findstring :,$(dep)), \

 $(word 2,$(subst :,$(space),$(dep))), \

 $(dep) \

 ))))

博主暂不解释,有点复杂,略过。其功能应该是找出依赖的库。

生成:


XXXX/.prepared : aa bb cc dd

tip5


 $(foreach FIELD, TITLE CATEGORY SECTION VERSION,

 ifeq ($($(FIELD)),)

 $$(error Package/$(1) is missing the $(FIELD) field)

 endif

 )

这几句是判断 FIELD, TITLE, CATEGORY, SECTION, VERSION 这几个变量有没有定义,如果没有定义那就就报错。

tip6


 $(if $(DUMP), \

 $(Dumpinfo/Package), \

 $(foreach target, \

 $(if $(Package/$(1)/targets),$(Package/$(1)/targets), \

 $(if $(PKG_TARGETS),$(PKG_TARGETS), ipkg) \

 ), $(BuildTarget/$(target)) \

 )

虽然写得有点复杂,但意思是:

将 Package/$(1)/targets 或 PKG_TARGETS 中的每一项或 ipk 作为$(target),引用 $(BuildTarget/$(target))。其中:

BuildTarget/bin 定义在 include/package-bin.mk

BuildTarget/ipk 定义在 include /package-ipk.mk

具体这两个变量的内容博主暂时不去研究。其内容莫非是描述如何生成bin与ipk目标。这里留个引子,下次再研究。

如果没有定义 Package/$(1)/targets 与 PKG_TARGETS,那么上次就默认将ipk作为target,引用 BuildTarget/ipk 变量了。


tip7


 $(if $(PKG_HOST_ONLY)$(DUMP),,$(call Build/DefaultTargets,$(1)))
如果没有定义 PKG_HOST_ONLY 与 DUMP,那么这句可以简化为:


$(call Build/DefaultTargets,$(1))

即默认的目标构建。


2. 研究Build/DefaultTargets

如下为 Build/DefaultTargets 的定义:


define Build/DefaultTargets

 $(if $(QUILT),$(Build/Quilt))

 $(if $(USE_SOURCE_DIR)$(USE_GIT_TREE),,$(if $(strip $(PKG_SOURCE_URL)),$(call Download,default)))

 $(call Build/Autoclean)

 download:

 $(foreach hook,$(Hooks/Download),

 $(call $(hook))$(sep)

 $(STAMP_PREPARED) : export PATH=$$(TARGET_PATH_PKG)

 $(STAMP_PREPARED):

 @-rm -rf $(PKG_BUILD_DIR)

 @mkdir -p $(PKG_BUILD_DIR)

 $(foreach hook,$(Hooks/Prepare/Pre),$(call $(hook))$(sep))

 $(Build/Prepare)

 $(foreach hook,$(Hooks/Prepare/Post),$(call $(hook))$(sep))

 touch $$@

 $(call Build/Exports,$(STAMP_CONFIGURED))

 $(STAMP_CONFIGURED): $(STAMP_PREPARED)

 $(foreach hook,$(Hooks/Configure/Pre),$(call $(hook))$(sep))

 $(Build/Configure)

 $(foreach hook,$(Hooks/Configure/Post),$(call $(hook))$(sep))

 rm -f $(STAMP_CONFIGURED_WILDCARD)

 touch $$@

 $(call Build/Exports,$(STAMP_BUILT))

 $(STAMP_BUILT): $(STAMP_CONFIGURED)

 $(foreach hook,$(Hooks/Compile/Pre),$(call $(hook))$(sep))

 $(Build/Compile)

 $(foreach hook,$(Hooks/Compile/Post),$(call $(hook))$(sep))

 $(Build/Install)

 $(foreach hook,$(Hooks/Install/Post),$(call $(hook))$(sep))

 touch $$@

 $(STAMP_INSTALLED) : export PATH=$$(TARGET_PATH_PKG)

 $(STAMP_INSTALLED): $(STAMP_BUILT)

 $(SUBMAKE) -j1 clean-staging

 rm -rf $(TMP_DIR)/stage-$(PKG_NAME)

 mkdir -p $(TMP_DIR)/stage-$(PKG_NAME)/host $(STAGING_DIR)/packages $(STAGING_DIR_HOST)/packages

 $(foreach hook,$(Hooks/InstallDev/Pre),\

 $(call $(hook),$(TMP_DIR)/stage-$(PKG_NAME),$(TMP_DIR)/stage-$(PKG_NAME)/host)$(sep)\

 $(call Build/InstallDev,$(TMP_DIR)/stage-$(PKG_NAME),$(TMP_DIR)/stage-$(PKG_NAME)/host)

 $(foreach hook,$(Hooks/InstallDev/Post),\

 $(call $(hook),$(TMP_DIR)/stage-$(PKG_NAME),$(TMP_DIR)/stage-$(PKG_NAME)/host)$(sep)\

 if [ -f $(STAGING_DIR)/packages/$(STAGING_FILES_LIST) ]; then \

 $(SCRIPT_DIR)/clean-package.sh \

 "$(STAGING_DIR)/packages/$(STAGING_FILES_LIST)" \

 "$(STAGING_DIR)"; \

 if [ -d $(TMP_DIR)/stage-$(PKG_NAME) ]; then \

 (cd $(TMP_DIR)/stage-$(PKG_NAME); find ./ $(TMP_DIR)/stage-$(PKG_NAME).files); \

 $(call locked, \

 mv $(TMP_DIR)/stage-$(PKG_NAME).files $(STAGING_DIR)/packages/$(STAGING_FILES_LIST) \

 $(CP) $(TMP_DIR)/stage-$(PKG_NAME)/* $(STAGING_DIR)/; \

 ,staging-dir); \

 rm -rf $(TMP_DIR)/stage-$(PKG_NAME)

 touch $$@

 ifdef Build/InstallDev

 compile: $(STAMP_INSTALLED)

 endif

 define Build/DefaultTargets

 endef

 prepare: $(STAMP_PREPARED)

 configure: $(STAMP_CONFIGURED)

 dist: $(STAMP_CONFIGURED)

 distcheck: $(STAMP_CONFIGURED)

endef

明日再续


若想与博文一起研究新技术,请关注我吧!


李名赫 博主从事的是物联网行业,目前在某知名智能家居科技公司担任家庭智能中心研发主管。欢迎交流!
OAP--Optimized Analytics Package for Spark Platform 立即下载