在OpenWrt中添加package
- 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
完结撒花!!!