Skip to content

feat(shorebird_cli): prepare for Flutter 3.44 libapp.so strip handover#3758

Merged
bdero merged 7 commits into
mainfrom
bdero/3.44-cli-prep
May 19, 2026
Merged

feat(shorebird_cli): prepare for Flutter 3.44 libapp.so strip handover#3758
bdero merged 7 commits into
mainfrom
bdero/3.44-cli-prep

Conversation

@bdero
Copy link
Copy Markdown
Member

@bdero bdero commented May 12, 2026

Upstream Flutter PR flutter/flutter#181275 (merged 2026-01-26, first shipped in 3.44) inverted the responsibility for stripping libapp.so. Before 3.44, Flutter stripped the AOT shared library itself. From 3.44 onward, Flutter expects AGP to strip it and emit a libapp.so.sym companion into the AAB's BUNDLE-METADATA. flutter_tools adds a post-build verification that fatal errors when the .sym companion is missing.

Two Shorebird flows trip the new check on 3.44:

  1. Obfuscated builds pass --extra-gen-snapshot-options=--strip so gen_snapshot pre-strips libapp.so. On 3.44 AGP then has nothing to strip, no .sym is produced, build fails.
  2. Existing users carry a legacy packaging.jniLibs.keepDebugSymbols.add(\"**/libapp.so\") line in android/app/build.gradle(.kts). On 3.44 that blocks AGP from stripping, same failure.

This PR lands forward-compatible, version-gated behavior in shorebird_cli so users updating their CLI today are pre-loaded with the right behavior when the actual 3.44 engine bump ships. On older Flutter pins behavior is unchanged.

shorebird_cli changes

  • Add libappStrippedByAgpConstraint (>= 3.44) to flutter_version_constraints.dart.
  • ShorebirdFlutter.shouldPreStripLibappInGenSnapshot({platform, flutterRevision}): single helper that owns the strip-gating policy (non-Android always pre-strips; Android pre-strips only below 3.44).
  • Releaser.addObfuscationMapArgs: on Android+3.44, drop --strip so AGP can strip and emit the .sym. Other platforms still pre-strip (AGP isn't in their pipeline). The .sym in BUNDLE-METADATA is only visible via Play Console and is stripped before APK delivery, so obfuscation protection in user-shipped APKs is preserved. Method becomes async because the version check is async.
  • PatchCommand: same gating for Android patch builds, keyed on release.flutterRevision so the patch's gen_snapshot behavior matches the release's.
  • New LegacyKeepDebugSymbolsValidator: scans android/app/build.gradle(.kts) for the legacy keepDebugSymbols line on Flutter 3.44+ pins and surfaces a warning pointing at the exact file and substring to remove. Matches both .add(...) and += forms, single and double quotes. No-op on older Flutter where the line is still appropriate.
  • Registered the validator in Doctor.androidCommandValidators and Doctor.initAndDoctorValidators.

artifact_proxy change

Flutter 3.44 also reorganized iOS USB artifact URLs from

ios-usb-dependencies/<artifact>/<hash>/<artifact>.zip

to

ios-usb-dependencies/arm64_x86_64/<artifact>/<hash>/<artifact>.zip

(see flutter/flutter#181539 and related). Upstream GCS serves both layouts indefinitely, but the download.shorebird.dev proxy's allowlist only recognized the pre-3.44 shape. Without this change every 3.44+ customer hits 404 on their first iOS or Android build during flutter precache (libimobiledevice, ios-deploy, etc.). Adds the seven arm64_x86_64/ variants alongside the existing patterns so customers on either Flutter version resolve.

The companion Flutter-fork patches (revert of the iOS Mach-O dylib PR and demotion of the libapp.so.sym check from fatal to warning) are still needed as a safety net for users on truly stale build.gradle files; this PR shrinks the population that needs the safety net.

Test plan

  • dart test test/src/validators/legacy_keep_debug_symbols_validator_test.dart (10 tests)
  • dart test test/src/shorebird_flutter_test.dart (full suite plus 4 new tests for shouldPreStripLibappInGenSnapshot)
  • dart test test/src/commands/release/ test/src/commands/patch/ (453 tests)
  • dart test test/src/validators/ test/src/commands/doctor_command_test.dart (103 tests)
  • packages/artifact_proxy dart test (11 existing proxy tests still pass)
  • dart analyze --fatal-warnings lib test clean
  • After proxy deploy: curl -sI .../arm64_x86_64/libimobiledevice/<hash>/libimobiledevice.zip returns 302 to storage.googleapis.com
  • After proxy deploy: curl -sI .../libimobiledevice/<hash>/libimobiledevice.zip (no arm64_x86_64 segment) still returns 302

bdero added 2 commits May 11, 2026 22:50
Upstream Flutter PR flutter/flutter#181275 (merged
2026-01-26, first shipped in 3.44) inverted the responsibility for stripping
libapp.so. Before 3.44, Flutter stripped the AOT shared library itself and
shipped the stripped binary directly. From 3.44 onward, Flutter leaves debug
symbols in libapp.so and expects AGP's stripReleaseDebugSymbols task to
strip them and produce a matching libapp.so.sym companion in the AAB's
BUNDLE-METADATA. flutter_tools adds a post-build verification that fatal
errors when the .sym companion is missing.

Two Shorebird flows trip the new check on 3.44:

1. Obfuscated release and patch builds pass --extra-gen-snapshot-options=
   --strip so gen_snapshot pre-strips libapp.so. On 3.44, AGP then has
   nothing to strip, the .sym companion is never produced, and the build
   fails with "libapp.so.sym or libapp.so.dbg not present when checking
   final appbundle for debug symbols."

2. Pre-3.44 Shorebird users (and the e2e fixture) carry a legacy
   `packaging.jniLibs.keepDebugSymbols.add("**/libapp.so")` line in
   android/app/build.gradle(.kts). On 3.44 that line blocks AGP from
   stripping, the .sym companion is not produced, and the build hits the
   same failure path.

Land version-gated behavior in shorebird_cli ahead of the actual 3.44
engine bump so users upgrading their CLI today are pre-loaded with the
correct behavior:

* Add a libappStrippedByAgpConstraint (>= 3.44) to flutter_version_constraints.
* In Releaser.addObfuscationMapArgs, drop --strip for Android+3.44 (other
  platforms still pre-strip; AGP is not in their pipeline). The .sym in
  BUNDLE-METADATA is only available to the developer via Play Console and
  is stripped before APK delivery, so the obfuscation protection in the
  user-shipped APK remains intact.
* In PatchCommand, drop --strip for Android patch builds when the release
  was built on 3.44, gated on release.flutterRevision so patch
  gen_snapshot behavior matches the release.
* Add a LegacyKeepDebugSymbolsValidator that scans
  android/app/build.gradle(.kts) for the legacy keepDebugSymbols line
  on Flutter 3.44 or newer pins and surfaces a warning pointing at the
  exact file and substring to remove. No-op on older Flutter where the
  line is still appropriate.

The companion Flutter-fork patches (revert of the iOS Mach-O dylib PR
and demotion of the libapp.so.sym check from fatal to warning) are still
needed as a safety net for users on truly stale build.gradle files; this
PR shrinks the population that needs the safety net.

Tracked in shorebirdtech/_shorebird#2150.
@codecov
Copy link
Copy Markdown

codecov Bot commented May 12, 2026

Codecov Report

❌ Patch coverage is 98.59155% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...orebird_cli/lib/src/commands/release/releaser.dart 93.33% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@bdero bdero requested a review from eseidel May 12, 2026 19:04
/// On older Flutter versions the line is still appropriate and this validator
/// is a no-op.
///
/// Tracked in https://github.com/shorebirdtech/_shorebird/issues/2150.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Remove ref

/// is a no-op.
///
/// Tracked in https://github.com/shorebirdtech/_shorebird/issues/2150.
class LegacyKeepDebugSymbolsValidator extends Validator {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I believe vanilla Flutter users will also run into this same surprise opaque build failure, and their AI agents will also flail around for an hours investigating. So considering possibly upstreaming something to Flutter doctor.

Comment thread packages/shorebird_cli/lib/src/commands/patch/patch_command.dart Outdated
Comment thread packages/shorebird_cli/lib/src/commands/release/releaser.dart Outdated
@Gaurav-CareMonitor
Copy link
Copy Markdown

any timeline for 3.44 support, builds are failing after adding support for built in kotlin
https://docs.flutter.dev/release/breaking-changes/migrate-to-built-in-kotlin/for-app-developers

@eseidel
Copy link
Copy Markdown
Contributor

eseidel commented May 19, 2026

We've prepared it. I expect we'll ship it today.

bdero added 4 commits May 19, 2026 12:57
…lper

Releaser.addObfuscationMapArgs and PatchCommand.createPatch both inlined
the same branching: on Android with Flutter 3.44+, AGP performs the
strip and we must omit --strip from gen_snapshot; everywhere else we
keep --strip. Lift the logic onto ShorebirdFlutter as a single
{platform, flutterRevision}-keyed method so both call sites reduce to a
helper call and the gating policy lives in one place.

Tests for the unresolvable-version edge case move from each call site
to the helper-level group, since the fallback to the constraint's
min-version is a property of the helper rather than its callers.

The non-Android-pipeline releaser tests (ios, ios_framework, linux,
macos, windows) now stub the helper to return true in their --obfuscate
setUps, matching the actual runtime behavior on those platforms.
…docs

The shorebirdtech/_shorebird repo is private, so links to its issues
read as broken refs to anyone reading the published CLI source.
Flutter 3.44 (flutter/flutter#181539 and related) reorganized iOS USB
artifact URLs from
  ios-usb-dependencies/<artifact>/<hash>/<artifact>.zip
to
  ios-usb-dependencies/arm64_x86_64/<artifact>/<hash>/<artifact>.zip

Upstream GCS serves both layouts. The proxy's allowlist only knew the
pre-3.44 shape, so any 3.44+ customer's first iOS or Android build
404s during `flutter precache` against download.shorebird.dev.

Add the 3.44+ patterns alongside the existing ones for each of the
seven iOS USB dependencies. Pre-3.44 customers keep resolving against
the old patterns unchanged.
After the strip-gating tests switched from mocking resolveFlutterVersion
to mocking shouldPreStripLibappInGenSnapshot, this file no longer
constructs Version objects. The unused import was a `dart analyze
--fatal-warnings` failure under CI.
@bdero bdero enabled auto-merge (squash) May 19, 2026 20:23
@bdero bdero disabled auto-merge May 19, 2026 20:28
@bdero bdero merged commit ecf79d8 into main May 19, 2026
36 of 37 checks passed
@bdero bdero deleted the bdero/3.44-cli-prep branch May 19, 2026 20:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants