探索过程总结
环境配置较复杂,遇到的问题、涉及到的技术栈较多,导致文章内容比较零散。
探索过程中最麻烦的是编译问题的实验和记录,不确定缺少了哪些库,遇到一个问题就去网上搜索解决,做了很多尝试,有时候误打误撞成功了,但是无法确定真正起作用的是哪一步操作。因此需要不断回退,重置环境。
有时候为了分析错误原因,验证自己的猜想,明知道有坑还得去踩上一遍。
背景和目标
目标:解决Flutter交叉编译Linux arm平台应用,在TV嵌入式设备上运行。
任务:使用Flutter开发Linux TV应用,研究Flutter应用和引擎编译,搭建编译环境,Flutter嵌入层定制和适配。
Flutter桌面应用编译只能在对应的平台上(Linux应用只能在Linux平台上编译),由于没有Linux电脑,无法编译和运行Linux程序(x86_64、arm、arm64)。参考Flutter桌面支持
解决思路:利用Docker Ubuntu容器环境。
- 容器内本地编译Flutter Linux桌面应用,并尝试在容器内运行显示界面。
- 容器内交叉编译arm/arm64平台应用,并安装到嵌入式Linux设备运行。
使用Docker有几个明显的好处:
- 尝试过程中需要装各种乱七八糟的环境,很容易破坏主机环境,且不知道真正缺少的是哪些环境,使用Docker可以随时删除容器重新验证。
- 准备好环境之后可以方便的进行打包和移植
注:Flutter官方的Linux桌面嵌入层使用GTK图形库,因此下面编译出的Linux应用只适用于Linux GTK环境。嵌入式平台如果使用其他图形系统(例如DRM、Wayland),需要定制嵌入层,修改编译工具链和sysroot
环境介绍
环境:(下文不再赘述)
- 主机(Host):
- 远程服务器:Linux CentOS,没有root权限(无法安装编译环境),但是可以使用Docker(容器中可以root)。
- 本地机器/主机:MacOS
- 目标平台:Linux嵌入式平台(arm架构)、Android嵌入式平台(arm、armv8架构)、Linux桌面平台(x64、arm64架构)
- 实验镜像:ubuntu(x64架构):作为Flutter交叉编译主机,默认不带图形界面,无法运行Flutter GUI界面
- 容器:本地机器和远程服务器都可以运行Docker容器。
- 本地Docker容器:通过QEMU可以运行Ubuntu arm镜像和arm64镜像
- 远程Docker容器:没有QEMU,只能运行x86_64 Ubuntu镜像
分别对比主机和容器系统版本差异,如下:
本地和远程Docker容器使用相同的Ubuntu官方镜像,但是查看版本有一定差异。本地Docker容器应该是QEMU模拟器运行Linux环境。
注意这里有个坑,下文会提到:同一个gen_snapshot程序,在本地Linux容器可以运行,在远程Linux容器无法运行,都是x86_64架构
结论:不同系统的主机上即使使用相同的镜像,容器环境也存在差异
大纲
- 编译Linux x86_64桌面应用:问题不大,根据Flutter官方文档构建即可。
- 编译Linux arm64引擎:问题不大
- 编译Linux arm64应用:
- 修改
flutter_tools
源码 - 指定
--target-sysroot
参数
- 修改
- 编译Linux arm引擎:
- 修改
src/build/toolchain/custom/BUILD.gn
,${custom_target_triple}
改为llvm
- 修改
srf/flutter/BUILD.gn
,将_build_engine_artifacts
改为false,跳过Dart SDK编译 --target-sysroot=$PWD/build/linux/debian_sid_arm-sysroot/
:使用install-sysroot.py
脚本下载--target-toolchain=$PWD/buildtools/linux-x64/clang
:引擎自带路径--target-triple=armv7-unknown-linux-gnueabihf
- 修改
- 编译Linux arm应用:需要修改
flutter_tools
- 手动进行Dart前后端编译
- 手动编译Linux平台代码,链接引擎和应用so库,生成可执行程序
- 运行应用:下载的Ubuntu镜像没有界面,无法运行GUI程序。
- x64和arm64应用使用VNC容器运行:Linux容器运行GUI程序
- arm应用装入嵌入式平台运行:Yocto嵌入式平台运行Flutter应用
引擎编译和应用编译有各自的sysroot和toolchain
- sysroot是编译需要的一些目标平台库和头文件等。
- toolchain是交叉编译工具链。
引擎获取方式:主要产物是libflutter_engine.so
库和gen_snapshot编译后端程序
- 手动下载Flutter官方预构建引擎:缺少arm平台
- 手动编译引擎
sysroot和toolchain获取方式:
- 手动下载Flutter官方预构建引擎sysroot,使用引擎
src/buildtools
- 自行制作:参考交叉编译
应用编译方式:
- 使用
flutter build
命令:需要修改flutter_tools源码 - 手动调用前后端编译
其他思路
在arm64容器中直接本地编译arm64的Flutter应用。
通用问题
进入容器后首次使用apt-get
安装软件报错:"E: Unable to locate package"
Linux的发行版维护了一个软件仓库,存储常用的软件,仓库地址存储在
/etc/apt/sources.list
文件中,使用apt-get
工具会从该文件中读取仓库地址,下载并安装软件。解决:执行下
apt update
,更新软件源地址列表
apt
命令是对apt-get
、apt-cache
等命令的封装,提供了统一的入口。
使用apt安装的交叉编译工具链可能有多个版本,可以搜索,例如:apt-cache search gcc | grep -E "arm|aarch64"
查看IP地址:ifconfig en0 | grep inet | awk '$1=="inet" {print $2}
Git clone仓库提示Permission denied:生成SSH Key,并在GitHub上配置Git SSH Key。或者将本地配置好的key拷贝到容器中使用。
Docker操作
关于Docker介绍和使用可以参考我的其他文章,这里简单介绍下涉及到的操作
1 | 获取Ubuntu镜像 |
容器中下载Flutter引擎和SDK源码会导致生成镜像很大,可以让用户自己在主机下载好源码,运行的时候再挂载到容器中。
Docker容器中配置.bash_profile
环境变量,退出容器再进入失效。
可以通过DockerFile配置镜像环境变量,或者执行
docker run
和docker exec
时使用-e参数。
查看Docker镜像和容器大小:docker system df -v
主机和Docker容器间文件拷贝:docker cp 主机路径 容器名:容器内路径
本地编译Linux桌面应用
容器内安装Flutter SDK,根据官方文档配置环境即可,问题不大。国内网络下载不了的话参考Using Flutter in China
- 下载Flutter SDK:
git clone https://github.com/flutter/flutter.git
- 配置环境变量
- 安装Linux编译环境:
apt install unzip git curl clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev
- 启用Linux编译:
flutter config --enable-linux-desktop
- 检查Flutter编译工具链:
flutter doctor
编译Flutter应用:生成myapp可执行文件
1 | 创建Flutter模版工程 |
通过Linux容器运行GUI程序,可以成功运行myapp应用。
交叉编译Linux arm64平台应用
Linux x64主机交叉编译Linux arm64目标平台应用:参考issue和说明
修改flutter_tools
Flutter SDK命令默认不支持交叉编译arm64和arm应用,需要修改flutter_tools
源码:
修改
flutter_tools/lib/src/commands/build_linux.dart
代码,注释掉交叉编译报错:修改
flutter_tools/lib/src/artifacts.dart
代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19//flutter_tools/lib/src/artifacts.dart
class CachedArtifacts implements Artifacts {
...
String _getDesktopArtifactPath(Artifact artifact, TargetPlatform? platform, BuildMode? mode) {
// When platform is null, a generic host platform artifact is being requested
// and not the gen_snapshot for darwin as a target platform.
if (platform != null && artifact == Artifact.genSnapshot) {
final String engineDir = _getEngineArtifactsPath(platform, mode)!;
// 添加代码块
if (getNameForHostPlatformArch(getCurrentHostPlatform())
!= getNameForTargetPlatformArch(platform)) {
final String hostPlatform = getNameForHostPlatform(getCurrentHostPlatform());
return _fileSystem.path.join(engineDir, hostPlatform, _artifactToFileName(artifact));
}
return _fileSystem.path.join(engineDir, _artifactToFileName(artifact));
}
return _getHostArtifactPath(artifact, platform ?? _currentHostPlatform(_platform, _operatingSystemUtils), mode);
}
}修改
flutter_tools/lib/src/flutter_cache.dart
代码(注意代码行数):用于下载Flutter官方arm64引擎1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45//flutter_tools/lib/src/flutter_cache.dart
class FlutterSdk extends EngineCachedArtifact {
...
List<List<String>> getBinaryDirs() {
// Currently only Linux supports both arm64 and x64.
return <List<String>>[
<String>['common', 'flutter_patched_sdk.zip'],
<String>['common', 'flutter_patched_sdk_product.zip'],
if (cache.includeAllPlatforms) ...<List<String>>[
<String>['windows-x64', 'windows-x64/artifacts.zip'],
// 修改代码:Modified to download the artifacts of both linux-x64 and linux-arm64
<String>['linux-x64', 'linux-x64/artifacts.zip'],
<String>['linux-arm64', 'linux-arm64/artifacts.zip'],
<String>['darwin-x64', 'darwin-x64/artifacts.zip'],
]
else if (_platform.isWindows)
<String>['windows-x64', 'windows-x64/artifacts.zip']
else if (_platform.isMacOS)
<String>['darwin-x64', 'darwin-x64/artifacts.zip']
else if (_platform.isLinux) ...<List<String>>[
// 修改代码:Modified to download the artifacts of both linux-x64 and linux-arm64
<String>['linux-x64', 'linux-x64/artifacts.zip'],
<String>['linux-arm64', 'linux-arm64/artifacts.zip'],
],
];
}
}
class LinuxEngineArtifacts extends EngineCachedArtifact {
List<List<String>> getBinaryDirs() {
if (_platform.isLinux || ignorePlatformFiltering) {
// 修改代码:Modified to download the artifacts of both linux-x64 and linux-arm64
return <List<String>>[
<String>['linux-x64', 'linux-x64/linux-x64-flutter-gtk.zip'],
<String>['linux-x64-profile', 'linux-x64-profile/linux-x64-flutter-gtk.zip'],
<String>['linux-x64-release', 'linux-x64-release/linux-x64-flutter-gtk.zip'],
<String>['linux-arm64', 'linux-arm64/linux-arm64-flutter-gtk.zip'],
<String>['linux-arm64-profile', 'linux-arm64-profile/linux-arm64-flutter-gtk.zip'],
<String>['linux-arm64-release', 'linux-arm64-release/linux-arm64-flutter-gtk.zip'],
];
}
return const <List<String>>[];
}
}删除
flutter_tools
重新构建:rm flutter/bin/cache/flutter_tools*
执行编译命令:
flutter build linux --target-platform linux-arm64 -v
-v查看编译具体信息
这里build失败是正常的,但是已经成功下载Flutter官方提供的Linux arm64引擎的构件,放到了SDK对应路径下,例如:flutter/bin/cache/artifacts/engine/linux-arm64-release/
。
踩坑:下载的Flutter引擎gen_snapshot程序可以在本地Docker容器中执行,但是无法在远程Docker容器中执行。提示cannot execute binary file: Exec format error
(二进制可执行程序格式不正确),导致编译失败。
前面介绍环境的时候提到不同主机上即使使用相同镜像,容器环境也存在差异。
编译器本身也是一个可执行程序,只能在目标平台运行。
既然官方提供的引擎构件无法使用,那只能自己下载引擎源码编译生成gen_snapshot了。
交叉编译Flutter arm64引擎
源码下载和Host编译参考Flutter源码下载和编译
注:Flutter SDK中内置了引擎的artifact
,如果自己编译引擎,需要确保Engine源码和Framework源码版本对应。本文使用2.8.1版本
1 | flutter --version |
在远程容器中下载好源码之后,开始交叉编译:
1 | apt install python3 git pkg-config vim curl unzip ninja-build |
源码如果已经下载,可以不用安装depot_tools,使用apt安装ninja即可
arm 64位引擎编译问题不大,比较顺利。
交叉编译Linux arm64应用
按照上文修改flutter_tools
,将引擎编译生成的关键产物拷贝到Flutter SDK路径下,替代官方Artifact。
1 | 拷贝gen_snapshot,目录不存在则创建 |
注:最关键的步骤是指定--target-sysroot
,否则会遇到很多错误。
这里使用Flutter引擎构建时用的sysroot,也可以自行制作sysroot
踩坑记录
一开始flutter build
没有指定--target-sysroot
一直报错。根据网上的办法解了一个又出现另一个,没完没了。都是弯路,其实问题原因就是缺少目标平台库sysroot,不应该聚焦单个问题,而是当作整体来看。
此时Dart代码可以正常编译,
app.so
位于.dart_tool
中。主要是编译Linux壳工程(main.cc等)失败,依赖GTK、X11等目标平台库和头文件。
如果是定制嵌入层,需要自己创建
main.cc
入口文件,进行交叉编译。
这里记录下单个问题的踩坑过程,用来参考:无法真正解决,正确的做法参考上面的步骤(Don’t Do It!)
问题1
1 | /usr/bin/ld: unrecognised emulation mode: aarch64linux |
原因:缺少GNU交叉编译工具链
解决:
apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
问题2
1 | Target aot_elf_release failed: ProcessException: Failed to find "/root/flutter/flutter/bin/cache/artifacts/engine/linux-arm64-release/linux-x64/gen_snapshot" in the search path |
原因:官方下载的引擎Artifact解压文件夹不对
解决:手动将gen_snapshot放到
linux-x64
目录下
问题3
1 | /root/flutter/flutter/bin/cache/artifacts/engine/linux-arm64-release/linux-x64/gen_snapshot: 1: Syntax error: "(" unexpected |
原因:官方下载的引擎Artifact的gen_snapshot可执行程序目标格式不正确,可以在
解决:将自己编译的引擎Artifact拷贝到对应目录下
问题4(X)
1 | //usr/include/limits.h:26:10: fatal error: 'bits/libc-header-start.h' file not found |
原因:缺少头文件
解决:
apt install gcc-multilib g++-multilib
备注:无用的步骤,下面会拷贝整个头文件目录。
问题5
1 | //lib/gcc-cross/aarch64-linux-gnu/9/../../../../include/c++/9/cstdlib:41:10: fatal error: 'bits/c++config.h' file not found |
原因:系统安装的gcc工具链版本是9,Flutter引擎中的
debian_sid_arm64-sysroot
中gcc版本是7,导致在--target-sysroot
路径中找不到头文件。解决:拷贝整个文件夹到头文件搜索路径。
1 cp -r /usr/aarch64-linux-gnu/include/c++/9/aarch64-linux-gnu/bits/ /lib/gcc-cross/aarch64-linux-gnu/9/../../../../include/c++/9/
问题6
1 | [ ] FAILED: intermediates_do_not_run/myapp |
原因:缺少GTK、Cairo等目标平台的链接库
解决思路:apt没有提供arm64的GTK平台库,需要自行下载,或者在arm64机器上使用apt下载,再拷贝出来。(sysroot其实就是这样制作的)
备注:踩坑到这里解不动了。最后跳出单个问题看,其实就是缺了--target-sysroot
。学习了下sysroot制作,验证成功,再后来发现其实用Flutter官方的sysroot也可以编译成功。
其他问题
下面的问题也是探索过程中遇到的,但是后面复现不到了,还是保留下踩坑过程。(看看就好,没有参考价值)
1 | /usr/bin/aarch64-linux-gnu-ld: cannot find -lstdc++ |
原因:找不到链接库
解决:
apt install libstdc++-9-dev-arm64-cross
1 | ERROR: qemu-aarch64: Could not open '/lib/ld-linux-aarch64.so.1': No such file or directory |
原因:找不到链接库
解决;
cp /usr/aarch64-linux-gnu/lib/ld-linux-aarch64.so.1 /lib/ld-linux-aarch64.so.1
1 | ERROR: /root/flutter/bin/cache/artifacts/engine/linux-arm64-release/gen_snapshot: error while loading shared libraries: libdl.so.2: cannot open shared object file: No such file or directory |
原因:找不到链接库
解决:export LD_LIBRARY_PATH=/usr/aarch64-linux-gnu/lib`:添加程序加载运行时查找链接库的路径
LIBRARY_PATH
:添加gcc编译时查找链接库的路径。
1 | Target unpack_linux failed: FileSystemException: Cannot open file, path = '/root/flutter/bin/cache/artifacts/engine/linux-arm64/icudtl.dat' (OS Error: No such file or directory, |
解决:从Flutter官方仓库下载引擎Artifact,或者自行编译引擎,拷贝到对应目录
交叉编译Linux arm应用
交叉编译Flutter arm引擎
修改
src/flutter/BUILD.gn
文件,将_build_engine_artifacts
改为false,跳过Dart SDK编译修改
build/toolchain/custom/BUILD.gn
,将${custom_target_triple}
改为llvm
1
2
3
4
5build/toolchain/custom/BUILD.gn
ar = "${toolchain_bin}/${custom_target_triple}-ar"
readelf = "${toolchain_bin}/${custom_target_triple}-readelf"
nm = "${toolchain_bin}/${custom_target_triple}-nm"
strip = "${toolchain_bin}/${custom_target_triple}-strip"执行命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15安装基础环境
apt install python3 git pkg-config vim curl unzip ninja-build clang
手动下载arm平台的sysroot
build/linux/sysroot_scripts/install-sysroot.py --arch=arm
gn生成构建配置
./flutter/tools/gn --target-os linux --linux-cpu arm --runtime-mode release \
--target-toolchain=$PWD/buildtools/linux-x64/clang \
--target-sysroot=$PWD/build/linux/debian_sid_arm-sysroot/ \
--target-triple=armv7-unknown-linux-gnueabihf \
--arm-float-abi=hard
ninja编译
ninja -C out/linux_release_arm
交叉编译Linux arm应用
由于Flutter官方还不支持Linux arm应用的编译,因此需要修改flutter_tools
源码:
- 让
flutter build
命令支持--target-platform=linux-arm
参数 - 编译时查找对应路径下的引擎产物,例如:
{flutter_sdk}/bin/cache/artifacts/engine/linux-arm-release/
)
可以参考Flutter应用构建流程分析,能读懂源码自然就会修改了。
这里直接调用前后端编译,不使用flutter build
命令(省略文件路径)
1 | 前端编译生成app.dill |
linux平台代码使用cmake和ninja编译,并链接libapp.so
、libflutter_engine.so
,需要修改CMakeList.txt
,这里暂时不做介绍。
踩坑记录
找不到交叉编译工具链
./flutter/tools/gn --target-os linux --linux-cpu arm --runtime-mode release --verbose
1 | Generating GN files in: out/linux_release_arm |
原因:找不到交叉编译工具链
分析:参考Flutter issue,需要添加
--target-toolchain
参数解决:添加
--target-toolchain
参数,同时需要添加--target-sysroot
和--target-triple
。否则断言会出错
1
2
3
4
5
6
7 #build/config/BUILDCONFIG.gn
if (custom_toolchain != "") {
assert(custom_sysroot != "") # 496
assert(custom_target_triple != "") # 497
host_toolchain = "//build/toolchain/linux:clang_$host_cpu"
set_default_toolchain("//build/toolchain/custom")
}
问题点在于这三个参数应该填什么?试验了很多次,ninja编译会出现各种各样的错。最终是参考meta-flutter
项目的Yocto编译配方才解决的。
错误试验:
--target-toolchain
使用/
或者/usr/lib/llvm-10/
- ``–target-triple
使用
arm-linux-gnueabihf或者
llvm`。
不带--arm-float-abi=hard
选项
1 | ninja: Entering directory `out/linux_release_arm' |
gn命令添加
--arm-float-abi=hard
。或者修改build/config/compiler/BUILD.gn
文件,如下
1
2
3 # build/config/compiler/BUILD.gn
# ...
"-mfloat-abi=$arm_float_abi", # 默认值为softfp,可以改为hard
--target-triple
使用arm-linux-gnueabihf
1 | ninja: Entering directory `out/linux_release_arm/' |
原因:找不到
arm-linux-gnueabihf-ar
工具。解决:修改
build/toolchain/custom/BUILD.gn
文件,将${custom_target_triple}
替换为llvm
1 | FAILED: exe.unstripped/gen_snapshot gen_snapshot |
解决:修改
--target-triple
为armv7-unknown-linux-gnueabihf
Dart SDK编译报错
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
等分析:这个时候
gen_snapshot
和libflutter_engine.so
已经编译完了,不需要编译Dart SDK,所以也可以不处理该问题。解决:只要跳过Dart SDK编译即可,修改
src/flutter/BUILD.gn
文件,将_build_engine_artifacts
改为false
其他问题
1 | [ +1 ms] /usr/bin/aarch64-linux-gnu-ld: CMakeFiles/myapp.dir/my_application.cc.o: in function `my_application_activate(_GApplication*)': |
ninja编译arm报错:缺少glibc库,需要编译libcxx和libcxxabi
1 | Using 'dlopen' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking |
结语
相关文章链接:
资源下载链接:
- Flutter官方预构建引擎:缺少arm平台
- 预构建ARM引擎:开发者预构建的arm引擎,需要注意版本对应,否则可能无法使用
- Flutter官方预构建引擎sysroot
- Flutter官方预构建Dart SDK
参考资料:
- Build Flutter engine for linux-arm/arm64、Flutter on Raspberry Pi (mostly) from scratch:自行制作toolchain,编译引擎
- Flutter Issue:编译arm引擎
Linux发行版说明
搭建环境过程中见到很多库的名词不认识,因此查了一下
- RHEL(Red Hat Enterprise Linux):Red Hat企业版,更稳定,需要付费,能获得相应的服务和技术支持
- CentOS(Community ENTerprise Operating System):源于RHEL,不需要付费
- Fedora:相当于RHEL的实验版本,迭代速度快,实验稳定之后可能会应用到RHEL中。
- Red Hat早期分为普通版(Red Hat Linux)和企业版(RHEL),2003年普通版不再发布,改为Fedora项目,
- Ubuntu源于Debian:Debian 和 Ubuntu:有什么不同?应该选择哪一个?
- Ubuntu和Debian版本对应关系如下:5是debian、6是squeeze、7是wheezy、8是jessie、9是stretch、10是buster。sid代表开发版本
1 | Ubuntu | Debian |