Buildroot 如何添加新的包(自建app)

本文翻译自官方社区文档

本文介绍如何将新包(用户空间库或应用程序)集成到 Buildroot 中。它还显示了现有包是如何集成的,这是解决问题或调整其配置所必需的。


包目录

首先,在package目录下为你的新软件新建文件夹,例如libfool

有些包按主题分组在子目录中:x11r7qt5gstreamer。如果您的包符合其中一个类别,那么在这些类别中创建您的包目录。但是,不建议创建新的子目录。

编码风格

编码风格规则可以帮助您在Buildroot中添加新文件或重构现有文件。

Config.in 文件

Config.in文件包含Buildroot几乎所有可配置的条目。

一个条目具有以下模式:

config BR2_PACKAGE_LIBFOO
        bool "libfoo"
        depends on BR2_PACKAGE_LIBBAZ
        select BR2_PACKAGE_LIBBAR
        help
          This is a comment that explains what libfoo is. The help text
          should be wrapped.

          http://foosoftware.org/libfoo/
  • bool, depends on, select and help行缩进一个制表符。
  • 帮助文本本身应该用一个制表符和两个空格缩进。
  • 帮助文本应该换行以适应72列,其中制表符计数为8,因此文本本身为62个字符。

.mk 文件

  • 头:文件以头文件开始。它包含模块名,最好是小写,用由80个散列组成的分隔符包围。标题后必须有一个空行:
################################################################################
#
# libfoo
#
################################################################################
  • 赋值:使用==前面和后面加一个空格,不需要对齐=
LIBFOO_VERSION = 1.0
LIBFOO_CONF_OPTS += --without-python-support
  • 缩进:只使用制表符:
define LIBFOO_REMOVE_DOC
        $(RM) -fr $(TARGET_DIR)/usr/share/libfoo/doc \
                $(TARGET_DIR)/usr/share/man/man3/libfoo*
endef

请注意,define块中的commands应该总是以tab开始,这样make将它们识别为命令。

  • 可选依赖:最好使用多行语法
# 使用如下语句
ifeq ($(BR2_PACKAGE_PYTHON3),y)
LIBFOO_CONF_OPTS += --with-python-support
LIBFOO_DEPENDENCIES += python3
else
LIBFOO_CONF_OPTS += --without-python-support
endif

# 而不是这样
LIBFOO_CONF_OPTS += --with$(if $(BR2_PACKAGE_PYTHON3),,out)-python-support
LIBFOO_DEPENDENCIES += $(if $(BR2_PACKAGE_PYTHON3),python3,)

保持配置选项和依赖项紧密相连。

  • 可选钩子:将钩子定义和赋值放在一个if块中:
# 使用这种写法:
ifneq ($(BR2_LIBFOO_INSTALL_DATA),y)
define LIBFOO_REMOVE_DATA
        $(RM) -fr $(TARGET_DIR)/usr/share/libfoo/data
endef
LIBFOO_POST_INSTALL_TARGET_HOOKS += LIBFOO_REMOVE_DATA
endif

# 而不是:
define LIBFOO_REMOVE_DATA
        $(RM) -fr $(TARGET_DIR)/usr/share/libfoo/data
endef

ifneq ($(BR2_LIBFOO_INSTALL_DATA),y)
LIBFOO_POST_INSTALL_TARGET_HOOKS += LIBFOO_REMOVE_DATA
endif

配置文件

对于要在配置工具中显示的包,您需要在您的包目录中创建一个Config文件。有两种类型:Config.in and Config.in.host

Config.in 文件

对于在目标上使用的包,创建一个名为Config.in的文件。这个文件将包含与我们的libfoo软件相关的选项描述,这些选项将在配置工具中使用和显示。它基本上应该包含:

config BR2_PACKAGE_LIBFOO
        bool "libfoo"
        help
          This is a comment that explains what libfoo is. The help text
          should be wrapped.
          http://foosoftware.org/libfoo/

关于配置选项的bool行、help行和其他元数据信息必须用一个tab缩进。帮助文本本身应该缩进一个制表符和两个空格,而且应该适当换行以适应72列。因为制表符计数为8,所以每行文本本身有62个字符。帮助文本必须在空行之后提到项目的上游URL。

作为Buildroot特有的惯例,属性的排序如下:

  • 选项的类型:包含promptboolstring 等类型
  • 必要的 default 数值
  • depends on 表单,描述的对目标的依赖
  • depends on 表单,描述的对工具链的依赖
  • depends on 表单,描述的对其它包的依赖
  • select 表单描述的依赖
  • help 关键字和帮助文本

您可以将其他子选项添加到if BR2_PACKAGE_LIBFOO…………endif语句中,以配置软件中的特定内容。您可以查看其他包中的示例。配置的语法与内核Kconfig文件相同。此语法的文档可在 http://kernel.org/doc/Documentation/kbuild/kconfig-language.txt 获得。

最后,您必须将新的libfoo/Config添加进package/Config.in(或者在一个类别子目录中,如果您决定将您的包放在一个现有类别中)。其中包含的文件按每个类别的字母顺序排序,除了包的名字之外不应该包含任何内容。

source "package/libfoo/Config.in"

Config.in.host 文件

还有一些包需要被主机系统构建。这里有两个选项:

  • 主机包只需要满足一个或多个目标包的构建时的依赖性。在这种情况下,在目标包的BAR_DEPENDENCIES变量中加入host-foo。不需要创建Config.in.host文件。
  • 用户应该可以从配置菜单中显式地选择主机包。在这个例子中,为该主机包创建一个Config.in.host文件:
config BR2_PACKAGE_HOST_FOO
        bool "host foo"
        help
          This is a comment that explains what foo for the host is.
          
          http://foosoftware.org/foo/

​ 与Config.in相同的编码风格和选项。在文件中是有效的。

​ 最后,必须添加新的libfoo/Config.in.hostpackage/Config.in.host。其中包含的文件是按字母顺序排序的,除了包的名字之外不应该包含任何内容。

source "package/foo/Config.in.host"

​ 然后主机包在Host utilities菜单中有效。

.mk 文件

最后,这是最困难的部分。创建一个名为libfoo.mk的文件。它描述了该包应该如何下载、配置、构建、安装等。

