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:

---
### 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目錄結構如下:

可以看到目錄底下有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碼}
```

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
```
