根据yocto版本构建(二)安装已有二进制和yocto版本构建(三)源地址dsc进行构建我们可以通过安装源的deb文件和通过dsc来执行一次构建操作,我们知道,通常情况下,代码都是通过git管理,这里还需要提供一个函数支持git方式的构建,如下:
对于gitlab2拉取源码的方式,我们需要指向到自己的SRC_URI后,根据deb.bbclass来进行代码构建,所以bbfile如下:
inherit kylin inherit deb SRC_URI = "git@gitlab2.kylin.com:shanghai-team/embedded/kylin-egf/ukui-menu.git;branch=egf/v101-tablet" S = "${WORKDIR}/ukui-menu" do_compile() { dpkg-buildpackage -uc -us : }
这里需要注意的是,我们没有使用yocto的本身git构建和打包deb的方式,我们使用了自己的命令,然后,我们需要在do_install的时候,进行dpkg -i的操作,这样sysroot才能正常安装。
为了使得调试更方便,这里提供了一些变量的说明,可以链接查看如下:
https://pages.openeuler.openatom.cn/embedded/docs/build/html/master/yocto/yocto_quick_start_manual/variables_and_tasks.html
主要解释如下:
MACHINE: 指定使用的硬件配置文件,通常在local.conf文件定义; DISTRO: 指定使用的发行版配置文件,通常在local.conf文件定义; PN: 软件包名,一般是根据文件名自动生成;除了一些交叉编译的包,如gcc-cross会在bb中重新定义; PV: 软件包版本; PR: 食谱的修订,默认为r0;当包管理器在已构建的镜像上动态安装包时,PR很重要,当前openEuler未启用; BPN: 软件包名,去除指定的前后缀(如-native、-cross等); BP: ${BPN}-${PV}; SRC_URI: 源码路径,可以为上游或者本地文件路径,上游源码需要使用校验值; LICENSE: 配方的源许可证列表,必须设置;如果设置为”CLOSED”则关闭; LIC_FILES_CHKSUM: 配方源代码中许可证文本的校验和,与LICENSE变量配合使用; PACKAGE_ARCH: 生成包的体系结构; TARGET_VENDOR: 指定目标供应商的名称,openEuler设置为”-openeuler”; TARGET_OS: 指定目标的操作系统; MULTIMACH_TARGET_SYS: 生成包的目标系统类型的唯一标识,默认为${PACKAGE_ARCH}${TARGET_VENDOR}-${TARGET_OS}; WORKDIR: 构建配方的工作目录的路径名,指向${TMPDIR}/work/${MULTIMACH_TARGET_SYS}/${PN}/${EXTENDPE}${PV}-${PR},EXTENDPE变量通常不被设置; S: 构建过程中源代码位置,默认为${WORKDIR}/${BPN}-${PV}; B: 构建过程中生成对象所在的目录,默认与S相同;一些类会将B设置为${WORKDIR}/build; D: 相当于 make install 后的目标目录,指向${WORKDIR}/image; PACKAGES: 表示配方创建的包列表; FILES_xxx: 放置在包中的文件和目录列表; PKGD: 要打包的文件的目录,指向${WORKDIR}/package; PKGDEST: 将文件拆分为单独的包后,指向要打包的文件的父目录,该目录是PACKAGES中指定的每个包的目录,指向${WORKDIR}/packages-split; DEPENDS: 列出配方的构建时依赖关系,配方在构建时需要其它配方的内容(例如头文件和共享库); RDEPENDS: 列出程序包的运行时依赖项,这些依赖项是必须安装的其他程序包,以便程序包正常运行; RECIPE_SYSROOT: 指向${WORKDIR}/recipe-sysroot; RECIPE_SYSROOT_NATIVE: 指向${WORKDIR}/recipe-sysroot-native; SYSROOT_DESTDIR: 指向${WORKDIR}/sysroot-destdir; SYSROOT_DIRS: 暂存到${SYSROOT_DESTDIR}的目录; STAGING_DIR_HOST: 组件运行所在的系统上的sysroot路径,默认为${RECIPE_SYSROOT}。 STAGING_DIR_NATIVE: 构建主机上运行的组件使用的sysroot的路径,默认为${RECIPE_SYSROOT_NATIVE}; STAGING_DIR_TARGET: 当构建在系统上执行的组件并为另一台机器生成代码(例如cross-canadian配方)时使用的sysroot路径; STAGING_KERNEL_DIR: 包含构建树外模块所需的内核头文件的目录(内核源码目录); STAGING_KERNEL_BUILDDIR: 指向包含内核构建工件的目录。需要访问内核构建工件的配方构建软件可以在内核构建后在STAGING_KERNEL_BUILDDIR变量指定的目录中查找这些工件; PACKAGE_CLASSES: 指定构建系统在打包数据时使用的包管理器(例如RPM、DEB或IPK),在local.conf文件设置; IMAGE_ROOTFS: 指定根文件系统在构建过程中的位置( do_rootfs 任务期间)。此变量不可配置,不要更改它; IMAGE_FEATURES: 指定要包含在镜像中的主要功能列表,这些功能大多数都映射到其他安装包; EXTRA_IMAGE_FEATURES: IMAGE_FEATURES的一部分; IMAGE_INSTALL: 指定要安装到镜像中的程序包; PACKAGE_EXCLUDE: :指定不应安装到image中的包; PACKAGE_INSTALL: 要安装到镜像中的程序包的列表,不要更改它,通常使用IMAGE_INSTALL变量间接进行修改; DEPLOY_DIR: 指向构建系统用于放置镜像、包、SDK和其他输出文件的常规区域,这些文件已准备好在构建系统之外使用。默认情况下,此目录位于指向${TMPDIR}/deploy。 DEPLOY_DIR_IMAGE: 指向构建系统用来放置准备部署到目标计算机上的镜像和其他相关输出文件的区域。该目录是特定于机器的默认情况下,此目录指向${DEPLOY_DIR}/images/${MACHINE}/; DEPLOYDIR: 当继承deploy类时,DEPLOYDIR指向已部署文件的临时工作区,默认指向${WORKDIR}/deploy-${PN},此目录内容会被拷贝到${DEPLOY_DIR_IMAGE}; CC: 用于运行C编译器的最小命令和参数; CFLAGS: 指定要传递给C编译器的标志; CXXFLAGS: 指定要传递给C++编译器的标志; CPPFLAGS: 指定要传递给C预处理器(即同时传递给C编译器和C++编译器)的标志; LDFLAGS: 指定要传递给链接器的标志; OVERRIDES: 以冒号分隔的当前应用的覆盖列表。覆盖是一种BitBake机制,允许在解析结束时选择性地覆盖变量; COMPATIBLE_MACHINE: 一种正则表达式,解析为一个或多个与配方兼容的目标机器。可以使用该变量来停止为配方不兼容的机器构建配方,停止这些构建对于内核特别有用。该变量还有助于提高解析速度,因为构建系统会跳过与当前机器不兼容的解析配方。
因为我们很多仓库都是通过git管理的,所以通过git地址来触发二进制编译的行为至关重要,根据上述描述,我们可以具备通过git链接地址进行二进制包的构建
根据yocto版本构建(三)源地址dsc进行构建我们可以通过输入一个内部源地址就可以拉取软件包安装,但是我们还需要提供一个功能,如果我们将文件放在recipes内的files下,我们需要手动将其解压和构建,这样可以不依赖源的dsc和文件
根据上面的描述,我们需要从files中获取文件,如下:
inherit kylin inherit deb SRC_URI = "file://ukui-menu_3.0.1-0720.1.dsc;md5=1b897ae127a2d8076a0b318daa720f91\ file://ukui-menu_3.0.1-0720.1.tar.xz;md5=e684d90a713f953179b1a06e8e3401c1 do_deb_prepend() { install -d ${S} install -m 0755 ${WORKDIR}/ukui-menu_3.0.1-0720.1.dsc${S} install -m 0644 ${WORKDIR}/ukui-menu_3.0.1-0720.1.tar.xz ${S} }
这样可以将ukui-menu的文件放到work目录下,然后我们执行dpkg命令即可正常构建
do_fetch
会使用SRC_URI变量定位源码文件;基于SRC_URI变量值中每个条目的前缀来确定使用哪个提取器来获取源文件, file://
开头为本地文件, http://
、git://
等为上游获取的源文件;
我们这里使用了file,这样将其拉到了sysroot内部,这样我们可以通过自己的函数来执行dpkg-buildpackage
。从而构建deb包
其形式类似于yocto版本构建(三)源地址dsc进行构建
至此,我们不仅可以通过远程源地址的dsc文件编译二进制包,也可以通过本地file链接来编译二进制包
根据yocto版本构建(二)安装已有二进制已经可以具备安装已有的二进制,这样一个完全仿照livebuild的方式(kybuilder)的yocto工程只需要花点时间就可以配置出来,但是我们不止如此,我们知道,麒麟v10操作系统软件包的开发是和deb开发方式一致的,通过dsc即可正常构建,如下是实现路径
为了支持debian包的构建,我们需要根据deb的安装方式设计一个bbclass用作bbfile的调用,我们知道,针对系统的软件开发,主要步骤如下:
wget https://xxxx.dsc dpkg -x xxxx.dsc dpkg-buildpackage -uc -us / dh_make
为了让yocto上也能使用dsc来通过源码的方式构建出一个deb包,我们能需要定义函数类似如下:
do_unpack_dsc() { dpkg-source -x ${DL_DIR}/${PN}_${PV}.dsc ${S} } do_compile_dsc() { dpkg-buildpackage -uc -us }
不仅如此,我们还需要在do_package中将其生成的deb文件,复制到recipes的work目录下。这样我们不需要使用yocto的本身构建方式来生成deb
对于bbfile,我们需要继承我们的类,然后提供一个宏定义,由我们的deb.bbclass获取代码开始构建,如下:
inherit kylin inherit deb DSC_URI = "https://dev.kylinos.cn/kylin-desktop/+archive/primary/+files/ukui-menu_3.0.1-0720.1.dsc;md5sum=1b897ae127a2d8076a0b318daa720f91"
为了让我们的bbfile支持调试,我们可以通过如下命令
bitback ukui-menu -c listtasks
这样可以列出我们的步骤,方便调试细节
如果我们需要进入devshell中,具体调试dpkg-buildpackage -uc -us
出现的错误,我们可以如下
bitback ukui-menu -c devshell
这样我们可以手动运行chroot进入此环境
至此,我们可以通过指定一个dsc文件,就可以触发yocto执行源码的编译动作
我们知道yocto是基于源码构建的工具,如果我们在开发系统的时候,直接使用全部构建的方式来生成操作系统,那代价将会异常的大。所以可以通过二进制安装
默认情况下,yocto会根据编译来构建一个sysroot,而每个程序都有自己单独的sysroot作为隔离。这种情况下,如果我们不需要通过构建的方式产生sysroot,我们需要如下:
在meta-kylin下定义一个recipes-debootstrap 运行debootstrap命令,通过麒麟发布的源地址,构建一个chroot环境,此环境是SYSROOT_DESTDIR环境变量 yocto默认使用此sysroot作为版本构建 对于的bb如下:
do_build() { sudo -E debootstrap --variant=minibase --include=systemd,apt kylin ${SYSROOT_DESTDIR} ${KYLIN_REPO} sudo -E chroot ${SYSROOT_DESTDIR} apt-get update sudo -E chroot ${SYSROOT_DESTDIR} apt-get install -y ${DEPENDS} }
我们知道yocto分如下步骤:
对于此,我们需要将此流程定制,inherit 我们自己的kylin.bbclass,如下:
在我们的layer中的bb file,需要inherit kylindeb
这样的类,这样默认就指向了我们自己的流程
我们在do_install
中会进入chroot环境中进行apt-get
安装包,这里bb file需要提供一个packages-list
文件,用于解析packages-list里面的包列表,用于安装,如下示例:
PACKAGE_LIST = "ukui-tablet-desktop ukui-control-center ukui-menu" do_install() { apt-get update apt-get install -y ${PACKAGE_LIST} }
最后我们通过命令即可构建
bitback kylin
至此,yocto可以具备通过安装二进制的方式来构建系统环境。
我们知道yocto可以用于操作系统版本构建,它与openembedded相辅相成。
而我们的工作内容中,我们是通过livebuild(u系系统)的方式构建的操作系统。这种方式是预先通过服务器进行预编译的二进制,我们通过livebuild工具执行一次构建操作,从而生成一个操作系统版本,这个优点是操作系统构建速度快,缺点是系统无法进行良好的定制扩展。
对于上述的问题,我们知道,对于livebuild的缺点,我们无法解决,因为软件的定制扩展需要依赖服务器的二进制发布,而针对yocto的缺点,我们可以解决,我们可以让yocto在构建系统的过程中,可选择性的执行全量编译,或部分编译,甚至进二进制构建的行为。
Yocto Project 是一个开源协作项目,可帮助开发人员为嵌入式产品和其他目标环境创建基于 Linux 的自定义系统,而不管硬件架构如何。该项目提供了一套灵活的工具和空间,世界各地的嵌入式开发人员可以在其中共享技术、软件堆栈、配置和最佳实践,这些技术、软件堆栈、配置和最佳实践可用于为嵌入式设备创建定制的 Linux 和 RTOS 映像
https://www.yoctoproject.org/development/technical-overview/
根据上面我们可以知道,yocto是一个可以创建基于Linux的自定义系统的工具,如果我们掌握yocto工具,那么可以为其他目标环境创建一个自定义的操作系统。
我们已经知道yocto是一套工具,它可以创建操作系统,那我们为什么要用yocto,而不去选择其他的系统构建工具呢,这里以livebuild为例,详细解释主要原因
对于yocto,它是基于源码构建操作系统的方式,如果系统中存在某个软件包需要根据实际情况定制修改,其步骤相比于livebuild更方便,如下:
对于livebuild:
在服务器上下载软件的源码 修改源码 编译源码生成二进制deb包 推送到二进制发布平台上 重新执行livebuild构建,形成新的操作系统
对于yocto:
修改源码,提供修改源码的patch文件 修改bb file,将patch文件通过quilt方式合入源码 执行recipes的编译 执行images的构建,形成新的操作系统
根据上面我们可以知道,对于livebuild,我们需要有专门的编译服务器,需要将编译的二进制包推送到发布平台,再通过livebuild脚本来执行构建,需要依赖的东西多,步骤复杂
而对于yocto,我们仅仅需要针对代码的patch,修改bb file,添加补丁,然后执行构建即可,在其规则内,简单方便即可完成定制需求
对于yocto,不同的bb file可以通过append方式修改和定制,不同的版本可以通过设置不同的layer实现,也就意味着基于yocto的构建版本,可以很好的处理多版本的现状,而livebuild很难做到,如下:
对于livebuild:
不同的定制版本需要上传到不同的二进制发布平台上 修改livebuild脚本,针对不同的版本设置不同的hook脚本,用于构建不同的版本 根据livebuild脚本构建操作系统镜像
对于yocto:
设置不同的meta-projectname的layer 构建不同的layer的操作系统镜像
根据上面我们可以知道,对于livebuild,如果定制版本特别多
例如上百量级,那么我们不同的二进制包需要上百个,二进制发布平台的源地址需要上百个,维护的hook脚本也有上百个
而yocto不同的是,我们所有的改动都放在一个layer中,那么我们只需要上百个layer即可。对应改动在layer上,而且yocto的layer之间可以实现overlay
也就意味着,我们不需要发布上百个二进制或源码配置,而仅仅是在原来的layer上做overlay,实现差分内容管理即可。
关于此概念,其实是构建系统的两个思路,如下解释:
根据这里,我们可以发现,正常情况下,如果我们的操作系统功能涵盖了一切,对于其用户需求而言,仅需要增加和减少两个功能,那么自顶向下的的方法并没有问题。所以它比较适用于例如服务器(对操作系统功能越丰富越好,性能问题无需格外关心),桌面(提供一个基本操作系统环境,用户通过安装包来提供新的功能)
但是它不适用于嵌入式,通常情况下,嵌入式是一个定制性非常强的系统,例如车机系统,机器人系统,工业控制系统等。每个系统的定制功能不同,但整体性能有限,所以无法安装服务器的版本(性能和空间受限),也无法使用桌面版本(操作系统环境无法定制)。
这里,livebuild是一种减法类操作系统构建工具,而yocto默认是一种加法类操作系统构建工具。
但是不一样的是:
所以我们可以发现,yocto比livebuild更优。
我们知道livebuild十分依赖本机环境,即使我们通过chroot进入一个新的环境,我们也是在chroot环境中进行构建版本,它没办法跨平台,例如在x86上构建arm64的操作系统。
当然,我们可以通过qemu static来实现跨平台的构建方式,但它并不友好。
yocto与其不同的是,yocto可以配置不同的平台层,使用不同的cross compile工具,使其能够从源码上构建不同平台的二进制软件
当然,yocto也可以通过qemu static来实现跨平台的二进制安装。
根据上述,我们可以知道,对于操作系统的二进制加减法,我们都可以通过qemu static工具实现,但是对于源码定制和构建,只有yocto能够做到。
放大了看,对于整个操作系统的构建行为,livebuild严格意义上是无法完成的,而yocto本身可以轻松的完成跨平台的构建系统版本,包括但不局限于如下:
x86 windows/linux 上构建 arm64 loongarch64 risc5-64 arm64 linux上构建x86-64 loongarch64 risc5-64 loongarch64 linux上构建 arm64 x86-64 risc5-64 risc5-64 linux上构建 arm64 x86-64 loongarch64
虽然我们通常是x64上构建其他平台,但是也足够说明了yocto可以做到其livebuild无法做到的事。
针对我们的行业,我们可以知道,我们主要提供操作系统的定制方案,我们已经获取了发布的操作系统版本。我们需要做的是,将这些操作系统版本应用到我们的行业场景上去。
假设我们是自顶向下的减法,定制操作系统版本,那么我们根据我们的各种行业,需要提供基于v10的各种版本。那么就会出现如下问题:
操作系统都叫V10,但是版本参差不齐,版本的数量太多,版本之间差异过多,但不清楚差异在哪,兼容困难 版本与版本之间都是通过kybuilder平台定制修改的,但是并没有直接明了的知道版本间的差异。
我们可能在某个行业,例如车载上有100个项目,那么这100个项目都集成某个基于车载的版本,最后衍生了100个项目版本。这100个项目版本均有差异。
操作系统都是车载V10版本,但是每个车载V10版本都不一样,只知道有100个车载项目版本要维护,但理不清楚每个车载版本的差异点 我们找不到这100个项目的差异,我们需要为这100个项目版本做好维护工作,而不是维护一个yocto配置这种行为。
统一的操作系统
yocto可以定义每个meta layer,我们可以根据行业来设置layer,不同的场景客户都基于原始v10版本上,新增layer。
这样,不同的layer中就是不同场景的定制功能需求,这些layer里面是各种功能的bb 文件
yocto可以为不同的项目设置项目的layer,然后项目的改动通过设置bbappend实现。bbappend可以针对原来的行业的layer上进行追加修改和定制。例如:
meta-vehicle-project/recipes-xxx/xxx_0.1/xxx.bbappend
这里xxx是具体功能,bbappend是对原meta-vehicle的定制追加
yocto可以统一拉取操作系统源码或二进制,统一输出镜像,能够使得成为真正意义上的全场景OS。
至此,我们的版本基于v10,提供了yocto的工程,根据此工程构建的版本都是基于v10,其他的衍生版本通过bb 和 bbappend实现。
如果客户拿到了我们的yocto仓库,客户不会认为我们存在100个不同的版本,而是1个统一的yocto版本,通过灵活构建生成了100个行业场景。