根据包类型的不同,.mk文件必须以不同的方式编写,使用不同的基础结构:

  • 通用包的生成文件(不使用自动工具或CMake):这些都是基于一个类似于使用自动工具构建系统包的基础设施,但需要开发人员多做一些工作。它们指定应该为包的配置、编译和安装做什么。对于所有不使用自动工具作为其构建系统的包,必须使用此基础结构。将来,可能会为其他构建系统编写其他专门的基础结构。
  • 为基于自动工具的软件(autoconf, automake等)生成文件:我们为这类包提供了专门的基础设施,因为自动工具是一个非常常见的构建系统。这个基础设施必须用于依赖于自动工具作为其构建系统的新包。
  • 基于CMake的软件的makefile:我们为这类包提供了专门的基础设施,因为CMake是一个越来越常用的构建系统,并且具有标准化的行为。这个基础设施必须用于依赖于CMake的新包。
  • Python模块的生成文件:我们有一个专门用于Python模块的基础结构,可以使用distutils或setuptools机制。
  • 为Lua模块生成文件:我们在LuaRocks网站上有专门的Lua模块基础设施。

.hash 文件

如果可能,您必须添加第三个名为libfoo.hash的文件。它包含libfoo包下载文件的哈希值。不添加.hash文件的唯一原因是由于下载包的方式而无法进行hash检查。

当一个包有版本选择选项时,哈希文件可以存储在以版本命名的子目录中,例如package/libfoo/1.2.3/libfoo.hash。如果不同的版本具有不同的许可条款,但它们存储在同一个文件中,那么这一点尤其重要。否则,hash 文件应该留在包的目录中。

存储在该文件中的 hash 用于验证下载文件和许可证文件的完整性。

用于具有特定构建系统的软件包的基础设施

我们所说的具有特殊构建系统的软件包是指所有构建系统不是标准系统的软件包,如autotools或CMake。这通常包括那些构建系统是基于手写的Makefiles或shell脚本的软件包。

generic-package 通用包教程

01: ################################################################################
02: #
03: # libfoo
04: #
05: ################################################################################
06:
07: LIBFOO_VERSION = 1.0
08: LIBFOO_SOURCE = libfoo-$(LIBFOO_VERSION).tar.gz
09: LIBFOO_SITE = http://www.foosoftware.org/download
10: LIBFOO_LICENSE = GPL-3.0+
11: LIBFOO_LICENSE_FILES = COPYING
12: LIBFOO_INSTALL_STAGING = YES
13: LIBFOO_CONFIG_SCRIPTS = libfoo-config
14: LIBFOO_DEPENDENCIES = host-libaaa libbbb
15:
16: define LIBFOO_BUILD_CMDS
17:     $(MAKE) $(TARGET_CONFIGURE_OPTS) -C $(@D) all
18: endef
19:
20: define LIBFOO_INSTALL_STAGING_CMDS
21:     $(INSTALL) -D -m 0755 $(@D)/libfoo.a $(STAGING_DIR)/usr/lib/libfoo.a
22:     $(INSTALL) -D -m 0644 $(@D)/foo.h $(STAGING_DIR)/usr/include/foo.h
23:     $(INSTALL) -D -m 0755 $(@D)/libfoo.so* $(STAGING_DIR)/usr/lib
24: endef
25:
26: define LIBFOO_INSTALL_TARGET_CMDS
27:     $(INSTALL) -D -m 0755 $(@D)/libfoo.so* $(TARGET_DIR)/usr/lib
28:     $(INSTALL) -d -m 0755 $(TARGET_DIR)/etc/foo.d
29: endef
30:
31: define LIBFOO_USERS
32:     foo -1 libfoo -1 * - - - LibFoo daemon
33: endef
34:
35: define LIBFOO_DEVICES
36:     /dev/foo c 666 0 0 42 0 - - -
37: endef
38:
39: define LIBFOO_PERMISSIONS
40:     /bin/foo f 4755 foo libfoo - - - - -
41: endef
42:
43: $(eval $(generic-package))

