前言

第 7 篇把 Engine 侧的合并与裁决收口:826 文件三方重放、15 个真冲突两分法、契约对齐 16 项(G3 门)。但「合并干净」离「构建通过」还差一整条流水线——本篇就是那条链路的实录:12458 条边的产物图,从第一次 ninja 跑到 ninja28 全绿 0 FAILED,中间踩掉 27 个坑。

这批坑有一个共同特征:它们不是代码写错,而是「同步的完整性」在构建期被反查。上游删了符号、DEPS 换了 pin、CIPD 少装了组件、libc++ 抬了最低编译器版本——每一个都在 diff 视野之外,只有编译器、链接器和 ninja 会告诉你。

本篇覆盖四组:

  1. 依赖树缺口(P1-P4):stale ninja 混合图、悬空引用、CIPD 组件缺失、子依赖漏拉;
  2. 工具链版本(P5-P6):libcxx 的 Clang 18 硬门槛,以及 LLVM 23 太新引出的连锁反应;
  3. musl 与交叉链接(P23-P26):_GNU_SOURCE builtin 链、链接三件套桥接、libcxxabi 真身、har 双修复;
  4. 收尾(P27-P27b):jsoncpp 幽灵依赖与测试面漂移五规律。

版本口径:2026-09 3.44.9 → 3.47.4 批次 M2 阶段实录(macOS 15.4.1 arm64 新机)。P7-P22 那组源码适配类的裁决细节在第 7 篇第六章,本篇不重复;两篇合起来才是完整的 P1-P29。

前置阅读:第 7 篇(engine 合并与契约对齐)、第 6 篇(Dart SDK 必须从源码构建)。

一、构建入口与问题谱

1.1 四个 target 一条链

# 构建根:engine/src/out/ohos_release_arm64
ninja -C src/out/ohos_release_arm64 default \
  flutter/shell/platform/ohos:flutter_ohos_unittests \
  flutter/shell/platform/ohos:flutter_ohos_app_test
# 产物链规模:12458 条边

default 目标链里必须过的四个产物:

target含义为什么是判据
libflutter.so引擎生产代码全量平台层 + Impeller + Skia 全部编过才算适配完整
flutter_ohos_unittests全量 OHOS 单测可执行文件(32 MB)测试面链接是最后一道 API 漂移探测器
flutter_har_ziphar 三件套打包消费侧拿到的就是这个产物
gen_snapshot_arm64AOT 快照工具profile/release 档依赖它

1.2 环境三要素

export PATH="$DEPOT_TOOLS:$LLVM/bin:$PATH"   # vpython3 引导 + 定版 clang
export DEVECO_SDK_HOME=".../Contents/sdk"    # sysroot 与 OHOS runtime 来源
export LUCI_CONTEXT=/tmp/luci-context        # CIPD 拉预编译组件需要
ulimit -n 16384                              # 12458 条边会打爆默认 fd 上限

一个细节值得记:用 /opt/homebrew/bin/ninja,不要用 depot_tools 的 ninja 包装层——后者在本机 bootstrap 不完整,会在最不该失败的地方失败。

1.3 29 个坑的分类图

在这里插入图片描述

上图是本批次的坑全景:五个分支按性质分类,括号里是编号区间——依赖树缺口 P1-P4、工具链 P5-P6、源码适配 P7-P19(数量最多的一类,13 个,归第 7 篇)、构建体系 P20-P26、收尾/发布 P27-P29。底部标了最值得记的三个:P10 私仓 pin 差出整个 API 面、P11 补丁吞括号长得像「类实现不完整」、P29a 发布物装错包(debug 过、profile 炸)。

类别编号归属篇目共同点
依赖树缺口P1-P4本篇 §二「文件不存在」类,编译器直接报
工具链版本P5-P6本篇 §三与同步无关,纯本机环境维度
源码适配P7-P22第 7 篇 §六diff 为零仍可能引用被删符号
构建体系P23-P26本篇 §四、§五(P22 首例见第 7 篇 §6.8,本篇只取它的批量规律)musl / 交叉链接 / 产物打包
收尾与发布P27-P29本篇 §五 + 第 10 篇幽灵依赖与装错包,验收阶段才炸

