本文主要内容为记录flutter命令执行流程与定制flutter tools过程。
当执行flutter xxx时,是如何执行的,安装flutter环境后,首先使用which flutter查看命令所在路径
which flutter
输出如下:
/Volumes/huc/opt/fvm/current/bin/flutter
本文就通过称这里开始研究一下flutter 命令执行过程,flutter命令执行时,会做一些什么处理,比如dart sdk下载,flutter版本升级时,flutter tools是怎么重新构建的。
flutter
此时的 flutter 文件是一个shell脚本。打开文件查看:
这里关键两步:
source "$BIN_DIR/internal/shared.sh"
shared::execute "$@"
shared.sh是一个具体实现的shell脚本文件,里面定义了 shared::execute方法,以及其他内容。
shared.sh
这个文件中定义了下面这些方法:
- pub_upgrade_with_retry
- _rmlock
- _lock
- _wait_for_lock
- upgrade_flutter
- shared::execute
锁文件
锁文件。当执行一个flutter命令时,会创建一个lock文件,当flutter命令运行结束会删除这个锁文件。
如果当前已经有一个lock文件,说明flutter已经在运行中,所以通过这种方式,就轻松避免了在电脑上同时运行多个flutter脚本而导致flutter安装dart sdk和编译flutter tools 环境出错的问题,保证了flutter执行的单个进程的工作环境。
从代码中可以看到这个锁文件路径为: $FLUTTER_ROOT/bin/cache/.upgrade_lock
如果我们平时使用attach或者debug 没有等上一个命令执行完,比如网络卡顿,或者某些原因导致flutter命令异常结束而没有删除锁文件,那么之后就无法执行flutter命令了,就需要我们手动删除锁文件,通过这里也就知道锁文件的路径在哪里了。
flutter tools与dart sdk 更新条件判断
if [[ ! -f "$SNAPSHOT_PATH" || ! -s "$STAMP_PATH" || "$(cat "$STAMP_PATH")" != "$compilekey" || "$FLUTTER_TOOLS_DIR/pubspec.yaml" -nt "$FLUTTER_TOOLS_DIR/pubspec.lock" ]]; then
# Waits for the update lock to be acquired. Placing this check inside the
# conditional allows the majority of flutter/dart installations to bypass
# the lock entirely, but as a result this required a second verification that
# the SDK is up to date.
_wait_for_lock
# A different shell process might have updated the tool/SDK.
if [[ -f "$SNAPSHOT_PATH" && -s "$STAMP_PATH" && "$(cat "$STAMP_PATH")" == "$compilekey" && "$FLUTTER_TOOLS_DIR/pubspec.yaml" -ot "$FLUTTER_TOOLS_DIR/pubspec.lock" ]]; then
exit $?
fi
几个文件定义如下:
FLUTTER_TOOLS_DIR="$FLUTTER_ROOT/packages/flutter_tools"
SNAPSHOT_PATH="$FLUTTER_ROOT/bin/cache/flutter_tools.snapshot"
STAMP_PATH="$FLUTTER_ROOT/bin/cache/flutter_tools.stamp"
SCRIPT_PATH="$FLUTTER_TOOLS_DIR/bin/flutter_tools.dart"
DART_SDK_PATH="$FLUTTER_ROOT/bin/cache/dart-sdk"
检查文件
- -nt 判断文件pubspec.yaml是否比pubspec.lock 文件修改时间新
- -ot 判断文件 pubspec.yaml是否比pubspec.lock文件修改时间旧
- /bin/cache/flutter_tools.snapshot文件不存在
- /bin/cache/flutter_tools.stamp文件不存在
- /bin/cache/flutter_tools.stamp里面的 记录的flutter项目的git commit与当前flutter版本git commit提交的版本不一致
- /packages/flutter_tools上一次pub upgrade之后生成了pubspec.lock 后, pubspec.yaml的文件又发生变更了
这些条件都会触发重新编译 flutter tools
这里用到了一个双重二次比较,防止文件锁多个flutter命令同时再执行。
update_dart_sdk.sh
更新dart sdk 即bin/cache下面的所有内容。
我们平时通过flutter官方文档flutter.cn..安装完flutter 项目之后, flutter的安装目录下的 bin/cache下的 dart sdk是没有的。
通过第一执行flutter xxx命令时候,则会自动下载 dart sdk。那么自动下载就是通过在这个脚本完成的。
同样,
ENGINE_STAMP="$FLUTTER_ROOT/bin/cache/engine-dart-sdk.stamp"
ENGINE_VERSION=`cat "$FLUTTER_ROOT/bin/internal/engine.version"`
$FLUTTER_ROOT/bin/cache/engine-dart-sdk.stamp 这个目录是flutter仓库管理的版本
$FLUTTER_ROOT/bin/internal/engine.version这个目录是通过curl下载的dart-sdk压缩包解压出来的版本,是随着flutter版本的切换而自动下载对应的dart sdk
dart sdk即/Volumes/xx/opt/fvm/versions/3.7.12/bin/cache/dart-sdk除了提供dart引擎的源码,也提供了frontend_server.dart.snapshot这个前端编译工具
在flutter build时候
case Artifact.frontendServerSnapshotForEngineDartSdk:
return 'frontend_server.dart.snapshot';
在compiler.dart中执行将会使用frontend_server.dart.snapshot:
final String frontendServer = _artifacts.getArtifactPath(
Artifact.frontendServerSnapshotForEngineDartSdk
);
即flutter run时对应会执行:
dart frontend_server.dart.snapshot --output-dill app.dill
upgrade_flutter
这一步检查 flutter_tools.snapshot 和flutter_tools.stamp文件是否存 用来构建 flutter_tools dart 命令行程序的。
下面分别展看一下看看:
执行 flutter_tools 项目的 pub upgrade
/Volumes/huc/opt/fvm/versions/3.0.5/bin/cache/dart-sdk/bin/dart __deprecated_pub upgrade --verbosity=error --no-precompile
这个命令就是我们平时运行flutter项目,需要执行pub upgrade操作一摸一样。 flutter_tools本身也就是一个 dart 项目。
查看 flutter_tools 项目目录如下:
这就一目了然了,flutter的所有功能 本身就是用 dart 编写。
编译 flutter_tools.dart ,生成快照 flutter_tools.snapshot
/Volumes/huc/opt/fvm/versions/3.0.5/bin/cache/dart-sdk/bin/dart
--verbosity=error --disable-dart-dev
--snapshot=/Volumes/huc/opt/fvm/versions/3.0.5/bin/cache/flutter_tools.snapshot
--packages=/Volumes/huc/opt/fvm/versions/3.0.5/packages/flutter_tools/.packages
--no-enable-mirrors
/Volumes/huc/opt/fvm/versions/3.0.5/packages/flutter_tools/bin/flutter_tools.dart
那么这一步是编译flutter_tools为dart可执行程序,目标产物为flutter_tools.snapshot可执行文件格式。
并且使用 echo "$compilekey" > "$STAMP_PATH" 命令将文件的索引值保存
f1875d570e39de09040c8f79aa13cc56baab8db1: > /Volumes/huc/opt/fvm/versions/3.0.5/bin/cache/flutter_tools.stamp
compilekey的生成规则为
local revision="$(cd "$FLUTTER_ROOT"; git rev-parse HEAD)"
local compilekey="$revision:$FLUTTER_TOOL_ARGS"
则主要就是获取flutter的git commit提交ID 作为 flutter snapshot的快照文件ID。 这样做目的就是确保了 flutter 版本更新,触发flutter tools 重新构建。
flutter命令最后通过dart来执行
/Volumes/huc/opt/fvm/versions/3.0.5/bin/cache/dart-sdk/bin/dart
--disable-dart-dev
--packages=/Volumes/huc/opt/fvm/versions/3.0.5/packages/flutter_tools/.packages
/Volumes/huc/opt/fvm/versions/3.0.5/bin/cache/flutter_tools.snapshot doctor
flutter doctor 这一条命令最后落地,省略默认参数, 简化命令如下:
dart flutter_tools.snapshot doctor
upgrade_flutter
update_dart_sdk.sh
$FLUTTER_ROOT/bin/internal/update_dart_sdk.sh
这个主要就是通过curl命令下载flutter版本对应的dart sdk,下载目标路径为$FLUTTER_ROOT/bin/cache
通过查看代码,可以看到核心下载命令为:
curl --retry 3 --continue-at -
--location
--output /Volumes/huc/opt/fvm/versions/3.0.5/bin/cache/dart-sdk-darwin-arm64.zip
https://storage.googleapis.com/flutter_infra_release/flutter/e85ea0e79c6d894c120cda4ee8ee10fe6745e187/dart-sdk-darwin-arm64.zip
通过以上flutter 运行步骤,我们总结一下主要内容就是:
执行任何 一条flutter 命令,都会执行以上步骤,或许是flutter --version,flutter doctor ,flutter pub get ,flutter attach等等,都需要经过下面转换:
1,如果需要更新flutter, 检查flutter 更新命令运行锁文件,只能运行一个更新进程,避免多个更新同时执行导致安装目录出现错误 $FLUTTER_ROOT/bin/cache/.upgrade_lock
2,下载 dart sdk。bin/cache目录下为dart sdk,下载flutter 匹配的dart engine和sdk等。
3,检查 flutter_tools.stamp即git commit提交版本号,判断是否需要重新编译flutter tools源码。
4,最后执行 dart flutter_tools.snapshot doctor/attach/pub等等子命令。。
如果我们需要定制flutter tools,那么我们在修改flutter tools源代码之后,手动删除 /Volumes/huc/opt/fvm/versions/3.0.5/bin/cache/flutter_tools.stamp文件,以达到重新编译flutter tools,从而在执行flutter命令即dart flutter_tools.snapshot xxx子命令 时候,用的是定制后的flutter tools。
闲鱼的Flutter AOP框架 aspectd 就是通过在编译flutter 产物时修改flutter本来的产物,通过注解方式插入hook代码,在编译期完成aop代码的插入。
到此flutter 命令执行过程简单梳理完毕。