Skip to content

feat: support Linux ARM64 by invalidating bundled x64 stamps - #58

Open
ooaklee wants to merge 2 commits into
asdf-community:masterfrom
ooaklee:feat/linux-arm64-support
Open

feat: support Linux ARM64 by invalidating bundled x64 stamps#58
ooaklee wants to merge 2 commits into
asdf-community:masterfrom
ooaklee:feat/linux-arm64-support

Conversation

@ooaklee

@ooaklee ooaklee commented Jul 5, 2026

Copy link
Copy Markdown

Problem

On Linux aarch64 hosts, asdf install flutter <version> completes successfully but the first flutter invocation fails with:

```
.../bin/internal/shared.sh: line 273:
.../bin/cache/dart-sdk/bin/dart: cannot execute binary file: Exec format error
```

Root cause

Flutter publishes a single Linux release tarball per version/channel. That tarball bundles an x86-64 dart-sdk under bin/cache/dart-sdk/ plus two stamp files that match on first run:

  • bin/cache/engine-dart-sdk.stamp
  • bin/cache/flutter_tools.stamp

Flutter's bootstrap (bin/internal/shared.sh) only invokes its self-heal script bin/internal/update_dart_sdk.sh when flutter_tools.stamp is missing/empty/mismatched. Because the shipped stamps match, update_dart_sdk.sh is never reached on first run, so it never gets the chance to detect aarch64 and fetch dart-sdk-linux-arm64.zip (which Flutter does publish on the engine bucket - it just isn't shipped inside the tarball).

The error surfaces from Flutter's own bootstrap, not from the plugin's install step, which made this hard to diagnose.

Note: macOS is unaffected because the install script already filters releases_macos.json by dart_sdk_arch. releases_linux.json exposes no dart_sdk_arch field (all 719 entries are x64), so the same filtering approach cannot be reused for Linux.

Fix

After extracting the tarball, on non-x86_64 Linux hosts delete both engine-dart-sdk.stamp and flutter_tools.stamp. On the first flutter invocation update_dart_sdk.sh then runs, detects the host arch via uname -m, and replaces the bundled dart-sdk with the matching one (e.g. dart-sdk-linux-arm64.zip).

The stamp-invalidation logic is extracted into a standalone function (invalidate_bundled_stamps_for_non_x64_linux) so it can be unit-tested in isolation. Architecture detection is deliberately delegated to Flutter's own update_dart_sdk.sh, which already handles aarch64 and riscv64 correctly.

Changes

  • bin/install - new invalidate_bundled_stamps_for_non_x64_linux() called at the end of install(); top-level execution guarded by a BASH_SOURCE check so the file can be sourced by tests; dirname $0 -> dirname "\${BASH_SOURCE[0]}" so paths resolve correctly when sourced.
  • bin/utils.sh - drive-by fix: `${VAR:=default}` -> `: "${VAR:=default}"`. The previous form tried to execute the URL as a command and returned exit 127 under `set -e` (bats), blocking the test harness.
  • test/install.bats - 7 bats tests covering x86_64 (preserve), aarch64 (remove each stamp), riscv64 (remove both), darwin arm64/x86_64 (preserve), and an unknown-arch default-branch guard.
  • .github/workflows/test.yml - CI matrix running bats on `ubuntu-24.04` (x86_64) and `ubuntu-24.04-arm` (aarch64).
  • docs/adr/adr001-linux-arm64-support.md - ADR documenting the context, decision, and consequences.
  • README.md - new troubleshooting section with a link to the ADR.

Verification

  • Reproduced the original Exec format error on `aarch64` Linux with `3.44.4-stable`.
  • After applying the fix and reinstalling: `file bin/cache/dart-sdk/bin/dart` reports `ELF 64-bit ... ARM aarch64`; `flutter --version` reports `Flutter 3.44.4 • channel stable`; `dart --version` reports `on "linux_arm64"`.
  • `bats test/` - 7/7 passing locally on aarch64.

