鸿蒙Flutter框架 0day适配实战:09 构建发布与产物分发:三套版本锁、11 个工件与覆盖行机制
前言
第 8 篇收尾时,ninja28 全绿 0 FAILED,四个 target 齐备——引擎「能编出来」这件事成立了。但编出来的东西躺在一台 Mac 的 out/ 目录里,对任何人都没有价值。本篇解决后半程:怎么把它变成别人机器上一条命令就能装到、装完就能 flutter build hap 的东西。
这条链路比想象中长,而且它的难点不在技术复杂度,在于四段之间任何一段"看起来成功"都可能是静默失败:构建产物取错目录、打包布局"顺手统一"了一下、发布时重传了同名附件、分发时改了覆盖行但没换新 tag——四种情况本机全都能跑通,只有别人装不上。
本篇主体是 3.44.9 → 3.47.4 批次 1.0.0 → 1.0.1 两次发布的实录,命令与产物均实测;其中 1.0.0 暴露的 flutter_patched_sdk.zip 装错产品档 bug,在 1.0.1 修正并复验,是第五章的主线案例。第五章同时把发布线拉到当前的 3.47.8-ohos-1.0.0:11 个自传附件、11 条覆盖键,并把常被漏掉的那一类产物讲清楚——Dart SDK 自己也是要为鸿蒙重编、为 mac 和 Windows 各发一份的工件。
前置阅读:第 8 篇(引擎构建链,产物怎么来)、第 7 篇(Engine 合并与契约对齐)、第 2 篇(消费侧安装视角,本篇第六章从生产侧解释它为什么会自动更新)。
一、链路全景:四段一条线
1.1 硬约束:用户零手工步骤
先把目标钉死,因为它决定了后面所有设计取舍:
用户侧:fvm install <tag> → flutter build hap
中间不允许出现任何"手工下载、手工解压、手工改路径"的步骤
这是这个 fork 的硬约束。所有分发设计都服从它——宁可维护一套复杂的覆盖行机制,也不给用户提供"装完再手动放个文件"的选项。这条约束反过来解释了本篇几乎所有看似绕弯的设计。
1.2 四段链路
构建(ninja → har) → 打包(5 个 zip) → 发布(atomgit Release + tag) → 分发(覆盖行 → 用户零操作)
§三 §四 §五 §六
这条是引擎侧主链。还有一条平行支路容易被漏:Dart SDK 本身也是自编自发的产物——第 6 篇从源码把它编出来,这里要多打包、多发布 4 个 zip(macOS / Windows 各两档),走的却是同一个 Release、同一个覆盖文件(§5.8、§6.1)。所以本篇真正的口径是"一次发布 11 个工件、11 条覆盖键",而不是只有那 5 个引擎 zip。