Makefile从第7到11行开始提供元数据信息:

  • 软件包的版本(LIBFOO_VERSION
  • 包含软件包的tarball的名字(LIBFOO_SOURCE)(推荐使用xz-ed tarball)
  • 可以下载tarball的互联网位置(LIBFOO_SITE
  • 许可证(LIBFOO_LICENSE
  • 包含许可证文本的文件(LIBFOO_LICENSE_FILES

所有的变量都必须以相同的前缀开始,本例中为LIBFOO_。这个前缀总是软件包名称的大写版本(见下文以了解软件包名称的定义)。

在第12行,我们指定这个包要安装一些东西到暂存空间。这对库来说是经常需要的,因为它们必须在暂存空间中安装头文件和其他开发文件。这将确保LIBFOO_INSTALL_STAGING_CMDS变量中列出的命令被执行。

在第13行,我们指定要对在LIBFOO_INSTALL_STAGING_CMDS阶段安装的一些libfoo-config文件做一些修正。这些*-config文件是可执行的shell脚本文件,位于$(STAGING_DIR)/usr/bin目录下,由其他第三方软件包执行,以找出该特定软件包的位置和链接标志。

而在这里指定的所有*-config文件都默认给出了错误的、不适合交叉编译的主机系统链接标志。所以对这些脚本做了一些sed魔法,以使它们给出正确的标志。给予LIBFOO_CONFIG_SCRIPTS的参数是需要修正的shell脚本的文件名。所有这些名字都是相对于$(STAGING_DIR)/usr/bin的,如果需要,可以给出多个名字。

此外,LIBFOO_CONFIG_SCRIPTS中列出的脚本会从$(TARGET_DIR)/usr/bin中删除,因为目标机上不需要它们。

在第14行,我们指定了这个包所依赖的依赖项列表。这些依赖关系是以小写的包名列出的,可以是目标的包(没有host-前缀),也可以是主机的包(有host-前缀)。Buildroot将确保所有这些软件包在当前软件包开始配置之前就被构建和安装。

在第16~29行,定义了在软件包配置、编译和安装的不同步骤中应该做什么:

  • LIBFOO_BUILD_CMDS 告诉了应该执行哪些步骤来构建软件包。
  • LIBFOO_INSTALL_STAGING_CMDS 讲述了在暂存空间安装软件包时应该执行哪些步骤。
  • LIBFOO_INSTALL_TARGET_CMDS 讲述了在目标空间安装软件包时应该执行哪些步骤。

所有这些步骤都依赖于$(@D)变量,它包含提取软件包源代码的目录。

在第31~33行,我们定义了一个被这个包使用的用户(例如,以非root身份运行一个守护程序)(LIBFOO_USERS)。

在第35~37行,我们定义了这个包所使用的一个设备节点文件(LIBFOO_DEVICES)。

在第39~41行,我们定义了对这个软件包所安装的特定文件的权限(LIBFOO_PERMISSIONS)。

最后,在第43行,我们调用generic-package函数,它根据之前定义的变量,生成所有必要的Makefile代码,使你的包能够工作。

generic-package 通用包引用介绍

通用目标有两种变体。generic-package 宏用于为目标进行交叉编译的包。host-generic-package 宏用于宿主软件包,为宿主进行本地编译。可以在一个.mk文件中同时调用这两个宏:一次用于创建生成目标包的规则,一次用于创建生成主机包的规则。

$(eval $(generic-package))
$(eval $(host-generic-package))

如果目标软件包的编译需要在主机上安装一些工具,这可能很有用。如果软件包的名称是 libfoo,那么目标软件包的名称也是 libfoo,而主机软件包的名称是 host-libfoo。如果其他软件包依赖于libfoohost-libfoo,则应在其他软件包的DEPENDENCIES变量中使用这些名称。

generic-packagehost-generic-package 宏的调用都必须在 .mk 文件的最后,在所有变量定义之后。

host-generic-package 的调用必须在对 generic-package 的调用之后,如果有的话。

对于目标包,generic-package 使用由 .mk 文件定义并以大写的包名为前缀的变量:LIBFOO_host-generic-package 使用 HOST_LIBFOO_ 变量。对于某些变量,如果HOST_LIBFOO_前缀的变量不存在,包的基础结构就会使用相应的以LIBFOO_为前缀的变量。这么做是为了那些可能对目标包和主机包有相同值的变量。

可用开始变量

下面列出可以在.mk文件中设置提供元数据信息的变量(假设软件包名称是libfoo):

  1. LIBFOO_VERSION必选,必须包含软件包的版本。
    注意,如果HOST_LIBFOO_VERSION不存在,它将被假定为与LIBFOO_VERSION相同。对于直接从版本控制系统中获取的包,它也可以是一个修订号或一个标签。

  2. LIBFOO_SOURCE可能包含包的tarball名称,Buildroot将使用它从LIBFOO_SITE下载tarball。
    如果未指定HOST_LIBFOO_SOURCE,则默认为LIBFOO_SOURCE。如果没有指定,则假定该值为libfoo-$(LIBFOO_VERSION).tar.gz.。

    例如:LIBFOO_SOURCE = foobar-$(LIBFOO_VERSION).tar.bz2

  3. LIBFOO_PATCH可能包含了空格分隔的补丁文件名列表,Buildroot将下载并应用于包源代码。
    如果一个条目包含://,那么Buildroot会假定它是一个完整的URL,并从这个位置下载补丁。否则,Buildroot会假定补丁应该从LIBFOO_SITE下载。如果未指定HOST_LIBFOO_PATCH,则默认为LIBFOO_PATCH

    请注意,包含在Buildroot中的补丁使用了一种不同的机制:所有形式为*.patch的文件。在解压后,Buildroot中的包目录中的补丁将被应用到包中(参见给包打补丁)。最后,在应用Buildroot包目录中的补丁之前,应用LIBFOO_PATCH变量中列出的补丁。

  4. LIBFOO_SITE提供了包的位置,可以是URL或本地文件系统路径。
    HTTP、FTP和SCP是用于从支持的URL类型检索包tarball,在这些情况下不要包含末尾的斜杠,它将被Buildroot添加到目录和文件名之间。Git、Subversion、Mercurial和Bazaar都是支持的URL类型,可以直接从源代码管理系统检索包。有一个帮助器函数可以使从GitHub下载源压缩包变得更容易(请参阅18.24.4节“如何从GitHub添加一个包”以获得详细信息)。
    文件系统路径可以用来指定一个tarball或包含包源代码的目录。请参阅下面的LIBFOO_SITE_METHOD了解更多关于检索如何工作的细节。注意,SCP url的格式应该是SCP://[user@]host:filepath,而且filepath是相对于用户的主目录的,所以您可能希望在路径前加一个斜杠来表示绝对路径:SCP://[user@]host:/absolutepath。如果未指定HOST_LIBFOO_SITE,则默认为LIBFOO_SITE

    例如:LIBFOO_SITE=http://www.libfoosoftware.org/libfoo
    LIBFOO_SITE=http://svn.xiph.org/trunk/Tremor
    LIBFOO_SITE=/opt/software/libfoo.tar.gz
    LIBFOO_SITE=$(TOPDIR)/…/src/libfoo .gz

  5. LIBFOO_DL_OPTS是一个以空格分隔的附加选项列表,用于传递给下载程序。
    对服务器端需要检查用户登录和密码的,或需要使用代理的文件很有用。支持所有对LIBFOO_SITE_METHOD有效的下载方法;有效的选项取决于下载方法(请查阅各下载工具的手册)。

  6. LIBFOO_EXTRA_DOWNLOADS是一个空格分隔的附加文件列表,Buildroot应该下载这些文件。
    如果一个条目包含://,那么Buildroot会假定它是一个完整的URL,并使用这个URL下载文件。否则,Buildroot会假定要下载的文件位于LIBFOO_SITE。Buildroot不会对这些额外的文件做任何事情,除了下载它们。它们将由软件包的配方决定是否从$(LIBFOO_DL_DIR)使用它们。

  7. LIBFOO_SITE_METHOD确定用于获取或复制包源代码的方法。
    在很多情况下,Buildroot从LIBFOO_SITE的内容猜测方法,所以LIBFOO_SITE_METHOD不一定需要设定。当未指定HOST_LIBFOO_SITE_METHOD时,默认为LIBFOO_SITE_METHOD的值。LIBFOO_SITE_METHOD可能的值是:

    • wget 用于tarball的普通FTP/HTTP下载。当LIBFOO_SITE 以http://、https://、ftp://开始时默认使用。
    • scp 用于通过SSH的scp下载tarballs。当LIBFOO_SITE以scp://开头时默认使用。
    • sftp 用于通过SSH的sftp下载tarballs。当LIBFOO_SITE以sftp://开头时默认使用。
    • svn 用于从Subversion存储库中检索源代码。当LIBFOO_SITE以svn://开头时默认使用。当LIBFOO_SITE中指定了一个http:// Subversion库的URL时,必须指定LIBFOO_SITE_METHOD=svn。Buildroot会执行一次签出,并在下载缓存中以tarball的形式保存;后续的构建会使用tarball而不是执行另一次签出。
    • cvs 用于从 CVS 仓库检索源代码。当LIBFOO_SITE以cvs://开头时默认使用。下载的源代码会像svn方法一样被缓存起来。除非在LIBFOO_SITE上明确定义,否则假定为匿名pserver模式。LIBFOO_SITE=cvs://libfoo.net:/cvsroot/libfooLIBFOO_SITE=cvs://:ext:libfoo.net:/cvsroot/libfoo 都被接受,在前者中假定有匿名的 pserver 访问模式。LIBFOO_SITE必须包含源URL以及远程仓库目录。模块是指包的名称。LIBFOO_VERSION是强制性的,必须是一个标签、一个分支或一个日期(例如 “2014-10-20”、“2014-10-20 13:45”、"2014-10-20 13:45+01 " 详情见 "man cvs "了解更多细节)。
    • git 用于从Git存储库中检索源代码。当LIBFOO_SITE以git://开头时默认使用。下载的源代码会像svn方法一样被缓存起来。
    • hg 用于从Mercurial仓库检索源代码。当LIBFOO_SITE包含一个Mercurial仓库的URL时,必须指定LIBFOO_SITE_METHOD=hg。下载的源代码会像svn方法一样被缓存起来。
    • bzr 用于从Bazaar资源库中检索源代码。当LIBFOO_SITE以bzr://开头时默认使用。下载的源代码会像svn方法那样被缓存起来。
    • file 用于本地的tarball。当LIBFOO_SITE指定一个软件包的tarball作为本地文件名时,应该使用这个。对于那些不公开或没有版本控制的软件很有用。
    • local 表示一个本地源代码目录。当 LIBFOO_SITE 指定了一个包含软件包源代码的本地目录路径时,应该使用它。Buildroot 将源码目录的内容复制到软件包的构建目录中。注意,对于本地软件包,不会应用补丁。如果你仍然需要对源代码打补丁,请使用 LIBFOO_POST_RSYNC_HOOKS
  8. LIBFOO_GIT_SUBMODULES设置为 YES 在仓库中创建一个包含 git 子模块的包。
    这只适用于用git下载的软件包(例如当LIBFOO_SITE_METHOD=git)。
    注意,当git子模块包含捆绑的库时,尽量不要使用这样的git子模块,在这种情况下,我们更愿意使用他们自己软件包中的这些库。

  9. LIBFOO_GIT_LFS被设置为YES如果Git仓库使用Git LFS来存储额外的大文件。
    这只适用于用git下载的软件包(即当LIBFOO_SITE_METHOD=git)。

  10. LIBFOO_STRIP_COMPONENTS是tar在提取时需要从文件名中去除的目录结构的数量。
    大多数软件包的tarball都有一个名为"-"的前导成分,因此Buildroot将-strip-components=1传递给tar来移除它。对于没有这个组件的非标准包,或者有多个前导组件需要去除的包,用需要传递给tar的值来设置这个变量。默认值:1。

  11. LIBFOO_EXCLUDES是一个用空格分隔的列表,列出了在提取包时要排除的选项。
    该列表中的每一项都作为 tar 的 --exclude 选项被传递。默认情况下是空的。

  12. LIBFOO_DEPENDENCIES列出了当前目标软件包在编译时需要的依赖关系(以软件包名称为单位)。
    这些依赖关系保证在当前包的配置开始之前已经编译和安装。然而,对这些依赖项的配置的修改不会强制重建当前包。以类似的方式,HOST_LIBFOO_DEPENDENCIES列出了当前主机包的依赖项。

  13. LIBFOO_EXTRACT_DEPENDENCIES列出了当前目标软件包被解压缩所需要的依赖关系(以软件包名称为准)。
    这些依赖关系保证在当前包的提取步骤开始之前被编译和安装。这只是软件包基础设施的内部使用,通常不应该被软件包直接使用。

  14. LIBFOO_PROVIDES列出了 libfoo 所实现的所有虚拟软件包。

  15. LIBFOO_INSTALL_STAGING默认为NO。如果设置为YES,那么LIBFOO_INSTALL_STAGING_CMDS变量中的命令将被执行,以将软件包安装到staging目录中。

  16. LIBFOO_INSTALL_TARGET默认为YES。如果设置为YES,那么LIBFOO_INSTALL_TARGET_CMDS变量中的命令将被执行,以将软件包安装到target目录。

  17. LIBFOO_INSTALL_IMAGES默认为NO。如果设置为YES,那么LIBFOO_INSTALL_IMAGES_CMDS变量中的命令将被执行,以将软件包安装到images目录。

  18. LIBFOO_CONFIG_SCRIPTS列出了 $(STAGING_DIR)/usr/bin 中需要进行特殊修正的文件,使它们对交叉编译友好。
    可以给出以空格分隔的多个文件名,所有文件名都是相对于$(STAGING_DIR)/usr/bin的。LIBFOO_CONFIG_SCRIPTS中列出的文件也将从 $(TARGET_DIR)/usr/bin中删除,因为它们在target里不需要。

  19. LIBFOO_DEVICES可选变量,列出了使用静态设备表时 Buildroot 要创建的设备文件。要使用的语法是makedevs的语法。

  20. LIBFOO_PERMISSIONS可选变量,列出了在构建过程结束时要做的权限修改。语法也是makedevs的语法。

  21. LIBFOO_USERS可选变量,为一个你想作为特定用户运行的程序(例如,作为一个守护程序,或者作为一个cron-job),列出要为这个软件包创建的用户。
    这个语法在精神上与makedevs的语法相似。

  22. LIBFOO_LICENSE可选变量,定义了发布软件包所依据的许可证(或许可证)。
    这个名字将出现在make legal-info制作的清单文件中。如果许可证出现在SPDX许可证列表中,请使用SPDX的简短标识符来使清单文件统一。否则,请以精确和简洁的方式描述许可证,避免使用诸如BSD之类的模糊名称,因为这些名称实际上是一个许可证系列的名称。如果没有定义,unknown将出现在此包的清单文件的license字段中。这个变量的预期格式必须符合以下规则:

    • 如果软件包的不同部分在不同的许可证下发布,那么用逗号分开许可证(例如,LIBFOO_LICENSE = GPL-2.0+, LGPL-2.1+)。如果哪个组件在什么许可证下有明显的区别,那么就在括号之间用该组件注释许可证(例如:LIBFOO_LICENSE = GPL-2.0+(programs),LGPL-2.1+(libraries))。
    • 如果一些许可证是以子选项的启用为条件的,请在有条件的许可证后面加上一个逗号(例如,FOO_LICENSE += , GPL-2.0+ (programs));基础设施会在内部删除逗号前的空格。
    • 如果软件包是双重许可的,那么就用or关键字分开许可(例如,LIBFOO_LICENSE = AFL-2.1 or GPL-2.0+)。
  23. LIBFOO_LICENSE_FILES可选变量,是一个以空格分隔的文件列表,它包含了软件包发布时的许可证。
    make legal-info将所有这些文件复制到legal-info目录中。如果没有定义,将产生一个警告让你知道,并且not saved将出现在这个软件包的清单文件的license files字段中。

  24. LIBFOO_ACTUAL_SOURCE_TARBALL只适用于这样的软件包内:其一对 LIBFOO_SITE / LIBFOO_SOURCE 指向的archive内包含的不是源代码而是二进制代码。
    这是一种非常不常见的情况,只适用于已经编译好的外部工具链,尽管理论上它可能适用于其他软件包。在这种情况下,通常会有一个单独的tarball来提供实际的源代码。设置 LIBFOO_ACTUAL_SOURCE_TARBALL 为实际的源代码存档的名称,Buildroot 就会下载它,并在你运行 make legal-info 时使用它来收集与法律有关的资料。注意这个文件不会在常规构建过程中被下载,也不会被make source下载。

  25. LIBFOO_ACTUAL_SOURCE_SITE提供了实际源码压缩包的位置。
    如果二进制文件和源码档案托管在同一个目录下,因为默认值是LIBFOO_SITE,你就不需要设置这个变量。如果LIBFOO_ACTUAL_SOURCE_TARBALL没有设置,那么定义LIBFOO_ACTUAL_SOURCE_SITE就没有意义了。

  26. LIBFOO_REDISTRIBUTE默认为YES。如果设置为YES,表示软件包的源代码允许被重新发布。
    对于非开放源代码的软件包,将其设置为NO。Buildroot在收集legal-info时将不会保存这个包的源代码。

  27. LIBFOO_FLAT_STACKSIZE定义了建立在FLAT二进制格式下的应用程序的堆栈大小。
    在NOMMU架构的处理器上,应用程序的堆栈大小在运行时不能被扩大。FLAT二进制格式的默认堆栈大小只有4k字节。如果应用程序需要消耗更多的堆栈,请在这里附加所需的数字。

  28. LIBFOO_BIN_ARCH_EXCLUDE是一个以空格分隔的路径列表(相对于目标目录),在检查软件包是否正确安装了交叉编译的二进制文件时忽略这些路径。
    你很少需要设置这个变量,除非软件包在默认位置/lib/firmware/usr/lib/firmware/lib/modules/usr/lib/modules/usr/share之外安装二进制blob,因为它们会被自动排除。

  29. LIBFOO_IGNORE_CVES是一个用空格分隔的CVE列表,它告诉Buildroot CVE跟踪工具哪些CVE应该被忽略。
    通常在CVE被包中的补丁修复,或CVE因某种原因不影响Buildroot包时使用。在这个变量中加入CVE时,必须先在Makefile中做注释。例如:

    # 0001-fix-cve-2020-12345.patch
    LIBFOO_IGNORE_CVES += CVE-2020-12345
    # only when built with libbaz, which Buildroot doesn't support
    LIBFOO_IGNORE_CVES += CVE-2020-54321
    
  30. LIBFOO_CPE_ID_*,这是一组变量,允许包定义其CPE标识符。可用的变量有:

    • LIBFOO_CPE_ID_PREFIX,指定CPE标识符的前缀,即前三个字段。如果没有定义,默认值是cpe:2.3:a
    • LIBFOO_CPE_ID_VENDOR,指定CPE标识符的供应商部分。如果没有定义,默认值是<pkgname>_project
    • LIBFOO_CPE_ID_PRODUCT,指定CPE标识符的产品部分。如果没有定义,默认值是<pkgname>
    • LIBFOO_CPE_ID_VERSION,指定CPE标识符的版本部分。如果没有定义,默认值是$(LIBFOO_VERSION)
    • LIBFOO_CPE_ID_UPDATE,指定CPE标识符的更新部分。当没有定义时,默认值为*

    如果这些变量中的任何一个被定义了,那么通用包基础设施就假定该包提供了有效的CPE信息。在这种情况下,generic package基础设施将定义LIBFOO_CPE_ID

    对于一个主机包来说,如果它的LIBFOO_CPE_ID_*变量没有被定义,它将从相应的目标包中继承这些变量的值。

定义这些变量的推荐方法是使用以下语法:

LIBFOO_VERSION = 2.32

可用运行变量

现在,定义在构建过程的不同步骤中应该执行什么的变量。

  1. LIBFOO_EXTRACT_CMDS列出了提取软件包所要执行的操作。
    这通常是不需要的,因为Buildroot会自动处理tarballs。然而,如果软件包使用了非标准的归档格式,比如ZIP或RAR文件,或者有一个非标准组织的压缩包,这个变量允许覆盖软件包基础设施的默认行为。
  2. LIBFOO_CONFIGURE_CMDS列出了在编译前配置软件包所要执行的操作。
  3. LIBFOO_BUILD_CMDS列出了编译软件包所要执行的操作。
  4. HOST_LIBFOO_INSTALL_CMDS列出了当软件包是一个主机软件包时,安装软件包所要执行的操作。
    该软件包必须将其文件安装到$(HOST_DIR)给出的目录中。所有的文件,包括开发文件,如头文件,都应该被安装,因为其他包可能会在这个包的基础上进行编译。
  5. LIBFOO_INSTALL_TARGET_CMDS列出了当软件包是一个目标软件包时,为将其安装到target目录中所要执行的操作。
    包必须将其文件安装到$(TARGET_DIR)给出的目录中。只有执行软件包所需的文件必须被安装。头文件、静态库和文档将在目标文件系统最终完成时被再次删除。
  6. LIBFOO_INSTALL_STAGING_CMDS列出了当软件包是一个目标软件包时,为将其安装到staging目录中所要执行的操作。
    包必须将其文件安装到$(STAGING_DIR)给出的目录中。所有的开发文件都应该被安装,因为它们可能被用来编译其他软件包。
  7. LIBFOO_INSTALL_IMAGES_CMDS列出了当软件包是一个目标软件包时,将软件包安装到images目录下所要执行的操作。
    包必须将其文件安装到$(BINARIES_DIR)给出的目录中。只有那些不属于TARGET_DIR,但对启动板来说是必要的二进制图像(又称image)的文件应该放在这里。例如,如果一个软件包有类似于内核镜像、引导程序或根文件系统镜像的二进制文件,就应该利用这个步骤。
  8. LIBFOO_INSTALL_INIT_SYSVLIBFOO_INSTALL_INIT_OPENRCLIBFOO_INSTALL_SYSTEMD列出了为类似systemV的初始系统(busybox、sysvinit等)、openrc或systemd单元安装初始脚本的操作。
    这些命令只有在安装了相关的初始系统后才会运行(即如果配置中选择了systemd作为初始系统,则只有LIBFOO_INSTALL_INIT_SYSTEMD会被运行)。唯一的例外是,当openrc被选为初始系统,而LIBFOO_INSTALL_INIT_OPENRC没有被设置,在这种情况下,LIBFOO_INSTALL_INIT_SYSV将被调用,因为openrc支持sysv初始脚本。如果使用 systemd 作为初始系统,buildroot 会在镜像构建的最后阶段使用 systemctl preset-all 命令自动启用所有服务。你可以添加预设文件来防止某个特定单元被 buildroot 自动启用。
  9. LIBFOO_HELP_CMDS列出了打印软件包帮助时的行为,这些行为被包含在主要的make help输出中。
    这些命令可以以任何格式打印任何东西。这一点很少使用,因为软件包很少有自定义的规则。不要使用这个变量,除非你真的知道你需要打印帮助。
  10. LIBFOO_LINUX_CONFIG_FIXUPS列出了构建和使用该软件包所需的 Linux 内核配置选项,如果没有这些选项,该软件包就会从根本上损坏。
    这应该是对kconfig调整选项:KCONFIG_ENABLE_OPT, KCONFIG_DISABLE_OPT, 或 KCONFIG_SET_OPT之一的调用。这很少使用,因为软件包通常对内核选项没有严格要求。

定义这些变量的首选方式是:

define LIBFOO_CONFIGURE_CMDS
        action 1
        action 2
        action 3
endef

在定义动作中,你可以使用以下变量:

  • $(LIBFOO_PKGDIR)变量拥有包括libfoo.mkConfig.in文件的目录路径。当需要将文件捆绑安装在Buildroot时,这个变量很有用,比如运行时的配置文件、启动镜像…
  • $(@D),它包含软件包源代码被解压缩的目录。
  • $(LIBFOO_DL_DIR)包含了Buildroot为libfoo进行的所有下载的目录路径。
  • $(TARGET_CC), $(TARGET_LD)等,以获得目标交叉编译的实用程序。
  • $(TARGET_CROSS) 以获得交叉编译工具链的前缀
  • $(HOST_DIR)$(STAGING_DIR)$(TARGET_DIR)这些变量可以用来正确安装软件包。
    这些变量指向全局主机、暂存和目标目录,除非使用了每个软件包的目录支持,在这种情况下,它们指向当前的软件包主机、暂存和目标目录。在这两种情况下,从软件包的角度来看没有任何区别:它应该简单地使用HOST_DIRSTAGING_DIRTARGET_DIR

可用钩子锚点

最后,你也可以使用钩子。通用基础架构(以及由此衍生的autotools和cmake基础架构)允许软件包指定钩子。这些定义了在现有步骤之后执行的进一步行动。大多数钩子对通用包来说并不真正有用,因为.mk文件已经完全控制了在包构建的每一步中执行的操作。

有以下挂钩点可供选择:

  • LIBFOO_PRE_DOWNLOAD_HOOKS
  • LIBFOO_POST_DOWNLOAD_HOOKS
  • LIBFOO_PRE_EXTRACT_HOOKS
  • LIBFOO_POST_EXTRACT_HOOKS
  • LIBFOO_PRE_RSYNC_HOOKS
  • LIBFOO_POST_RSYNC_HOOKS:可以在Buildroot复制完源代码后,运行额外的命令。例如,可以用从目录树中提取的信息生成手册的一部分。
  • LIBFOO_PRE_PATCH_HOOKS
  • LIBFOO_POST_PATCH_HOOKS
  • LIBFOO_PRE_CONFIGURE_HOOKS
  • LIBFOO_POST_CONFIGURE_HOOKS
  • LIBFOO_PRE_BUILD_HOOKS
  • LIBFOO_POST_BUILD_HOOKS
  • LIBFOO_PRE_INSTALL_HOOKS (仅适用于主机包)
  • LIBFOO_POST_INSTALL_HOOKS (仅适用于主机包)
  • LIBFOO_PRE_INSTALL_STAGING_HOOKS (仅适用于主机包)
  • LIBFOO_POST_INSTALL_STAGING_HOOKS(仅适用于主机包)
  • LIBFOO_PRE_INSTALL_TARGET_HOOKS(仅适用于主机包)
  • LIBFOO_POST_INSTALL_TARGET_HOOKS(仅适用于主机包)
  • LIBFOO_PRE_INSTALL_IMAGES_HOOKS
  • LIBFOO_POST_INSTALL_IMAGES_HOOKS
  • LIBFOO_PRE_LEGAL_INFO_HOOKS
  • LIBFOO_POST_LEGAL_INFO_HOOKS
  • LIBFOO_TARGET_FINALIZE_HOOKS

这些变量列出了包含要在这个钩点执行动作的变量名称。这允许在一个给定的钩子点上注册几个钩子。下面是一个例子。

define LIBFOO_POST_PATCH_FIXUP
        action1
        action2
endef

LIBFOO_POST_PATCH_HOOKS += LIBFOO_POST_PATCH_FIXUP

autotools-package 通用包教程

首先,让我们看看如何为基于autotools的软件包编写一个.mk文件,以一个.mk文件为例:

01: ################################################################################
02: #
03: # libfoo
04: #
05: ################################################################################
06:
07: LIBFOO_VERSION = 1.0
08: LIBFOO_SOURCE = libfoo-$(LIBFOO_VERSION).tar.gz
09: LIBFOO_SITE = http://www.foosoftware.org/download
10: LIBFOO_INSTALL_STAGING = YES
11: LIBFOO_INSTALL_TARGET = NO
12: LIBFOO_CONF_OPTS = --disable-shared
13: LIBFOO_DEPENDENCIES = libglib2 host-pkgconf
14:
15: $(eval $(autotools-package))

在第7行,我们声明软件包的版本。

在第8行和第9行,我们声明tarball的名称(推荐使用xz-ed tarball)和tarball在网上的位置。Buildroot会自动从这个位置下载tarball。

在第10行,我们告诉Buildroot将软件包安装到暂存目录。位于output/staging/的staging目录是安装所有软件包的目录,包括其开发文件等。默认情况下,软件包不会被安装到staging目录下,因为通常只有库需要安装到staging目录下:它们的开发文件需要用来编译其他库或依赖它们的应用程序。同样默认情况下,当启用暂存安装时,软件包会使用make install命令安装到这个位置。

在第11行,我们告诉Buildroot不要把包安装到目标目录。这个目录包含了将成为在目标机上运行的根文件系统的内容。对于纯粹的静态库,没有必要将它们安装到目标目录下,因为它们在运行时不会被使用。默认情况下,目标安装是启用的;将这个变量设置为NO几乎是不需要的。同样在默认情况下,软件包会通过make install命令安装在这个位置。

在第12行,我们告诉Buildroot传递一个自定义的configure选项,该选项将在配置和构建软件包之前传递给./configure脚本。

在第13行,我们声明我们的依赖项,这样它们就会在软件包的构建过程开始前被构建。

最后,在第15行,我们调用autotools-package宏,它生成所有的Makefile规则,实际上允许包被构建。

autotools-package 通用包引用介绍

autotools软件包基础设施的主要宏是autotools-package。它类似于generic-package宏。通过host-autotools-package宏,我们也可以拥有目标包和主机包。

可用开始变量

就像通用基础设施一样,在调用autotools-package宏之前,autotools基础设施通过定义一系列的变量来工作。

首先,所有存在于通用基础设施中的软件包元数据信息变量也存在于autotools基础设施中。libfoo_version, libfoo_source, libfoo_patch, libfoo_site, libfoo_subdir, libfoo_dependencies, libfoo_install_staging, libfoo_install_target

具体在autotools基础设施中,一些额外的变量也可以被定义。其中许多变量只在非常特殊的情况下有用,因此典型的软件包只会使用其中的几个。

  • LIBFOO_SUBDIR可以包含软件包中包含 configure 脚本的子目录的名字。
    这很有用,例如,如果主 configure 脚本不在 tarball 所提取的树的根部。如果没有指定HOST_LIBFOO_SUBDIR,它就默认为LIBFOO_SUBDIR
  • LIBFOO_CONF_ENV用于指定传递给 configure 脚本的额外环境变量。默认为空。
  • LIBFOO_CONF_OPTS, 用于指定传递给 configure 脚本的额外配置选项。默认为空。
  • LIBFOO_MAKE, 用于指定一个备用的 make 命令。
    当配置中启用了并行编译(使用BR2_JLEVEL),但由于某种原因,这个功能应该被禁用于指定的软件包时,这个命令通常很有用。默认情况下,设置为$(MAKE)。如果软件包不支持并行编译,那么应该设置为LIBFOO_MAKE=$(MAKE1)
  • LIBFOO_MAKE_ENV, 用于指定在编译过程中传递给make的额外环境变量。
    这些变量会在make命令之前传递。默认为空。
  • LIBFOO_MAKE_OPTS, 用于指定在编译过程中传递给make的额外变量。
    这些变量会在make命令之前传递。默认为空。
  • LIBFOO_AUTORECONF, 告诉软件包是否应该被自动配置。
    (例如,指定配置脚本和Makefile.in文件是否应该通过重新运行autoconf、automake、libtool等而重新生成)。默认为 NO。
  • LIBFOO_AUTORECONF_ENV, 如果LIBFOO_AUTORECONF=YES,可以指定额外的环境变量传递给autoreconf程序。
    这些变量会在autoreconf命令的环境中传递。默认为空。
  • LIBFOO_AUTORECONF_OPTS用于指定在LIBFOO_AUTORECONF=YES时传递给autoreconf程序的额外选项。
    默认为空。
  • LIBFOO_GETTEXTIZE告诉软件包是否应该被gettextize(也就是说,如果软件包使用的gettext版本与Buildroot提供的不同,并且需要运行gettextize。)
    只有在LIBFOO_AUTORECONF=YES时有效。有效值是YES和NO。默认为 NO。
  • LIBFOO_GETTEXTIZE_OPTS如果LIBFOO_GETTEXTIZE=YES,用于指定传递给gettextize程序的额外选项。
    例如,你可以当.po文件不在标准位置(比如在软件包root目录的po/)时使用它。
  • LIBFOO_LIBTOOL_PATCH告诉你是否应该应用Buildroot补丁来修复libtool的交叉编译问题。
    有效值是 YES 和 NO。默认为 YES。
  • LIBFOO_INSTALL_STAGING_OPTS包含用于将软件包安装到暂存目录的 make 选项。
    默认情况下,该值是DESTDIR=$(STAGING_DIR) install,这对大多数自动工具包来说是正确的。仍然可以覆盖它。
  • LIBFOO_INSTALL_TARGET_OPTS 包含用于将软件包安装到目标目录的 make 选项。
    默认情况下,该值为DESTDIR=$(TARGET_DIR) install。默认值对大多数自动工具包来说是正确的,但如果需要,仍然可以覆盖它。

定制运行过程

有了autotools基础架构,所有构建和安装软件包所需的步骤都已经定义好了,而且对于大多数基于autotools的软件包来说,它们通常都能很好地工作。然而,当需要时,仍然可以定制任何特定步骤中的工作内容。

  • 通过添加一个后操作钩子(在提取、打补丁、配置、构建或安装之后)。参见之前钩子介绍。
  • 通过覆盖其中一个步骤。例如,即使使用了 autotools 基础设施,如果软件包 .mk 文件定义了自己的 LIBFOO_CONFIGURE_CMDS 变量,它将被用来代替默认的autotools one。但是,使用这种方法应该限制在非常特殊的情况下。不要在一般情况下使用它。

cmake-package 通用包教程

首先,让我们看看如何为基于CMake的软件包编写一个.mk文件,以一个.mk文件为例:

01: ################################################################################
02: #
03: # libfoo
04: #
05: ################################################################################
06:
07: LIBFOO_VERSION = 1.0
08: LIBFOO_SOURCE = libfoo-$(LIBFOO_VERSION).tar.gz
09: LIBFOO_SITE = http://www.foosoftware.org/download
10: LIBFOO_INSTALL_STAGING = YES
11: LIBFOO_INSTALL_TARGET = NO
12: LIBFOO_CONF_OPTS = -DBUILD_DEMOS=ON
13: LIBFOO_DEPENDENCIES = libglib2 host-pkgconf
14:
15: $(eval $(cmake-package))

在第7行,我们声明软件包的版本。

在第8行和第9行,我们声明tarball的名称(推荐使用xz-ed tarball)和tarball在网上的位置。Buildroot会自动从这个位置下载tarball。

在第10行,我们告诉Buildroot将软件包安装到暂存目录。位于output/staging/的staging目录是安装所有软件包的目录,包括其开发文件等。默认情况下,软件包不会被安装到staging目录下,因为通常只有库需要安装到staging目录下:它们的开发文件需要用来编译其他库或依赖它们的应用程序。同样默认情况下,当启用暂存安装时,软件包会使用make install命令安装到这个位置。

在第11行,我们告诉Buildroot不要把包安装到目标目录。这个目录包含了将成为在目标机上运行的根文件系统的内容。对于纯粹的静态库,没有必要将它们安装到目标目录下,因为它们在运行时不会被使用。默认情况下,目标安装是启用的;将这个变量设置为NO几乎是不需要的。同样在默认情况下,软件包会通过make install命令安装在这个位置。

在第12行,我们告诉Buildroot在配置包的时候向CMake传递自定义选项。

在第13行,我们声明我们的依赖项,这样它们就会在软件包的构建过程开始前被构建。

最后,在第15行,我们调用了cmake-package宏,它生成了所有的Makefile规则,使软件包得以构建。

cmake-package 通用包引用介绍

CMake软件包基础设施的主要宏是cmake-package。它与 generic-package 宏类似。通过host-cmake-package宏,也可以拥有目标包和宿主包的能力。

可用开始变量

就像通用基础设施一样,CMake基础设施在调用cmake-package宏之前通过定义一些变量来工作。

首先,所有存在于通用基础设施中的软件包元数据信息变量也存在于CMake基础设施中。libfoo_version, libfoo_source, libfoo_patch, libfoo_site, libfoo_subdir, libfoo_dependencies, libfoo_install_staging, libfoo_install_target

一些额外的变量,具体到CMake基础设施,也可以被定义。其中许多变量只在非常特殊的情况下有用,因此典型的软件包只会使用其中的几个。

  • LIBFOO_SUBDIR可以包含软件包中包含 CMakeLists.txt 脚本的子目录的名字。
    这很有用,例如,如果主 CMakeLists.txt 脚本不在 tarball 所提取的树的根部。如果没有指定HOST_LIBFOO_SUBDIR,它就默认为LIBFOO_SUBDIR
  • LIBFOO_CONF_ENV用于指定传递给 CMake 的额外环境变量。默认为空。
  • LIBFOO_CONF_OPTS, 用于指定传递给 CMake 的额外配置选项。默认为空。
    一些常见的 CMake 选项是由 cmake-package 基础结构设置的;因此通常没有必要在软件包的 *.mk 文件中设置它们,除非你想覆盖它们:
    • CMAKE_BUILD_TYPEBR2_ENABLE_RUNTIME_DEBUG驱动;
    • CMAKE_INSTALL_PREFIX
    • BUILD_SHARED_LIBSBR2_STATIC_LIBS 驱动;
    • BUILD_DOC, BUILD_DOCS 被关闭;
    • BUILD_EXAMPLE, BUILD_EXAMPLES 被关闭;
    • BUILD_TEST, BUILD_TESTS, BUILD_TESTING 被关闭。
  • LIBFOO_SUPPORTS_IN_SOURCE_BUILD = NO 应该在软件包不能在源代码树内构建而需要一个单独的构建目录时设置。
  • LIBFOO_MAKE以指定一个备用的make命令。
    当配置中启用了并行编译(使用BR2_JLEVEL),但由于某种原因,这个功能应该被禁用于指定的软件包时,这个命令通常很有用。默认情况下,设置为$(MAKE)。如果软件包不支持并行编译,那么应该设置为LIBFOO_MAKE=$(MAKE1)
  • LIBFOO_MAKE_ENV, 用于指定在编译过程中传递给make的额外环境变量。
    这些变量会在make命令之前传递。默认为空。
  • LIBFOO_MAKE_OPTS, 用于指定在编译过程中传递给make的额外变量。
    这些变量会在make命令之前传递。默认为空。
  • LIBFOO_INSTALL_OPTS 包含用于将软件包安装到主机目录的make选项。
    默认为该值为install,这对大多数CMake包来说是正确的,但如果需要仍然可以覆盖它。
  • LIBFOO_INSTALL_STAGING_OPTS 包含用于将软件包安装到暂存目录的 make 选项。
    默认该值为DESTDIR=$(STAGING_DIR) install/fast,这对大多数CMake包来说是正确的,但如果需要仍然可以覆盖它。
  • LIBFOO_INSTALL_TARGET_OPTS 包含用于将软件包安装到目标目录的 make 选项。
    默认该值为DESTDIR=$(TARGET_DIR) install/fast,这对大多数CMake包来说是正确的,但如果需要仍然可以覆盖它。

定制运行过程

有了CMake基础架构,所有构建和安装软件包所需的步骤都已经定义好了,而且对于大多数基于CMake的软件包来说,它们通常都能很好地工作。然而,当需要时,仍有可能定制任何特定步骤的工作内容:

  • 通过添加一个后操作钩子(在提取、打补丁、配置、构建或安装之后)。参见之前钩子介绍。
  • 通过覆盖其中一个步骤。例如,即使使用了 CMake 基础设施,如果软件包 .mk 文件定义了自己的 LIBFOO_CONFIGURE_CMDS 变量,它将被用来代替默认的CMake变量。但是,使用这种方法应该限制在非常特殊的情况下,不要在一般情况下使用它。

参考文献

[1]: 《The Buildroot user manual》

[2]: Buildroot 用户手册 (中文)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr.Idleman

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值