熟悉 openwrt 环境

掌握如何编写、编译 openwrt 程序包

Posted by Jerry Chen on April 24, 2021

一些问题

编译 openwrt 时部分工具报错 No such file or directory

64 位系统运行 32 位程序,需要 32 位库支持;

1
sudo apt-get install lib32z1 lib32stdc++6

如何编译 openwrt 程序包

编译步骤

  1. 先在 openwrt 根目录加载环境变量和 shell 函数;

    1
    
    source build/envsetup.sh
    
  2. 选择平台(配置)

    1
    
    lunch <你的平台号>
    
  3. 在 openwrt 包目录(如 package/xxx/your_app 目录)下使用:

    该命令可以重新编译当前目录的 openwrt 包;

    1
    
    mm -B
    

mm 命令来自哪里?

mm 命令是一个 shell 函数(在 build/envsetup.sh 中有定义,实际指向了 scripts/mm.sh);

script/mm.sh 中可以看到 mm 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
function mm() {
    local T=$1/
    local orgin=`pwd`
    local path=`pwd`
    #find target makefile
    #trap 'echo $orgin >> ~/mm; trap - SIGINT; ' SIGINT
    while [ x`pwd` != x$T ] && [ x`pwd` != x"/" ]
    do
        find  -maxdepth 1 -name Makefile | xargs cat | grep "define Package" > /dev/null
        is_package=$?
        find  -maxdepth 1 -name Makefile | xargs cat | grep "define KernelPackage" > /dev/null
        is_kernel_package=$?
        if [ $is_package -eq 1 ] && [ $is_kernel_package -eq 1 ]; then
            cd ../
        else
            path=`pwd`
            target=${path#*$T}
            cd $T
            cmd="install V=s"
            for i in $*; do
                [ x$i = x"-B" ] && {
                    # -B clean the package
                    print_red "make $target/clean V=s"
                    make $target/clean V=s
                }
                [ x${i:0:2} = x"-j" ] && cmd=$cmd" "$i
		[ x${i:0:2} = x"-z" ] && export COMPILE_QUICK=1
            done
            print_red "compile option: ${COMPILE_QUICK}"
            print_red "make $target/$cmd"
            make $target/$cmd
            cd $orgin
            #trap - SIGINT
            return
        fi
    done
    cd $orgin
    #trap - SIGINT
    print_red "Can't not find Tina Package Makefile!"
}

package/test/myapp 目录中执行 mm -B,其作用和在 sdk 根目录执行如下命令效果相同:

  1. make package/test/myapp/clean V=s
  2. make package/test/myapp/install

那么像 package/test/myapp/your_cmd 这么长的 target 是怎么匹配的呢?

  • sdk 根目录 makefile:include build/toplevel.mk
  • build/toplevel.mk:有一个万能匹配符号 %::,这里调用了子目录 makefile;

查看下 package/test/myapp/Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
include $(TOPDIR)/rules.mk

PKG_NAME:=myapp
PKG_RELEASE:=1
PKG_BUILD_DIR := $(COMPILE_DIR)/$(PKG_NAME)

include $(BUILD_DIR)/package.mk

Package/myapp=XXXXXX
Build/Prepare=YYYYYY
Package/myapp/install=ZZZZZZ
 
$(eval $(call BuildPackage,$(PKG_NAME)))

目标 makefile 中的靶还应该加上 include 文件中的 target:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
.PHONY: prepare-package-install
prepare-package-install:
	@mkdir -p $(PKG_INFO_DIR)
	@touch $(PKG_INSTALL_STAMP).clean
	@echo "$(filter-out essential,$(PKG_FLAGS))" > $(PKG_INSTALL_STAMP).flags

$(PACKAGE_DIR):
	mkdir -p $@
	
dumpinfo:
download:
prepare:
configure:
compile: prepare-package-install
install: compile

clean: FORCE
	$(CleanStaging)
	$(call Build/UninstallDev,$(STAGING_DIR),$(STAGING_DIR_HOST))
	$(Build/Clean)
	rm -f $(STAGING_DIR)/packages/$(STAGING_FILES_LIST) $(STAGING_DIR_HOST)/packages/$(STAGING_FILES_LIST)
	rm -rf $(PKG_BUILD_DIR)

dist:
	$(Build/Dist)

distcheck:
	$(Build/DistCheck)

也就是,默认执行 prepare-package-install 操作,可选择执行的操作有:clean、install 等;

接着查看 build/package.mk 中 BuildPackage:

  • inlcude $(TOPDIR)/overlay/*/$(PKG_NAME).mk 文件;
  • 如果没有定义 TITLE 就使用一个默认值;
  • 将包添加到 BUILD_PACKAGES 变量中;
  • 找出包的依赖库;

  • 判断目标是否定义了 FIELD, TITLE CATEGORY SECTION VERSION 变量,没定义就报错;
  • $(call Build/DefaultTargets,$(1)) 进行默认目标构建流程;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
define BuildPackage
  $(Build/IncludeOverlay)
  $(eval $(Package/Default))
  $(eval $(Package/$(1)))

  ifdef DESCRIPTION
    $$(error DESCRIPTION:= is obsolete, use Package/PKG_NAME/description)
  endif

  ifndef Package/$(1)/description
    define Package/$(1)/description
	  $(TITLE)
    endef
  endif

  BUILD_PACKAGES += $(1)
  $(STAMP_PREPARED): $$(if $(QUILT)$(DUMP),,$(call find_library_dependencies,$(DEPENDS)))

  $(foreach FIELD, TITLE CATEGORY SECTION VERSION,
    ifeq ($($(FIELD)),)
      $$(error Package/$(1) is missing the $(FIELD) field)
    endif
  )

  $(if $(DUMP), \
    $(Dumpinfo/Package), \
    $(foreach target, \
      $(if $(Package/$(1)/targets),$(Package/$(1)/targets), \
        $(if $(PKG_TARGETS),$(PKG_TARGETS), ipkg) \
      ), $(BuildTarget/$(target)) \
    ) \
  )
  $(if $(PKG_HOST_ONLY)$(DUMP),,$(call Build/DefaultTargets,$(1)))
endef

如何添加自己的软件包

无需编译的纯脚本

在 sdk 根目录新建目录 package/tina_demo/demo_sh

文件如下:

1
2
3
.
|--demo_echo.sh
|--Makefile

其中 Makefile 如下:

  • Package/$(PKG_NAME) 描述包的基础信息;

    make menuconfig 时首先会扫描所有的包信息存放到 tmp/.config-package.in 中,然后通过字符界面进行显示;

  • Package/$(PKG_NAME)/description 描述包;

    进入 make menuconfig 模式后,在 demo_sh 选项上按下 “?” 后会显示;

  • Build/Prepare 是编译准备流程,一般需要建立编译目录,然后将需要的资源拷贝进去;

    如果不设置,默认会执行 Build/Prepare/Default 流程;

  • Build/Compile 是编译流程,不需要编译就将该项覆盖为空;

    如果不设置,默认会执行 Build/Compile/Default 流程;

  • Package/$(PKG_NAME)/install 是包的安装流程;

    需要 make menuconfig 选中 demo_sh 项后,mm -B 的 install 步骤才能生效;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
include $(TOPDIR)/rules.mk

PKG_NAME:=demo_sh
PKG_VERSION:=1.0.0
PKG_RELEASE:=1

PKG_BUILD_DIR:=$(COMPILE_DIR)/$(PKG_NAME)_$(PKG_VERSION)

include $(BUILD_DIR)/package.mk

define Package/$(PKG_NAME)
  SECTION:=exe
  CATEGORY:=tina demo
  TITLE:=echo shell
endef

define Package/$(PKG_NAME)/description
	demo_sh
	 a echo shell script
endef

define Build/Prepare
	mkdir -p $(PKG_BUILD_DIR)
	$(CP) ./* $(PKG_BUILD_DIR)/
endef

define Build/Compile
endef

define Package/$(PKG_NAME)/install
	$(INSTALL_DIR) $(1)/usr/robot/bin

	$(INSTALL_BIN) ./demo_echo.sh $(1)/usr/robot/bin/
endef

$(eval $(call BuildPackage,$(PKG_NAME)))

验证步骤:

  1. 新建包目录和其中的文件;

    • Makefile 内容参考上面;
    • demo_echo.sh 测试脚本,内容任意,不需要执行权限,$(INSTALL_BIN) 操作会赋予该文件权限;
  2. 进入 sdk 根目录,输入 make menuconfig 选中新增的项,然后保存退出;

  3. 回到第一步建立的包目录中,输入 mm -B 进行编译;

    如果你使用的 vscode,发现左侧列表中没有生成文件夹或文件,刷新下文件列表;

    • out/my_platform/compile_dir/target 中会创建 demo_sh_1.0.0 目录(Prepare 产生);

    • out/my_platform/staging_dir/target/rootfs 中会存在 usr/robot/bin/demo_echo.sh 文件(install 产生);

      值得注意的是,上述目录的 rootfs 是一个样板目录,很多文件的权限和所有者并不正确,仅供参考,不要把它和小机运行的根文件系统画等号;

  4. 再次进入 sdk 根目录,进行打包操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    # [可选] 编译(不要用 root 账户)
    # 字符选择模式去掉一个软件包后应该重新 make 一次,可以有效去除 rootfs 中多余的文件
    # 应该是把 rootfs 全部清理,然后将目前勾选的软件包添加到 rootfs
    make -j6
       
    # 打包镜像,路径 out/my_platform/(vscode 需要刷新下文件列表)
    pack
       
    # 生成 ota 包,路径 out/my_platform/ota(vscode 需要刷新下文件列表)
    make_ota_image
    

Makefile 编译形式

在 sdk 根目录新建目录 package/tina_demo/demo_makefile

文件如下:

1
2
3
4
5
6
.
|--src
|   |--demo_print.c
|   |--Makefile
|
|--Makefile

src/demo_print.c 如下:

1
2
3
4
5
6
7
#include <stdio.h>

int main(int argc, char *argv[])
{
	printf("hello world!\r\n");
    return 0;
}

src/Makefile 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
target			= demo_print

INCLUDES        += -I.

SRCS = demo_print.c
OBJS = $(SRCS:.c=.o)

install:$(target)
	@mkdir -p $(CONFIG_PREFIX)/usr/bin
	@cp $(target) $(CONFIG_PREFIX)/usr/bin

%.o:%.c
	$(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $<

$(target): $(OBJS)
	$(CC) $(INCLUDES) $(LDFLAGS) $^ -o $@

all: install
clean:
	rm -rf $(target) $(OBJS)

顶层 Makefile 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
include $(TOPDIR)/rules.mk

PKG_NAME:=demo_makefile
PKG_VERSION:=1.0.0
PKG_RELEASE:=1

PKG_BUILD_DIR:=$(COMPILE_DIR)/$(PKG_NAME)_$(PKG_VERSION)

include $(BUILD_DIR)/package.mk

define Package/$(PKG_NAME)
  SECTION:=exe
  CATEGORY:=tina demo
  TITLE:=print c(makefile)
endef

define Package/$(PKG_NAME)/description
	demo_makefile
	 a print c program
endef

define Build/Prepare
	mkdir -p $(PKG_BUILD_DIR)
	$(CP) -r ./src/* $(PKG_BUILD_DIR)/
endef

define Build/Compile
	$(MAKE) -C $(PKG_BUILD_DIR)/ \
		ARCH="$(TARGET_ARCH)" \
		AR="$(TARGET_AR)" \
		CC="$(TARGET_CC)" \
		CXX="$(TARGET_CXX)" \
		CFLAGS="$(TARGET_CFLAGS)" \
		LDFLAGS="$(TARGET_LDFLAGS)" \
		CONFIG_PREFIX="$(PKG_INSTALL_DIR)" \
		all
endef

define Package/$(PKG_NAME)/install
	$(INSTALL_DIR) $(1)/usr/robot/bin

	$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/* $(1)/usr/robot/bin/
endef

$(eval $(call BuildPackage,$(PKG_NAME)))

cmake 编译形式

在 sdk 根目录新建目录 package/tina_demo/demo_cmake

文件如下:

1
2
3
4
5
6
.
|--src
|   |--demo_print.c
|   |--CMakeLists.txt
|
|--Makefile

src/demo_print.c 如下:

1
2
3
4
5
6
7
#include <stdio.h>

int main(int argc, char *argv[])
{
	printf("hello world!\r\n");
    return 0;
}

src/CMakeLists.txt 如下:

1
2
3
4
5
6
cmake_minimum_required(VERSION 3.4)

project(demo_cmake)

add_executable(demo_cmake demo_print.c)
install(TARGETS demo_cmake RUNTIME DESTINATION bin)

顶层 Makefile 如下:

使用默认 cmake 方法进行编译;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
include $(TOPDIR)/rules.mk

PKG_NAME:=demo_cmake
PKG_VERSION:=1.0.0
PKG_RELEASE:=1

PKG_BUILD_DIR:=$(COMPILE_DIR)/$(PKG_NAME)_$(PKG_VERSION)

include $(BUILD_DIR)/package.mk
include $(BUILD_DIR)/cmake.mk

define Package/$(PKG_NAME)
  SECTION:=exe
  CATEGORY:=tina demo
  TITLE:=print c(cmake)
endef

define Package/$(PKG_NAME)/description
	demo_cmake
	 a print c program
endef

define Build/Prepare
	mkdir -p $(PKG_BUILD_DIR)
	$(CP) -r ./src/* $(PKG_BUILD_DIR)/
endef

define Package/$(PKG_NAME)/install
	$(INSTALL_DIR) $(1)/usr/robot/bin

	$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/* $(1)/usr/robot/bin/
endef

$(eval $(call BuildPackage,$(PKG_NAME)))