上图三个板块对应本篇的三个核心论点:① 四段链路各自职责,用户侧收口在 fvm install + flutter build hap 两条命令;② 中间的红/绿对照是本篇最重要的一条铁律——同名 tag 重传附件,已装机器永远拿不到新工件,而且不报错;③ 底部四类工件的过期判据完全不同,这是所有"改了没生效"误判的来源。
1.3 为什么这条链路长这样
鸿蒙引擎的官方默认下载地址在华为云 OBS 上,指向 engine.ohos = 10a8d012——那是 3.44.9 时代的产物,内带 dart 3.12.2 / kernel 130。而本分支的 dart 是 3.13.3 / kernel 138。
直接构建会得到这样一条错误:
Unexpected Kernel Format Version 130 (expected 138)
官方地址不能改(改了就不是兼容上游的 fork 了),所以走覆盖行:在仓库里放一个映射文件,把特定工件的下载地址改指到自己发布的 atomgit Release 附件上。这就是 §六的全部内容。
二、版本锁:三套版本源别搞混
2.1 三个锁文件的分工
bin/internal/ 下有三个长得很像的文件,管三件不同的事:
| 版本文件 | 当前值 | 管什么 | 谁会动它 |
|---|---|---|---|
bin/internal/engine.version | 06a2e2a1 | 上游 FlutterSdk:common/flutter_patched_sdk[_product].zip、宿主工具(const_finder / impellerc / flutter_tester,实测已是 138 版,不需要重建) | 同步上游时 |
bin/internal/engine.ohos.version | 10a8d012 | FlutterSdkOhos(darwin-$arch/artifacts.zip、sky_engine.zip)+ OHOSGenSnapshotArtifacts(ohos-arm64{,-profile,-release}/darwin-x64.zip) | 同步上游时 |
bin/internal/engine.ohos.har.version | e068c218 | OHOSInternalBuildArtifacts:三个 har | 同步上游时 |
后两个的完整值是 40 位提交号(10a8d012d86cd7ee13e0acc859fea796c8a01764、e068c218c3d7f693cc86df27fe8c1bb6d066823b),它们决定的是"要下 OBS 上哪个目录"这个上游口径。
2.2 关键结论:重发工件不动这三个文件
这是本节唯一需要背下来的东西:
# 发布新工件时,正确的做法是只改一个文件:
git diff --stat HEAD~1 -- bin/internal/
# bin/internal/dart-sdk-url.ohos ← 只有这一行变了
# 三个 *.version 文件保持原样
# 真正的"推进信号"是覆盖行里的 URL 变了,不是版本号变了
grep -c '3\.47\.4-ohos-1\.0\.1' bin/internal/dart-sdk-url.ohos
很多同学的直觉是"我发了 1.0.1,总得把
engine.ohos.version改成 1.0.1 吧"。**不要。**这三个锁文件只在同步上游时才动,改它们等于把默认回退地址也一起改到一个不存在的 OBS 目录上,会直接搞坏没有覆盖行的场景。
2.3 三类版本信息的正确读法
一个常见的误判(消费侧 CP12 的生产侧版本):拿 engine.version 去对比鸿蒙引擎。三者关系是:
engine.version是上游 FlutterSdk 的口径,跟鸿蒙引擎无关;engine.ohos.version是鸿蒙引擎默认(OBS)地址的口径,有覆盖行时它只是回退目标;- 覆盖行 URL 里的 tag(
3.47.4-ohos-1.0.1)才是实际生效的产物版本。
判断"我装的到底是哪个引擎",唯一可靠的方法是查产物本身——libflutter.so 里的版本字符串,第 2 篇的三层核验讲过这个。
三、构建:规范化入口是 wrapper,不是手搓 gn + ninja
3.1 engine/ohos 只有一行有效代码
# flutter_flutter/engine/ohos
python3 ./src/flutter/attachment/scripts/ohos.py "$@"
为什么要这么个壳?因为 ohos.py 的 checkEnvironment() 强制要求 cwd = 引擎构建根,cwd 错了直接被拒。所以正确调用姿势是:
cd $ROOT/flutter_flutter/engine # 这一步不能省,否则直接被 checkEnvironment 拒掉
export PATH="$ROOT/depot_tools:/opt/homebrew/opt/llvm@19/bin:$PATH"
unset LUCI_CONTEXT # 见 §3.6 注意事项
./ohos -n config -t debug profile release
./ohos -n compile -t debug profile release
./ohos -n har -t debug profile release
3.2 参数表(已核 ohos.py 的 argparse)
| 参数 | 取值 | 说明 |
|---|---|---|
-n / --name | clean | config | har | compile | zip | zip2 | upload | host | 可多值;不传 -n 也不传 -b 时默认 ["config","compile","host"] |
-t / --type | debug | profile | release | 可多值,外层循环遍历类型,内层遍历名称 |
-b / --build | — | 与 -n 互斥的另一套入口 |
几个 name 的实际含义:
config→src/flutter/tools/gn --runtime-mode <type> ...,生成out/ohos_<type>_arm64/(首次会先跑附件同步,见 §3.4);compile→ninja -C src/out/ohos_<type>_arm64;har→ 产出arm64_v8a_<mode>.har/flutter.har/flutter_embedding_<mode>.har;host→ 同时构建宿主工具(out/host_<type>[_unopt][_arm64]);zip/zip2→ 打 har 包;upload→ 走auto_compile.py,OBS 时代的老路,本项目不用。
3.3 --llvm18 是禁令,不是建议
./ohos -n config -t release --llvm18 # ✗ 绝对不要
它把 toolchain 指到 DevEco 自带的 clang 15,编 zlib 时就挂。args.gn 里必须落到 Homebrew 的 llvm@19:
# out/ohos_release_arm64/args.gn 中必须出现的一行
custom_toolchain = "/opt/homebrew/opt/llvm@19"
为什么偏偏是 19:第 8 篇 §三讲过,libc++ 有 Clang 18 的硬门槛(下限),而 LLVM 23 太新会引入告警毒化(上限)——19 是两个约束夹出来的可行区间。
3.4 args.gn 的脆弱性:换机必看
args.gn 位于 out/ 下,而 out/ 是未跟踪目录——换机、清 out/ 之后它就没了。更坑的是增量 gn gen 不会补新 args(out/ 已存在时 config 基本是空转)。所以:
- 拿到新机或清空
out/后:先跑config,再手工把custom_toolchain等必要项写进对应args.gn,然后才compile; - 生成的
args.gn存一份到sync-data/留档,别指望它能从仓库里恢复。
⚠️ 诚实口径:
out/清空后首次args.gn的完整生成链在本机没有实证(本批次机器上out/一直存在,只验过增量路径)。第一次在干净机上做,要把生成结果与原值逐项比对,不要假设 wrapper 会替你写全。
3.5 附件机制:端口源码怎么进树
src/flutter/attachment/scripts/config.json 定义 4 个 type:"files" 任务,把 attachment/ 下的内容复制到未跟踪的第三方树里:
| name | target | 内容 |
|---|---|---|
bootstrap | ./ | 引擎构建辅助 |
skia_ohos | ./src/flutter/third_party/skia/src/ports | 华为 skia 的 ohos 端口实现 |
skia_ohos_api | ./src/flutter/third_party/skia/include/ports | 对应头文件 |
jsoncpp | ./src/third_party | jsoncpp 代理 target |
由 ohos_setup.py 执行(config 阶段自动跑,updateCode 里也会跑)。这套机制的存在理由,正是第 8 篇 §五那条被反转的结论:"DEPS 拉来的 checkout 里没有、构建却需要"的文件,靠 attachment 托管,不靠就地改树(端口源码镜像见 fluttertpc_skia)。
⚠️
copy_dir在目标目录已存在时是 no-op。这意味着升级模式不会覆盖旧的端口源码——端口源码改了、或者新批次要换端口,必须先删掉目标目录再让 attachment 重放。
# 换端口源码后的正确重放姿势
rm -rf src/flutter/third_party/skia/src/ports/ohos \
src/flutter/third_party/skia/include/ports/ohos
./ohos -n config -t debug profile release # attachment 重新 copy_dir
这条是本链路最容易复现的运维失败面:改了 attachment/ 里的文件,重新构建,树里还是旧的,而且没有任何提示。
3.6 gen_snapshot 双份,取错即 AOT 直接不可用
每个 out/ohos_<mode>_arm64/ 下有两个同名 gen_snapshot:
| 路径 | 格式 | 跑在哪 | 用途 |
|---|---|---|---|
out/ohos_<mode>_arm64/clang_arm64/gen_snapshot | Mach-O | Mac 宿主 | AOT 编译时用——这个才是要发布的 |
out/ohos_<mode>_arm64/gen_snapshot | ELF / arm64 | OHOS 设备上 | 设备侧运行时 |
file -b out/ohos_release_arm64/clang_arm64/gen_snapshot # Mach-O 64-bit executable arm64 ✓
file -b out/ohos_release_arm64/gen_snapshot # ELF 64-bit LSB pie executable ✓(但不是要发的)
打包脚本已经内置 file -b 断言(不是 Mach-O 就报错退出),取错会被拦住。手工操作时不要绕过这个断言——把 ELF 当 Mach-O 发出去,症状是用户 flutter build hap --release 在 AOT 阶段炸,且报错信息完全不指向 gen_snapshot。
另外记一条不在坑编号里的:src/flutter/.dart_tool/package_config.json 缺失会让 profile 的 vmservice kernel_blob.bin 失败,补一个 {"configVersion":2,"packages":[]} 即可。LUCI_CONTEXT 是否必要未独立验证(本机是在未设它的状态下走通的)。
四、打包:5 个 zip 的布局不能"顺手统一"
4.1 脚本与产出
bash $ROOT/scripts/package_ohos_engine_artifacts.sh [目标目录]
# 默认输出到 $ROOT/flutter_flutter/engine/src/ohos-engine-artifacts
脚本可重跑、带断言。产出 5 个 zip,布局逐个与 1.0.0 附件核对过:
| zip | 内容 |
|---|---|
ohos-arm64-artifacts.zip | LICENSE.flutter_har_zip.md + flutter_embedding_debug.har + arm64_v8a_debug.har + flutter.har |
ohos-arm64-profile-artifacts.zip | 同上但 _profile |
ohos-arm64-release-artifacts.zip | 同上但 _release |
ohos-arm64-profile-darwin-x64.zip | LICENSE.gen_snapshot.md + gen_snapshot(宿主 Mach-O) |
ohos-arm64-release-darwin-x64.zip | 同上 |
4.2 为什么 debug 的名字少了后缀
注意第一行:debug 的 zip 名是 ohos-arm64-artifacts.zip,不带模式后缀,另两个带 -profile / -release。这是 OBS 原始布局的不对称,不要"顺手统一"——覆盖行的键是按 urlPath / basename 精确匹配的(§6.2),统一命名会直接让 debug 档失去覆盖通道。
for z in *.zip; do echo "=== $z"; unzip -l "$z" | sed -n '4,12p'; done
# 逐个核对包内文件名与布局表,尤其是 .har 的模式后缀
4.3 两个 LICENSE 文件从哪来
bin/cache/artifacts/engine/ohos-arm64/LICENSE.flutter_har_zip.md 与 ohos-arm64-profile/darwin-x64/LICENSE.gen_snapshot.md——从 OBS 时代的原始 bundle 里带下来的元数据,重打包时原样沿用。
仓库里没有生成它们的脚本,不要去找。找不到生成器不是 bug,是历史。
4.4 断言点
脚本内置两道断言,任一不满足直接 exit 1,不会产出半成品:
- 三个 har 存在;
- 宿主
gen_snapshot存在且是 Mach-O。
五、发布:tag、附件与两类产物
5.1 上传这一环由人通过网页完成
atomgit 的上传环节由人手工在网页上做。本机只有 SSH key,没有 atomgit HTTP 凭据——所以这一环无法、也不应该被自动化。脚本化/API 上传属于未验证项(见 §十)。
5.2 tag 命名惯例
从既有 tag 反推,非官方规范:<上游版本>-ohos-<自版本>,例如 3.47.4-ohos-1.0.0、3.47.4-ohos-1.0.1;候选期另有一种 oh-3.47.4-rc1。新批次沿用 X.Y.Z-ohos-N.M.K 即可,但这是惯例不是铁律。
Release 附件的 URL 形态:
https://atomgit.com/oh-flutter/flutter_flutter/releases/download/<tag>/<附件名>
附件名必须与文件名逐字一致——覆盖行是按名字匹配的。
5.3 铁律:发新版必须换新 tag,不能重传同名附件
这是本篇最重要的一节。覆盖机制靠 <cacheDir>/.ohos-override-url 标记文件比对 URL 判断缓存是否过期:
cache.dart的isUpToDateInner拿标记里记的 URL 与当前覆盖行比对,一致就判"最新"、不重下;下载后updateInner才写入标记;- 因此覆盖同一 URL ⇒ 标记仍匹配 ⇒ 改动传不到任何已装好的 SDK。
✗ 同名 tag 重传附件:URL 不变 → 已装机器标记比对通过 → 永远拿不到新工件(静默)
✓ 换新 tag 发布: URL 变化 → 标记比对失败 → 自动重新下载 → 用户侧无需任何动作
发布流程必须是四步:新建 tag → 传附件 → 改覆盖行 → 提交推送。只做前三步中的前两步等于没发。
本批次实证:1.0.0 的 flutter_patched_sdk.zip 装错包(§6.6),1.0.1 换新 tag 修掉——如果当时选择"在 1.0.0 上重传同名附件",这个 bug 到今天还在所有已装机器上活着。
5.4 最小可验证动作
第一次用某个 tag 时,先做这个再批量传:
# 新建 tag → 先只传 1 个附件 → 直连验证 → 再传其余(1.0.1 那批是 6 个,当前批次是 10 个)
curl -sI 'https://atomgit.com/oh-flutter/flutter_flutter/releases/download/3.47.4-ohos-1.0.1/ohos-arm64-artifacts.zip' \
| sed -n '1p;/[Cc]ontent-[Ll]ength/p'
# 期望:HTTP/2 200,且 Content-Length 与本地文件字节数完全一致
stat -f%z ohos-arm64-artifacts.zip
先传一个验证的价值在于:附件名拼错、上传截断、URL 大小写不一致这类问题,在传第 1 个之后发现的成本是传 7 个的七分之一。
5.5 1.0.1 实例:7 个附件
| 附件 | 大小 | 说明 |
|---|---|---|
ohos-arm64-artifacts.zip | 31.9 MB | debug 引擎 har(含字体端口) |
ohos-arm64-profile-artifacts.zip | 18.0 MB | profile 引擎 har |
ohos-arm64-release-artifacts.zip | 15.5 MB | release 引擎 har |
ohos-arm64-profile-darwin-x64.zip | 2.6 MB | profile 宿主 gen_snapshot |
ohos-arm64-release-darwin-x64.zip | 2.2 MB | release 宿主 gen_snapshot |
flutter_patched_sdk.zip | 4.1 MB | 1.0.1 的关键修复(§6.6) |
flutter_patched_sdk_product.zip | 4.1 MB | 内容同 1.0.0,为让新 tag 自包含而重传 |
这一批是纯引擎侧的 5+2:3 个 har + 2 个宿主 gen_snapshot(5 个,出自 §4.1 的打包脚本)+ 2 个 patched_sdk。当时的 dart-sdk-ohos.zip 还挂在 3.47.4-ohos-1.0.0 上没随新 tag 重传,所以覆盖行里那一条必须继续指旧 tag(提交 947610d6e0c1 修的就是这个)。
大小口径说明:本文的"MB"是十进制(字节数 ÷ 1e6),与托管平台网页显示一致;AtomGit API 的
assets[].size返回字节,换算成 MiB 会小一圈(31,941,046 B= 31.9 MB = 30.5 MiB)。核对时以字节数为准,别拿 MB 去对 Content-Length。
5.6 当前批次 3.47.8-ohos-1.0.0:11 个自传附件(网页显示 15)
发布线走到 3.47.8,附件从 7 个长到 11 个——多出来的正是 4 个 Dart SDK 包(§5.8):
| 组 | 附件 | 字节数 | MiB | 是什么 |
|---|---|---|---|---|
| Dart SDK | dart-sdk-darwin-arm64-ohos.zip | 225,700,875 | 215.2 | macOS(arm64) 宿主的定制 Dart SDK |
| Dart SDK | dart-sdk-windows-x64-ohos.zip | 216,595,889 | 206.6 | Windows(x64) 宿主的定制 Dart SDK |
| Dart SDK | dart-sdk-ohos.zip | 8,908,910 | 8.5 | HAP 构建运行时(macOS / Linux 用) |
| Dart SDK | dart-sdk-ohos-windows-x64.zip | 8,561,722 | 8.2 | HAP 构建运行时(Windows 用,PE 二进制) |
| 引擎 har | ohos-arm64-artifacts.zip | 31,941,046 | 30.5 | debug 引擎 har(含字体端口) |
| 引擎 har | ohos-arm64-profile-artifacts.zip | 18,040,539 | 17.2 | profile 引擎 har |
| 引擎 har | ohos-arm64-release-artifacts.zip | 15,485,641 | 14.8 | release 引擎 har |
| 宿主 AOT | ohos-arm64-profile-darwin-x64.zip | 2,574,532 | 2.5 | profile 档 gen_snapshot(Mach-O) |
| 宿主 AOT | ohos-arm64-release-darwin-x64.zip | 2,164,948 | 2.1 | release 档 gen_snapshot(Mach-O) |
| patched_sdk | flutter_patched_sdk.zip | 4,109,460 | 3.9 | 非产品档平台 dill(P29a 修的就是它) |
| patched_sdk | flutter_patched_sdk_product.zip | 4,109,463 | 3.9 | 产品档平台 dill |
为什么网页上写 15:托管平台会为每个 Release 自动生成 4 个源码包(.zip / .tar.gz / .tar.bz2 / .tar,API 里 size 为 null),和 11 个自传附件一起计数。核对覆盖行时只看这 11 个,15 是页面计数,不是工件数。
5.7 附件数演进:1 → 8 → 7 → 13 → 11
把每个 tag 的自传附件数(不含自动源码包)排出来,能直接读出发布策略的变化:
| tag | 发布时刻 | 自传附件 | 关键变化 |
|---|---|---|---|
oh-3.47.4-rc1 | 2026-09-14 10:16 | 1 | 只挂 dart-sdk-darwin-arm64-ohos.zip,引擎工件仍指 OBS |
3.47.4-ohos-1.0.0 | 09-14 11:31 | 8 | 首次成组:5 个引擎工件 + 2 个 patched_sdk + dart-sdk-ohos.zip |
3.47.4-ohos-1.0.1 | 09-14 23:41 | 7 | P29a 修复(§6.6);dart-sdk-ohos.zip 内容未变没重传 → 覆盖行留旧 tag |
3.47.4-ohos-1.0.2 | 09-15 08:47 | 7 | 内容逐字节未变,随新 tag 重挂载,只为让 flutter doctor -v 报出的版本号配套 |
3.47.4-ohos-1.0.3 | 09-15 17:57 | 8 | 只重建了 release 档,debug/profile 仍是 1.0.1 的旧二进制(黑屏未修) |
3.47.4-ohos-1.0.4 | 09-16 00:23 | 13 | 首次出现 Windows 工件:两个 Windows Dart SDK + 两个 ohos-arm64-{profile,release}-windows-x64.zip;9 行覆盖全部并入本 tag |
3.47.5-ohos-1.0.0 | 09-23 23:57 | 11 | Dart 升到 3.13.4(kernel 仍 138);两个 Windows gen_snapshot 没随批次重传 → 宿主 AOT 回到 darwin 两档 |
3.47.7-ohos-1.0.0 | 10-13 20:09 | 11 | 覆盖行首次加两个 Windows Dart SDK 键,从 9 行长到 11 行 |
3.47.7-ohos-1.0.1 | 10-21 15:56 | 11 | 纯框架批次(第 4 篇自动签名);11 个附件逐字节重挂载 |
3.47.8-ohos-1.0.0 | 10-24 20:59 | 11 | 纯框架批次;engine.ohos.version 与 Dart SDK 均未变,逐字节重挂载 |
两条可直接抄去用的经验:
- 附件数和覆盖行数必须一起核对。1.0.4 已经把 Windows 的两个
gen_snapshot传上去了,但覆盖文件里当时还没有对应的键——上传了 ≠ 用户拿得到,反之键留着而附件没重传就是 404。发布前把两边的清单做一次comm(§6.9 给了命令)。 - "逐字节相同也重挂载"是有意的:1.0.2 / 3.47.7-1.0.1 / 3.47.8-1.0.0 三个批次的内容都没变,重挂载只为了让
flutter doctor -v显示的版本号与实际拉到的工件一一对应。这不是浪费带宽,是把"版本号 ↔ 工件"这条映射做成可核对的。
5.8 Dart SDK 是第二类自编自发的工件:mac 与 Windows 各一份
前面 §4.1 的打包脚本只产 5 个引擎 zip。另外 4 个 Dart SDK 包不在那条链路上,它们是第 6 篇"Dart 必须从源码构建"这件事在发布侧的延续——编一次,要发好几份。
为什么官方 Dart SDK 不能用(bin/internal/dart-sdk-url.ohos 文件头原文):
官方 dart-sdk 的 dart:ffi 缺少 Abi.ohosArm / ohosArm64 / ohosX64,编译
fluttertpc_dart_native(code_assets / hooks_runner 的 ohos 补丁)时必然报
"Member not found: 'ohosArm'"。
第 6 篇已经实证过:给 bin/cache/dart-sdk 打补丁是零影响的(JIT 二进制内嵌 platform kernel)。所以定制版只能"基于官方 Dart + ohos 补丁按目标架构重新构建",而且注释里写得很硬:二进制全量重建,跨架构不可复用。这一句就是"要发好几份"的根因——每支持一个宿主平台,就多一整套要编要发的产物。
四份产物分别是什么、给谁用:
| 覆盖键(zip 名) | 落地位置 | 内容 | 谁读它 |
|---|---|---|---|
dart-sdk-darwin-arm64.zip | bin/cache/dart-sdk | macOS arm64 宿主的完整 Dart SDK(dart / dartanalyzer / snapshots) | update_dart_sdk.sh |
dart-sdk-windows-x64.zip | bin/cache/dart-sdk | Windows x64 宿主的完整 Dart SDK | update_dart_sdk.ps1 |
dart-sdk-ohos.zip | bin/cache/dart-sdk-ohos | HAP 构建专用运行时:dartaotruntime(Mach-O)+ frontend_server_aot.dart.snapshot(18,620,752 B),kernel 138 | update_dart_sdk.sh |
dart-sdk-ohos-windows-x64.zip | bin/cache/dart-sdk-ohos | 同上,但 dartaotruntime.exe 是 PE32+,snapshot 17,158,320 B | update_dart_sdk.ps1 |
这里有两个实测出来的硬约束,任何一条违反都会在构建期炸:
# 1) 运行时是宿主二进制,macOS 那份拿到 Windows 上用不了
file -b x_dart-sdk-ohos/dart-sdk-ohos/bin/dartaotruntime
# Mach-O 64-bit executable arm64
file -b x_dart-sdk-ohos-windows-x64/dart-sdk-ohos/bin/dartaotruntime.exe
# PE32+ executable (console) x86-64, for MS Windows
# 2) 两份 frontend_server snapshot 大小不同(18,620,752 vs 17,158,320),
# 说明它们是各自主机上重新产出的产物,不能跨平台复制粘贴
所以 update_dart_sdk.ps1 在解压后专门校验 PE magic,不匹配就告警:
# bin/internal/update_dart_sdk.ps1
Write-Host "WARNING: dart-sdk-ohos/bin/dartaotruntime.exe is not a Windows PE; `
dart-sdk-ohos-windows-x64.zip may be built for another platform."
键名怎么拼出来的:.sh 与 .ps1 先按宿主算出 DART_ZIP_NAME(Darwin → dart-sdk-darwin-${ARCH}.zip,Linux → dart-sdk-linux-${ARCH}.zip,MINGW*/MSYS* → dart-sdk-windows-x64.zip),主 SDK 用它去查覆盖行;HAP 运行时则拼 "dart-sdk-ohos-$DART_ZIP_NAME",于是 Windows 上要找的键就是 dart-sdk-ohos-windows-x64.zip。两个脚本必须保持同一套逻辑(.sh 文件头有明确 NOTE 提醒),改一边不改另一边是这类双实现最常见的失配。
没配键会怎样:覆盖行未命中时脚本不会失败,而是打印告警后回退官方源——
WARNING: OHOS customized Dart SDK (dart-sdk-linux-x64.zip) not published;
falling back to official SDK (build may fail on Abi.ohos* members).
这正是 Linux 宿主的现状:覆盖文件里 11 行没有任何 linux 键,Release 里也没有 Linux 的 Dart SDK 附件,所以 Linux 上 bootstrap 会装上官方 Dart SDK,直到编译 fluttertpc_dart_native 时才报 Member not found: 'ohosArm'。Windows 则是先补脚本、后补键:宿主 bootstrap 的改造是提交 18e36518663f(fix: Windows 宿主 dart-sdk bootstrap(补齐 OHOS 定制 SDK 与 dart-sdk-ohos))与合并提交 77ae67d1b007,而覆盖行里那两条 Windows 键直到 3.47.7-ohos-1.0.0 批次才加上。
一句话收口:"适配鸿蒙"要编的不只是引擎,Dart SDK 本身也是产物;它是"每个宿主架构一份"的,发布清单里必须和引擎工件一起数。
六、分发:覆盖行机制
6.1 覆盖行长什么样
bin/internal/dart-sdk-url.ohos 一个文件承担两件事(update_dart_sdk.sh 与 cache.dart 共用):
当前形态(3.47.8-ohos-1.0.0,11 条键,… 代表 https://atomgit.com/oh-flutter/flutter_flutter/releases/download):
# ① Dart SDK(update_dart_sdk.sh / .ps1 读,键 = 按宿主算出的 DART_ZIP_NAME)
dart-sdk-darwin-arm64.zip:…/3.47.8-ohos-1.0.0/dart-sdk-darwin-arm64-ohos.zip
dart-sdk-windows-x64.zip:…/3.47.8-ohos-1.0.0/dart-sdk-windows-x64-ohos.zip
dart-sdk-ohos.zip:…/3.47.8-ohos-1.0.0/dart-sdk-ohos.zip
dart-sdk-ohos-windows-x64.zip:…/3.47.8-ohos-1.0.0/dart-sdk-ohos-windows-x64.zip
# ② 引擎工件(cache.dart 的 _ohosArtifactOverrideUrl 读,键 = cache 里的 urlPath,也接受文件名)
ohos-arm64/artifacts.zip:…/3.47.8-ohos-1.0.0/ohos-arm64-artifacts.zip
ohos-arm64-profile/artifacts.zip:…/3.47.8-ohos-1.0.0/ohos-arm64-profile-artifacts.zip
ohos-arm64-release/artifacts.zip:…/3.47.8-ohos-1.0.0/ohos-arm64-release-artifacts.zip
ohos-arm64-profile/darwin-x64.zip:…/3.47.8-ohos-1.0.0/ohos-arm64-profile-darwin-x64.zip
ohos-arm64-release/darwin-x64.zip:…/3.47.8-ohos-1.0.0/ohos-arm64-release-darwin-x64.zip
flutter_patched_sdk.zip:…/3.47.8-ohos-1.0.0/flutter_patched_sdk.zip
flutter_patched_sdk_product.zip:…/3.47.8-ohos-1.0.0/flutter_patched_sdk_product.zip
对着这 11 行能读出一条命名约定:引擎侧的键是 cache 里的 urlPath(带 /),Release 上的附件名是把那个 / 换成 -(ohos-arm64-profile/artifacts.zip → ohos-arm64-profile-artifacts.zip);Dart SDK 侧的键与附件名只差一个 -ohos 后缀(dart-sdk-darwin-arm64.zip → dart-sdk-darwin-arm64-ohos.zip)。这不是代码要求,是 11 条键与 11 个附件名逐条对照出来的惯例——但既然是惯例就得沿用,因为 §5.2 说过覆盖行是按名字精确匹配的。
历史形态更值得看(3.47.4-ohos-1.0.1 时只有 9 条,且三个 tag 混用):
dart-sdk-darwin-arm64.zip:…/oh-3.47.4-rc1/dart-sdk-darwin-arm64-ohos.zip
dart-sdk-ohos.zip:…/3.47.4-ohos-1.0.0/dart-sdk-ohos.zip
ohos-arm64/artifacts.zip:…/3.47.4-ohos-1.0.1/ohos-arm64-artifacts.zip
# …其余 6 条同样指 1.0.1
这不是随意的:1.0.1 那次只重传了 7 个引擎侧附件,两个 Dart SDK 包没动,所以它们的键必须继续留在 rc1 与 1.0.0 上(提交 947610d6e0c1 就是修这个错指)。直到 1.0.4 才做到"全部 9 条指同一个 Release"(覆盖行注释原文:“使全部 9 个附件指向同一个 Release,不再混用 oh-3.47.4-rc1 与 3.47.4-ohos-1.0.3”)。
一条容易忽略的判据差异:Dart SDK 这四条与引擎工件的过期逻辑相反。引擎工件改 URL 会让已装机器重下(§5.3);Dart SDK 走的是
update_dart_sdk.sh/.ps1,改 URL 不会让已装副本重下,只影响全新安装(主 SDK 看ENGINE_VERSION比对,运行时看"文件在不在")。覆盖行注释专门用粗体提醒了这一点。
6.2 键的匹配规则与一个历史 bug
_ohosArtifactOverrideUrl 按 urlPath 或 basename 匹配。历史 bug(1.0.0 之前,已修):旧代码用 urlPath.startsWith('flutter_patched_sdk') 判断,而实际 urlPath 是 common/flutter_patched_sdk.zip——永不命中,覆盖形同虚设。
这个 bug 的教训值得单独记:覆盖机制"配了"和"生效了"是两件事,必须用行为验证(换新 tag 后看是否真的重下),不能只读文件内容。
6.3 ⚠️ 四类工件的行为都不一样
这是整条链路最容易误判的地方,也是所有"我改了怎么没生效"提问的标准答案:
| 工件 | 走哪条通道 | 改覆盖行会重下吗 | 强制刷新 |
|---|---|---|---|
| 三个 har、两个 gen_snapshot、两个 patched_sdk | 各 artifact 类的 getBinaryDirs() → 覆盖机制生效 | ✅ 会(换新 tag 即触发) | — |
sky_engine / flutter_gpu | FlutterSdk.getPackageDirs() → pkg 循环,完全不查覆盖行 | ❌ 不会,且没有覆盖通道 | 只能改 OBS 默认址或自建分发 |
dart-sdk-ohos | update_dart_sdk.sh | ❌ 不会(“缺了才下载”) | rm -rf bin/cache/dart-sdk-ohos |
主 dart-sdk | ENGINE_VERSION 版本比对 | ❌ 不会(URL 变但版本没变) | 删 bin/cache/dart-sdk |
6.4 通道分界的源码证据
不是猜的,是核过 cache.dart 的:
// packages/flutter_tools/lib/src/cache.dart
// _ohosArtifactOverrideUrl 只在 getBinaryDirs() 的两处被调用:
// isUpToDateInner (:1072)
// updateInner (:1123)
// getPackageDirs() 的循环(:1057 / :1099)从不调用它。
而 FlutterSdk 这一个类同时实现了两条通道:
getPackageDirs()=['sky_engine', 'flutter_gpu'];getBinaryDirs()=['common', 'flutter_patched_sdk.zip', 'common', 'flutter_patched_sdk_product.zip']。
这正是"patched_sdk 能覆盖、sky_engine 不能"的原因——同一个类,两条通道,只有一条查覆盖行。
6.5 sky_engine 为什么长期未修
pkg 循环里 sky_engine 被特判成 $ohosStorageBaseUrl/.../$ohosEngineVersion/sky_engine.zip,即 OBS 上 3.44.9 时代的版本,无法通过覆盖行改指。
它长期未修的原因是影响面不阻塞构建:sky_engine 只服务 analyzer(flutter analyze 用的 dart:ui 镜像)。但若将来新引擎改了 dart:ui 接口,这条链路传不出去——这是本机制上已知的、有记录的缺口(对应待办"补 sky_engine 覆盖")。
dart-sdk-ohos 的判据在 bin/internal/update_dart_sdk.sh:218:
[ -n "$OHOS_RUNTIME_URL" ] && [ ! -x "$FLUTTER_ROOT/bin/cache/dart-sdk-ohos/bin/dartaotruntime" ]
行为与引擎工件完全相反:只看"文件在不在",不看 URL。3.47.4 批次没撞上它,只因为该目录当时天然不存在(首次 bootstrap 补齐)。换 Dart 版本的批次必然踩——改 URL 不会让任何已装 SDK 重下它。
6.6 patched_sdk:为什么它也在覆盖行里
上游的 flutter_patched_sdk.zip 不含鸿蒙的 Platform.isOhos / Abi.ohosArm(实测 ohosArm = 0),编译鸿蒙应用必报 Member not found,所以这两个条目必须覆盖。
1.0.0 的历史 bug(P29a):flutter_patched_sdk.zip 包内 flutter_patched_sdk/platform_strong.dill 装的其实是产品档 dill,导致 profile 模式一启动就 FATAL:
Dart Error: Type '_NetworkProfiling' not found in library 'dart.io'
→ FATAL:flutter/lib/io/dart_io.cc(38)] Check failed: !CheckAndHandleError(setup_result)
根因:非 PRODUCT 引擎会去查 _NetworkProfiling,而该类型靠 @pragma('vm:entry-point', !dart.vm.product) 在产品 dill 里已被树摇掉。
教训:非 PRODUCT 引擎 + 非产品平台 dill 是必须同时对上的两件事;debug 不触发不代表 profile 不触发。
1.0.1 已修,修复后的量化证据:libapp.so = 6,685,576 B,_NetworkProfiling 符号计数 0 → 2。
6.7 P29b:两个 entry 共用一个 marker(未根治)
flutter_patched_sdk.zip 与 flutter_patched_sdk_product.zip 解压到同一个 artifact 目录 engine/common/,但标记文件只有一个,两者互相覆盖 ⇒ 每次构建至少有一个被判过期而重下。
对正常流程无害(发布物是对的,重下装回来的就是对的),但有一条推论必须记住:任何"直接改 cache 里文件"的手工热修都会被下一次构建抹掉——验证这类改动必须改发布物。根治要给两个 entry 各配一个标记文件,属 cache.dart 的独立改动,本批未做。
6.8 用户侧怎么拿到更新
fvm 侧的事实(本机 fvm 4.3.1 实测)。自定义 fork 源存在 ~/Library/Application Support/fvm/.fvmrc 里,注意这个文件是 JSON 不是 YAML,本系列用的那条别名节选如下:
{
"forks": [
{ "name": "nutpi", "url": "https://atomgit.com/oh-flutter/flutter_flutter.git" }
]
}
版本引用形如 nutpi/3.47.8-ohos-1.0.0(<别名>/<tag>)。项目级引用有两处:根目录 .fvmrc 写 {"flutter": "nutpi/3.47.8-ohos-1.0.0"},.fvm/fvm_config.json 写 {"flutterSdkVersion": "nutpi/3.47.8-ohos-1.0.0"},.fvm/flutter_sdk 是指向 ~/fvm/versions/<别名>/<版本> 的软链。
分发链路可以一条命令验证:全新执行 fvm install <别名>/<新 tag>,日志里就会出现覆盖行指向的 Release 附件地址(第 1 篇 5.2 节的真实输出):
OHOS: using customized Dart SDK (dart-sdk-darwin-arm64.zip) from
https://atomgit.com/oh-flutter/flutter_flutter/releases/download/3.47.8-ohos-1.0.0/dart-sdk-darwin-arm64-ohos.zip
也就是说:换新 tag + 改覆盖行之后,用户侧什么都不用做——fvm install 新 tag 或已装 SDK 的下一次构建,都会从新地址把工件拉下来。
⚠️ fvm 装出来的副本在 detached HEAD 上(无当前分支),
git pull会失败——别误读成"传播机制没生效"。正确取法:
git -C ~/fvm/versions/nutpi/3.47.8-ohos-1.0.0 fetch origin
git -C ~/fvm/versions/nutpi/3.47.8-ohos-1.0.0 merge --ff-only origin/oh-3.47-dev
# 快进 detached HEAD,不动 fvm 布局
该副本的 origin = https://atomgit.com/oh-flutter/flutter_flutter.git,与 fork nutpi 一致。它是独立目录,有自己的 bin/cache,不是软链——所以覆盖行的改动要传播到它,必须先让它 merge 到新的覆盖行,再靠 flutter build hap 触发重下。
另外记一条:浅克隆没有 tag 会报 0.0.0-unknown 导致 pub 解析失败;真实 fvm 安装带 tag,不受影响(消费侧 CP9 的成因之一)。
用户侧全流程(零手工步骤):
fvm install nutpi/<tag>;flutter build hap;- bootstrap 自动下载
dart-sdk与dart-sdk-ohos; - 首次构建时按覆盖行下载引擎 har 与 patched_sdk。
全新克隆端到端已验证:debug / profile / release 三种 flutter build hap --no-codesign 全过,产物 libflutter.so = 3.13.3。
6.9 反向清单:发布新批次到底要改哪几行、哪几个 URL
把前面所有机制收敛成一张"发布时的动作表"。要改的文件只有一个:bin/internal/dart-sdk-url.ohos;三个 *.version 锁文件保持不动(§2.2)。
| # | 覆盖键 | 目标附件 | 谁读这行 | 改了会不会立刻传播 |
|---|---|---|---|---|
| 1 | dart-sdk-darwin-arm64.zip | dart-sdk-darwin-arm64-ohos.zip | update_dart_sdk.sh | ❌ 只影响全新安装 |
| 2 | dart-sdk-windows-x64.zip | dart-sdk-windows-x64-ohos.zip | update_dart_sdk.ps1 | ❌ 同上 |
| 3 | dart-sdk-ohos.zip | dart-sdk-ohos.zip | update_dart_sdk.sh | ❌ 只认"文件在不在",要 rm -rf bin/cache/dart-sdk-ohos |
| 4 | dart-sdk-ohos-windows-x64.zip | dart-sdk-ohos-windows-x64.zip | update_dart_sdk.ps1 | ❌ 同上(判据是 dartaotruntime.exe) |
| 5 | ohos-arm64/artifacts.zip | ohos-arm64-artifacts.zip | cache.dart | ✅ 换新 tag 即触发重下 |
| 6 | ohos-arm64-profile/artifacts.zip | ohos-arm64-profile-artifacts.zip | cache.dart | ✅ |
| 7 | ohos-arm64-release/artifacts.zip | ohos-arm64-release-artifacts.zip | cache.dart | ✅ |
| 8 | ohos-arm64-profile/darwin-x64.zip | ohos-arm64-profile-darwin-x64.zip | cache.dart | ✅ |
| 9 | ohos-arm64-release/darwin-x64.zip | ohos-arm64-release-darwin-x64.zip | cache.dart | ✅ |
| 10 | flutter_patched_sdk.zip | flutter_patched_sdk.zip | cache.dart | ✅(但每次构建至少重下一份,§6.7) |
| 11 | flutter_patched_sdk_product.zip | flutter_patched_sdk_product.zip | cache.dart | ✅ |
| — | sky_engine / flutter_gpu | 没有覆盖通道 | FlutterSdk.getPackageDirs() | ❌ 只能改 OBS 默认址(§6.5) |
两种 URL 形态与可覆写的基址:
# A. 自建分发(实际生效的通道)
https://atomgit.com/oh-flutter/flutter_flutter/releases/download/<tag>/<附件名>
# B. 默认回退址(OBS,3.44.9 时代产物,dart 3.12.2 / kernel 130)
{base}/flutter_infra_release/flutter/{engine.ohos.version}/{pkg}.zip
base 默认 https://flutter-ohos.obs.cn-south-1.myhuaweicloud.com
可用环境变量覆写:FLUTTER_OHOS_STORAGE_BASE_URL(鸿蒙侧) / FLUTTER_STORAGE_BASE_URL(官方侧)
非法 URI 会被 cache.dart 直接 throwToolExit 拦下,不会静默用错地址。
发布前后各跑一条命令,把"键写了但附件没传"和"附件传了但键没写"两类错都堵掉:
# 逐条 HEAD 覆盖行里的 URL:404 = 名字打错或该 tag 下没传这个附件
grep -E '^[a-z0-9_.-]+:https' bin/internal/dart-sdk-url.ohos | cut -d: -f2- |
while read -r u; do
printf '%s %s\n' "$(curl -sS -o /dev/null -w '%{http_code}' -IL --max-time 20 "$u")" "${u##*/}"
done
(比对口径用完整 URL,不要用文件名:三个 har 的键 basename 都是 artifacts.zip,用 basename 去 comm 会误报。)
三条最容易漏的:
- 没重传的附件,它的键要留在旧 tag——别"顺手统一"成新 tag,那是 404(§6.1 的 1.0.1 教训);
- Dart SDK 那四条改了也不会让老安装更新,验证只能全新克隆或删
bin/cache/dart-sdk{,-ohos}; - 宿主矩阵别只写 mac:Windows 的两个键(第 2、4 行)是 3.47.7 批次才补的,Linux 至今没有键——
file -b与 PE 校验(§5.8)说明这三档不能共用一份产物。
七、发布前回归验收清单
7.1 十一条,按执行顺序逐条过
[ ] 1 构建:三档 out/ohos_<mode>_arm64/ 齐全;ninja 0 FAILED
[ ] 2 字体端口在位(别跳过,丢了编译不报错)——按第 10 篇流程三档都查
[ ] 3 宿主 gen_snapshot 是 Mach-O:file -b out/ohos_<mode>_arm64/clang_arm64/gen_snapshot
[ ] 4 打包:package_ohos_engine_artifacts.sh 无断言失败,5 个 zip 的 unzip -l 布局与 §4.1 逐条一致;
Dart SDK 4 个包按宿主平台齐备(macOS + Windows,键名与 §5.8 一致)
[ ] 5 新 tag:已建,且不是在旧 tag 上重传同名附件
[ ] 6 先传 1 个附件 → curl -I 直连验证(200 + Content-Length 与实际一致)→ 再传其余
[ ] 7 覆盖行:11 行逐条改成新 tag(内容未变、没重传的附件那几行**保留旧 tag**)
[ ] 7b 逐条 HEAD 覆盖行 URL,确认 11 条全 200(§6.9 的命令)
[ ] 8 提交推送:覆盖行改动推到发布分支(本批次为 hxa)
[ ] 9 本机传播验证:flutter build hap 观察是否触发重下;再对 fvm 副本跑一遍 §6.8 的 fetch+merge
[ ] 10 本机清缓存端到端:rm -rf bin/cache 后重跑三档 flutter build hap --no-codesign
[ ] 11 设备侧:三档 HAP 渲染正常(重点看文字与图标)
7.2 三条最容易跳过的
第 7 条用 grep 而不是行号定位(行号会随文件变动):
grep -n '3\.47\.4-ohos-1\.0\.0' bin/internal/dart-sdk-url.ohos
# 期望:无输出。有任何输出 = 还有旧 tag 残留 = 该工件不会更新
三条最容易被"我本地都好了"心态跳过的:
- 第 2 条(字体端口):丢了编译不报错,只有跑起来看画面才发现全屏无文字——详见第 10 篇;
- 第 6 条(先传一个验证):跳过它的代价是全部附件传完才发现名字不对;
- 第 9 条(fvm 副本传播):本机仓库和 fvm 副本是两个独立 cache,本机过了不代表 fvm 那边过。
八、故障索引
| 现象 | 根因 → 出路 |
|---|---|
Unexpected Kernel Format Version 130 (expected 138) | 覆盖行没生效或没拉到 → §1.3 / §6.1;先确认 git log -1 -- bin/internal/dart-sdk-url.ohos |
| 改了覆盖行、已有安装没反应 | 重传了同名附件(URL 未变)→ §5.3,必须换新 tag |
换 Dart 版本后 dart-sdk-ohos 还是旧的 | 它是"缺了才下载" → §6.5,rm -rf bin/cache/dart-sdk-ohos |
| 每次构建都在重下 patched_sdk | P29b 标记碰撞 → §6.7,正常现象,不是故障 |
| 手工改 cache 里的 dill 下次构建失效 | 同上:重下会覆盖 → 改发布物,别热修 |
profile 一启动就 _NetworkProfiling FATAL | P29a:patched_sdk 装成产品档 dill → §6.6 |
通了 build/ohos/ 但 AOT 没重建 | P29c:AOT 快照缓存在 .dart_tool/flutter_build/ |
| AOT 产物在设备上不能用 | 取错了 gen_snapshot(ELF 当 Mach-O)→ §3.6 |
| 端口源码改了但树里还是旧的 | copy_dir 对已存在目录是 no-op → §3.5,先删目标目录 |
gn gen 编不过 zlib | --llvm18 指到 clang 15 → §3.3,换 llvm@19 |
fvm 副本 git pull 失败 | detached HEAD → §6.8,用 fetch + merge --ff-only |
| Windows 上 HAP 构建报"dartaotruntime.exe is not a Windows PE" | 覆盖行指到了 macOS 的 dart-sdk-ohos.zip → §5.8,Windows 要用 dart-sdk-ohos-windows-x64.zip |
Linux 宿主编 fluttertpc_dart_native 报 Member not found: 'ohosArm' | 覆盖文件没有 Linux 键,bootstrap 回退官方 Dart SDK 并告警 → §5.8 |
换了 Dart 版本但 bin/cache/dart-sdk-ohos 还是旧的 | 它的判据是"文件在不在",不看 URL → §6.3 / §6.9,rm -rf bin/cache/dart-sdk-ohos |
用户装完报 Abi.ohosArm Member not found | dart-sdk 或 patched_sdk 覆盖行没生效 → §6.1 / §6.6 |
flutter analyze 的 dart:ui 与运行期不一致 | sky_engine 无覆盖通道 → §6.5,已知缺口 |
九、常见问题 FAQ
9.1 我改了引擎源码,重传同名附件行不行?
不行,而且这是最危险的做法。URL 没变 ⇒ 已装机器的 .ohos-override-url 标记仍然匹配 ⇒ 判定"已最新"不重下 ⇒ 改动传不出去,且不报任何错。必须新建 tag。
9.2 发布要改 engine.ohos.version 吗?
**不要。**三个 *.version 锁文件只在同步上游时动。重发工件的唯一推进信号是覆盖行 URL 变了(§2.2)。
9.3 为什么我改了覆盖行,sky_engine 没跟着更新?
因为它走 getPackageDirs() 的 pkg 循环,那段代码从不调用 _ohosArtifactOverrideUrl(§6.4)。当前它只影响 analyzer,不阻塞构建。
9.4 每次构建都重下 flutter_patched_sdk,是我配错了吗?
不是,是 P29b 的已知未根治问题:两个 entry 共用一个标记文件(§6.7)。对正确性无影响,只是浪费一点下载。
9.5 能不能把上传也自动化?
本批次没有验证过脚本化/API 上传路径。本机只有 SSH key,无 atomgit HTTP 凭据。想做的话请先在自己的仓库上验证,并把结论补进 §十。
9.6 Linux / Windows 宿主的工件在哪?
这个问题要分两类产物回答,口径不一样:
| 产物 | macOS | Windows | Linux |
|---|---|---|---|
宿主 AOT gen_snapshot | ✅ 两档(profile / release) | ❌ 1.0.4 传过,3.47.5 起未再随批次发布 | ❌ 从未发布 |
定制 Dart SDK(bin/cache/dart-sdk) | ✅ | ✅ | ❌ 无键、无附件 |
HAP 运行时 dart-sdk-ohos | ✅ | ✅(独立 PE 包) | ❌ |
没有覆盖键的宿主不会报错,而是回退 OBS 默认址。实测该址上 ohos-arm64-profile/windows-x64.zip 与 ohos-arm64-release/linux-x64.zip 都是 200,包内分别是 gen_snapshot.exe(PE32+)与 ELF 二进制,但 strings 出的版本是:
3.12.2 (stable) (Tue Aug 11 09:49:18 2026 +0800) on "windows_simarm64"
3.12.2 (stable) (Tue Aug 11 09:49:18 2026 +0800) on "linux_simarm64"
——3.44.9 时代、kernel 130 的产物,与本分支的 3.13.4(kernel 138)不配套,症状就是 §1.3 那条 Unexpected Kernel Format Version 130 (expected 138)。
结论:Windows 上装 Dart SDK 与 HAP 运行时已经零操作,但 Windows / Linux 宿主还拿不到配套的 AOT 产物。这是明确的未做项,不是"某次发布漏传了几个包"。
十、未验证项与诚实口径
以下不要当成已确认的步骤:
- 上传环节由人通过网页完成,本机无 atomgit HTTP 凭据;脚本化/API 上传路径完全未验证;
- tag 命名规范是从既有 tag 反推的惯例,非官方约定;
out/清空后首次args.gn的完整生成链无实证(本机只验过增量路径);package_ohos_engine_artifacts.sh的目录名与 LICENSE 源路径是 3.47.4 批次写死的,新批次需复核;--ohos_api_int取值存在矛盾(ohos.py默认 13 且注释标废弃,args.gn里是 13,实测整链按 API 24 走)——未定论;- 宿主矩阵只验证了 macOS:Windows 与 Linux 上既没有配套的宿主 AOT 产物,
update_dart_sdk.ps1的行为也只做过源码级核对(本机是 macOS arm64,Windows 端到端未实跑)——键与附件都存在,但"在 Windows 机器上从零装一遍"这一步没有证据。
另有一处跨文档勘误要说明:早期资料把发布动作写成"上传 OBS、更新 engine.ohos.version",那是 OBS 时代的链路。本条链路上这两个动作都不需要——OBS 现仅作默认回退地址,版本锁文件只在同步上游时才会动。
十一、本篇小结与系列导航
本篇走完 M4(发布分发)阶段:
- 版本锁:三套版本源分工清楚,重发工件不动锁文件,推进信号是覆盖行 URL;
- 构建:
engine/ohoswrapper 是唯一规范入口,--llvm18是禁令,args.gn在未跟踪的out/里、换机即丢; - 附件机制:4 个 files 任务把端口源码复制进第三方树,
copy_dir对已存在目录是 no-op——换端口先删目标; - 两类来源、11 个附件:引擎侧 5 个 zip 出自打包脚本(3 个 har + 2 个宿主
gen_snapshot),加上 2 个平台 dill(flutter_patched_sdk{,_product})与 Dart SDK 侧 4 个包(macOS / Windows 各一套),凑成当前 Release 的 11 个自传附件;Linux 宿主从未发布; - 打包:5 个 zip 布局不对称(debug 不带后缀),不要顺手统一;两个 LICENSE 是 OBS 遗产;
- 发布:铁律是换内容必须换新 tag,同名重传对已装机器静默无效;
- 分发:覆盖行一个文件两用,四类工件过期判据各不相同,patched_sdk 的 P29a/P29b 是两个真实事故;
- 收口:11 条发布前验收清单 + 13 行故障索引。
「鸿蒙Flutter框架 0day适配实战」全系列 10 篇,本篇为第 9 篇(生产侧 M4 发布分发):
| # | 篇目 | 侧 | 一句话主题 |
|---|---|---|---|
| 01 | 背景、分叉仓库、版本命名与 fvm fork 安装 | 消费 | 三层架构、三个接缝、七层工作量、fvm 直装 tag、版本墙 |
| 02 | tag 版本的手动安装与三层核验 | 消费 | 浅克隆 bootstrap、首次初始化、SSH 修复、异常分诊 |
| 03 | 鸿蒙模拟器/真机跑 Flutter + 消费侧踩坑集 | 消费 | 工具链三件套、HAP 构建/安装/运行、占位签名、截图取证、CP1-CP14 |
| 04 | flutter build hap 自动签名 | 消费 | P12/CSR、AGC 证书与 Profile、纯 Dart AES-GCM、C0-C10 复用 |
| 05 | 上游同步总览、BC 全景与端到端 SOP | 生产 | 批次解剖、五阶段验证门、12 项适配表、端到端 SOP 与 P1-P29 索引 |
| 06 | Phase 1+2:基线建立与 Framework 适配 | 生产 | 代理 HTTP/1.1 修障、421 文件重打、Dart SDK 源码构建 |
| 07 | Phase 3 Engine:三方重放与冲突裁决 | 生产 | 826 文件、15 真冲突两分法、契约对齐 16 项 |
| 08 | 引擎构建链攻防 | 生产 | 依赖树、工具链版本、musl 交叉链接、测试面漂移 |
| 09 | 构建发布与产物分发(本篇) | 生产 | 版本锁、11 个工件(含 Dart SDK 双宿主)、11 条覆盖键、换新 tag 铁律 |
| 10 | 验证与真机验收 | 生产 | 三档引擎、设备侧单测、字体图标静默故障 |
总结
这条链路在 3.47.4 批次已经闭环:从改引擎源码到用户零操作安装,1.0.0 → 1.0.1 完整走通一遍,1.0.0 暴露的 patched_sdk 产品档 dill bug 在 1.0.1 修正并实测。
六条要记住的:
- 规范化构建入口是
engine/ohos→ohos.py,不是手搓 gn/ninja;args.gn在未跟踪的out/里,换机即丢; - 要发的是 11 个附件、两类来源:引擎 5 个 zip(3 har + 2 宿主
gen_snapshot)+ 2 个平台 dill + Dart SDK 4 个包(bin/cache/dart-sdk与bin/cache/dart-sdk-ohos),后者按宿主架构各编一份,Linux 至今没有; - 发布时唯一要改的文件是
bin/internal/dart-sdk-url.ohos的 11 条键,三个*.version不动;改完逐条 HEAD 验证(§6.9); - 发新版必须换新 tag——同名重传对已装机器静默无效;
- 四类工件的过期判据不一样:引擎工件看 URL 标记,
dart-sdk-ohos只看"文件在不在",sky_engine根本没有覆盖通道; - 发布前必须单独查字体端口——它丢了编译不报错(详见第 10 篇)。
到这里,"编出来"和"发出去"都成立了。下一篇(第 10 篇)是这个系列的收口:验证与真机验收——四道质量闸、设备侧单测 1723/1723 的真实跑法、三档引擎全部落地时的两个装错包事故,以及那个把整条链路逼出"发布前必查字体"制度的静默故障。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- 鸿蒙分叉仓库(本篇发布物所在):atomgit.com/oh-flutter/flutter_flutter
- 各批次 ReleaseNote(含工件清单与验证记录):release-notes/,当前批次 Flutter 3.47.8-ohos 1.0.0 ReleaseNote.md
- 上游 Flutter 仓库(
cache.dart双通道源码):github.com/flutter/flutter - Skia 字体端口镜像(attachment 的
skia_ohos来源):atomgit.com/CPF-Flutter/fluttertpc_skia - fvm(fork 源与版本目录布局):github.com/leoafarias/fvm
- depot_tools(
engine/ohos依赖的 vpython3 / gn / ninja):chromium.org 官方安装指引 - ninja 官方手册(
-C/ 断言失败排查):ninja-build.org - LLVM(
custom_toolchain的 llvm@19):github.com/llvm/llvm-project - DevEco Studio(
DEVECO_SDK_HOME来源):developer.huawei.com - Flutter OH 环境搭建指导:OpenHarmony-flutter 环境搭建指导
- Flutter 中国社区版文档:docs.flutter.cn/community/china
- OpenHarmony 文档仓库:atomgit.com/openharmony/docs
- 开源鸿蒙跨平台社区:openharmonycrossplatform.csdn.net
环境与版本
本文的示例与截图基于以下环境组合(2026-10 实测):
| 组件 | 版本 | 说明 |
|---|---|---|
| 上游 Flutter SDK | 3.47.8 | github.com/flutter/flutter |
| Flutter SDK(OH 适配版) | 3.47.8-ohos-1.0.0 | oh-flutter/flutter_flutter,oh-3.47-dev 分支 |
| DevEco Studio | 26.0.0 Release | 提供 hvigor / ohpm / hdc / JBR 等工具链 |
| OpenHarmony SDK / API | API 26(26.0.0) | 模拟器与 SDK 均为 API 26 |
| 运行设备 | OpenHarmony 模拟器 | API 26,1280×2832 |
环境搭建(分叉获取、DevEco Studio 与 SDK 配置、模拟器创建)请按官方指导操作:OpenHarmony-flutter 环境搭建指导。
本文正文是 2026-09 3.44.9 → 3.47.4 同步批次的实录:其中的版本号、提交号与实测数字(适配清单文件数、构建边数、单测用例数、工件字节数等)都是那一批的现场值,不随上表更新;上表是当前可复现、可照做的环境组合。方法论、验证门与坑模型对 3.47.8 线同样适用。
更多推荐


所有评论(0)