1 工作流

下图表示OpenEmbedded构建系统生成的高级工作流:


Yocto理论篇 | OpenEmbedded构建系统之BitBake_Bitbake

通常,构建的工作流由几个功能区域组成:

  • User Configuration:可用于控制生成过程的元数据。
  • Metadata Layers:提供软件、板子和发行版元数据的各种层。
  • Source Files:上游发布、本地项目和SCMs。
  • Build System:在BitBake控制下的进程。这个模块扩展了BitBake如何获取源代码、应用补丁、完成编译、分析生成包的输出、创建和测试包、生成镜像以及生成交叉开发工具。
  • Package Feeds:包含输出包(RPM、DEB或IPK)的目录,这些输出包随后用于构建由构建系统生成的镜像或软件开发工具包(SDK)。如果启用了运行时包管理,还可以使用web服务器或其他方式复制和共享这些提要,以便于在运行时扩展或更新设备上的现有镜像。
  • Images:工作流生成的镜像。
  • Application Development SDK:与镜像一起或与BitBake单独生成的交叉开发工具。

2 BitBake

OpenEmbedded构建系统使用BitBake生成镜像和软件开发工具包(SDK)。从通用工作流图中可以看到,BitBake区域由多个功能区域组成。

2.1 Source Fetching

构建配方的第一步是获取和对源代码解包:

Yocto理论篇 | OpenEmbedded构建系统之BitBake_Bitbake_02

图中do_fetch​do_unpack​任务会获取源文件并将它们解压到​Build​目录中。