Notes

  • The first `flutter` invocation on a non-x86_64 Linux host incurs a one-time ~220 MB download plus a `flutter_tools` rebuild. This is inherent to swapping the dart-sdk.
  • riscv64: `update_dart_sdk.sh` maps `riscv64` -> `dart-sdk-linux-riscv64.zip`, but as of engine `a10d8ac38d` (3.44.4-stable) that zip returns 404. riscv64 support is therefore aspirational and depends on Flutter publishing the artifact.
  • The fix depends on Flutter's stamp protocol staying as described. If `shared.sh` is refactored to call `update_dart_sdk.sh` unconditionally, the workaround silently becomes a no-op (the bundled x86-64 binary would stay in place). The bats suite and the aarch64 CI runner guard against this regression.

Summary by cubic

Fixes the “Exec format error” on Linux ARM64 by removing Flutter’s bundled x64 stamp files after install so update_dart_sdk.sh downloads the correct Dart SDK on first run. Adds tests and CI on x86_64 and ARM64 to prevent regressions.

  • Bug Fixes
    • On non-x86_64 Linux, delete bin/cache/engine-dart-sdk.stamp and bin/cache/flutter_tools.stamp after extraction to trigger update_dart_sdk.sh.
    • Add invalidate_bundled_stamps_for_non_x64_linux() in bin/install; guard top-level execution for sourcing and use BASH_SOURCE for paths.
    • Fix env defaulting in bin/utils.sh with : "${FLUTTER_STORAGE_BASE_URL:=...}" to avoid set -e failures.
    • Add bats tests covering Linux x86_64/arm64/riscv64 and macOS; add CI matrix on ubuntu-24.04 and ubuntu-24.04-arm.
    • Update README troubleshooting, add ADR001, and include a reusable ADR template at docs/adr/_adr-XXXX-template.md.

Written for commit ccbf839. Summary will update on new commits.

Review in cubic

Flutter's official Linux tarball bundles an x86-64 dart-sdk under
bin/cache/dart-sdk/ plus matching engine-dart-sdk.stamp and
flutter_tools.stamp. On non-x86_64 Linux hosts (aarch64, riscv64) the
bundled dart binary cannot execute, and the first `flutter` invocation
fails with:

  .../bin/internal/shared.sh: line 273:
  .../bin/cache/dart-sdk/bin/dart: cannot execute binary file: Exec format error

Because both shipped stamps match on first run, Flutter's self-heal
(update_dart_sdk.sh) is never reached - it is only invoked when
flutter_tools.stamp is invalid - so the wrong-arch dart binary is never
replaced.

Fix: after extraction, on non-x86_64 Linux hosts delete both
engine-dart-sdk.stamp and flutter_tools.stamp so update_dart_sdk.sh
runs on first invocation, detects the host arch, and fetches the
matching dart-sdk (e.g. dart-sdk-linux-arm64.zip).

The stamp-invalidation logic is extracted into a standalone function
(invalidate_bundled_stamps_for_non_x64_linux) so it can be unit-tested
in isolation. A bats suite (test/install.bats) guards the regression on
both x86_64 (stamps preserved) and aarch64 (stamps removed), and CI
runs the suite on ubuntu-24.04 and ubuntu-24.04-arm runners.

Drive-by fix: bin/utils.sh line 3 used ${VAR:=default} without a
leading ':' no-op, which tried to execute the URL as a command and
returned exit 127 under `set -e`. Fixed to the standard
`: "${VAR:=default}"` idiom.

See docs/adr/adr001-linux-arm64-support.md for the full diagnosis and
rationale, and the README troubleshooting section for user-facing
context.
@ooaklee
ooaklee requested a review from a team as a code owner July 5, 2026 11:33

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found and verified against the latest diff

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".github/workflows/test.yml">

<violation number="1" location=".github/workflows/test.yml:33">
P2: The bats installation step clones from the repository's default branch without pinning to a specific tag or commit, which means upstream changes can break CI unexpectedly. Consider cloning a specific release tag for reproducible builds.</violation>
</file>

<file name="docs/adr/adr001-linux-arm64-support.md">

<violation number="1" location="docs/adr/adr001-linux-arm64-support.md:139">
P2: The ADR documents the fix incorrectly here. Since the plugin deletes `engine-dart-sdk.stamp` as well as `flutter_tools.stamp`, `update_dart_sdk.sh` running unconditionally would still see a missing `engine-dart-sdk.stamp` and trigger re-download of the correct architecture SDK — the workaround would still work. The more accurate regression risk would be if `update_dart_sdk.sh` itself stops checking `engine-dart-sdk.stamp` or is bypassed entirely, not simply if it's no longer gated behind `flutter_tools.stamp`. Consider rephrasing this consequence so future maintainers understand the real dependency on the engine-stamp check inside `update_dart_sdk.sh` rather than on the outer `flutter_tools.stamp` gate.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic


