目录
- 前言
- 一、设置构建系统
- 安装依赖包
- 获取源码
- 配置、构建交叉编译工具链
- 修改PATH环境变量
- 二、创建“Hello, world!”应用程序
- 创建源代码目录和文件
- 编译、链接、测试应用程序
- 三、从应用程序创建包
- 为包创建包源
- 创建包的清单文件
- 四、将包源包含到OpenWrt构建系统中
- 新增包源配置文件
- 更新并安装源
- 五、构建、部署、测试应用程序
- 构建包
- 部署、测试包
- 移除包
- 六、在应用程序中使用GNU make
- 为什么使用GUN make
- 创建Makefile
- 使用本机工具测试makefile
- 修改包清单,并测试构建
- 七、修补应用程序:添加新文件
- 关于补丁
- 准备源代码
- 创建第一个补丁文件
- 将第一个补丁文件包含到包中
- 八、修补应用程序:已存在的文件
- 创建第二个补丁文件
- 附录:参考文档
前言
本文参阅OpenWrt WiKi的https://openwrt.org/docs/guide-developer/helloworld/start而做的简单笔记,记录如何在OpenWrt中新建一个应用程序、使用补丁等基础开发知识。
一、设置构建系统
本文的实验环境是Ubuntu 18.04.6。
安装依赖包
~$ sudo apt update
~$ sudo apt install build-essential ccache ecj fastjar file g++ gawk \
gettext git java-propose-classpath libelf-dev libncurses5-dev \
libncursesw5-dev libssl-dev python python2.7-dev python3 unzip wget \
python3-distutils python3-setuptools python3-dev rsync subversion \
swig time xsltproc zlib1g-dev
获取源码
执行以下指令,将源码克隆到本地的source目录下:~/study/openwrt$ git clone https://git.openwrt.org/openwrt/openwrt.git source
检出一个稳定的版本,并清除构建生成文件:
~/study/openwrt$ cd ./source
~/study/openwrt/source$ git checkout v17.01.2
~/study/openwrt/source$ make distclean
建议更新并安装“feed”包以避免构建时出现问题:
~/study/openwrt/source$ ./scripts/feeds update -a
~/study/openwrt/source$ ./scripts/feeds install -a
配置、构建交叉编译工具链
调用图形配置菜单:
~/study/openwrt/source$ make menuconfig
本文使用的配置: Target System、Subtarget、Target Profile分别对应Lantiq、XRX200、ZyXEL P2812HNU-F1。
修改完保存退出。开始构建独立于目标(target-independent)的工具和交叉编译工具链:
~/study/openwrt/source$ make toolchain/install
注意:执行上面命令编译到tools/make-ext4fs时,可能会出现报错 error: conflicting types for ‘copy_file_range’ ,参考以下链接:
host-e2fsprogs 1.43.3 Building conflict with glibc · Issue #6 · enclustra-bsp/bsp-xilinx · GitHub: https://github.com/enclustra-bsp/bsp-xilinx/issues/6 把build_dir/host/e2fsprogs-1.43.3/debugfs/misc/create_inode.c文件中的copy_file_range改为copy_file_chunk,即可解决报错。
修改PATH环境变量
上一步生成的工具在 staging_dir/host/ 和 staging_dir/toolchain/ 目录下,为了方便使用 staging_dir/host/bin 下面的工具,需要添加 PATH 变量:
~$ export PATH=/home/harlen/study/openwrt/source/staging_dir/host/bin:$PATH
二、创建“Hello, world!”应用程序
创建源代码目录和文件
基于软件开发中的一项基本原则——关注点分离(the separation of concerns),所以,在OpenWrt之外的目录创建应用程序:
~$ mkdir -p ~/study/app/helloword
~$ cd ~/study/app/helloword
~/study/app/helloword$ touch helloworld.c
编辑源代码,最终内容如下:
~/study/app/helloword$ cat helloworld.c
#include <stdio.h>
int main(void)
{
printf("\nHello, world!\n\n");
return 0;
}
编译、链接、测试应用程序
先确保应用程序在本机可以正常使用:
~/study/app/helloword$ gcc -c -o helloworld.o helloworld.c -Wall
~/study/app/helloword$ gcc -o helloworld helloworld.o
编译、链接成功后,执行:
~/study/app/helloword$ ./helloworld
三、从应用程序创建包
为包创建包源
OpenWrt 构建系统主要围绕包的概念展开。无论什么软件,几乎都有一个软件包,包括目标的工具、交叉编译工具链、目标固件的Linux内核、内核的模块、安装到目标根文件系统的各种应用程序,等等。
除了集成在构建系统的包,其它包的主要交付系统是包源(package feed)。包源是包的一个存储库。存储库可以是本地的目录,也可以位于网络共享上,也可以位于版本控制系统(如 GitHub)上。通过创建和维护包源,可以将与包相关的文件与示例应用程序的源代码分开,以达到关注点分离的目的。
本文中,我们将一个新的包存储库创建到本地目录中。此存储库的名称是“mypackages”,它包含一个名为“examples”的类别。在此类别中,只有一个条目,即我们的“helloworld”应用程序:
~$ mkdir -p ./study/mypackages/examples/helloworld
创建包的清单文件
OpenWrt 构建系统中的每个包都由包清单(package manifest)文件描述。 清单文件负责描述包及其作用,并且必须至少提供有关从何处获取源代码、如何构建源代码以及最终可安装包中应包含哪些文件的说明。 包清单可能还包含可选配置脚本的选项,指定包之间的依赖关系等。
为了让helloword源代码成为一个包,并成为包存储库的一部分,需要创建一个包清单文件(Makefile)把它联系起来:
~$ cd ./study/mypackages/examples/helloworld
~/study/mypackages/examples/helloworld$ touch Makefile
具体内容如下:
~/study/mypackages/examples/helloworld$ cat 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/harlen/study/app/helloword
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)
$(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 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))
定义包及其构建指令的方法有很多,这是其中一个例子。
注意:define Build/Prepare、define Build/Compile、define Package/helloworld/install这三个部分的命令需要使用Tab缩进,而不是空格。
四、将包源包含到OpenWrt构建系统中
新增包源配置文件
OpenWrt 构建系统使用一个名为 feeds.conf 的配置文件,该文件将在固件配置阶段指定可用的包源。
OpenWrt源码目录下默认不存在这个文件,所以需要创建:
~$ cd ~/study/openwrt/source
~/study/openwrt/source$ touch feeds.conf
指定本地包源,具体内容如下:
~/study/openwrt/source$ cat feeds.conf
src-link mypackages /home/harlen/study/mypackages
更新并安装源
指定了新的包源后,可以执行以下指令,使构建系统根据此包源的可用信息来更新包的索引,并将包加入到配置菜单中:
~/study/openwrt/source$ ./scripts/feeds update mypackages
~/study/openwrt/source$ ./scripts/feeds install -a -p mypackages
最后一步执行成功后,会有以下打印输出:
Installing package ‘helloworld’ from mypackages
注意,以上指令执行一次即可。后续如果有修改包的清单文件,包源系统都会自动检测到,并会在完成其他命令(例如“make menuconfig”)或构建包之前执行更新。
五、构建、部署、测试应用程序
构建包
至此,OpenWrt构建系统已经可以把helloworld包集成到我们的固件里。
1、执行以下命令打开配置菜单,选择“Examples”子菜单,在此菜单下有个“helloworld”条目,单击“Y”键将此包包含到固件配置中,保存并退出菜单。
~/study/openwrt/source$ make menuconfig
2、然后执行以下命令生成包:~/study/openwrt/source$ make package/helloworld/compile
如果顺利,会在bin/packages//mypackages目录下看到一个名为helloworld_1.0-1_.ipk的包。
部署、测试包
生成包之后,放到目标路由器去验证。作者建议使用SCP客户端,如WinSCP,把文件从主机发送到目标路由器。
这里假设已经把软件包保存到路由器上的/tmp目录下,接着用opkg工具安装:
root@OpenWrt:/# opkg install /tmp/helloworld_1.0-1_<arch>.ipk
Installing helloworld (1.0-1) to root...
Configuring helloworld.
之后,就可以执行该应用程序了:
root@OpenWrt:/# helloworld
Hello, world!
移除包
已安装的包,也可以通过opkg来移除:
root@OpenWrt:/# opkg remove helloworld
Removing package helloworld from root...
不再需要的包,可以删除它的ipk:
root@OpenWrt:/# rm /tmp/helloworld_1.0-1_<arch>.ipk
六、在应用程序中使用GNU make
为什么使用GUN make
我们的测试应用程序很简单,只有一个源文件。但是在包清单中编译和链接指令需要指定很多选项,包括编译和链接标志、源文件和目标文件名,甚至最终的可执行文件名:
# 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
当执行包清单文件中的指令时,构建系统的工作目录是包清单文件本身所在的根目录。 因此,如果不使用$(PKG_BUILD_DIR) 等文件夹变量,则可能找不到源文件,或者构建工件可能会放置在不正确的目录中。
使用 GNU make 是解决其中一些问题的一种方法。
创建Makefile
在应用程序的源目录下创建一个Makefile,并填充以下内容:
~$ cd ~/study/app/helloword
~/study/app/helloword$ vi Makefile
# Global target; when 'make' is 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
注意,Makefile每行命令前面需要添加一个Tab键,而不是空格。
使用本机工具测试makefile
在本机编译helloworld源码,只需要执行:
~/study/app/helloword$ make
如果输出提示 make: Nothing to be done for all
,则表示可执行文件已经是最新的。为了模拟代码中的更改,用touch更新源文件,然后再次尝试 make 命令:
~/study/app/helloword$ touch helloworld.c
~/study/app/helloword$ make
修改包清单,并测试构建
把上面的makefile跟OpenWrt构建系统关联起来,需要修改包清单构建部分的内容,如下:
~/study/mypackages/examples/helloworld$ cat Makefile
...
# 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
...
修改包清单后,从 OpenWrt 构建系统目录下再次测试包构建过程:
~$ cd ~/study/openwrt/source
~/study/openwrt/source$ make package/helloworld/{clean,compile}
上面命令会先后执行包清理和编译。
如果执行时报错,可能要手动更新和安装包源:
~/study/openwrt/source$ ./scripts/feeds update mypackages
~/study/openwrt/source$ ./scripts/feeds install -a -p mypackages
如果还报错,请确保源目录(/home/harlen/study/app/helloword)除了.c 和 Makefile,其它文件需清除。
七、修补应用程序:添加新文件
关于补丁
在应用程序的生命周期中,从初始设计到应用程序退役,通常需要对原始源代码或相关文件进行更改或修复才能正常运行。 在移植软件以在不同的计算机体系结构上运行时,更改应用程序源代码尤为常见。 在 OpenWrt 构建系统中,这种变更管理是通过一个名为 quilt 的工具来完成的。
下面将介绍有关如何创建 .quiltrc 文件的重要信息,已确保创建的补丁符合 OpenWrt 构建系统的既定标准。
在第一章中,OpenWrt 构建系统已将 quilt 工具安装到目标独立工具目录下(/home/harlen/study/openwrt/source/staging_dir/host/bin)。 可通过以下指令验证:
~$ quilt --version
准备源代码
在 OpenWrt 构建系统中创建补丁非常简单,但在我们开始应用补丁之前,需要使用一个特殊选项来准备源代码:
~/study/openwrt/source$ make package/helloworld/{clean,prepare} QUILT=1
注意,当使用 QUILT=1 参数调用“make”时,源代码并非用于构建最终包。 该参数会在构建目录中创建额外的目录和文件。
最后的准备步骤是切换到源代码所在的构建目录,并确保所有已有的补丁被应用:
~/study/openwrt/source$ cd build_dir/target-.../helloworld-1.0/
~/study/openwrt/source/build_dir/target-.../helloworld-1.0/$ quilt push -a
此时 quilt push
命令并没有执行任何操作,提示“No series file found”,因为我们的应用程序尚未包含任何补丁。 然而,如果是多人合作开发的项目,这个步骤是必要的,因为其它开发者可能新增了别的补丁。
创建第一个补丁文件
首先为 Quilt 创建一个补丁上下文(patch context):
~/study/openwrt/source/build_dir/target-.../helloworld-1.0/$ quilt new 100-add_module_files.patch
补丁文件的名称来自 OpenWrt 构建系统的约定。 这些名称通常以一个自由序号开头,然后是对其用途的简短描述。 序数在某些情况下具有特定含义,但通常可以简单地使用从“000”开始的编号。本文作者选择数字“100”表示此补丁为现有源代码库添加了新功能,尚未集成到原始源代码(上游)中。
该命令的输出显示此补丁文件已创建并且现在位于 Quilt 补丁堆栈(patch stack)的顶部。
在这个补丁中,我们将为helloworld程序新增两个不同的文件,一个头文件和一个C源文件。 因此需要使用“quilt”工具开始在当前补丁上下文中跟踪这些文件:
~/study/openwrt/source/build_dir/target-.../helloworld-1.0/$ quilt add functions.c
~/study/openwrt/source/build_dir/target-.../helloworld-1.0/$ quilt add functions.h
下面开始编辑文件:
~/study/openwrt/source/build_dir/target-.../helloworld-1.0/$ touch functions.c
~/study/openwrt/source/build_dir/target-.../helloworld-1.0/$ quilt edit functions.c
int add(int a, int b)
{
return a + b;
}
~/study/openwrt/source/build_dir/target-.../helloworld-1.0/$ touch functions.h
~/study/openwrt/source/build_dir/target-.../helloworld-1.0/$ quilt edit functions.h
int add(int, int);
至此,我们可以通过quilt diff
来查看修改记录。
修改完成后,通过quilt refresh
刷新到补丁文件中。
将第一个补丁文件包含到包中
在 OpenWrt 构建系统中,补丁在源代码目录中创建和修改,然后迁移到它们所属的包中。 为了让我们将刚刚创建的补丁数据迁移到正确的包中,执行以下命令:
~$ cd ~/study/openwrt/source
~/study/openwrt/source$ make package/helloworld/update
可以浏览包源目录和源代码目录,来查看变化:
~$ ls -la /home/harlen/study/mypackages/examples/helloworld
~$ ls -la /home/harlen/study/app/helloword
正如我们所看到的,OpenWrt 构建系统将我们新创建的补丁文件迁移到包清单所在的目录中,而 原始源代码目录没有变化。
以下指令用于确认构建过程应用了补丁:
~$ cd ~/study/openwrt/source
~/study/openwrt/source$ make package/helloworld/{clean,prepare}
~/study/openwrt/source$ ls -la build_dir/target-<arch>_<subarch>_<clib>_<clibversion>/
最终看到新文件存在于构建目录中。
八、修补应用程序:已存在的文件
创建第二个补丁文件
为了调用上一章新增的函数,我们创建第二个补丁来修改 helloworld.c 文件。 首先,准备打补丁的源代码,并创建一个新的补丁上下文:
~$ cd ~/study/openwrt/source
~/study/openwrt/source$ make package/helloworld/{clean,prepare} QUILT=1
~/study/openwrt/source$ cd build_dir/target-.../helloworld-1.0/
~/study/openwrt/source/build_dir/target-.../helloworld-1.0/$ quilt push -a
~/study/openwrt/source/build_dir/target-.../helloworld-1.0/$ quilt new 101-use_module.patch
我们的任务是修改 helloworld.c 文件,因此执行以下命令将文件添加到补丁上下文中,并打开它进行编辑:
~/study/openwrt/source/build_dir/target-.../helloworld-1.0/$ 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 add
将文件添加到补丁上下文中。
保存更改,使用 quilt diff
查看改动,并使用 quilt refresh
将它们移动到补丁上下文。
最后回到 OpenWrt 构建系统的根目录并更新我们的新补丁到helloworld包:
~/study/openwrt/source/build_dir/target-.../helloworld-1.0/$ cd ~/study/openwrt/source
~/study/openwrt/source$ make package/helloworld/update
附录:参考文档
[OpenWrt Wiki] “Hello, world!” for OpenWrt: https://openwrt.org/docs/guide-developer/helloworld/start