Reland: Ship gen_snapshot for linux-arm64 hosts targeting Android#187591
Reland: Ship gen_snapshot for linux-arm64 hosts targeting Android#187591dbebawy wants to merge 3 commits into
Conversation
|
It looks like this pull request may not have tests. Please make sure to add tests or get an explicit test exemption before merging. If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix? Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. If you believe this PR qualifies for a test exemption, contact "@test-exemption-reviewer" in the #hackers channel in Discord (don't just cc them here, they won't see it!). The test exemption team is a small volunteer group, so all reviewers should feel empowered to ask for tests, without delegating that responsibility entirely to the test exemption group. |
There was a problem hiding this comment.
Code Review
This pull request introduces support for cross-compiling host binaries (such as gen_snapshot for linux-arm64 on a linux-x64 builder) by adding a --host-cpu argument to the GN build tool. It introduces host_clang_cpu to represent the CPU architecture of the toolchain binaries running on the build machine, updates GN build configurations and CI configurations, and adds a new linux_arm64_android_aot_engine builder. The review feedback suggests simplifying the argument parsing logic in engine/src/flutter/tools/gn to reduce duplication and wrapping a help string to adhere to the 80-character line limit of the Google Python Style Guide.
| if is_host_build(args): | ||
| gn_args['host_os'] = get_host_os() | ||
| gn_args['host_cpu'] = get_host_cpu() | ||
| gn_args['host_cpu'] = args.host_cpu or get_host_cpu() | ||
| gn_args['target_os'] = gn_args['host_os'] | ||
| gn_args['target_cpu'] = get_target_cpu(args) | ||
| gn_args['dart_target_arch'] = gn_args['target_cpu'] | ||
| else: | ||
| gn_args['host_cpu'] = args.host_cpu or get_host_cpu() | ||
| gn_args['target_os'] = args.target_os | ||
| gn_args['target_cpu'] = get_target_cpu(args) | ||
| gn_args['dart_target_arch'] = gn_args['target_cpu'] |
There was a problem hiding this comment.
We can simplify this block by pulling out the common assignments (host_cpu, target_cpu, and dart_target_arch) that are identical in both the if and else branches. This improves readability and maintainability by reducing code duplication, adhering to the Repository Style Guide's recommendation to suggest simplification and refactoring.
| if is_host_build(args): | |
| gn_args['host_os'] = get_host_os() | |
| gn_args['host_cpu'] = get_host_cpu() | |
| gn_args['host_cpu'] = args.host_cpu or get_host_cpu() | |
| gn_args['target_os'] = gn_args['host_os'] | |
| gn_args['target_cpu'] = get_target_cpu(args) | |
| gn_args['dart_target_arch'] = gn_args['target_cpu'] | |
| else: | |
| gn_args['host_cpu'] = args.host_cpu or get_host_cpu() | |
| gn_args['target_os'] = args.target_os | |
| gn_args['target_cpu'] = get_target_cpu(args) | |
| gn_args['dart_target_arch'] = gn_args['target_cpu'] | |
| gn_args['host_cpu'] = args.host_cpu or get_host_cpu() | |
| gn_args['target_cpu'] = get_target_cpu(args) | |
| gn_args['dart_target_arch'] = gn_args['target_cpu'] | |
| if is_host_build(args): | |
| gn_args['host_os'] = get_host_os() | |
| gn_args['target_os'] = gn_args['host_os'] | |
| else: | |
| gn_args['target_os'] = args.target_os |
References
- Suggest simplification and refactoring: Assess whether the code can be made simpler or refactored to enhance readability and maintainability. (link)
| parser.add_argument('--host-cpu', type=str, choices=['x64', 'arm64'], | ||
| help='Override the auto-detected host CPU. Use to cross-compile host binaries (e.g., gen_snapshot for linux-arm64 from a linux-x64 builder).') |
There was a problem hiding this comment.
The help string for the --host-cpu argument exceeds the 80-character line limit specified in the Google Python Style Guide (referenced in the Repository Style Guide). We should wrap the string to comply with the style guide and improve readability.
| parser.add_argument('--host-cpu', type=str, choices=['x64', 'arm64'], | |
| help='Override the auto-detected host CPU. Use to cross-compile host binaries (e.g., gen_snapshot for linux-arm64 from a linux-x64 builder).') | |
| parser.add_argument('--host-cpu', type=str, choices=['x64', 'arm64'], | |
| help='Override the auto-detected host CPU. Use to cross-compile ' | |
| 'host binaries (e.g., gen_snapshot for linux-arm64 from a ' | |
| 'linux-x64 builder).') |
References
- Python code is formatted using yapf, linted with pylint, and should follow the Google Python Style Guide (which enforces an 80-character line limit). (link)
|
Runtime smoke test on real arm64 Linux — PASS, end-to-end. Done locally on Apple Silicon under a Steps and outputs: End-to-end AOT compile (the gold-standard test): Attempting to run the AOT'd snapshot under the linux-arm64 dartaotruntime produces the most reassuring possible "failure": The snapshot is tagged Full results recorded at |
|
Tagging in the folks closest to the previous round on this work:
@gmackall — already auto-requested by CODEOWNERS, no extra ping needed; just noting you'll see this come through normal review. Happy to discuss any of the architectural choices on the PR or hop on Discord (#hackers-new or #hackers-engine). |
Two style/readability fixes flagged on flutter#187591: 1. Factor out the assignments that are identical in both branches of the `is_host_build(args)` check (`host_cpu`, `target_cpu`, `dart_target_arch`). The if/else now only differs in how it sets `host_os` and `target_os`. Behavior is unchanged -- `get_target_cpu(args)` reads `args.target_os`, not the partially-built `gn_args` dict, so ordering doesn't matter. 2. Wrap the new `--host-cpu` argparse help string to fit the 80-character line limit from the Google Python Style Guide (referenced by Flutter's `.gemini/styleguide.md`). No functional change.
|
Thanks @gemini-code-assist. Both applied in a4a8ee8:
|
|
On @flutter-dashboard's tests notice: this PR is test-exempt per the Tree Hygiene policy — it's a build-system / CI configuration change with no Dart/runtime surface to unit-test. The behavior is exercised by CI builders themselves: the new Out-of-CI validation that the build system mechanics are correct:
The framework half of #182552 — which DID have unit tests — is split out to #187593 (currently draft, will flip to ready when artifacts ship). |
|
The provided validation results and the strategy for handling test exemption for build-system changes are noted. This approach aligns with standard practices for CI configuration updates. |
reidbaker
left a comment
There was a problem hiding this comment.
Thank you for your continued effort helping expand flutter. I added some teammates with a better understanding of our engine build process.
gaaclarke
left a comment
There was a problem hiding this comment.
@jason-simmons made the fair point that google doesn't officially support developing Android apps on linux-arm64, so it may not be wise for us to try to make it an officially supported platform before the fact.
| # CPU arch of toolchain binaries that run on the build machine (clang, llvm | ||
| # tools). Equals host_cpu normally; differs when cross-compiling host | ||
| # binaries (e.g. linux-arm64 gen_snapshot on a linux-x64 builder). | ||
| host_clang_cpu = host_cpu |
There was a problem hiding this comment.
This variable is confusing, can't we derive what we want from a combination of looking at host_cpu and target_cpu?
There was a problem hiding this comment.
builder_cpu might be more clear?
There was a problem hiding this comment.
Done in 3ab8fab — agree, builder_cpu reads better. Updated the declare_args doc comment to use the new name and reflect the broader scope (covers both clang and the build-time Dart SDK path resolution, not just clang). Pure mechanical rename, no behavioral change.
|
@gaaclarke — fair concern, and worth addressing directly. The "Google doesn't officially support developing Android apps on linux-arm64" framing maps cleanly to the original PR (#182552), which needed arm64 LUCI bots and depended on the Android SDK getting a This reland is deliberately a different shape:
The actual user demand: #75864 has been open since 2021 (a year before Graviton, well before T2A); #168980 is real users hitting Validation done (four independent runs, posted in this thread):
If there's a specific maintenance risk you're worried about that this framing misses, happy to dig into it. The goal here is to add a producible artifact without inheriting any of the support obligations from the original PR. |
Per @gaaclarke's review feedback on flutter#187591 (BUILDCONFIG.gn:154). builder_cpu is clearer than host_clang_cpu: - Says what the var ROLE is (the build host's CPU) rather than what it IS (the CPU clang runs on). - Generic -- covers both the clang binary AND the build-time Dart SDK, not just clang. - Maps to existing CI vocabulary (LUCI "builder", "bot"). - Decouples the name from an implementation detail. Pure mechanical rename across 4 files. No behavioral change. Updated the declare_args() doc comment in BUILDCONFIG.gn to use the new name and reflect the broader scope.
|
Fourth independent validation: native arm64 build (Apple Silicon → linux/arm64 Docker, native — no QEMU).
The byte-exact size match (5,314,408 across all three) confirms the patches don't regress the native arm64 path. SHA differs only because arm64 clang on native arm64 and x64 clang The runs above ran against the One small finding worth noting (filed separately as #187627): Full results + artifacts saved to |
jason-simmons
left a comment
There was a problem hiding this comment.
With these changes, can flutter run and flutter build apk successfully build an Android APK package on a linux-arm64 host?
My understanding was that other parts of the tools (such as the Gradle scripts) would be unable to complete the build process until the official Android SDK starts distributing a build for linux-arm64.
| gn_args['enable_lto'] = enable_lto | ||
|
|
||
| # Set OS, CPU arch for host or target build. | ||
| gn_args['host_cpu'] = args.host_cpu or get_host_cpu() |
There was a problem hiding this comment.
The host_cpu variable in GN should represent the platform where the build is running. Its value should not be replaced by the target CPU.
Cross-compilation can be done by specifying a different toolchain in the label for a target.
See https://gn.googlesource.com/gn/+/HEAD/docs/cross_compiles.md and https://gn.googlesource.com/gn/+/main/docs/reference.md#label_pattern
|
I'm under the impression we may not even want this until Android proper support linux-arm. @jtmcdole can we give this PR a definitive direction before asking the OP the spend more time addressing feedback? |
|
Gently bumping this — last activity was @gaaclarke's request for direction from @jtmcdole on 2026-06-08 (17 days ago). Whatever the call:
Just want to avoid sitting on a stale signed branch for too long. Whichever direction works — let me know and I'll move on it. Happy to hop on Discord (#hackers-engine) if a sync conversation is faster. |
Relands flutter#182552, reverted in flutter#186693 because the linux-arm64 builder introduced in that PR had no bots in the LUCI pool and `flutter precache --all` started 404'ing on missing `linux-arm64.zip` artifacts after the beta cut. This reland takes the direction @jtmcdole laid out on the original PR on 2026-05-20 ("bypass the linux_arm64 tooling requirement since we don't have official binaries in cipd"): the new `linux_arm64_android_aot_engine` builder runs on the **existing linux-x64 bot pool** and cross-compiles the host `gen_snapshot` binary to linux-arm64. No new arm64 bots, no dependency on the `flutter/android/sdk/all` CIPD package getting a linux-arm64 platform build. Engine code changes (+20 / -7 across 6 files): - DEPS: drop `host_cpu == "arm64"` clause on the linux-arm64 clang CIPD download so x64 builders also fetch it (the clang binary is never executed; cross-compile happens via `--target=aarch64-linux-gnu` using the x64 clang). - engine/src/build/config/BUILDCONFIG.gn: introduce `host_clang_cpu`, a new gn arg defaulting to `host_cpu`. Represents the CPU of build-time tools that run on the builder, distinct from `host_cpu` (the CPU the produced host binary runs on). No-op for native builds. - engine/src/build/toolchain/linux/BUILD.gn: clang binary path is now keyed on `host_clang_cpu` instead of `host_cpu`, so the executable used during the build stays the x64 clang even when producing arm64 output. - engine/src/flutter/common/config.gni: the prebuilt Dart SDK path for build-time tooling (frontend server, kernel-to-AOT-snapshot) follows `host_clang_cpu`, so it resolves to the x64 dart-sdk on the builder. - engine/src/flutter/shell/platform/android/BUILD.gn: zip names use `linux-\$host_cpu.zip` interpolation (matching what `flutter precache` looks for on arm64 hosts), and the gen_snapshot zip's deps now mirror what analyze_snapshot already does -- depend only on the host binary instead of `generate_snapshot_bins`, which runs gen_snapshot during the build (impossible when cross-compiled to aarch64). - engine/src/flutter/tools/gn: add a `--host-cpu` flag, apply it in both `is_host_build()` and the cross-target (`else`) branches (the original PR only set host_cpu in the `is_host_build` branch, which is why `--host-cpu=arm64 --android` silently produced x86_64), and set `host_clang_cpu` to the build-machine CPU only when cross-compiling. Plus CI plumbing for this reland: - engine/src/flutter/ci/builders/linux_arm64_android_aot_engine.json: new builder config based on the reverted PR's version with four deltas: drone_dimensions drop `cpu=arm64` (runs on x64 pool), gn args add `--host-cpu arm64`, gclient_variables add `download_android_deps: false` and `download_jdk: false`, and ninja targets are the explicit `zip_archives/.../linux-arm64.zip` paths rather than the `flutter/shell/platform/android:gen_snapshot` umbrella target. - engine/src/flutter/.ci.yaml: matching builder entry, same shape as the reverted PR's entry, `cpu=arm64` removed from drone_dimensions, `bringup: true`. The framework changes from the reverted PR (flutter_tools cache / artifact-path resolution) are intentionally NOT included here. They land in a follow-up PR once the new builder has produced `linux-arm64.zip` to GCS for at least one release. That sequencing is the direct fix for the `flutter precache --all` 404 that triggered the original revert: artifacts before manifest. Empirically validated on native linux-x64 hardware (Debian 12 Docker, real Intel x86_64, no QEMU): - Original validation 2026-05-29 against `c7f49fe0` produced `linux-arm64.zip` (1.77 MB) and `analyze-snapshot-linux-arm64.zip` (2.8 MB). - Fresh re-validation 2026-06-04 against `14c05c42` (then-current `main`): patches apply cleanly, `ninja clang_arm64/gen_snapshot` exits 0 in 6m37s `-j8`, output binary is ELF aarch64 PIE, glibc-linked, not Android bionic. Binary SHA256: 898413a122ef33d42044fb0a2aa3259803f051ef9063b4d8ddd5e4eb7fa49cd0. Runtime smoke test on real arm64 hardware (Graviton / arm64 VM / etc.) is still owed before flipping `bringup: true` to false. QEMU user-mode segfaults on gen_snapshot -- a known qemu limitation with executable mmap / TLS-heavy runtimes, not a binary defect. Fixes flutter#75864 Fixes flutter#168980
Two style/readability fixes flagged on flutter#187591: 1. Factor out the assignments that are identical in both branches of the `is_host_build(args)` check (`host_cpu`, `target_cpu`, `dart_target_arch`). The if/else now only differs in how it sets `host_os` and `target_os`. Behavior is unchanged -- `get_target_cpu(args)` reads `args.target_os`, not the partially-built `gn_args` dict, so ordering doesn't matter. 2. Wrap the new `--host-cpu` argparse help string to fit the 80-character line limit from the Google Python Style Guide (referenced by Flutter's `.gemini/styleguide.md`). No functional change.
Per @gaaclarke's review feedback on flutter#187591 (BUILDCONFIG.gn:154). builder_cpu is clearer than host_clang_cpu: - Says what the var ROLE is (the build host's CPU) rather than what it IS (the CPU clang runs on). - Generic -- covers both the clang binary AND the build-time Dart SDK, not just clang. - Maps to existing CI vocabulary (LUCI "builder", "bot"). - Decouples the name from an implementation detail. Pure mechanical rename across 4 files. No behavioral change. Updated the declare_args() doc comment in BUILDCONFIG.gn to use the new name and reflect the broader scope.
|
Also rebased the branch onto current |
3ab8fab to
1ade06d
Compare
Summary
Reland of #182552 ("Ship gen_snapshot for linux-arm64 hosts targeting Android"), reverted in #186693 because the new
linux_arm64_android_aot_enginebuilder had no bots in the LUCI pool andflutter precache --allbegan 404'ing on missinglinux-arm64.zipartifacts after the beta cut.This reland takes the approach @jtmcdole suggested on the original PR (2026-05-20: "bypass the linux_arm64 tooling requirement since we don't have official binaries in cipd"): the new builder runs on existing linux-x64 bots and cross-compiles the host
gen_snapshotbinary tolinux-arm64. No new arm64 bots, no dependency on the Android SDK CIPD package adding alinux-arm64platform build (#186695 stays in scope for the broader story but is no longer blocking this).Validated empirically on native linux-x64 hardware: original run 2026-05-29 against
c7f49fe0; fresh re-validation 2026-06-04 against14c05c42a5556a515deea59a1e93553f10575cbf(currentmainat the time) producing the same result — patches apply cleanly, build succeeds, output binary isELF aarch64 PIElinked against glibc. Build wall-clock: 6 m 37 s-j8. Output binary:5,314,408bytes, SHA256898413a122ef33d42044fb0a2aa3259803f051ef9063b4d8ddd5e4eb7fa49cd0. From the 2026-05-29 run:zip_archives/android-arm64-release/linux-arm64.zip(1.77 MB) — the exact artifact whose absence caused the revert.Fixes #75864
Fixes #168980
Engine changes (+20 / −7 across 6 files)
Introduces a
host_clang_cpugn arg distinct fromhost_cpu. Declared inengine/src/build/config/BUILDCONFIG.gnwith defaulthost_clang_cpu = host_cpu, it represents the CPU of build-time tools that run on the builder — distinct fromhost_cpu, which is the CPU the produced host binary runs on. The two are equal for native builds (no-op) and diverge only when--host-cpuis set to cross-compile.DEPS(1 line) — drop thehost_cpu == "arm64"clause on theflutter/buildtools/linux-arm64/clangCIPD download so x64 builders also fetch it (the clang binary is never executed; only its sysroot/headers are consumed for--target=aarch64-linux-gnulinking via the x64 clang). Cost: one extra CIPD download (~150 MB) per linux-x64 builder.engine/src/build/config/BUILDCONFIG.gn(5 lines added) — declarehost_clang_cpu = host_cpuas a new gn arg.engine/src/build/toolchain/linux/BUILD.gn(1 line changed) — the clang binary path is now keyed onhost_clang_cpu(= x64 on the builder), so the executable used during the build remains the x64 clang. Producing arm64 output still happens via--target=aarch64-linux-gnu, whichbuild/config/compiler/BUILD.gn:400-402already adds based oncurrent_cpu.engine/src/flutter/common/config.gni(1 line changed) — the underscore-prefixed_host_cpuused to constructhost_prebuilts_pathnow followshost_clang_cpu, so the prebuilt Dart SDK used by build-time tooling (frontend server, kernel→AOT-snapshot) resolves to the x64 dart-sdk on an x64 builder.engine/src/flutter/shell/platform/android/BUILD.gn(3 lines changed) — three fixes in the gen_snapshot/analyze_snapshot zip bundles: host-aware zip name (linux-x64.zip→linux-$host_cpu.zip) for both zips, and thegen_snapshotzip's dep mirrors whatanalyze_snapshotalready does — depends only on the host gen_snapshot binary instead ofgenerate_snapshot_bins, which runs gen_snapshot during the build (impossible when cross-compiled to aarch64; verified failure:qemu-aarch64: Could not open '/lib/ld-linux-aarch64.so.1').engine/src/flutter/tools/gn(10 lines added) — three changes:args.host_cpu or get_host_cpu()in bothis_host_build()ANDelse/cross-target branches (the original PR's wrapper only set host_cpu in theis_host_buildbranch, which is why--host-cpu=arm64 --androidsilently produced x86_64); a separate 4-line block that setshost_clang_cpuonly when cross-compiling (args.host_cpu and args.host_cpu != get_host_cpu()); and the new--host-cpuargparse flag mirroring the existing--linux-cpupattern.The validated diff lives at
.claude/plan-b-validation/planb.diff(5,275 bytes). Hunk-by-hunk walkthrough: .claude/reland-diff-sketch.md.Plus, added for this PR (not in the validated diff)
engine/src/flutter/ci/builders/linux_arm64_android_aot_engine.json— based on the reverted PR's JSON (fromf914eaeb3fb) with four deltas: (a)drone_dimensionsdropscpu=arm64so the builder runs on the existing x64 pool, (b)gnargs add--host-cpu,arm64, (c)gclient_variablesadddownload_android_deps: falseanddownload_jdk: false(gen_snapshot doesn't need either at build time — verified empirically, Step 4 of verification results), (d)ninjatargets are the explicitzip_archives/android-<cpu>-<mode>/linux-arm64.zip(andanalyze-snapshot-linux-arm64.zipfor 64-bit) rather than theflutter/shell/platform/android:gen_snapshotumbrella target. 8 build configs total (arm/arm64/x64/riscv64 × {profile, release}).engine/src/flutter/.ci.yaml— matching builder entry, same shape as the reverted PR's entry,cpu=arm64removed fromdrone_dimensions,bringup: true.Framework changes — held back
The framework changes from the original PR (
packages/flutter_tools/lib/src/flutter_cache.dart,artifacts.dart, tests) are NOT included in this PR. They will land in a follow-up once the new builder has producedlinux-arm64.zipartifacts to GCS for at least one release. This sequencing avoids the precache-404 regression that triggered the original revert: framework code must not advertise artifact paths that don't yet exist.Test plan
Empirically validated on native linux-x64
Two independent build runs, both on a Debian 12 (
debian:12) Docker container on a native Intel x86_64 host (Intel i7-9750H — real CPU inside the container, no QEMU). Patches applied cleanly to both base commits. Full notes: .claude/plan-b-validation/VERIFICATION_RESULTS.md (fresh 2026-06-04 rebuild), .claude/planb_verification_results.md (original 2026-05-29).flutter/flutter@14c05c42a5556a515deea59a1e93553f10575cbf(current main at the time)flutter/flutter@c7f49fe0engine/src/flutter/buildtools/linux-arm64/clang/bin/clangexistsengine/src/build/linux/debian_bullseye_arm64-sysroot/populateddownload_android_deps: false+download_jdk: falsehonored;third_party/android_tools/sdkabsentargs.gnreflects overridehost_cpu = "arm64",target_os = "android",target_cpu = "arm64",host_clang_cpu = "x64"flutter/buildtools/linux-x64/clang/bin/clang++ --target=aarch64-linux-gnu --sysroot=<arm64-sysroot> -o clang_arm64/gen_snapshotninja -C out/android_release_arm64 clang_arm64/gen_snapshot-j8, 1212 targets (fresh run)file clang_arm64/gen_snapshotELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, strippedreadelf -hELF64,UNIX - System V,DYN,AArch64NEEDEDlibslibdl.so.2 libpthread.so.0 libm.so.6 libc.so.6 ld-linux-aarch64.so.1(glibc only — confirms linux-gnu host, not android-bionic)5,314,408bytes,898413a122ef33d42044fb0a2aa3259803f051ef9063b4d8ddd5e4eb7fa49cd0zip_archives/android-arm64-release/linux-arm64.zip(2026-05-29 run)gen_snapshotinside is the same ELF aarch64 PIEzip_archives/android-arm64-release/analyze-snapshot-linux-arm64.zip(2026-05-29 run)Existing CI coverage (x64 hosts)
linux_android_aot_engine(x64) —linux-x64.ziparchives still produced (no-op for native builds)mac_android_aot_engine—darwin-{x64,arm64}.zipunaffectedwindows_android_aot_engine—windows-x64.zipunaffected.ci.yaml validation— builder JSON and.ci.yamlentry structurally validStill owed before flipping bringup off
gen_snapshotpasses every static check (file,readelf,NEEDED). qemu user-mode emulation segfaults on it — a known qemu limitation with executable mmap / TLS-heavy runtimes, not a defect in the binary. Real-hardware confirmation has to be done outside this PR's CI (the new builder isbringupand is the only x64-side coverage available).linux-arm64.zipis in GCS for several engine SHAs, land the framework follow-up (flutter_toolscache/artifact path resolution).Pre-launch Checklist
Notes for reviewers
flutter/android/sdk/all/linux-arm64CIPD platform. The conceptual abstraction ishost_clang_cpu: the CPU of the build-time toolchain on the builder, distinct fromhost_cpu(the CPU the produced host binary runs on).linux-arm64.zippaths that did not exist in GCS, breakingflutter precache --allfor arbitrary users at the next release. This time the artifacts ship first, framework follows once GCS is populated.bringup: trueuntil smoke test on real arm64. Static checks on the produced binary are all green (ELF aarch64, glibc-linked, correct interpreter). Runtime verification on real hardware is the final step.