- name: Install bats
run: |
git clone --depth 1 https://github.com/bats-core/bats-core.git /tmp/bats-core

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The bats installation step clones from the repository's default branch without pinning to a specific tag or commit, which means upstream changes can break CI unexpectedly. Consider cloning a specific release tag for reproducible builds.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/test.yml, line 33:

<comment>The bats installation step clones from the repository's default branch without pinning to a specific tag or commit, which means upstream changes can break CI unexpectedly. Consider cloning a specific release tag for reproducible builds.</comment>

<file context>
@@ -0,0 +1,43 @@
+
+      - name: Install bats
+        run: |
+          git clone --depth 1 https://github.com/bats-core/bats-core.git /tmp/bats-core
+          /tmp/bats-core/install.sh "$HOME/.local"
+          echo "$HOME/.local/bin" >> "$GITHUB_PATH"
</file context>
Suggested change
git clone --depth 1 https://github.com/bats-core/bats-core.git /tmp/bats-core
git clone --depth 1 --branch v1.11.0 https://github.com/bats-core/bats-core.git /tmp/bats-core

Comment on lines +139 to +143
stops gating `update_dart_sdk.sh` behind it, this workaround may silently
regress back to shipping an x86-64 binary on arm64 hosts. The failure mode
would be the original "Exec format error" again.
- Deleting `flutter_tools.stamp` also forces a rebuild of `flutter_tools.snapshot`
from source on first run. This is unavoidable: the shipped snapshot was built

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The ADR documents the fix incorrectly here. Since the plugin deletes engine-dart-sdk.stamp as well as flutter_tools.stamp, update_dart_sdk.sh running unconditionally would still see a missing engine-dart-sdk.stamp and trigger re-download of the correct architecture SDK — the workaround would still work. The more accurate regression risk would be if update_dart_sdk.sh itself stops checking engine-dart-sdk.stamp or is bypassed entirely, not simply if it's no longer gated behind flutter_tools.stamp. Consider rephrasing this consequence so future maintainers understand the real dependency on the engine-stamp check inside update_dart_sdk.sh rather than on the outer flutter_tools.stamp gate.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/adr/adr001-linux-arm64-support.md, line 139:

<comment>The ADR documents the fix incorrectly here. Since the plugin deletes `engine-dart-sdk.stamp` as well as `flutter_tools.stamp`, `update_dart_sdk.sh` running unconditionally would still see a missing `engine-dart-sdk.stamp` and trigger re-download of the correct architecture SDK — the workaround would still work. The more accurate regression risk would be if `update_dart_sdk.sh` itself stops checking `engine-dart-sdk.stamp` or is bypassed entirely, not simply if it's no longer gated behind `flutter_tools.stamp`. Consider rephrasing this consequence so future maintainers understand the real dependency on the engine-stamp check inside `update_dart_sdk.sh` rather than on the outer `flutter_tools.stamp` gate.</comment>

<file context>
@@ -0,0 +1,145 @@
+  is noticeable.
+- The fix depends on Flutter's internal stamp protocol staying as described.
+  If a future Flutter release changes how `flutter_tools.stamp` is computed or
+  stops gating `update_dart_sdk.sh` behind it, this workaround may silently
+  regress back to shipping an x86-64 binary on arm64 hosts. The failure mode
+  would be the original "Exec format error" again.
</file context>
Suggested change
stops gating `update_dart_sdk.sh` behind it, this workaround may silently
regress back to shipping an x86-64 binary on arm64 hosts. The failure mode
would be the original "Exec format error" again.
- Deleting `flutter_tools.stamp` also forces a rebuild of `flutter_tools.snapshot`
from source on first run. This is unavoidable: the shipped snapshot was built
- The fix depends on Flutter's internal stamp protocol staying as described.
- If a future Flutter release changes `update_dart_sdk.sh` to not check
`engine-dart-sdk.stamp`, or bypasses `update_dart_sdk.sh` entirely,
this workaround may silently regress back to shipping an x86-64 binary
on arm64 hosts. The failure mode would be the original "Exec format
error" again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant