在OpenWrt中新增package

邵玄發表於2024-08-02
  • 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包(在OpenWrtfeed是一組共享相同位置的包的集合。更近一步的資訊參考OpenWrt Feeds),更新的命令如下:

./scripts/feeds update -a
./scripts/feeds install -a

可用的feed列表在feeds.conffeeds.conf.default下進行配置,預設的情況下feeds.conf.defaultopenwrt目錄下,也就是本文中的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

OpenWrtfeeds.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 diffquilt refresh

最後,更新我們的包包:

cd /home/buildbot/source
make package/helloworld/update

完結撒花!!!

相關文章