- Add new package to Openwrt
- 1. Preparing your OpenWrt build system for use
- 2. Creating a simple "Hello,world!" application
- 3. Creatint a package from your application
- 4. Inculding your package feed into OpenWrt build system
- 5. Building, deploying and testing your applicatin
- 6. Migrating to use GNU make in your application
- 7. Patching your application: Adding new files
- 8. Patching your application: Editing existing files
Add new package to Openwrt
主要記錄筆者學習官方教程"Hello, world!" package for OpenWrt的經歷,並對在OpenWrt下加包的流程進行梳理。
在開始之前我們先約定開發環境:
- 開發的主目錄為
/home/buildbot
- 使用的
OpenWrt
版本為v23.05.3 - WSL2+Ubuntu20.04
- gcc的版本是9.4.0
1. Preparing your OpenWrt build system for use
公司的網路可以直接連到OpenWrt
官網並提供可觀的下載速度,因此我們可以直接使用以下命令下載原始碼:
git clone https://git.openwrt.org/openwrt/openwrt.git source
這會把程式碼下載到source
資料夾下,即/home/buildbot/source
。此外在GitHub上也有OpenWrt
的映象。
進入source
目錄,切換到我們需要的分支上:
cd /home/buildbot/source
git checkout v23.05.3
make distclean
此時推薦更新我們的feeds
包(在OpenWrt
中feed
是一組共享相同位置的包的集合。更近一步的資訊參考OpenWrt Feeds),更新的命令如下:
./scripts/feeds update -a
./scripts/feeds install -a
可用的feed
列表在feeds.conf
或feeds.conf.default
下進行配置,預設的情況下feeds.conf.default
在openwrt
目錄下,也就是本文中的source
。我們可以看一下的它的內容:
src-git packages https://git.openwrt.org/feed/packages.git^063b2393cbc3e5aab9d2b40b2911cab1c3967c59
src-git luci https://git.openwrt.org/project/luci.git^b07cf9dcfc37e021e5619a41c847e63afbd5d34a
src-git routing https://git.openwrt.org/feed/routing.git^648753932d5a7deff7f2bdb33c000018a709ad84
src-git telephony https://git.openwrt.org/feed/telephony.git^86af194d03592121f5321474ec9918dd109d3057
由於某些原因對這些網址的訪問將會變得艱難(是的,就是那個原因),因此在自己電腦上執行的時候可以考慮把網址修改一下,比如去gitee看看有沒有好心人搬運,或者乾脆自己動手。但好在公司的網路依然給力。feeds.conf
可以由我們自己建立,我們自己寫的包就可以放在這裡。
接下來配置我們的交叉編譯鏈:
make menuconfig
在選單裡我們可以選擇Target System
(目標架構),Subtarget
(更細化的架構)和Target Profile
(一些配置)。教程作者選用了Lantiq、XRX200、P2812HNU-F1,但是筆者什麼也沒有。選擇結束之後儲存你的配置並且退出,此時他會讓你給配置檔案命名。這裡的建議是直接採用預設的.config
,不然還要連帶著修改很多別的地方。
以上結束之後開始編譯我們的工具鏈:
make toolchain/install
按照教程作者的說法這個時候你可以去喝杯咖啡,但實際操作中耗時遠比想象中更長。主要的時間浪費在下載Linux5.15的核心,在這一點上公司的網路也沒有太大幫助。建議上午開始做這件事情,不然就等著拿餐補吧。
工具鏈會被放在staging_dir/host/
和staging_dir/toolchain/
下面,所有一個建議是把他們新增到環境變數裡:
export PATH=/home/buildbot/source/staging_dir/host/bin:$PATH
以上。
2. Creating a simple "Hello,world!" application
出於SoC(Separation of Concerns)的目的我們單開一個資料夾寫程式碼:
cd /home/buildbot
mkdir helloworld
cd helloworld
touch helloworld.c
文件的程式碼如下,當然你可以不按照它的寫:
#include <stdio.h>
int main(void)
{
printf("\nHello, world!\n\n");
return 0;
}
接下來就是執行一下你的程式碼看看有沒有問題,注意到這裡還是使用本地的編譯工具。此外編譯選項建議加上-Wall
和-Werror
(多加點小心總歸是好事),對了,結束之後記得把這些生成檔案刪除掉,包程式碼裡面不需要這些東西。
3. Creatint a package from your application
接下里為我們的包建立feed
:
cd /home/buildbot
mkdir -p mypackages/examples/helloworld
OpenWrt
構建系統中的每一個包都由一個包清單檔案(package mnanifest file)描述,包清單檔案描述了包的功能,原始碼位置,編譯方式以及最終安裝包中含有的檔案,並可能包括一些配置指令碼。所以接下來我們建立這個清單檔案:
cd home/buildbot/mypackages/examples/helloworld
touch Makefile
一個可參考的Makefile
如下:
include $(TOPDIR)/rules.mk
# Name, version and release number
# The name and version of your package are used to define
# the variable to point to the build directory of your package : $(PKG_BUILD_DIR)
PKG_NAME:=helloworld
PKG_VERSION:=1.0
PKG_RELEASE:=1
# Source settings (i.e. where to find the source codes)
# This is a custom variable, used below
SOURCE_DIR:=/home/wjm/projects/buildbot/helloworld
include $(INCLUDE_DIR)/package.mk
# Package definition; instructs on how and where our package will appear in the overall
# configuration menu ('make menuconfig')
define Package/helloworld
SECTION:=examples
CATEGORY:=Examples
TITLE:=Hello,World!
endef
# Package description; a more verbose description on what our package does
define Package/helloworld/description
A simple "Hello, world!" -application.
endef
# Package preparation instructions; create the build directory and copy the source code.
# The last command is necessary to ensure our preparation instructions remain compatible
# with the patching system.
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
cp $(SOURCE_DIR)/* $(PKG_BUILD_DIR) -r
$(Build/Patch)
endef
# Package build instructions; invoke the target-specific compiler to first compile the source file,
# and then to link the file into the final executable
# define Build/Compile
# $(TARGET_CC) $(TARGET_CFLAGS) -o $(PKG_BUILD_DIR)/helloworld.o -c $(PKG_BUILD_DIR)/helloworld.c
# $(TARGET_CC) $(TARGET_LDFLAGS) -o $(PKG_BUILD_DIR)/$1 $(PKG_BUILD_DIR)/helloworld.o
# endef
# Package build instructions; invoke the GNU make tool to build our package
define Build/Compile
$(MAKE) -C $(PKG_BUILD_DIR) \
CC="$(TARGET_CC)" \
CFLAGS="$(TARGET_CFLAGS)" \
LDFLAGS="$(TARGET_LDFLAGS)"
endef
# Package install instructions; create a directory inside the package to hold our executable,
# and then copy the executable we built previously into the folder
define Package/helloworld/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/helloworld $(1)/usr/bin
endef
# This command is always the last, it uses the definitions and variables we give above in order to
# get the job done
$(eval $(call BuildPackage,helloworld))
注意到這裡的cp
指令其實比教程中多了一個-r
,考慮到實際的包往往結構複雜,所以這裡未雨綢繆一下(單押成功)。關於Build/Compile
,教程提供的是註釋掉的版本,在後面會解釋為什麼會做這樣的修改。此外make對空格極不友好,比如CC="$(TARGET_CC)"
中在賦值號左右加上空格就會報錯,因為它會把這些理解成選項。
4. Inculding your package feed into OpenWrt build system
OpenWrt
用feeds.conf
來表示在韌體配置階段提供的feeds
,那麼為了把我們自己的feed
包括進去,我們需要建立feeds.conf
:
cd /home/buildbot/source
touch feeds.conf
接下來我們修改feeds.conf
來指定本地的package feed
:
src-link mypackages /home/buildbot/mypackages
既然加入了新的feed
,那麼當然要更新一下啦:
cd /home/buildbot/source
./scripts/feeds update mypackages
./scripts/feeds install -a -p mypackages
feeds
系統會自動檢測清單檔案內的變化,並在需要時進行更新。
5. Building, deploying and testing your applicatin
為了將我們的包整合進來,執行make menuconfig
並選中我們的包。在離開選單之後執行下述命令編譯我們的包:
make package/helloworld/compile
如果一切順利,那我們將在bin/package/<arch>/mypackages
資料夾下看到helloworld_1.0-1_<arch>.ipk
。
之後是部署我們的包,因為筆者手頭並沒有合適的裝置,所以我們略過這裡...
6. Migrating to use GNU make in your application
考慮到實際包的程式碼不會像我們的helloworld一樣簡單,所以把它的編譯方式硬編碼到清單檔案中其實並不合適,所以我們會在原始碼中額外提供一個Makefile
;
cd /home/buildbot/helloworld
touch Makefile
一個可參考的Makefile
如下:
# Global target; when 'make' si run without arguments, this is what it should do
all:helloworld
# These variables hold the name of the compilation tool, the compilation flags and the link flags
# We make use of these variables in the package manifest
CC = gcc
CFLAGS = -Wall
LDFLAGS =
# This variable identifies all header files in the directory; we use it to create a dependency chain
# between the object files and the source files
# This approach will re-build your application whenever any header file changes. In a more complex application,
# such behavior is often undesirable
DEPS = $(wildcard *.h)
# This variable holds all source files to consider for the build; we use a wildcard to pick all files
SRC = $(wildcard *.c)
# This variable holds all object file names, constructed from the source file names using pattern substitution
OBJ = $(patsubst %.c, %.o, $(SRC))
# This rule builds individual object files, and depends on the corresponding C source files and the header files
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
# To build 'helloworld', we depend on the object files, and link them all into a single executable using the
# compilation tool
# We use automatic variables to specify the final executable name 'helloworld', using '$@' and the '$^' will hold
# the names of all the dependencies of this rule
helloworld: $(OBJ)
$(CC) -o $@ $^ $(LDFLAGS)
# To clean build artifacts, we specify a 'clean; rule, and use PHONY to indicate that this rule never matches with
# a potential file in the directory
.PHONY: clean
clean:
rm -f helloworld *.o
接可以跑一下make
看看有沒有問題(再次提醒沒有把握的不要亂加空格),一切安好的話可以更新一下清單檔案,也就是我們第三部分提供的Makefile
(如果你一開始用的就是更新過的Makefile,那就什麼都不用做)。
接下來測試一下我們的包:
cd /home/buildbot/source
make package/helloworld/{clean,compile}
如果這一步遇到問題的話可能有兩種情況:
- 更新
feeds
。cd /home/buildbot/source ./scripts/feeds update mypackages ./scripts/feeds install -a -p mypackages
- 記得刪除原始碼目錄下的目標檔案和可執行檔案。
7. Patching your application: Adding new files
考慮到程式的修改需求,OpenWrt
支援patch
,完成這項任務的工具是Quilt
。關於Quilt
可以參考這篇文件Working with patches,在開始後續操作之前最好先配置一下。
還記得第一部分中新增的環境變數嗎?如果你做了這件事你會發現在bin
下面存在一個quilt
,但是在筆者的操作中會發現這個quilt
的功能並不完善,所以筆者還是建議sudo
一下(sudo,啟動!)。
cd /home/buildbot/source/
make package/helloworld/{clean,prepare} QUILT=1
cd build_dir/target-.../helloworld-1.0/
quilt push -a
在這裡quilt
報錯是很正常的事情,因為我們還沒有patch
。穩住。
quilt new 100-add_module_files.patch
patch
的名字來源於OpenWrt
的慣例:一個可能含有某些意義的數字+一段簡短的描述。這個命令的輸出將告訴你patch
被建立並且處於patch stack
的頂端。
接下來是我們patch
的原始碼:
quilt add functions.c
quilt add functions.h
touch functions.c
quilt edit functions.c
touch functions.h
quilt functions.h
具體的內容如下:
//functions.c
int add(int a, int b)
{
return a + b;
}
//functions.h
int add(int, int);
如果你參考了quilt
文件的預設配置,那使用的編輯器應該是nano
。寫完程式碼之後用Ctrl+o
儲存,Enter
確定檔名,Ctrl+x
退出。
可以使用quilt diff
檢視前後的差別,如果確認修改正確,使用quilt refresh
接受。
在OpenWrt
中,patch
在原始碼路徑建立,然後遷移到所屬的package
中。為了遷移我們的patch
,執行如下的命令:
cd /home/buildbot/source
make package/helloworld/update
為了確保我們的patch
被正確應用了:
cd /home/buildbot/source
make package/helloworld/{clean,prepare}
ls -la build_dir/target-<arch>_<subarch>_<clib>_<clibversion>/
透過上述命令我們可以看到我們的patch
被應用了,並且新的檔案在構建目錄中。
8. Patching your application: Editing existing files
因為我們不是總需要建立新的檔案,很多時候只是在之前的檔案上修修補補,所以有了這麼一節 😃。這一節我們的目的是修改helloworld.c
,首先建立一個新的patch
:
cd /home/buildbot/source
make package/helloworld/{clean,prepare} QUILT=1
cd build_dir/target-.../helloworld-1.0/
quilt push -a
quilt new 101-use_module.patch
因為只是修改,所以:
quilt edit helloworld.c
新的程式碼如下:
#include <stdio.h>
#include "functions.h"
int main(void)
{
int result = add(2, 3);
printf("\nHello, world!\nThe sum is '%d'", result);
return 0;
}
修改之後別忘了quilt diff
和quilt refresh
。
最後,更新我們的包包:
cd /home/buildbot/source
make package/helloworld/update
完結撒花!!!