二、依赖树缺口:P1-P4

2.1 P1 旧 obj/*.ninja 与 build.ninja 混合图(症状最迷惑)

ninja: error: '../../flutter/third_party/skia/src/ports/SkLog_ohos.cpp',
        needed by '.../obj/flutter/third_party/skia/skia/src/ports/SkLog_ohos.o',
        missing and no known rule to make it

诡异之处在于:全树 grep SkLog_ohos 结果为零,而同一趟 gn 写出的 compile_commands.json 里却有这条编译命令。

机制是 gn 的增量 regen 行为:只重写顶层的 build.ninja / toolchain.ninja / args.gn(9 个文件),1047 个 per-dir obj/**/*.ninja 保持旧 mtime;而 build.ninja 的 subninja toolchain.ninja 链照样把这些旧文件拉进图——旧文件里带着 fork-skia 时代的边。

# 两条命令定位:先看新旧分布,再确认边是否真在加载的图里
find obj -name '*.ninja' -newermt '2026-09-13 11:00' | wc -l
ninja -t query //flutter/third_party/skia:skia/src/ports/SkLog_ohos.o
# 修复:全量重建,不留旧图
mv obj obj.stale && gn gen . --root=../.. --check --export-compile-commands
# → 1690 targets / 389 files,exit 0

2.2 P2 SkLog_ohos.cpp 悬空引用(同步产物,非事故)

src/flutter/skia/BUILD.gn:792 的 is_ohos 分支引用 $_skia_root/src/ports/SkLog_ohos.cpp——这是 fork skia 仓库里的文件;3.47.4 同步后 skia pin 换成 vanilla 上游(核对 DEPS pin 8df24be6 = 本地 HEAD,树内无 ohos 残留)。

// 按 vanilla SkLog_android.cpp 同构补写 src/ports/SkLog_ohos.cpp(hilog_ndk 后端)
#include <hilog/log.h>
void SkDebugf(const char format[], va_list args) {
  char buf[4096];
  vsnprintf(buf, sizeof(buf), format, args);   // OH_LOG_Print 是变参接口,先落 buffer
  OH_LOG_Print(LOG_APP, kPriorityMap[severity], LOG_DOMAIN, "Flutter", "%{public}s", buf);
}

这是「仓库 pin 切换后 fork 补丁文件需要重新叠加」的典型样本:引擎侧 BUILD.gn 的 ohos 分支是 fork 资产,skia 侧实现文件也是 fork 资产,两者的缺口之和才是同步任务的真实工作量。

2.3 P3 esbuild 预编译缺失

# flutter_web_sdk/flutter.js 打包依赖 host 预编译 esbuild(CIPD 包)
cipd install flutter/tools/esbuild/mac-arm64 0.19.5 \
  -root "$ENGINE/src/flutter/build/prebuilts/macos-arm64/esbuild"
# esbuild_version 来自 DEPS vars,别手填版本号
ninja -n default | tail -3   # 干跑通过标志:OHOS 产物边全部可构建

2.4 P4 libcxx 缺 shared/fp_bits.h:漏拉了一个子依赖

from_chars_floating_point.h:13:10: fatal error: 'shared/fp_bits.h' file not found

先别怀疑 libcxx 版本——用两条命令确认它真的不在这个 commit 的树里:

git -C third_party/llvm_libcxx ls-tree -r HEAD --name-only | grep fp_bits.h   # 无输出
git -C third_party/llvm_libcxx cat-file -e HEAD:libcxx/include/__support/...  # 不存在

根因是依赖树落地漏了 third_party/llvm_libc(DEPS:246 llvm_git/llvm-project/libc@5af39a19):libcxx 的 from_chars/to_chars 与 LLVM-libc 共用头库,编译命令里的 -I../../flutter/third_party/llvm_libc 暴露了期望位置。按标准流程补拉该 pin(git init + fetch <sha> + checkout FETCH_HEAD,HEAD 精确对齐)后头文件就位。

