OpenWRT package === ### OpenWRT package 基本介紹 OpenWRT為Package的集合,並對其進行維護和發布。除了Linux之外,OpenWRT幾乎所有功能都是由Package所提供的。 #### 一個完整的Package目錄包含: 1. Package Makefile: 描述軟體如何獲得,建置和打包(必須)。 2. Package patch: 獲得修改的Source code(選用)。 3. 其他的靜態檔案,像是初始化腳本,預設設定,腳本等等(選用)。 例如,Package libusb包含了Package Makefile和Package patch: ![](https://i.imgur.com/0zTwZdC.png) --- ### Source packages source packages描述一個軟體包如何下載,patch,編譯和打包,並應用在目標系統上。除此之外,source packages也描述了軟體包執行時和編譯時,對其他軟體包的依賴關係。 #### 結構 一個source package包含了一個子目錄。該目錄預設包含一個OpenWRT package,選用的src,files或patches目錄。 * Makefile -- 編譯規則 * The files directory -- 存放檔案 * The patches directory -- Patch * The src directory -- 存放本地軟體原始碼 (Package 也支援從git等等外部來源獲取原始碼)。 [參考OpenWrt packages](https://openwrt.org/docs/guide-developer/package-policies) 為了更了解Package,我們採用實際的package當作範例(wireless-regdb, 路徑在package/farmware/wireless-regdb)。 Package wireless-regdb目錄結構如下: ![](https://i.imgur.com/RzaRwpX.png) 可以看到目錄底下有patches資料夾和Makefile。 Makefile: ```make= include $(TOPDIR)/rules.mk PKG_NAME:=wireless-regdb PKG_VERSION:=2022.02.18 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz PKG_SOURCE_URL:=@KERNEL/software/network/wireless-regdb/ PKG_HASH:=8828c25a4ee25020044004f57374bb9deac852809fad70f8d3d01770bf9ac97f PKG_MAINTAINER:=Felix Fietkau <nbd@nbd.name> include $(INCLUDE_DIR)/package.mk define Package/wireless-regdb PKGARCH:=all SECTION:=firmware CATEGORY:=Firmware URL:=https://git.kernel.org/pub/scm/linux/kernel/git/sforshee/wireless-regdb.git/ TITLE:=Wireless Regulatory Database endef define Build/Compile $(STAGING_DIR_HOST)/bin/$(PYTHON) $(PKG_BUILD_DIR)/db2fw.py $(PKG_BUILD_DIR)/regulatory.db $(PKG_BUILD_DIR)/db.txt endef define Package/wireless-regdb/install $(INSTALL_DIR) $(1)/lib/firmware $(CP) $(PKG_BUILD_DIR)/regulatory.db $(1)/lib/firmware/ endef $(eval $(call BuildPackage,wireless-regdb)) ``` 變數定義: :::info PKG_NAME: 套件包名稱。 PKG_VERSION:套件包版本。 PKG_RELEASE: 套件包內的Makefile版本。 PKG_SOURCE: 原始來源的檔案名稱。 PKG_SOURCE_URL: 原始碼在哪裡下載。 PKG_HASH: Checksum,用以驗證下載的檔案。 PKG_MAINTAINER: 套件包維護者 ::: 隱含特殊變數: :::info PKG_BUILD_DIR: 解壓縮到BUILD_DIR之後,哪裡可以找到這個套件包。 PKG_INSTALL_DIR: 在呼叫make install之後,哪些檔案會被copy。 ::: ```make= $(eval $(call BuildPackage,wireless-regdb)) ``` Makefile最後一行呼叫Macro BuildPackage,並傳入引數--套件包名稱。Macro BuildPackage會自動依據套件包定義的資訊和macro,建置套件包。 #### Macro BuildPackage Package/<套件包名稱> 該macro裡的參數會傳遞給buildroot(OpenWRT是建立在Buildroot之上的)處理。 ```make= define Package/wireless-regdb PKGARCH:=all SECTION:=firmware CATEGORY:=Firmware URL:=https://git.kernel.org/pub/scm/linux/kernel/git/sforshee/wireless-regdb.git/ TITLE:=Wireless Regulatory Database endef ``` Build/Compile 如何編譯這個套件包 ```make= define Build/Compile $(STAGING_DIR_HOST)/bin/$(PYTHON) $(PKG_BUILD_DIR)/db2fw.py $(PKG_BUILD_DIR)/regulatory.db $(PKG_BUILD_DIR)/db.txt endef ``` Package<套件包名稱>/install 編譯好後,如何安裝 ```make= define Package<套件包名稱>/install $(INSTALL_DIR) $(1)/lib/firmware $(CP) $(PKG_BUILD_DIR)/regulatory.db $(1)/lib/firmware/ endef ``` BuildPackage提供了不只子上的macro,由於篇幅關係,完整請參考: [Creating packages](https://openwrt.org/docs/guide-developer/packages#packagedescription) --- ## OpenWRT package建置 [參考 openwrt target分析](https://insidelinuxdev.net/article/a0b1tf.html) [參考 openwrt打包過程](https://www.twblogs.net/a/5b9057d92b71776722192a3b) [參考 OpenWrt源码分析之编译系统](https://blog.csdn.net/iampisfan/article/details/78128688) 為了瞭解整個Package是如何建置的,我們舉一個實際的例子package wireless-regdb [Ref wireless-regdb makefile](https://github.com/openwrt/openwrt/blob/master/package/firmware/wireless-regdb/Makefile): --- ```make= include $(TOPDIR)/rules.mk PKG_NAME:=wireless-regdb PKG_VERSION:=2021.08.28 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz PKG_SOURCE_URL:=@KERNEL/software/network/wireless-regdb/ PKG_HASH:=cff370c410d1e6d316ae0a7fa8ac6278fdf1efca5d3d664aca7cfd2aafa54446 PKG_MAINTAINER:=Felix Fietkau <nbd@nbd.name> include $(INCLUDE_DIR)/package.mk define Package/wireless-regdb PKGARCH:=all SECTION:=firmware CATEGORY:=Firmware URL:=https://git.kernel.org/pub/scm/linux/kernel/git/sforshee/wireless-regdb.git/ TITLE:=Wireless Regulatory Database endef define Build/Compile $(STAGING_DIR_HOST)/bin/$(PYTHON) $(PKG_BUILD_DIR)/db2fw.py $(PKG_BUILD_DIR)/regulatory.db $(PKG_BUILD_DIR)/db.txt endef define Package/wireless-regdb/install $(INSTALL_DIR) $(1)/lib/firmware $(CP) $(PKG_BUILD_DIR)/regulatory.db $(1)/lib/firmware/ endef $(eval $(call BuildPackage,wireless-regdb)) ``` 整個Makefile定義了wireless-regdb相關的編譯資訊,然而真正執行的為下列這一句。它呼叫了BuildPackage函式,並傳入引數wireless-regdb。 ```make= $(eval $(call BuildPackage,wireless-regdb)) ``` BuildPackage函式定義在include/package.mk。[Ref package.mk](https://github.com/openwrt/openwrt/blob/master/include/package.mk) ```make= define BuildPackage $(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,$(1))) $(foreach FIELD, TITLE CATEGORY SECTION VERSION, ifeq ($($(FIELD)),) $$(error Package/$(1) is missing the $(FIELD) field) endif ) $(if $(DUMP), \ $(if $(CHECK),,$(Dumpinfo/Package)), \ $(foreach target, \ $(if $(Package/$(1)/targets),$(Package/$(1)/targets), \ $(if $(PKG_TARGETS),$(PKG_TARGETS), ipkg) \ ), $(BuildTarget/$(target)) \ ) \ ) $(if $(PKG_HOST_ONLY),,$(call Build/DefaultTargets,$(1))) endef ``` 我們只關注如何build package,於是將無關的內容省略。 ```make= define BuildPackage $(eval $(Package/Default)) $(eval $(Package/$(1))) ... $(if $(DUMP), \ $(if $(CHECK),,$(Dumpinfo/Package)), \ $(foreach target, \ $(if $(Package/$(1)/targets),$(Package/$(1)/targets), \ $(if $(PKG_TARGETS),$(PKG_TARGETS), ipkg) \ ), $(BuildTarget/$(target)) \ ) \ ) $(if $(PKG_HOST_ONLY),,$(call Build/DefaultTargets,$(1))) endef ``` 將以上拆成三部分來看 1. ```make= $(eval $(Package/Default)) $(eval $(Package/$(1))) ``` 會被代換成 ```make= Package/wireless-regdb ``` 2. ```make= $(if $(DUMP), \ $(if $(CHECK),,$(Dumpinfo/Package)), \ $(foreach target, \ $(if $(Package/$(1)/targets),$(Package/$(1)/targets), \ $(if $(PKG_TARGETS),$(PKG_TARGETS), ipkg) \ ), $(BuildTarget/$(target)) \ ) \ ) ``` 在這個例子當中,$(1)為wireless-regdb,並且變數DUMP是空的。將上述了例子代換成: ```make= $(foreach target, \ $(if $(Package/wireless-regdb/targets),$(Package/wireless-regdb/targets), \ $(if $(PKG_TARGETS),$(PKG_TARGETS), ipkg) \ ), $(BuildTarget/$(target)) \ ) \ ``` 代換完成後,可以看到變數PKG_TARGETS是空的和Package/wireless-regdb/targets也為空。於是,再次代換: ```make= $(BuildTarget/ipkg) ``` BuildTarget/ipkg位於package-ipkg,主要目標是將目標打包成ipkg。[Ref package-ipkg](https://github.com/openwrt/openwrt/blob/3869ccbcc891a7185550a2a422e2db01fd994b7d/include/package-ipkg.mk) 3. ```make= $(if $(PKG_HOST_ONLY),,$(call Build/DefaultTargets,$(1))) ``` 將參數代入: ```make= $(if $(PKG_HOST_ONLY),,$(call Build/DefaultTargets,wireless-regdb)) ``` 因為$(PKG_HOST_ONLY)為空,於是該語句去呼叫Build/DefaultTargets函式,並傳入參數wireless-regdb。 Build/DefaultTargets一樣定義在同一個makefile (package.mk)。 ```make= define Build/DefaultTargets ... $(if $(DUMP),,$(Build/CoreTargets)) define Build/DefaultTargets endef endef ``` 當然變數DUMP依然為空,直接呼叫Build/CoreTargets。Build/CoreTargets一樣定義在同一個makefile (package.mk)。 ```make= define Build/CoreTargets STAMP_PREPARED:=$$(STAMP_PREPARED) STAMP_CONFIGURED:=$$(STAMP_CONFIGURED) ... $(STAMP_PREPARED) : export PATH=$$(TARGET_PATH_PKG) $(STAMP_PREPARED): $(STAMP_PREPARED_DEPENDS) @-rm -rf $(PKG_BUILD_DIR) @mkdir -p $(PKG_BUILD_DIR) touch $$@_check $(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) $(STAMP_CONFIGURED_DEPENDS) rm -f $(STAMP_CONFIGURED_WILDCARD) $(CleanStaging) $(foreach hook,$(Hooks/Configure/Pre),$(call $(hook))$(sep)) $(Build/Configure) $(foreach hook,$(Hooks/Configure/Post),$(call $(hook))$(sep)) touch $$@ $(call Build/Exports,$(STAMP_BUILT)) $(STAMP_BUILT): $(STAMP_CONFIGURED) $(STAMP_BUILT_DEPENDS) rm -f $$@ touch $$@_check $(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) rm -rf $(TMP_DIR)/stage-$(PKG_DIR_NAME) mkdir -p $(TMP_DIR)/stage-$(PKG_DIR_NAME)/host $(STAGING_DIR)/packages $(foreach hook,$(Hooks/InstallDev/Pre),\ $(call $(hook),$(TMP_DIR)/stage-$(PKG_DIR_NAME),$(TMP_DIR)/stage-$(PKG_DIR_NAME)/host)$(sep)\ ) $(call Build/InstallDev,$(TMP_DIR)/stage-$(PKG_DIR_NAME),$(TMP_DIR)/stage-$(PKG_DIR_NAME)/host) $(foreach hook,$(Hooks/InstallDev/Post),\ $(call $(hook),$(TMP_DIR)/stage-$(PKG_DIR_NAME),$(TMP_DIR)/stage-$(PKG_DIR_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_DIR_NAME) ]; then \ (cd $(TMP_DIR)/stage-$(PKG_DIR_NAME); find ./ > $(TMP_DIR)/stage-$(PKG_DIR_NAME).files); \ $(call locked, \ mv $(TMP_DIR)/stage-$(PKG_DIR_NAME).files $(STAGING_DIR)/packages/$(STAGING_FILES_LIST) && \ $(CP) $(TMP_DIR)/stage-$(PKG_DIR_NAME)/* $(STAGING_DIR)/; \ ,staging-dir); \ fi rm -rf $(TMP_DIR)/stage-$(PKG_DIR_NAME) touch $$@ ifdef Build/InstallDev $(_pkg_target)compile: $(STAMP_INSTALLED) endif $(_pkg_target)prepare: $(STAMP_PREPARED) $(_pkg_target)configure: $(STAMP_CONFIGURED) $(_pkg_target)dist: $(STAMP_CONFIGURED) $(_pkg_target)distcheck: $(STAMP_CONFIGURED) ... endef ``` 首先,先從該函式定義的兩個變數開始解析。 ```make= STAMP_PREPARED:=$$(STAMP_PREPARED) STAMP_CONFIGURED:=$$(STAMP_CONFIGURED) ``` 在package.mk可以找到變數STAMP_PREPARED和STAMP_CONFIGURED。 ```make= PKG_DIR_NAME:=$(lastword $(subst /,$(space),$(CURDIR))) STAMP_NO_AUTOREBUILD=$(wildcard $(PKG_BUILD_DIR)/.no_autorebuild) PREV_STAMP_PREPARED:=$(if $(STAMP_NO_AUTOREBUILD),$(wildcard $(PKG_BUILD_DIR)/.prepared*)) ifneq ($(PREV_STAMP_PREPARED),) STAMP_PREPARED:=$(PREV_STAMP_PREPARED) CONFIG_AUTOREBUILD:= else STAMP_PREPARED=$(PKG_BUILD_DIR)/.prepared$(if $(QUILT)$(DUMP),,_$(shell $(call find_md5,${CURDIR} $(PKG_FILE_DEPENDS),))_$(call confvar,CONFIG_AUTOREMOVE $(PKG_PREPARED_DEPENDS))) endif STAMP_CONFIGURED=$(PKG_BUILD_DIR)/.configured$(if $(DUMP),,_$(call confvar,$(PKG_CONFIG_DEPENDS))) STAMP_CONFIGURED_WILDCARD=$(PKG_BUILD_DIR)/.configured_* ``` STAMP_PREPARED: ```make= PKG_DIR_NAME:=$(lastword $(subst /,$(space),$(CURDIR))) STAMP_NO_AUTOREBUILD=$(wildcard $(PKG_BUILD_DIR)/.no_autorebuild) PREV_STAMP_PREPARED:=$(if $(STAMP_NO_AUTOREBUILD),$(wildcard $(PKG_BUILD_DIR)/.prepared*)) ifneq ($(PREV_STAMP_PREPARED),) STAMP_PREPARED:=$(PREV_STAMP_PREPARED) CONFIG_AUTOREBUILD:= else STAMP_PREPARED=$(PKG_BUILD_DIR)/.prepared$(if $(QUILT)$(DUMP),,_$(shell $(call find_md5,${CURDIR} $(PKG_FILE_DEPENDS),))_$(call confvar,CONFIG_AUTOREMOVE $(PKG_PREPARED_DEPENDS))) endif ``` 以範例來說,檔案.no_autorebuild不存在。所以,STAMP_PREPARED可寫作成,直接用檔案名稱紀錄MD5碼: ```make= STAMP_PREPARED=$(PKG_BUILD_DIR)/.prepared{MD5碼} ``` ![](https://i.imgur.com/LrzvQGb.png) STAMP_CONFIGURED: ```make= STAMP_CONFIGURED=$(PKG_BUILD_DIR)/.configured$(if $(DUMP),,_$(call confvar,$(PKG_CONFIG_DEPENDS))) STAMP_CONFIGURED_WILDCARD=$(PKG_BUILD_DIR)/.configured_* ``` 可簡化成: ```make= STAMP_CONFIGURED=$(PKG_BUILD_DIR)/.configured ``` ![](https://i.imgur.com/A85BSsA.png)