本文参考yocto官方手册,如有理解不当之处,欢迎留言指出。
项目概述和概念手册:https://docs.yoctoproject.org/overview-manual/index.html
项目参考手册:https://docs.yoctoproject.org/ref-manual/index.html
bitbake是OpenEmbedded构建系统的引擎,通过解析一系列配置文件(主要为recipes,即bb/bbappend文件)来创建任务列表,并根据依赖关系依次执行。通过bitbake -c listtasks xxx(模块名或映像名)命令可以查看编译一个模块或整个映像所需任务名称。编译一个模块/映像的主要执行过程如下(内核除外):
下面按四个阶段来讲解bitbake的执行过程及涉及的变量,其中第四阶段(上图标黄的两任务)不是编译每个模块都有的。
这个阶段包含三个任务,分别为do_fetch、do_unpack、do_patch。
原文件来源
yocto编译一个模块所需的源代码或开源组件从哪里获取?
如上图所示,源码可以从上游开源项目(如busybox-1.28.3.tar.bz2)、本地项目(本地源代码)、软件配置管理(如git)中获取。
do_fetch任务:根据配置文件中SRC_URI变量所指定的方式获取源代码。
do_unpack任务:如果源文件需要解压,则该任务会将源码包解压到指定目录下。
do_patch任务:如果对开源项目进行了修改,则这个任务可以为解压后的源码打补丁,比如对linux开源内核源码进行了修改。
这个大阶段涉及的配置变量有以下:
SRC_URI
源文件列表变量,位于配方(recipe)文件中。每个recipe必须有一个指向源的SRC_URI变量。
SRC_URI = "git://github.com/openbmc/linux;protocol=git;branch=${KBRANCH}" #表示从github上下载linux内核源码
SRC_URI += "file://defconfig" #表示从本地目录获取内核默认配置,目录的路径由FILESPATH变量指定
以下列举常用的获取方式,更多的获取方式请参阅SRC_URI
。
file://
-从本地机器 获取文件,通常是元数据附带的文件(例如补丁、内核配置、uboot配置文件),路径是相对于FILESPATH
变量的。构建系统默认在配方同目录下名为“${BP}”
、"${BPN}"
、"files"
的目录下查找指定文件,如需增加额外路径,便通过FILESPATH
和FILESEXTRAPATHS
指定。
注意:如果源码是本地文件,确保每个文件都使用file://写出,而不是使用指定文件夹方式下载整个文件夹,因为yocto需要单独检查每个文件是否被修改。
bzr://
-从 Bazaar 版本控制存储库中获取文件。
git://
-从 Git 版本控制存储库中获取文件。
如果是从本地仓库下载,则先配置好本地Git仓库。yocto依赖与git的版本控制,简单来说就是yocto每次编译时候,会去检查软件包的源文件是否修改,只有修改过才会让软件包重新编译,那么对于git仓库就是对比上一次与当次版本差异。所以,对于源码在本地的git仓库的软件包,需要在每次编译前进行以下操作:
git add ./*
git commit -m "test"
这样就可以确保本地git仓库源码修改后能参与yocto工程编译。
svn://
-从 Subversion (svn
) 版本控制存储库中获取文件。
http://
-使用http。
https://
-使用https。
DL_DIR
用于指定开源组件包(tar、git等)下载的存放路径,位于编译目标层的conf/local.conf.sample
文件中(如有需要可在此文件中修改),这个文件将被解析到build/conf/local.conf
文件中,默认情况下该变量指定的目录为build/downloads/
,如果多用户在一台编译机器上使用,可以指定一个公共目录,避免重复下载,比如DL_DIR ?= "/opt/downloads"
,同时修改chmod 777 /opt/downloads
即可。
通常一个组件包下载完毕后,在存放目录下会生成一个包名加".done"的文件表示该包下载完成,比如:
root:~/work/open_source/openbmc/build/downloads$ ls busybox-1.32.0.tar.bz2* -l
-rw-r--r-- 1 root root 2439463 Jun 27 2020 busybox-1.32.0.tar.bz2
-rw-r--r-- 1 root root 463 Mar 7 2021 busybox-1.32.0.tar.bz2.done
一个技巧就是某个开源组件一直下载失败,那就从其他地方下载同版本组件拷贝到存放目录下,并复制一个*.done
文件改为对应名字即可。
FILESPATH
构建系统搜索本地文件(本地源码、补丁、配置文件等)的目录集合,位于配方(recipe)文件中。在构建过程中,bitbake查找SRC_URI变量的file://
语句指定的本地文件时,会依次搜索FILESPATH
变量指定的目录集合,该变量的默认值在meta/classes/base.bbclass中定义:
FILESPATH = "${@base_set_filespath(["${FILE_DIRNAME}/${BP}", \
"${FILE_DIRNAME}/${BPN}", "${FILE_DIRNAME}/files"], d)}"
一个简单的示例如下。
SRC_URI += "file://defconfig" #只是将配置文件拷贝到${WORKDIR}目录下
SRC_URI += "file://patch/0001-XXX.patch" #将补丁文件拷贝到${WORKDIR}目录下,并将补丁应用到${S}目录
SRC_URI += "file://patch/0002-XXX.diff" #将补丁文件拷贝到${WORKDIR}目录下,并将补丁应用到${S}目录
SRC_URI += "file://patch/0003-XXX.patch;apply=yes" #将补丁文件拷贝到${WORKDIR}目录下,并将补丁应用到${S}目录,这是显示指定应用补丁
SRC_URI += "file://patch/0004-XXX.patch;apply=no" #将补丁文件拷贝到${WORKDIR}目录下,但不应用补丁
默认情况,构建系统会将file://
指定带".diff"或".patch"的补丁文件应用到${S}目录,如果不想使用这个补丁,可以显示指定不应用。
注意:很多时候我们会在配方文件(.bb)中看见如下类似代码。
FILESPATH := "${THISDIR}/../../sources/${PN}:"
这种强制更改FILESPATH
变量默认值的方法是不正确的,正确做法是采用FILESEXTRAPATHS
变量来扩展搜索目录,比如下面做法:
FILESEXTRAPATHS_prepend := " ${THISDIR}/../../sources/XXX:${THISDIR}/../../sources/YYY:" #同时扩展2个路径
FILESEXTRAPATHS_append := " ${THISDIR}/../EEE:" #尾部的分号必须存在
额外说明一下,在.bbappend文件中只能使用FILESEXTRAPATHS
变量。关于"_prepend"
和"_append"
操作符说明将在后续讲解。
THISDIR
bb或bbappend文件所在目录,位于配方(recipe)文件中。比如某个配方文件位于如下路径:
/work/open_source/openbmc/meta-aspeed/recipes-kernel/linux/linux-aspeed_git.bb
那么在linux-aspeed_%.bbappend配方文件中的${THISDIR}
变量值为也包含/work/open_source/openbmc/meta-aspeed/recipes-kernel/linux/
。
注意,${THISDIR}
变量值是配方文件所在目录,也就是说在bbappend文件中使用${THISDIR}
变量,即包含bb文件所在目录,也包含bbappend文件所在目录,比如上面和下面两个目录都包含。
/work/open_source/openbmc/meta-ibm/meta-romulus/recipes-kernel/linux/linux-aspeed_%.bbappend
TMPDIR
此变量是 OpenEmbedded 构建系统用于所有构建输出和中间文件(共享状态缓存除外)的基本目录,位于编译目标层的conf/local.conf.sample
文件中(如有需要可在此文件中修改),这个文件将被解析到build/conf/local.conf
文件中,默认情况下该变量指定的目录为build/tmp/
。
PACKAGE_ARCH
包架构名,位于配方(recipe)文件中(一般不会在配方中自己指定)。查看示例:
~/work/bmc/build$ bitbake -e obmc-phosphor-image | grep ^PACKAGE_ARCH
PACKAGE_ARCH="4u_x201"
PACKAGE_ARCHS="all any noarch arm armv4 armv4t armv5 armv5t armv5e armv5te armv6 armv6t 4u_x201"
其中obmc-phosphor-image
为映像名(可使用包名),最终该映像就会生成于build\tmp\deploy\images\4u-x201
目录中。
TARGET_OS
指定目标的操作系统。对于基于 glibc
的系统(GNU C
库),该变量可以设置为“linux”
,对于 musl libc
可以设置为“linux-musl”
。对于 ARM/EABI
目标,存在“linux-gnueabi”
和“linux-musleabi”
可能的值。查看示例:
~/work/bmc/build$ bitbake -e obmc-phosphor-image | grep ^TARGET_OS
TARGET_OS="linux-gnueabi"
PN
用于构建包的配方名称或包的名称,该变量位于配方文件中(如有需要可在此文件中修改,一般会自动捕获配方名称)。例如,如果配方名为 expat_2.0.1.bb
,则PN
默认值为“expat”。这里需要注意一下,包名或配方名称中不能使用下划线_
,在yocto下划线为版本分隔符。查看示例:
~/work/bmc/build$ bitbake -e obmc-phosphor-image | grep ^PN
PN="obmc-phosphor-image"
PV
配方版本,该变量位于配方文件中(如有需要可在此文件中修改,一般会自动捕获配方版本)。例如,如果配方名为 expat_2.0.1.bb
,则 PV
的默认值将为“2.0.1”。查看示例:
~/work/bmc/build$ bitbake -e obmc-phosphor-image | grep ^PV
PV="1.0"
PR
配方的修订版本,该变量位于配方文件中(如有需要可在此文件中修改,一般需要手动指定)。此变量的默认值为“r0”,配方的后续修订通常具有值“r1”、“r2”等。当PV
增加时,PR
通常重置为“r0”。查看示例:
~/work/bmc/build$ bitbake -e obmc-phosphor-image | grep ^PR
PR="r0"
BP
该变量的值基本配方名称和版本,但没有任何特殊配方名称后缀(即 -native、lib64- 等)。 BP
由组成为${BPN}-${PV}
,其查看示例为:
~/work/bmc/build$ bitbake -e obmc-phosphor-image | grep ^BP
BP="obmc-phosphor-image-1.0"
WORKDIR
OpenEmbedded 构建系统在其中构建配方的工作目录的路径名。该目录位于TMPDIR
目录下,实际路径基于正在构建的配方和正在构建的系统,默认定义如下:
${TMPDIR}/work/${MULTIMACH_TARGET_SYS}/${PN}/${EXTENDPE}${PV}-${PR}
TMPDIR
: 顶层构建输出目录MULTIMACH_TARGET_SYS
: 目标系统标识符,默认值为${PACKAGE_ARCH}${TARGET_VENDOR}-${TARGET_OS}
,示例值为4u_x201-openbmc-linux-gnueabi
或armv6-openbmc-linux-gnueabi
,详细请参阅https://www.yoctoproject.org/docs/2.7/ref-manual/ref-manual.html#var-MULTIMACH_TARGET_SYSPN
: 配方名称EXTENDPE
: 扩展前缀 ,如果PE
未指定,EXTENDPE`则为空白,大多数食谱通常都是这种情况PV
: 配方版本PR
: 配方修改BPN
用于构建包的配方名称,它是PN变量的变种,去掉了常用的前缀和后缀,比如nativesdk-、-cross、-native,以及multilib的lib64-和lib32-。
~/work/byobmc/build$ bitbake -e obmc-phosphor-image | grep ^BPN
BPN="obmc-phosphor-image"
S
解压后的配方源代码所在路径,该变量位于配方文件中(如有需要可在此文件中修改)。默认情况下,此目录为${WORKDIR}/${BPN}-${PV}
,如果源压缩包将代码提取到名为 ${BPN}-${PV}
以外的任何目录,或者如果源代码是从 SCM(例如 Git 或 Subversion)获取的,则必须在配方中设置 S
,以便OpenEmbedded 构建系统知道在哪里可以找到解压的源代码。
假设bb文件中指定源码来源于git,则在 do_fetch 期间,源码将被被克隆到 ${WORKDIR}/git
目录中。由于此路径与S的默认值不同,所以必须专门设置,才能定位到源:
SRC_URI = "git://path/to/repo.git"
S = "${WORKDIR}/git"
总结:源码获取及处理阶段就是根据${SRC_URI}
变量指定方式来获取源码存放到${WORKDIR}
路径,若是压缩包则解压到S路径下,若有补丁文件则应用到S
目录。
源代码打好补丁后,bitbake 执行配置和编译源代码的任务。编译完成后,成果物文件将复制到保存区域(暂存)以准备打包:
这个阶段主要包含四个任务,分别为do_prepare_recipe_sysroot
、do_configure
、do_compile
和do_install
。
是不是很好奇,突然跑出一个奇怪任务do_prepare_recipe_sysroot
?虽然这个任务没有出现在上图中,但它却非常重要。
do_prepare_recipe_sysroot任务
网上大多资料都简要带过这个任务,未能讲明白这个任务是做什么的,但想要了解yocto的配方文件共享机制就必须弄明白这个任务是做什么的!
do_prepare_recipe_sysroot
与do_populate_sysroot
是staging.bbclass
类中关键任务,用于共享配方之间成果物!抛出一个问题思考一下,如果一个配方B需要使用配方A的成果物怎么办(比如头文件、动态/静态链接库、配置文件)?yocto为了解决这种问题,提供了一套配方成果物共享机制,该机制分为两阶段:
第一阶段在A配方构建时完成。A配方在构建时,需要在do_install
任务中将需要共享的文件安装至${D}
目录,后续执行的do_populate_sysroot
任务将自动拷贝${D}
目录下部分子目录到${SYSROOT_DESTDIR}
,而${SYSROOT_DESTDIR}
目录最终会放置到共享区(默认为build/tmp/sysroots-components
)暂存,其他配方构建时就可以从共享区拷贝。
那么,${D}
目录下哪些子目录会被自动拷贝?自动拷贝的目录由三个变量指定,分别为SYSROOT_DIRS
(目标设备需要保存的子目录)、SYSROOT_DIRS_BLACKLIST
(目标设备不需要保存的子目录)、SYSROOT_DIRS_NATIVE
(本机设备需要保存的目录),以SYSROOT_DIRS
变量为例,其默认值为:
SYSROOT_DIRS = " \
${includedir} \
${libdir} \
${base_libdir} \
${nonarch_base_libdir} \
${datadir} \
"
如果需要添加其他额外保存的目录,可以在配方文件中增加SYSROOT_DIRS += “YYY”
。
第二阶段在B配方构建时完成。B配方中添加DEPENDS += "A"
,便可使用A配方的成果物了。bitbake执行构建任务时会保证B配方的do_prepare_recipe_sysroot
任务执行前,A配方的成果物已位于build/tmp/sysroots-components
中。
do_prepare_recipe_sysroot
任务会在${WORKDIR}
目录中创建两个sysroot
目录并填充(所有依赖拷贝到其中),这两个目录名分别为"recipe-sysroot"
和"recipe-sysroot-native"
(本机),其中"recipe-sysroot"
给目标设备使用,A配方生成的成果物就在里面,另一个"recipe-sysroot-native"
是给本机设备使用的。
不知道目标设备与本机设备的差异?简要讲解一下:假设要给arm平台编译flash固件,编译主机是x86平台,那么目标设备就是arm设备,本机设备就是x86编译主机。我们知道为arm设备编译代码需要使用交叉编译链(如arm-linux-gcc
),编译链需要使用根文件系统下的各种库(或其他配方生成的头文件及库),因此便设置"recipe-sysroot"
为编译器使用的文件系统。注意了,源码编译do_compile
任务只是bitbake众多任务当中的一个,那其他任务也需要使用库或工具(如制作文件系统工具、压缩工具、cmake工具)怎么办?所以yocto将本机执行其他任务所需库或工具都放置于"recipe-sysroot-native"
!
这里只介绍了构建依赖DEPENDS
,但还有一种运行时依赖RDEPENDS
,一般情况运行依赖会由构建系统自动添加,详情请参阅RDEPENDS。
do_configure任务
此任务用于完成编译源码前的配置,配置可以来自配方本身,也可以来自继承的类,一般情况我们都会使用autotools(配方中使用inherit autotools)、cmake
类(配方中使用inherit cmake)或默认的make(不需要额外配置)。该任务运行时将当前工作目录设置为${B}
(一般与${S}
相同),该任务有个默认行为,即如果找到一个makefile
(makefile
, makefile
,或GNUmakefile
)并且CLEANBROKEN
没有设置为“1”,则运行oe_runmake clean
。
简单说明一下该任务怎么用:
如果你的软件包编译是基于autotools
的,则可以使用EXTRA_OECONF或 PACKAGECONFIG_CONFARGS 变量添加其他配置选项,比如在配方文件中添加如下:
EXTRA_OECONF += "--with-mib-modules="mib" \
--with-openssl=openssl \
--with-default-snmp-version="3" \
--with-logfile="/var/log/snmpd.log" \
--with-persistent-directory="/etc" \
--enable-privacy \
--enable-md5 \
--enable-des \
--prefiex=/xxx/yyy/ \
"
则do_configure
任务就如同手动执行./configure ${EXTRA_OECONF} ${PACKAGECONFIG_CONFARGS}
一样。
如果你的软件包编译是基于cmake
的,则可以使用EXTRA_OECMAKE
变量添加其他配置选项,比如在软件包的配方文件添加如下:
EXTRA_OECMAKE = " \
-DBMCWEB_INSECURE_ENABLE_REDFISH_FW_TFTP_UPDATE=ON -DBMCWEB_INSECURE_DISABLE_SSL=ON \
-DBMCWEB_ENABLE_DEBUG=ON -DBMCW EB_ENABLE_LOGGING=ON \
"
则do_configure
任务就如同手动执行mkdir build/ && cd build/ && cmake ${EXTRA_OECMAKE}
一样。
如果你的软件包编译是基于make
的,则可以使用EXTRA_OEMAKE
变量添加其他配置选项,比如在软件包的配方文件添加如下:
EXTRA_OEMAKE = "INSTALL_PREFIX=${D} OTHERLDFLAGS='${LDFLAGS}' HOST_CPPFLAGS='${BUILD_CPPFLAGS}'"
则do_configure
任务相当于只做默认行为(EXTRA_OEMAKE
在执行make
时才传入)。
do_compile任务
编译源代码。该任务运行时将当前工作目录设置为${B}
(一般与${S}
相同),该任务有个默认行为,即如果找到一个makefile
(makefile
, makefile
,或GNUmakefile
),则运行oe_runmake
,若未找到此类文件将不执行任何操作。
如果在执行oe_runmake
时需要传入额外编译选项或链接库,则可以使用在配方中以下变量:
CFLAGS += "-I${WORKDIR}/recipe-sysroot/usr/include/xxx -DBMCW=ON" #gcc的编译选项,增加额外头文件检索路径,定义BMCW宏
CXXFLAGS = " -fPIC" #g++的编译选项,告诉编译器产生位置无关代码
LDFLAGS += "-L${WORKDIR}/recipe-sysroot/usr/lib -yyy" #编译器链接选项
注意,如果源码是在${WORKDIR}${BPN}-${PV}
以外的任何目录,需要显性指定S
变量值,比如从本地直接获取源码xxx.c和Makefile,则需要在配方中添加S = "${WORKDIR}"
,这样编译任务才能正常进行。
do_install任务
文件或成果物的安装任务。该任务会将编译目录${B}
中需要打包的文件(放到目标设备中去的及其他配方依赖的)复制到保存区${D}
中。注意:安装文件时不要把所有者和组ID设置错误,特别是使用cp
命令时会保留原始文件的UID和GID,以下是推荐的安全方法:
使用install命令
使用cp命令时加上"--no-preserve=ownership"选项
使用tar命令时加上"--no-same-owner"选项
还记得上面提过的依赖吗?这个任务就是把其他配方所需依赖安装到${D}
目录,然后do_populate_sysroot
任务才能去${D}
目中拷贝。假设A模块执行do_install
的一个简单示例:
do_install() {
oe_runmake DESTDIR=${D}${libdir} install #执行Makefile中安装任务(安装.so),传入安装目录
install -m 0644 -d ${D}${includedir}/api #创建头文件目录
install -m 0644 ${S}/api_common.h ${D}${includedir}/api #安装头文件
install -m 0644 ${S}/api_xxx.h ${D}${includedir}/api #安装头文件
}
之后B模块在配方中添加如下:
DEPENDS += " A"
CFLAGS += " -I${WORKDIR}/recipe-sysroot/usr/include/api"
这样B模块就可以使用A模块编译的动态库。
该大阶段涉及但未解释的变量有以下:
B
包构建的编译目录,一般情况${B}
与${S}
相同,即为${WORKDIR}/${BPN}-${PV}
。
D
包构建成果物的安装目录,也称为目标目录。默认情况这个目录为${WORKDIR}/image
。
SYSROOT_DESTDIR
指向包构建工作目录下的临时目录,其默认值为${WORKDIR}/sysroot-destdir
。
总结:源码配置、源码编译及成果物安装阶段就是引用依赖(如果有)完成源码配置、编译及成果物安装,安装的成果物可能是目标设备使用的,也可能是其他模块所需依赖。
在配置、编译和安装完成后,构建系统分析结果并将包拆分处理,比如将文件stripped后放入packages-split目录:
该阶段分为三个任务,分别为do_package
、do_packagedata
、do_populate_sysroot
。
do_package、do_packagedata任务
do_package
和do_packagedata
任务组合起来分析在${D}
目录中找到的文件,并根据可用的包和文件将它们分成子集。分析处理过程包括以下内容:去除调试符号,查看包之间的共享库依赖关系,以及查看包之间的关系。
do_packagedata
任务根据分析创建包元数据放置Package Feeds
(即PKGDATA_DIR
指定目录)中,这样构建系统就可以从拿到包生成最终的image。
do_populate_sysroot任务
do_populate_sysroot
任务在之前已经介绍过。该任务将自动拷贝${D}
目录下部分子目录到${SYSROOT_DESTDIR}
,并将${SYSROOT_DESTDIR}
目录内容暂存至共享区(默认为build/tmp/sysroots-components
)。自动拷贝的子目录由三个变量指定,分别为SYSROOT_DIRS
(目标设备需要保存的子目录)、 SYSROOT_DIRS_BLACKLIST
(目标设备不需要保存的子目录)、SYSROOT_DIRS_NATIVE
(本机设备需要保存的目录),前面已经简要介绍过它们,详情请点入进官网参看。
该大阶段会涉及到以下变量:
PACKAGE_CLASSES
用于指定构建系统在打包文件时使用何种包管理器,该变量位于编译目标层的conf/local.conf.sample
文件中(如有需要可在此文件中修改),这个文件将被解析到build/conf/local.conf
文件中,比如设置其值为PACKAGE_CLASSES ?= "package_rpm package_tar"
。
PKGD
在将包拆分为单独的包之前,包的目标目录。
PKGDESTWORK
do_package
任务用来保存包元数据的临时工作区(即pkgdata)。
PKGDEST
拆分后的包的父目录(即packages-split)。
PKGDATA_DIR
一个共享的全局状态目录,其中包含打包过程中生成的打包元数据。打包过程将元数据从PKGDESTWORK
复制到PKGDATA_DIR
区域,在那里元数据成为全局可用的。
STAGING_DIR_HOST
要运行组件的系统的系统根路径(即recipe-sysroot),也就是当前配方源码编译时的根文件系统,里面包含配方所需依赖及交叉编译器所需依赖。
STAGING_DIR_NATIVE
为构建主机构建组件时使用的系统根路径(即recipe-sysroot-native)。
STAGING_DIR_TARGET
当构建在系统上执行的组件并为另一台机器生成代码(例如cross-canadian recipes)时使用的 sysroot 路径,一般与STAGING_DIR_HOST
一样。
FILES
用于指定包(模块)安装在${D}
目录中哪些成果物要打包,该变量位于配方文件中(如有需要可在此文件中修改)。简单来说就是在do_install
任务中安装在${D}
目录下的文件不会都打包,以rsa
模块为例该变量的默认值为:
FILES_rsa="/usr/bin/* /usr/sbin/* /usr/libexec/* /usr/lib/lib*.so.* /etc /com /var /bin/* /sbin/* /lib/*.so.* /lib/udev /usr/lib/udev /lib/udev /usr/lib/udev /usr/share/hikrsa /usr/lib/hikrsa/* /usr/share/pixmaps /usr/share/applications /usr/share/idl /usr/share/omf /usr/share/sounds /usr/lib/bonobo/servers"
也就是说,只要成果物安装在上面这些目录下的都会参与打包,当然如果不放心或需要额外增加文件,可以在配方文件中显性指定:
FILES_${PN} += " \
${sbindir}/rsaverify \
" #${sbindir}默认为/usr/sbin/
总结:包拆分处理阶段就是根据conf
配置将${D}目录中成果物打包放置于Package Feeds 区域,同时生成包元数据,最后将其他配方可能用到的文件放置于文件共享区(为其他配方提供依赖)。
注意,这个阶段在一般包(模块)编译过程中不存在!!!可通过bitbake -c listtasks XXX(包名或固件名)
命令查看编译任务列表。
一旦软件包被拆分并存储在 Package Feeds 区域中,构建系统将使用 bitbake 生成根文件系统映像(image):
该阶段涉及两个任务,分别为do_rootfs
、do_image
。
do_rootfs任务
该任务将创建目标设备的根文件系统(将需要打包至目标设备的程序、库、文件等都放置到根文件系统中),这个根文件系统最终打包到image中。do_rootfs任务会通过ROOTFS_POSTPROCESS_COMMAND
来优化文件大小(如mklibs
过程优化了库的大小,同时prelink
优化了共享库的动态链接以减少可执行文件的启动时间),ROOTFS_POSTPROCESS_COMMAND
如下:
ROOTFS_POSTPROCESS_COMMAND() {
write_package_manifest; license_create_manifest; ssh_allow_empty_password; ssh_allow_root_login; postinst_enable_logging;
rootfs_update_timestamp ; write_image_test_data ; set_systemd_default_target; systemd_create_users; empty_var_volatile;
remove_etc_version ; set_user_group; sort_passwd; rootfs_reproducible;
}
创建的文件系统所在位置由IMAGE_ROOTFS
变量指定,查看示例:
~/work/bmc$ bitbake -e obmc-phosphor-image | grep ^IMAGE_ROOTFS
IMAGE_ROOTFS="~/work/bmc/build/tmp/work/4u_x201-openbmc-linux-gnueabi/obmc-phosphor-image/1.0-r0/rootfs"
如果我们修改了某个程序,但又不想重新烧写整个固件,那就去这个目录下找到程序,再通过TFTP
方式(或NFS
直接挂载)下载到目标是设备调试即可。
do_image任务
do_image
任务会通过 IMAGE_PREPROCESS_COMMAND
对image进行预处理,主要是优化image大小,IMAGE_PREPROCESS_COMMAND
如下:
IMAGE_PREPROCESS_COMMAND() {
mklibs_optimize_image; prelink_setup; prelink_image; reproducible_final_image_task;
}
构建系统do_image
根据需要动态生成支持的 do_image_*
任务,生成的任务类型取决于IMAGE_FSTYPES变量。do_image_*
任务将所有内容转换为一个image
文件或一组image
文件,并且可以压缩根文件系统image
大小,以减小最终烧写到目标设备的image
整体大小。用于根文件系统的格式取决于 IMAGE_FSTYPES变量,压缩取决于格式是否支持压缩。
image
生成完成后执行最后一个任务do_image_complete
,该任务将通过IMAGE_POSTPROCESS_COMMAND
完成image的后续处理,默认情况IMAGE_POSTPROCESS_COMMAND
为空,查看示例:
~/work/bmc$ bitbake -e obmc-phosphor-image | grep ^IMAGE_POSTPROCESS_COMMAND
IMAGE_POSTPROCESS_COMMAND=""
该大阶段涉及变量如下:
IMAGE_INSTALL
该变量指明Package Feeds
区域安装的基本软件包集中,哪些包(模块)最终要打包到image,该变量一般位于编译目标层的conf/local.conf.sample
文件中(如有需要可在此文件中修改),这个文件将被解析到build/conf/layer.conf
文件中。注意与FILES
差异,FILES
是指明软件包内部哪些文件需要参与打包,而IMAGE_INSTALL
是指明哪个软件包需要参与打包。
一个简单示例:
IMAGE_INSTALL_append += ”rsa"
PACKAGE_EXCLUDE
指定不应安装到image
中的包。
IMAGE_FEATURES
指定要包含在图像中的特征,大多数这些功能映射到其他安装包(未能弄明白具体作用)。
IMAGE_LINGUAS
确定安装附加语言支持包的语言,该变量一般位于编译目标层的conf/local.conf.sample
文件中(如有需要可在此文件中修改),这个文件将被解析到build/conf/layer.conf
文件中。默认情况下为:
~/work/bmc$ bitbake -e rsa | grep ^IMAGE_LINGUAS
IMAGE_LINGUAS="en-us en-gb"
PACKAGE_INSTALL
传递给包管理器以安装到映像中的包的最终列表。
DEPLOY_DIR
最终image和SDK输出的目录,默认值为build/tmp/deploy/
。
总结:image生成生成阶段就是创建目标设备的根文件系统,并将需要打包至目标设备的程序、库、文件等都放置到根文件系统中,然后对文件和整个文件系统进行优化压缩,最终生成image。
以api.bb
的配方为例,其内容如下:
SUMMARY = "This is an example"
SECTION = "Examples"
HOMEPAGE = "http://www.xxx.com.cn/"
PR = "r1" #修订版本
#PV = "1.0" #这里不用指定配方版本,因为配方api.bb本身就不带版本,所以默认PV就是1.0
LICENSE = "MIT" #许可证类型
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302" #校验许可证
FILESPATH := "${THISDIR}/../../sources/${PN}:" #本地文件搜索路径,PN默认为配方名称,即api
#此配方所依赖的其他配方
DEPENDS += "audit"
DEPENDS += "phosphor-ipmi-host"
#由于源码下载的目录是${WORKDIR},而非${WORKDIR}/${BPN}-${PV},因此需设置S,否则编译失败
S = "${WORKDIR}"
#指定需要下载的文件
SRC_URI += "\
file://Makefile \
file://make.libs \
file://api_common.h \
file://api_common.c \
file://api_ethernet.c \
file://api_ethernet.h \
"
#增加额外文件
SRC_URI += " \
file://api_systems.c \
file://api_systems.h \
"
#追加额外路径下的文件,还记得之前提过吗?尽量不要直接改变FILESPATH变量的值,所以下面方法虽然可用,但不是合理的,正确做法是使用FILESEXTRAPATHS变量
FILESPATH_append := "${THISDIR}/../../../meta-common/sources/host-ipmid/ipmi/:" #换成FILESEXTRAPATHS_append
SRC_URI_append = " \
file://sharememory.c \
"
#传入参数
TARGET_CC_ARCH += "${LDFLAGS}"
EXTRA_OEMAKE = " 'RECIPE_SYSROOT=${RECIPE_SYSROOT}' "
CFLAGS_prepend = "-I${WORKDIR}/recipe-sysroot/usr/include/audit "
#这里注意没有出现do_configure、do_compile任务,故默认执行构建系统的do_configure、do_compile任务
#在本配方基于make构建包,所以do_configure任务相当于啥也没干,do_compile任务则执行oe_runmake并传入参数
#重载do_install任务,即构建系统原始的do_install任务不再执行,转而执行以下do_install任务
do_install() {
oe_runmake DESTDIR=${D}${libdir} install #执行Makefile文件中的安装任务,并传入安装目录
install -m 0644 -d ${D}${includedir}/api
install -m 0644 ${S}/api_common.h ${D}${includedir}/api
install -m 0644 ${S}/api_common.c ${D}${includedir}/api #不仅可以安装头文件,c文件也可以给其他模块使用,但这不是合理的方式
install -m 0644 ${S}/api_ethernet.h ${D}${includedir}/api
install -m 0644 ${S}/api_systems.h ${D}${includedir}/api
}
#FILES变量默认会加上${includedir}和${libdir}目录,所以下面语句可以不要
#FILES_${PN} += "${includedir}/api"
#FILES_${PN} += "${libdir}"
下篇文章再见啦~~~~