三、工具链版本:P5-P6

3.0 先说结论:这两个坑与同步无关

提示:「同步拉新代码」与「本机工具链版本」是两个独立维度。P5-P6 全批最耗时的两个坑,跟 3.47.4 一行代码都没关系。

3.1 P5 libcxx 的 Clang 18 硬门槛(本轮最关键)

libcxx/include/__configuration/compiler.h:37:5: error:
  "Libc++ only supports Clang 18 and later" [-Werror,-W#warnings]

放开 #warning 之后是更深的错误:no template named '__libcpp_remove_reference_t'、unknown type name '__remove_cv'——2026 版 libcxx 大量使用 clang 18 才引入的内建类型特性,clang 15 上无解。

根因:custom_toolchain 指向 DevEco SDK 自带 native/llvm = clang 15.0.4(这台机器的 SDK 是 LLVM 15 时代;hms/native/BiSheng 也是 15.0.4)。官方 OHOS CI 的 --llvm18 flag 语义是「新版 SDK 的 native/llvm 是 LLVM 18」,老 SDK 没有这个选项。

brew install llvm
# args.gn
custom_toolchain = "/opt/homebrew/opt/llvm"   # 首版指向 23.1.1 → 引出 P6
gn gen . --root=../.. --check
# libcxx + libcxxabi 70/70 编译通过;sysroot 继续用 DevEco 的

判断是「升级工具链」还是「回退 pin」的定式:先量化差距(cat-file 验证文件树、按错误类别区分「内建缺失」还是「语法问题」)。内建类型特性缺失是硬约束,只能升级。

3.2 P6 LLVM 23 太新:-Wdeprecated-attributes 毒化老第三方代码

升到 23.1.1 后,一批 third_party 老代码开始大面积报错——新编译器把老写法判成 deprecated 并配合 -Werror 直接失败。这类错误的特征:报错面很宽,但根因只有一个开关。

# 结论:定版 llvm@19 —— 高于 libcxx 的 Clang 18 门槛,又不至于毒化第三方老代码
# /opt/homebrew/opt/llvm@19
custom_toolchain = "/opt/homebrew/opt/llvm@19"
候选结果原因
clang 15.0.4(DevEco 自带)✗libcxx 硬门槛 18,内建类型特性缺失
llvm 23.1.1(brew 默认)✗-Wdeprecated-attributes + -Werror 大面积毒化第三方老代码
llvm@19✓过门槛且不触发新告警;runtime 与 DevEco 交叉库可桥接(见 P24)

方法论:工具链版本选择不是「越新越好」,而是夹在两个约束之间的可行区间:下界由第三方源码的最低编译器要求决定,上界由老代码的告警容忍度决定。

四、musl 与交叉链接:P23-P26

4.1 P23 flatbuffers × _GNU_SOURCE × strto*_l

flatbuffers/util.h:247:24: error: use of undeclared identifier 'strtoll_l'
flatbuffers/util.h:252:24: error: use of undeclared identifier 'strtoull_l'

注意这个 TU 不是 mac host 工具,而是 --target=aarch64-linux-ohos + DevEco sysroot——flatbuffers 源码嵌入 target 的 libflutter.so。完整机理是一条五环链,缺一环都编不过:

  1. clang 对 aarch64-linux-ohos target 的 builtin 宏自带 _GNU_SOURCE=1(-dM 实证,无需任何 -D);
  2. OHOS sysroot 的 musl <features.h> 在 _GNU_SOURCE 下定义 _XOPEN_SOURCE 700;
  3. <unistd.h> 再据之定义 _XOPEN_VERSION 700;
  4. flatbuffers base.h 的 FLATBUFFERS_LOCALE_INDEPENDENT 自判定条件 (defined(_XOPEN_VERSION) && >= 700) && !Fuchsia && !Android 判真 → 走 strto*_l 分支;
  5. OHOS musl 只提供 newlocale/duplocale/freelocale(no-op 占位),整个 strto*_l 族不存在(全 sysroot grep 零命中)。
// flatbuffers/include/flatbuffers/base.h —— 与 __Fuchsia__ 同款排除模式
#if !defined(FLATBUFFERS_LOCALE_INDEPENDENT) && \
    defined(_XOPEN_VERSION) && (_XOPEN_VERSION >= 700) && \
    !defined(__Fuchsia__) && !defined(__ANDROID__) && !defined(__OHOS__)  // musl 无 *_l 族

教训:OHOS 是「clang 固有宏 × musl 函数面」的交叉盲区。任何按 _XOPEN_VERSION / _GNU_SOURCE 自判定启用 POSIX 高级特性的第三方库,都要在 sync 审查时过一遍。排查定式:clang -E -dM -x c++ /dev/null 导出 target builtin,再对 sysroot grep 函数面——条件编译的真值 ≠ 平台能力。

另需注意:flatbuffers 是独立嵌套 git repo、主仓库不 track,这处改动属于 vendored DEPS 目录内的本地修改,gclient 同步后必须复查。

4.2 P24 链接三件套:host clang 驱动 + DevEco runtime 桥接

error: unable to find plugin 'lld'          ← 第一层:ld.lld 不存在
ld.lld: error: cannot open crtbeginS.o      ← 第二层:runtime 文件缺失
ld.lld: error: cannot open libclang_rt.builtins.a (aarch64-linux-ohos)
ld.lld: error: cannot open libunwind.a

机理:homebrew llvm 只分发 host(darwin) 的 clang runtime 与 crt;aarch64-linux-ohos 是华为 fork 专有 triple,交叉 runtime 只在 DevEco SDK 内。而 clang driver 按 CLANG_RESOURCE_DIR/lib/<triple>/ 构造 builtins 绝对路径、按 GCC 方式裸名传递 crtbeginS.o(由 lld 在 -L 里搜)。

版本兼容判断:LLVM 15 的 runtime / 机器码库与 clang 19 驱动、lld 23 兼容(crt/builtins/libunwind 都是普通 ELF 对象,ABI 稳定;-flto 的 bitcode 由 lld 23 消费,19 的 bitcode 向后兼容),可直接复用,不必自己编 compiler-rt。

# 三处部署,全部落在既有搜索路径上,零 gn 改动
cp libclang_rt.builtins.a  llvm@19/lib/clang/19/lib/aarch64-linux-ohos/   # driver 绝对路径命中
cp clang_rt.crtbegin-o.aarch64-ohos.so → crtbeginS.o  llvm@19/lib/        # 链接命令显式 -L
cp libunwind.a(DevEco)                                              llvm@19/lib/
ninja dart && file out/host/.../dart | grep ELF
# → ELF 64-bit aarch64, interpreter /lib/ld-musl-aarch64.so.1  ✓

为什么不放 sysroot:macOS TCC 让 sysroot/usr/lib/aarch64-linux-ohos 写不进去(Operation not permitted)。

教训:交叉 toolchain 的三件套(driver / runtime / linker)必须同源或显式桥接。排查顺序是先让链接器工作(报 executable missing = 环境层),再逐项补 runtime 文件(报 cannot open = 搜索路径层),全部按 driver/lld 的真实搜索路径放置,不臆测。升级 llvm 或 brew 后要复查部署点。

4.3 P25 wasm-opt 缺异常运行时:libcxxabi 的 OHOS 分支必须编真身

ld.lld: error: undefined symbol: __cxa_throw
ld.lld: error: undefined symbol: __cxa_begin_catch
ld.lld: error: undefined symbol: __cxa_get_exception_ptr
ld-temp.o: (after all relocations processed, undefined symbol: DW.ref.__gxx_personality_v0)

四环机理:

  1. binaryen 带异常构建(enable_exceptions)→ 产出异常元数据 → 链接必须解析 Itanium ABI 运行时;
  2. OHOS 工具链走 use_flutter_cxx(is_clang && is_ohos || android || ios)→ 链接命令带 -nostdlib++ → 系统 C++ 库被切断,异常运行时只能来自引擎自编 libcxxabi;
  3. 但 libcxxabi 的源集选择只在 is_mac || is_ios 分支编 cxa_exception.cpp / cxa_personality.cpp,OHOS 落 else 分支编 cxa_noexception.cpp(异常即 abort 的 stub);
  4. DevEco 自带库救不了:libc++abi.a 只提供 __cxa_* 三个符号,__gxx_personality_v0 的真提供者在动态 libc++_shared.so——工具二进制是链接产物,不能靠运行时 .so 喂。

官方 host 构建为什么不炸?因为 host(mac/linux)工具链不设 use_flutter_cxx,链接系统 libc++/libstdc++,异常运行时由系统库兜底——这个依赖被「官方能过」的表象掩盖了。

# build/secondary/flutter/third_party/libcxxabi/BUILD.gn 与 libcxxabi_ohos/BUILD.gn —— 两处都要改
} else if (is_ohos) {
  configs -= [ "//build/config/gcc:no_exceptions" ]
  sources += [
    "src/cxa_exception.cpp",    # 镜像 Darwin 处理
    "src/cxa_personality.cpp",
  ]
}
gn gen . --root=../.. --check && ninja wasm-opt     # 18 步 CXX + LINK 全绿
nm -g out/.../wasm-opt | grep -E '__cxa_|personality|_Unwind_'
# __cxa_throw / __cxa_begin_catch / __cxa_get_exception_ptr → T(已解析)
# __gxx_personality_v0 → 小写 t(-fvisibility=hidden + LTO 本地化)
# _Unwind_RaiseException/Resume/GetLanguageSpecificData → 由 P24 部署的 DevEco libunwind.a 提供