对于配方的SRC_URI语句中的每个本地文件(例如​file://​),OpenEmbedded构建系统将获取配方文件的校验和,并将校验和插入​do_fetch​任务的签名中。如果修改了任何本地文件,则会重新执行​do_fetch​任务和依赖它的所有任务。

默认情况下,所有操作都在Build目录中完成,该目录具有已定义的结构。

每个配方在构建目录中都有一个区域,解压后的源代码就在那里。S变量指向此区域,以获取配方的未打包源代码。任何给定配方的目录名都是从几个不同的变量定义的。上图和下表描述了构建目录的层次结构:

  • TMPDIR:OpenEmbedded构建系统在构建期间执行所有工作的基目录。默认的基本目录是​tmp​ 目录。
  • PACKAGE_ARCH:构建的一个或多个包的体系结构。因一个或多个包的最终目的(即机器架构、构建主机、SDK或特定机器),​PACKAGE_ARCH​ 会有所不同。
  • TARGET_OS:目标设备的操作系统。典型就是“linux”(例如“qemux86-poky-linux”)。
  • PN:用于构建包的配方名称。这个变量可以有多种含义。但是,当在输入文件的上下文中使用时,​PN​ 表示配方的名称。
  • WORKDIR:OpenEmbedded构建系统构建配方的位置(即创建包的工作处)。
  • PV:用于构建包的配方版本。
  • PR:用于构建包的配方的修订版。
  • S: 给定配方的解压源文件。
  • BPN:用于构建包的配方名称。​BPN​ 变量是​PN​ 变量的一个版本,但是去掉了常见的前缀和后缀。
  • PV:用于构建包的配方版本。

在上图中,请注意存在两个示例层次结构:一个基于包的体系结构(即PACKAGE_ARCH),另一个基于机器的(即​MACHINE​)。它们底层结构是相同的,区别是OpenEmbedded构建系统用作构建目标(例如,通用体系结构、构建主机、SDK或特定机器)。

2.2 Patching

获取并解包源代码后,BitBake将查找补丁文件并将其应用于源文件:

Yocto理论篇 | OpenEmbedded构建系统之BitBake_Yocto_03

do_patch 任务会使用配方的​SRC_URI​ 语句和​FILESPATH​ 变量来定位适用的补丁文件。

补丁文件的默认处理文件具有*.patch 或 ​*.diff​文件类型。可以使用​SRC_URI​ 参数来更改构建系统识别补丁文件的方式。

BitBake将按照查找补丁程序的顺序为单个配方查找并应用多个补丁程序。FILESPATH 变量在构建系统时用于搜索补丁文件的默认目录集。一旦找到,补丁程序就会应用到配方的源文件中,这些文件位于​S​ 目录中。

2.3 Configuration, Compilation, and Staging

给源代码打补丁后,BitBake将执行配置和编译源代码的任务。一旦编译开始后,文件将被复制到保留区域(暂存),以准备打包:

Yocto理论篇 | OpenEmbedded构建系统之BitBake_OpenEmbedded_04

构建过程中的步骤包括以下任务:

  • do_prepare_recipe_sysroot:此任务在​${​​WORKDIR​​}​中设置两个sysroots (即​recipe-sysroot​​recipe-sysroot-native​),以便在打包阶段,sysroots 可以包含任务配方所依赖的​do_populate_sysroot​ 任务内容。sysroot存在于在主机系统上运行的目标和本机二进制文件。
  • do_configure:此任务通过启用和禁用一些软件的构建时间和配置选项来配置源代码。配置可以来自配方本身,也可以来自继承的类。此外,软件本身可能会根据构建它的目标进行自我配置。​do_configure​ 任务处理的配置特定于由配方生成的源代码的配置。如果使用的是​autotools​ 类,则可以通过使用额外的​EXTRA_OECONF​ 或​PACKAGECONFIG_CONFARGS​ 变量添加其他配置选项。
  • do_compile:一旦完成了一个配置的任务,BitBake将使用​do_compile​ 任务编译源代码。编译发生在​B​ 变量指向的目录中。默认情况下,​B​ 目录与​S​ 目录相同。
  • do_install:编译完成后,BitBake执行​do_install​ 任务。此任务从​B​ 目录复制文件,并将它们放在​D​ 变量指向的保留区域中。稍后将使用此保存目录中的文件进行打包。

2.4 Package Splitting

在configured、compiled和staged源代码之后,构建系统将分析结果并将输出拆分为包:

Yocto理论篇 | OpenEmbedded构建系统之BitBake_Yocto_05

do_package 和​do_packagedata​ 任务会结合起来分析在​D​ 目录中找到的文件,并根据可用的包和文件将它们拆分为子集。分析包括以下内容以及其他项目:拆分调试符号、查看包之间的共享库依赖项以及查看包关系。

do_packagedata 任务根据分析创建包元数据,以便生成系统最终的包。​do_populate_sysroot​ 任务将​do_install​ 任务安装的文件的子集转移(复制)到相应的sysroot中。分析和包拆分过程的工作、阶段和中间结果使用几个方面:

  • PKGD:在将包拆分为单个包之前,包的目标目录(即​package​)。
  • PKGDESTWORK​PKGDESTWORK​任务用于保存包元数据的临时工作区(即​pkgdata​)。
  • PKGDEST:拆分包后的父目录(即​packages-split​)。
  • PKGDATA_DIR:一个共享的全局状态目录,用于保存打包过程中生成的打包元数据。打包过程会将​PKGDESTWORK​ 中的元数据复制到​PKGDATA_DIR​ 区域,在那里它将成为全局可用的。
  • STAGING_DIR_HOST:要在其上运行组件的系统sysroot路径(即​recipe-sysroot​)。
  • STAGING_DIR_NATIVE:为构建主机生成组件时使用的sysroot路径(即​recipe-sysroot-native​)。
  • STAGING_DIR_TARGET:当一个组件被构建在一个系统上执行,并且它为另一台机器生成代码时的sysroot路径(例如cross-canadian recipes)。

FILES 变量定义包中每个包中的文件。如果要知道这是如何实现的,可以看看​package.bbclass​​。​

根据正在创建的包的类型(RPM、DEB或IPK), do_package_write_*任务会创建实际的包并将它们放在包提要区域中,即​${TMPDIR}/deploy​

注意:不支持直接从deploy/*目录创建数据元。创建这样的数据元通常需要某种类型的数据元维护机制,将新的包上传到一个官方的包数据元中(例如Ångström 发行版)。此功能是特定的发行版,因此不是开箱即用的。

2.5 Image Generation

一旦将包拆分并存储在Package Feeds区域中,构建系统将使用BitBake生成根文件系统镜像:

Yocto理论篇 | OpenEmbedded构建系统之BitBake_嵌入式_06

镜像生成过程由几个阶段组成,并依赖于多个任务和变量。do_rootfs 任务为镜像创建根文件系统(文件和目录结构)。此任务使用几个关键变量来帮助创建要实际安装的包列表:

通过IMAGE_ROOTFS 指向正在构建的文件系统的位置,​PACKAGE_INSTALL​ 变量提供要安装的包的最终列表,就创建了根文件系统。

包安装由包管理器(例如dnf/rpm、opkg或apt/dpkg)控制,无论目标是否启用包管理。在进程结束时,如果没有为目标启用包管理,则包管理器的数据文件将从根文件系统中删除。作为包安装的最后阶段的一部分,将运行作为包一部分的安装后脚本。第一次启动目标系统时,任何未能在生成主机上运行的脚本都将在目标系统上运行。如果使用的是只读根文件系统,那么在包安装阶段,所有安装后脚本都必须在构建主机上成功,因为目标上的根文件系统是只读的。

do_rootfs 任务的最后阶段控制后期处理。后期处理包括创建清单文件和优化。

清单文件(.manifest)与根文件系统镜像位于同一目录中。此文件逐行列出已安装的软件包。清单文件对于​testimage​ 类非常有用,例如,用于确定是否运行特定的测试。

在镜像中运行的优化进程包括mklibs​prelink​​ROOTFS_POSTPROCESS_COMMAND​ 命令变量定义的任何其他后处理命令。​mklibs​进程优化库的大小,而​prelink​进程优化共享库的动态链接,以减少可执行文件的启动时间。

在构建根文件系统之后,通过do\u image任务开始对映像进行处理。构建系统运行由IMAGE_PREPROCESS_COMMAND变量定义的任何预处理命令。此变量指定在生成系统创建最终图像输出文件之前要调用的函数列表。

构建系统根据需要动态创建do_image_*任务,基于​IMAGE_FSTYPES​ 变量中指定的镜像类型。该过程将所有内容转换为一个镜像文件或一组镜像文件,并可以压缩根文件系统镜像以减小映像的总体大小。根文件系统使用的格式取决于​IMAGE_FSTYPES​ 变量。压缩取决于格式是否支持压缩。

例如,创建特定镜像type 时动态创建的任务将采用以下形式:

do_image_type

 因此,如果IMAGE_FSTYPES 指定的​type​ 是​ext4​,那么动态生成的任务将如下所示:

do_image_ext4

镜像创建中涉及的最后一个任务是do_image_complete 任务。此任务通过​IMAGE_POSTPROCESS_COMMAND​ 命令变量定义的一些镜像后处理来完成镜像。该变量指定生成系统创建最终镜像输出文件后要调用的函数列表。

注意:整个镜像生成过程在Pseudo下运行。在Pseudo下运行可以确保根文件系统中的文件拥有正确的所有权。

2.6 SDK Generation

OpenEmbedded构建系统使用BitBake为标准SDK和可扩展SDK(eSDK)生成Software Development Kit(SDK)安装程序脚本:

Yocto理论篇 | OpenEmbedded构建系统之BitBake_OpenEmbedded_07

与镜像生成一样,SDK脚本过程由几个阶段组成,并且依赖于许多变量。do_populate_sdk 和​do_populate_sdk_ext​ 任务使用这些关键变量来帮助创建要实际安装的包列表。

do_populate_sdk 任务帮助创建标准sdk并处理两个部分:目标部分和主机部分。目标部件是为目标硬件构建的部件,包括库和头文件。主机部分是​SDKMACHINE​运行的SDK一部分。

do_populate_sdk_ext 任务有助于创建可扩展的sdk,并处理主机和目标部件的方式不同于标准SDK的计数器部分。对于可扩展的SDK,任务封装了构建系统,包括SDK所需的一切(主机和目标)。

不管构建的是哪种类型的SDK,任务都会执行一些清理,然后创建一个跨开发环境设置脚本和任何需要的配置文件。最终输出的是交叉开发工具链安装脚本(.sh文件),其中包括环境设置脚本。

2.7 Stamp Files and the Rerunning of Tasks

对于成功完成的每个任务,BitBake会将一个stamp文件写入STAMPS_DIR 目录。stamp文件的文件名的开头由​STAMP​ 变量确定,名称的结尾由任务名称和当前​input checksum​组成。

注意:这个命名方案假设BB_SIGNATURE_HANDLER 处理程序是“OEBasicHash”,在当前OpenEmbedded中几乎总是这样。若要确定是否需要重新运行任务,BitBake将检查该任务是否存在具有匹配输入校验和的stamp文件。如果存在这样的stamp文件,则假定任务的输出存在并且仍然有效。如果文件不存在,则任务将重新运行。

注意:stamp机制比“Setscene Tasks and Shared State”部分中描述的共享状态(sstate)缓存机制更通用。如果任务的缓存不能被加速,则可以避免重新运行任何文件。

但是,stamp文件只作为一个标记,表明一些工作已经完成,并且这些文件不记录任务输出。实际的任务输出通常在TMPDIR 中的某个地方(例如在某个配方的​WORKDIR​中)。sstate缓存机制添加的是一种缓存任务输出的方法,然后这些输出可以在构建机器之间共享。

由于STAMPS_DIR 通常是​TMPDIR​的子目录,删除​TMPDIR​也将删除​STAMPS_DIR​ ,这意味着任务将正确地重新运行以重新填充​TMPDIR​

如果你想让某个任务总是被认为是“过时”的,你可以用nostamp  var flag标记它。如果其他任务依赖于此类任务,则该任务也将始终被视为过期,这可能不是您想要的。

2.8 Setscene Tasks and Shared State

到目前为止,任务的描述假定BitBake需要构建所有内容,并且不存在可用的预构建对象。如果预构建的对象可用,BitBake确实支持跳过任务。这些对象通常以共享状态(sstate)缓存的形式提供。

setscene任务(即do_​​taskname​_setscene​)是任务的一个版本,其中BitBake可以跳到最终结果,只需根据需要将一组文件放置到特定位置。在某些情况下,有一个setscene任务变量是有意义的(例如,在​do_package_write_*​任务中生成包文件)。在其他情况下,由于所涉及的工作量等于或大于基础任务,因此没有意义(例如​do_patch​ 任务或​do_unpack​ 任务)。

在构建系统中,具有setscene变量的常见任务有do_package​do_package_write_*​​do_deploy​​do_packagedata​​do_populate_sysroot​。请注意,这些任务表示输出为最终结果的大多数任务。

构建系统了解这些任务和其他先前任务之间的关系。例如,如果BitBake运行do_populate_sysroot_setscene 执行某些操作,则运行任何​do_fetch​​do_unpack​​do_patch​​do_configure​​do_compile​​do_install​ 任务都没有意义。但是,如果需要运行​do_package​ ,BitBake则需要运行其他任务。

如果所有内容都可以来自sstate缓存,则会变得更加复杂,因为有些对象根本就不需要。例如,如果不存在可编译或修补的内容,则不需要编译器或本机工具(如quilt)。如果do_package_write_*包可从sstate获得,则BitBake不需要​do_package​ 任务数据。

为了处理所有这些复杂性,BitBake分两个阶段运行。第一个阶段是“setscene”阶段。在此阶段,BitBake首先检查sstate缓存中它计划构建的任何目标。BitBake会快速检查对象是否存在,而不是完整的下载。如果什么都不存在,那么第二阶段,即setscene阶段,就完成了,主构建继续进行。

如果在sstate缓存中找到对象,则生成系统将从用户指定的最终目标向后运行。例如,如果正在构建镜像,则构建系统首先查找该镜像所需的包以及构造镜像所需的工具。如果这些可用,则不需要编译器。因此,编译器甚至不被下载。如果发现某些内容不可用,或者下载或setscene任务失败,则生成系统将尝试从缓存安装依赖项,例如编译器。

sstate缓存中对象的可用性由BB_HASHCHECK_FUNCTION 函数变量指定的函数处理,并返回可用对象的列表。​BB_SETSCENE_DEPVALID​ 变量指定的函数是一个函数,用于确定是否需要遵循给定的依赖关系,以及是否需要为任何给定关系传递函数。函数返回True 或False 值。