OpenWRT開發之——BuildPackage剖析
本文將詳細走讀OpenWrt的Makefile中BuildPackage,并剖析編譯過程
前言
在之前的博文里詳細地講述了如何在OpenWrt下建立一個軟件包(package),如:[OpenWrt對C++11的支持],[OpenWrt創建軟件包]。
但是有個問題博主始終沒有弄明白。為什么我們 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
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)"; \ fi 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); \ fi 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
來自:my.oschina.net/hevakelcj/blog/417448