异常运行时的四个来源在此闭环:引擎 libcxxabi 真身 + libunwind + DevEco builtins + crt。少任何一个,症状都是「undefined symbol 换个名字继续出现」。

4.4 P26 har 双修复:扫描层级与 API 26 面

flutter_embedding_release.har 打包阶段两处问题:DEVECO_SDK 目录扫描层级不对(找不到 SDK 根),以及 ArkTS autofill 相关 API 在 API 26 面上的签名差异。两处都在 har 打包脚本内修,属于「产物面」而非「代码面」——细节归入第 9 篇的发布链路一起讲。

五、收尾:P27 幽灵依赖与 P27b 测试面漂移

5.1 P27 libflutter.so 的 Json:😗 未定义

ld.lld: error: undefined symbol: Json::CharReaderBuilder::newCharReader()
>>> referenced by flutter_ohos_sources.ohos_vsync_voting_mgr.o

定位方法值得单独学——rsp 对象清单 × 符号名做二分,比全 obj/ 扫描快一个数量级:

tr ' ' '\n' < libflutter.so.rsp | grep '\.o$' \
  | xargs grep -l 'newCharReader'      # → 唯一命中 ohos_vsync_voting_mgr.o

链条:LTPO vsync voting 管理器用 jsoncpp 解析可选的 framesconfig.json 翻译表(约 23 行);而 //third_party/jsoncpp:jsoncpp 只出现在 flutter/skia/BUILD.gn 的 fontmgr_ohos optional target deps 里,且 skia_enable_fontmgr_ohos = false(fork 同步时加的 declare_args 默认值,官方 CI 即 false)→ optional 未启用 → deps 不解析、jsoncpp 实现从不进构建图;但 shell/platform/ohos/BUILD.gn 的 include_dirs 却把 spirv-headers 自带的 amalgamated jsoncpp 头路径注入了全体 OHOS TU。

