前言
使用Yocto编译嵌入式Linux系统,原厂提供系统代码,内置支持Wayland协议
使用westeros作为Wayland Compositor,由RDK(Reference Design Kit)提供
使用丰田开发的ivi-homescreen作为Flutter嵌入层,基于Waylannd+EGL
RDK(Reference Design Kit)是管理嵌入式应用参考设计套件的开源联盟
Yocto介绍
Yocto是一个开源项目,用于构建嵌入式Linux系统。类似于BuildRoot(基于Makefile和Kconfig配置)
关键概念:
- Poky:指整个构建系统,包括BitBake工具、编译工具链、BSP,以及诸多程序包和层
- Metadata:元数据集
- Recipes:
.bb/.bbappend
配方文件,配置源码下载路径、如何编译等。一个配方(Recipe)可以包含多个软件包(Package) - Class:
.bbclass
文件,抽象的公共代码,给各个package使用 - Configuration:
.conf
配置文件,构建配置
- Recipes:
- Layers:即各种
meta-xxx
目录,包含Metadata的存储库,可以单独发布、下载,便于项目维护,例如meta-flutter
、meta-clang
等。官方支持的层级包和配方文件 - BitBake:任务执行引擎,解析Metadata,执行软件包的Task
在Yocto环境中构建Flutter:依赖meta-flutter项目,包含devtools工具项目、Flutter引擎项目、Flutter应用案例项目,以及各种定制嵌入层(sony、toyota、树莓派等)项目等,通过BitBake统一构建
对于Yocto,个人理解是类似于AOSP项目:
- Yocto使用Bitbake工具执行构建任务,AOSP使用make或者ninja构建
- Yocto的
.bb/.bbappend
配方文件和.conf
配置文件,类似于AOSP的.mk/.bp
配置文件 - Yocto项目只包含构建Metadata,代码从网上下载,AOSP需要下载所有代码进行编译。
Yocto的发行版:zeus(3.0)、dunfell(3.1)、gatesgarth(3.2)、hardknott(3.3)、honister(3.4)
Yocto目录结构说明
1 | . |
build/tmp
目录说明:构建的输出目录
1 | └── build |
poky/meta/
目录说明:OE Core构建配置
1 | └── poky # Poky工程通用目录结构 |
local.conf配置
保存通用配置、全局变量等。
- 配置downloads目录:
DL_DIR ?= "${TOPDIR}/../downloads"
- 配置使用的包管理器:
PACKAGE_CLASSES ?= "package_ipk/package_rpm/package_deb"
- 镜像中安装软件:IMAGE_INSTALL_append = “ openjdk-8”
- 排除软件:PACKAGE_EXCLUDE = “ openjdk-8”
Bitbake笔记
bitbake [options] [recipename/target recipe:do_task ...]
bitbake -s
:列出可用的recipes。
原理:解析
bblayers.conf
中配置的Layer路径,从Layer中找到bb文件,解析出需要编译的recipe。可以修改
bblayers.conf
文件,添加或移除模块。也可以使用以下命令
bitbake-layers show-layers/add-layer/remove-layer
:查看/添加/移除可用的Layers
bitbake -g lib32-amlogic-yocto && cat pn-buildlist | grep -ve "native" | sort | uniq
:查看镜像包依赖信息。
-g
保存依赖信息到pn-buildlist
文件中
bitbake -e <recipe> | grep ^S=
:查看Package编译工作目录
bitbake -e lib32-flutter-sdk | grep ^SRC_URI=
:查看软件源码下载路径
1 | 32位工作目录 |
bitbake下载软件源码失败:可以手动下载到本地,切到期望的分支和commit,修改对应的
.bb
文件,将远程Git仓库改为本地路径。
每个软件包都有自己的工作目录,包括源码、编译配置、编译输出、交叉编译需要的目标平台sysroot等。
其中temp临时目录中包含软件的Task脚本(例如run.do_compile
、run.do_fetch
等),Task执行log(例如log.do_fetch
、log.do_compile
)等。
注:可以在temp目录下查看和修改Task脚本,分析软件编译过程,手动执行编译命令。
不同的Package有不同的Task,Yocto有一些通用的Task,例如fetch、clean、build、listtasks等。
bitbake <recipe> -c <CMD>
:执行特定Task。默认执行build任务。例如bitbake lib32-flutter-engine-release -c clean
bitbake <recipe> -c listtasks
:查看软件包可执行的Task。
分析flutter-engine构建过程
查看软件工作目录temp
文件夹,分析Task脚本大致过程
do_fetch
:根据meta-flutter
中的配方文件下载Flutter引擎源代码do_unpack
:将源码解包到工作目录do_patch
:打补丁,修改Flutter引擎源代码,主要是修改引擎编译配置,meta-flutter/recipes-graphics/flutter-engine/files
do_prepare_recipe_sysroot
:在workdir目录下创建两个sysroot(recipe-sysroot和recipe-sysroot-native)do_configure
:执行gn命令生成构建配置do_compile
:执行ninja编译do_install
:将Flutter引擎产物拷贝到等候区,一般位于${WORKDIR}/image
目录下,使用${D}
表示do_package
:根据需要打包生成packagedo_package_write_ipk
:打包ipk- …
${WORKDIR}
:软件构建工作目录${D}
:${WORKDIR}/image
${PN}
表示PackageName,例如lib32-flutter-engine-release
do_package相关:
1 | pkgdata只存放了包路径信息 |
Yocto编译Linux系统
原始系统
公司服务器已经搭建好了编译的Docker环境,并且提供了编译的脚本。
1 | ssh登录服务器 |
加入meta-flutter
meta-flutter是Yocto Layer,用于Yocto编译。recipes包括:
- flutter-sdk:默认使用最新版,通过
FLUTTER_SDK_TAG
变量设置版本 - flutter-engine:默认使用和Flutter SDK对应的版本
- flutter-gallery:Sample应用
- ivi-homescreen:Toyota/AGL - Wayland嵌入层
- flutter-pi:树莓派DRM嵌入层
- sony:Sony嵌入层flutter-eLinux
- flutter-wayland、waylandpp:基于Wayland的嵌入层,类似ivi-homescreen。最新的meta-flutter已经去除
步骤如下:
- 下载
meta-flutter
到源码根目录:git clone https://github.com/meta-flutter/meta-flutter.git
- 下载
meta-clang
到源码根目录:git clone https://github.com/kraj/meta-clang.git
meta-clang
和meta-flutter
切换到dunfell分支:git checkout -b dunfell origin/dunfell
bblayers.conf
中配置Layer路径,或者执行bitbake-layers add-layer /home/code/meta-clang
、bitbake-layers add-layer /home/code/meta-flutter
- 设置Flutter SDK版本:
local.conf
中添加变量FLUTTER_SDK_TAG = "2.8.1"
整包编译
- 整包编译时安装软件包:
local.conf
中添加变量IMAGE_INSTALL_append = " lib32-flutter-engine-release lib32-ivi-homescreen-release lib32-flutter-gallery-release"
(注意开头有空格) - 整包编译:
bitbake lib32-amlogic-yocto
局部编译
局部编译ivi-homescreen
和flutter-engine
,再将生成的库手动push到系统中。
1 | bitbake lib32-ivi-homescreen-release |
ivi-homescreen
运行只需要几个关键的库,这里收集起来放到一起,避免去构建目录中查找
1 | tree |
踩坑记录
gn不支持--no-build-embedder-examples
参数
bitbake lib32-flutter-engine-release -v
构建失败,提示gn不支持--no-build-embedder-examples
参数
原因:Flutter引擎版本问题
分析:默认的PACKAGECONFIG包含
disable-embedder-examples
,导致gn命令会添加--no-build-embedder-examples
选项。
1
2
3
4
5
6
7
8
9 meta-flutter/recipes-graphics/flutter-engine/flutter-engine.inc
PACKAGECONFIG ??= "disable-desktop-embeddings \
disable-embedder-examples \
embedder-for-target \
fontconfig \
${FLUTTER_RUNTIME} \
"
PACKAGECONFIG[disable-desktop-embeddings] = "--disable-desktop-embeddings"
PACKAGECONFIG[disable-embedder-examples] = "--no-build-embedder-examples"解决:
local.conf
中添加如下变量,去掉disable-embedder-examples
,覆盖默认的PACKAGECONFIG
1
2
3 PACKAGECONFIG_pn-flutter-engine-release = "disable-desktop-embeddings embedder-for-target fontconfig release"
PACKAGECONFIG_pn-flutter-engine-debug = "disable-desktop-embeddings embedder-for-target fontconfig debug"
PACKAGECONFIG_pn-flutter-engine-profile = "disable-desktop-embeddings embedder-for-target fontconfig profile"
无法编译Dart SDK
bitbake lib32-flutter-engine-release -v
构建失败,报错如下
1 | Command failed: /home/code/build/tmp/work/armv7at2hf-neon-pokymllib32-linux-gnueabi/lib32-flutter-engine-release/git-r0/src/out/linux_release_arm/clang_x64/dart |
原因:编译的ARM架构的
out/linux_release_arm/clang_x64/dart
程序无法执行。导致生成Kernel快照失败,例如frontend_server.dart.snapshot
、analysis_server.dart.snapshot
、dartdoc.dart.snapshot
等
bitbake flutter-engine-release
编译正常:ARM64架构的dart程序可以正常执行。分析:其实引擎编译的关键产物只有
gen_snapshot、libflutter_engine.so
。生成的Dart程序是平台无关的,直接用Flutter SDK中现成的即可,没必要编译Dart SDK。解决:只要跳过Dart SDK编译即可,修改
src/flutter/BUILD.gn
文件,将_build_engine_artifacts
改为false旧版本
meta-flutter
还需要修改flutter-engine.bb
文件:
1
2
3
4
5
6
7
8
9
10
11 meta-flutter/recipes-graphics/flutter-engine/flutter-engine.bb
删掉full-dart-sdk
PACKAGECONFIG ?= "disable-desktop-embeddings \
embedder-for-target \
fontconfig \
full-dart-sdk \
mode-release \
"
...
删掉下面一行:将编译好的dart前端安装到image中。
install -m 644 ${S}/${OUT_DIR_REL}/dart-sdk/bin/snapshots/frontend_server.dart.snapshot ${D}/${datadir}/flutter/sdk/新版本
meta-flutter
配置改为了flutter-engine.inc
文件:
- 去除掉了上面的两个配置:高版本Dart虚拟机不再接收Dart源文件,只接收Kernel代码,将编译前后端分离。因此直接在主机编译Kernel即可,没必要安装编译前端。
- gn命令默认加了
--no-stripped
选项,可以去掉
1
2 meta-flutter/recipes-graphics/flutter-engine/flutter-engine.inc
GN_ARGS = "${PACKAGECONFIG_CONFARGS} --clang --lto --no-goma --no-stripped "
整包编译空间不足
根据文档预装软件到系统中:IMAGE_INSTALL_append = " lib32-flutter-engine-release lib32-ivi-homescreen-release lib32-flutter-gallery-release"
1 | 整包编译报错 |
解决:只保留homescreen编译成功,
IMAGE_INSTALL_append = " lib32-ivi-homescreen-release"
homescreen依赖flutter-engine,因此引擎也会被打包进去。升级软件之后可以直接运行homescreen
10月12日补充问题
上面配置的flutter引擎版本是2.8.1,时隔几个月之后,Google关掉了部分三方库的服务,迁移了地址,导致其他人用2.8.1版本,出现很多新问题,例如gclient同步依赖库失败。
建议:更新Flutter引擎版本,重新调试环境。下面介绍下使用2.8.1版本遇到的问题
Pseudo Abort
编译过程中遇到Pseudo Abort问题。参考Wiki说明,是由于“路径不匹配”。
解决方法:bitbake lib32-glib-2.0 -c clean
,清除然后重新构建对应的库
liberror库编译失败
原因未知,bitbake lliberrror-perl -c clean
之后再次编译即可
libcxx not found
编译flutter引擎bitbake lib32-flutter-engine-release
出错,执行do_patch脚本的时候提示libcxx not found
分析:查看DEPS文件,libcxx在https://fuchisa.googlesource.com
中,访问该地址,发现libcxx已经被移除
查看新版本引擎代码,已经迁移到了llvm项目地址中
临时解决:直接将之前构建好的libcxx拷贝给其他人使用,在src/thirdtparty
目录
linter找不到远程引用
修复libcxx之后,do_patch脚本又出现linter找不到远程HEAD问题。原因未知,仓库地址还是可以访问的。
分析do_patch脚本,找到出问题的命令:gclient sync --shallow --no-history -R -D --revision "890a5fca2e34db413be624fc83aeea8e61d42ce6" -j 48
890a5fca2e34db413be624fc83aeea8e61d42ce6即我们指定的2.8.1的版本
解决:
- 先注释掉DEPS中linter的依赖库,版本是1.14.0,跳过下载
- 再手动执行
gclient sync
,下载其他的依赖库。注意不要指定--revision
,否则会还原掉DEPS的修改。 - 这个时候patch的动作已经差不多被我们手动执行完了,所以可以跳过
do_patch
的过程 - 修改
meta-flutter/recipes-graphics/flutter-engine/flutter-engine.inc
配置文件,如下
- 顺便注释掉git apply,否则会出现打补丁失败
找不到linter库
再次编译flutter引擎bitbake lib32-flutter-engine-release
,提示找不到linter库
分析:由于上面我们注释掉了linter的依赖库和patch脚本,所以找不到。依赖的linter库的tag是1.14.0
解决:手动克隆linter库,切到对应tag1.14.0对应的commit id
Flutter Linux ARM应用编译
1 | 前端编译生成app.dill |
文件和路径说明:
dart
:使用Flutter SDK自带的Dart。Flutter SDK版本要和Flutter引擎版本对应flutter/bin/dart
frontend_server.dart.snapshot
:使用Flutter SDK缓存的前端编译器,或者引擎编译生成的前端编译器flutter/bin/cache/artifacts/engine/linux-x64/frontend_server.dart.snapshot
out/linux_release_arm/frontend_server.dart.snapshot
flutter_patched_sdk
:使用Flutter SDK缓存的文件,或者引擎编译生成的文件flutter/bin/cache/artifacts/engine/common/flutter_patched_sdk
out/linux_release_arm/flutter_patched_sdk
gen_snapshot
:引擎编译生成的后端编译器,用于在Linux x86_64平台上交叉编译出ARM平台目标代码。out/linux_release_arm/clang_x64/gen_snapshot
Flutter应用启动
- 升级软件开机
- 杀掉WPExxx的进程(有两个进程)
su
进入root,或者export XDG_RUNTIME_DIR=/run
westeros-init &
:启动westeros服务端westeros_test
:启动westeros示例客户端,运行成功显示UI- 将
aml_armv7at2hf_yocto
下的文件夹push到板卡对应路径下(整包编译不需要再push引擎和homescreen,只需要装入应用即可) homescreen --a=/usr/share/homescreen/image_list/flutter_assets
:运行Flutter程序,提前编了4个sample
--a
指定应用路径,不指定的话默认会找/usr/share/homescreen/bundle
目录。可以将应用路径链接到bundle目录,如下
1
2
3
4
5 cd /usr/share/homescreen
将程序链接到bundle
ln -sf particle_background/ bundle
homescreen查找默认目录
homescreen
默认系统环境:AML 950D4 Linux平台,版本如下
1 | sh-5.0# uname -a |
系统默认使用WPEFramework运行应用。WPEFramework是嵌入式设备的WebKit引擎,能够运行Web应用。执行以下命令可以打开WPE Launcher应用
1 | 启动WPEFramework引擎,开启了一个网络端口 |
结语
编译环境搭建会遇到很多问题,需要缩小范围,不断调试。例如
- bitbake整编软件出问题,分析log找到报错的模块,局部编译
- 局部编译模块出问题,分析log找到对应的task脚本,手动执行单个脚本
- 单个脚本执行失败,分析log找到对应的命令,手动执行单条命令
- 分析命令执行原理和过程,尝试修改命令和脚本再运行。
- 由于temp目录的脚本是临时的,每次bitbake都会覆盖掉修改的脚本,所以需要想办法修改bb配置文件