天天看点

[OpenWrt Wiki][开发基础]新建一个简单程序前言一、设置构建系统二、创建“Hello, world!”应用程序三、从应用程序创建包四、将包源包含到OpenWrt构建系统中五、构建、部署、测试应用程序六、在应用程序中使用GNU make七、修补应用程序:添加新文件八、修补应用程序:已存在的文件附录:参考文档

目录

  • 前言
  • 一、设置构建系统
    • 安装依赖包
    • 获取源码
    • 配置、构建交叉编译工具链
    • 修改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 变量:

二、创建“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工具安装:

[email protected]:/# opkg install /tmp/helloworld_1.0-1_<arch>.ipk
Installing helloworld (1.0-1) to root...
Configuring helloworld.
           

之后,就可以执行该应用程序了:

[email protected]:/# helloworld

Hello, world!
           

移除包

已安装的包,也可以通过opkg来移除:

[email protected]:/# opkg remove helloworld
Removing package helloworld from root...
           

不再需要的包,可以删除它的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源码,只需要执行:

如果输出提示 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)。 可通过以下指令验证:

准备源代码

在 OpenWrt 构建系统中创建补丁非常简单,但在我们开始应用补丁之前,需要使用一个特殊选项来准备源代码:

注意,当使用 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

继续阅读