这就是「半配置」形态:给了全体 TU 头 include 路径,却没有配套实现 target。同理 optional disabled target 的 deps 是幽灵依赖——gn 图里完全不存在,label 即使不定义也不报错,但 grep 源码看得见。判断「这个依赖到底存不存在」只能信构建图:ninja -t targets | grep jsoncpp。

修复四步(零新增网络依赖):

# (a) 新建 engine/src/third_party/jsoncpp/BUILD.gn:代理 target,指向 spirv-headers 的 amalgamated 1.6.2
source_set("jsoncpp") {
  sources = [ "//third_party/spirv_headers/.../dist/jsoncpp.cpp" ]
  include_dirs = [ "//third_party/spirv_headers/.../dist" ]
  # (b) jsoncpp 1.6.2 的 RuntimeError 直接 throw,全局 -fno-exceptions 编不过
  configs -= [ "//build/config/gcc:no_exceptions", "//build/config/compiler:no_rtti" ]
}
# (c) flutter_ohos_sources deps += "//third_party/jsoncpp"(libflutter.so 与两个测试 exe 自动继承)
// (d) 同轮修两个测试 TU 的 3.47.4 签名适配
// paint_ohos_unittests.cc:CreateContents 由 (const Geometry*) 扩为
//   (const ContentContext&, const Geometry*, std::optional<Matrix>)
//   —— 测试链上没有 MockContext,且 ContentContext 构造即解引用 context,无法轻量造假
//   → 重写为「SolidColorContents + SetColorWithSpace 的产物契约断言」
// qos_ohos_unittests.cc:AddFence 插入 submit_callback → 补 [](vk::Fence){ return fml::Status(); }

⚠️ 本条实录里「开关恢复不可行」的结论后来被 P28 反转(见第 10 篇):前半句「3.47.4 的 skia checkout 没有 src/ports/skia_ohos/」是事实,但推不出「不可恢复」——skia 树是 DEPS 拉取、零 tracked 文件,本来就不该就地改;仓库早有 attachment 机制专门托管「checkout 里没有、构建却需要」的文件。正确的三分法是:checkout 里有没有 / 仓库能不能托管 / 构建图认不认。

5.2 P27b 测试面漂移的五个规律

测试面是 API 漂移的最后暴露面。本批次五个规律已沉淀,下一批次可直接预判:

#规律本批实例修法
1工厂函数搬家DlImage::Make → DlImageSkia::Make调用点迁移 + 显式 include dl_image_skia.h(dl_sk_canvas.h 只给前向声明)
2既有函数扩参FenceWaiterVK::AddFence 插 submit_callback补占位 lambda,先读实现再填语义
3新增纯虚Context::FinishQueue()、PlatformView::Delegate::OnPlatformViewHitTestfake 补实现,返回 {.has_platform_view = false}(抄上游 ios test 同款)
4测试 hack 与新标准库冲突#define private public × libc++ <ranges><ranges> 预热(见下)
5产物契约断言替代 mockPaint::CreateContents 扩参且无法造假 context改成对最终产物断言,不再 mock

规律 4 的批量修复(P22 是首例,ninja25-27 在另外 4 个 TU 复现):

// 只要 #define private public(或 protected)窗口内首次展开 libc++ <ranges> 就会炸,
// 与 hack 的目标头无关 —— 所以修复是「预热」,不是「挪窗口」
#include <ranges>   // 在干净环境里完整展开一次,之后 include guard 让宏窗口内变 no-op
#define private public
#include "flutter/shell/platform/ohos/xxx.h"
// (本文件无 #undef,宏泄漏到后续 include —— 属历史遗留,预热后可安全保留)

取舍判据按访问面规模:个位数私有成员 → friend + tunnel(干净、无 libc++ 风险);数十上百处 → 预热保留 hack(改动最小)。本批次 4 个文件用预热,external_view_embedder_unittests(4 个私有成员)是唯一 friend 例外。

三层教训:① 老 hack 在新工具链下的污染面 ≠ 原点,要问「宏窗口里还有什么头」(include trace),而不是只看 hack 目标;② hack/friend 按访问面规模取舍;③ 接口漂移的适配点可以从上游同平台测试(android/ios 的 _test.mm)抄「正确写法」,比猜 3.47.4 语义快且稳。

六、诊断方法论:构建链的四把尺子

6.1 ninja 图与磁盘文本可能不同步

「图上存在、文件树没有」的边,先看 per-dir .ninja 的 mtime 分布——增量 regen 只重写顶层文件是 gn 的正常行为,不是 bug。确认之后再决定要不要全量重建。

6.2 -t query / -t targets 才是图的真相

ninja -t query <target-or-edge>      # 这条边是否真在「当前加载的图」里
ninja -t targets all | grep jsoncpp  # 这个依赖是否真的进了图
gn desc . deps //path:to/target      # 反查某个 target 的依赖闭包

grep 源码会骗你(幽灵依赖),compile_commands.json 也会骗你(P1 里它和 ninja 图不一致),只有 ninja 自己的查询工具吃的是当前加载的清单。

6.3 grep 的盲区:-r 不跟随符号链接

grep -r  'OHOSLastFontPath' engine/src   # 可能漏(不跟随 symlink)
grep -R  'OHOSLastFontPath' engine/src   # 正确:-R 跟随符号链接

本批次 P1 的现象一度把诊断引向「grep 失灵」的歧途,最后靠 ninja -t query 一锤定音。

6.4 undefined symbol 的二分定位

# 不要全 obj/ 扫描;用链接命令的对象清单做二分
tr ' ' '\n' < libflutter.so.rsp | grep '\.o$' | xargs grep -l '<symbol>'
# 再验证符号提供方:nm 看 T / t / U
nm -g --undefined-only <obj> | grep '<symbol>'

七、M2 终态与验收判据

7.1 四 target 齐过

ninja28 全绿,0 FAILED
  libflutter.so                生产代码全量
  flutter_ohos_unittests       32 MB,全量 OHOS 单测链接
  flutter_har_zip              flutter.har 7.4MB + arm64_v8a_release.har 7.1MB
                               + flutter_embedding_release.har(hvigor BUILD SUCCESSFUL)
  clang_arm64/gen_snapshot_arm64

7.2 配套产物清单

产物用途缺失后果
flutter_patched_sdk/platform_strong.dillJIT/AOT 编译的 platform kernelframework 编译失败或行为不一致
icudtl.datICU 数据运行期国际化崩
libflutter.so.TOC符号表索引增量链接判定失效
so.unstripped/libflutter.so带符号产物崩溃栈无法解析

M1 的 gn gen 成功不代表构建图完整:ninja 加载与子命令阶段仍会暴露树层面的缺口(文件缺失、pin 错位)。两类问题合流,才构成「同步完整性」的完整定义——这也是本篇和 P7-P22 那组源码适配坑必须分开记账的原因。

八、常见问题 FAQ

8.1 为什么坑分两篇写?

按发现阶段分:P7-P22 在「合并与编译生产代码」时暴露(第 7 篇),P1-P6 与 P23-P27b 在「构建链与产物」阶段暴露(本篇)。混在一起写会丢掉最有价值的信息——每个阶段该看什么证据。

8.2 「编译不过」第一步查什么?

先分类,别急着改代码:报 file not found / missing and no known rule → 依赖树(本篇 §二);报 #error 或内建类型缺失 → 工具链(§三);报 undeclared identifier 且是平台扩展 → musl/头面(§四);报 undefined symbol → 链接与幽灵依赖(§四、§五)。

8.3 能不能直接固定 llvm@19 跳过 P5-P6?

可以跳过坑,跳不过判据。可行区间由两个约束夹出来:下界 = 第三方源码的最低编译器要求(libcxx 要 Clang 18+),上界 = 老代码的告警容忍度(LLVM 23 的 -Wdeprecated-attributes)。换批次、换上游版本后这两个约束都会移动,每次都要重新量化。

8.4 vendored 目录里改代码安全吗?

flatbuffers、spirv-headers 这类是独立嵌套 git repo,主仓库不 track。改动必须:① 加注释说明平台原因;② 记入「本地修改清单」;③ gclient / DEPS 同步后复查——否则下一次拉取就静默丢失。

8.5 har 打包失败算构建问题还是发布问题?

算构建链的产物面(P26 本篇记账),但修复策略要连着发布链路一起看——覆盖行、工件布局、版本锁在第 9 篇。只修 har 不修发布面,会出现「本机能出包、别人装不上」。

8.6 测试面漂移为什么值得单列五规律?

因为它是最便宜的上游 API 变化探测器:生产代码可以靠守卫躲过去,测试 fake 躲不过——纯虚新增、工厂搬家、扩参都会在这里以最小代价暴露。下一批次按五规律预判,能省掉一半的测试面返工。

九、本篇小结与系列导航

本篇走完 M2(引擎构建)阶段:

  • 依赖树(P1-P4):stale 混合图用 find -newermt + -t query 判;悬空引用是 fork 资产缺口;CIPD 与子依赖按 pin 补拉;
  • 工具链(P5-P6):libcxx 的 Clang 18 硬门槛 + LLVM 23 的告警毒化,定版 llvm@19 是「两约束夹出的可行区间」;
  • musl 与交叉链接(P23-P25):_GNU_SOURCE builtin 链、三件套桥接、libcxxabi 必须编真身;
  • 产物与收尾(P26-P27b):har 双修复、jsoncpp 幽灵依赖、测试面漂移五规律;
  • 终态:ninja28 全绿 0 FAILED,四 target + 四类配套产物齐备。

「鸿蒙Flutter框架 0day适配实战」全系列 10 篇,本篇为第 8 篇(生产侧 M2 构建链):

#篇目侧一句话主题
01背景、分叉仓库、版本命名与 fvm fork 安装消费三层架构、三个接缝、七层工作量、fvm 直装 tag、版本墙
02tag 版本的手动安装与三层核验消费浅克隆 bootstrap、首次初始化、SSH 修复、异常分诊
03鸿蒙模拟器/真机跑 Flutter + 消费侧踩坑集消费工具链三件套、HAP 构建/安装/运行、占位签名、截图取证、CP1-CP14
04flutter build hap 自动签名消费P12/CSR、AGC 证书与 Profile、纯 Dart AES-GCM、C0-C10 复用
05上游同步总览、BC 全景与端到端 SOP生产批次解剖、五阶段验证门、12 项适配表、端到端 SOP 与 P1-P29 索引
06Phase 1+2:基线建立与 Framework 适配生产代理 HTTP/1.1 修障、421 文件重打、Dart SDK 源码构建
07Phase 3 Engine:三方重放与冲突裁决生产826 文件、15 真冲突两分法、契约对齐 16 项
08引擎构建链攻防(本篇)生产依赖树、工具链版本、musl 交叉链接、测试面漂移
09构建发布与产物分发生产版本锁、11 个工件(Dart SDK 双宿主)、11 条覆盖键、换新 tag 铁律
10验证与真机验收生产三档引擎、设备侧单测、字体图标静默故障

总结

M2 这一阶段最大的认知收益是三句话:「gn gen 成功」不等于「构建图完整」,树层面的缺口要到 ninja 加载期才现形;构建失败的第一手信息是错误类别,不是错误文本——依赖树、工具链、musl 函数面、幽灵依赖四类各有对应的取证命令;测试面是最便宜的上游 API 探测器,五规律(工厂搬家、扩参、新增纯虚、hack × 标准库、契约断言替代 mock)足以预判下一批次一半的返工。

到这里,引擎侧「能编出来」已经成立。下一篇(第 9 篇)解决「怎么把它发出去、让别人一条命令装到」:bin/internal/ 的版本锁体系、cache.dart 的下载接线、11 个工件(引擎 5 + Dart SDK 4 + patched_sdk 2,其中 Dart SDK 按宿主架构各编一份)、11 条覆盖键机制,以及那条用真机事故换来的铁律——换内容必须换新 tag。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

环境与版本

本文的示例与截图基于以下环境组合(2026-10 实测):

组件版本说明
上游 Flutter SDK3.47.8github.com/flutter/flutter
Flutter SDK(OH 适配版)3.47.8-ohos-1.0.0oh-flutter/flutter_flutter,oh-3.47-dev 分支
DevEco Studio26.0.0 Release提供 hvigor / ohpm / hdc / JBR 等工具链
OpenHarmony SDK / APIAPI 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 线同样适用。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