feat(shorebird_cli): prepare for Flutter 3.44 libapp.so strip handover (#3758)
This commit is contained in:
@@ -122,6 +122,7 @@ final engineArtifactPatterns = {
|
||||
|
||||
/// Patterns for Flutter artifacts which don't depend on an engine revision.
|
||||
final flutterArtifactPatterns = {
|
||||
// Pre-3.44 layout: ios-usb-dependencies/<artifact>/<hash>/<artifact>.zip
|
||||
r'flutter_infra_release\/ios-usb-dependencies\/usbmuxd\/(.*)\/usbmuxd\.zip',
|
||||
r'flutter_infra_release\/ios-usb-dependencies\/libusbmuxd\/(.*)\/libusbmuxd\.zip',
|
||||
r'flutter_infra_release\/ios-usb-dependencies\/openssl\/(.*)\/openssl\.zip',
|
||||
@@ -129,6 +130,17 @@ final flutterArtifactPatterns = {
|
||||
r'flutter_infra_release\/ios-usb-dependencies\/libimobiledevice\/(.*)\/libimobiledevice\.zip',
|
||||
r'flutter_infra_release\/ios-usb-dependencies\/libimobiledeviceglue\/(.*)\/libimobiledeviceglue\.zip',
|
||||
r'flutter_infra_release\/ios-usb-dependencies\/ios-deploy\/(.*)\/ios-deploy\.zip',
|
||||
// 3.44+ layout: ios-usb-dependencies/arm64_x86_64/<artifact>/<hash>/<artifact>.zip
|
||||
// Flutter 3.44 (flutter/flutter#181539 + related) reorganized iOS USB
|
||||
// dependency URLs to namespace by architecture. Upstream GCS serves both
|
||||
// layouts; we list both so customers on either Flutter version resolve.
|
||||
r'flutter_infra_release\/ios-usb-dependencies\/arm64_x86_64\/usbmuxd\/(.*)\/usbmuxd\.zip',
|
||||
r'flutter_infra_release\/ios-usb-dependencies\/arm64_x86_64\/libusbmuxd\/(.*)\/libusbmuxd\.zip',
|
||||
r'flutter_infra_release\/ios-usb-dependencies\/arm64_x86_64\/openssl\/(.*)\/openssl\.zip',
|
||||
r'flutter_infra_release\/ios-usb-dependencies\/arm64_x86_64\/libplist\/(.*)\/libplist\.zip',
|
||||
r'flutter_infra_release\/ios-usb-dependencies\/arm64_x86_64\/libimobiledevice\/(.*)\/libimobiledevice\.zip',
|
||||
r'flutter_infra_release\/ios-usb-dependencies\/arm64_x86_64\/libimobiledeviceglue\/(.*)\/libimobiledeviceglue\.zip',
|
||||
r'flutter_infra_release\/ios-usb-dependencies\/arm64_x86_64\/ios-deploy\/(.*)\/ios-deploy\.zip',
|
||||
r'flutter_infra_release\/gradle-wrapper\/(.*)\/gradle-wrapper\.tgz',
|
||||
r'flutter_infra_release\/flutter\/fonts\/(.*)\/fonts\.zip',
|
||||
r'flutter_infra_release\/cipd\/flutter\/web\/canvaskit_bundle\/\+\/(.*)',
|
||||
|
||||
@@ -462,10 +462,27 @@ Building with Flutter $flutterVersionString to determine the release version...
|
||||
'--obfuscate',
|
||||
'--extra-gen-snapshot-options='
|
||||
'--load-obfuscation-map=${obfuscationMapFile.path}',
|
||||
// Strip unobfuscated DWARF debug info from the compiled snapshot so
|
||||
// it doesn't leak identifiers that obfuscation was meant to hide.
|
||||
'--extra-gen-snapshot-options=--strip',
|
||||
]);
|
||||
|
||||
// Gate --strip on the release's Flutter revision (not the user's
|
||||
// currently-installed pin) so the patch's gen_snapshot behavior
|
||||
// matches the release's. On Android with Flutter 3.44+ AGP performs
|
||||
// the strip; passing --strip here would pre-strip the snapshot,
|
||||
// leaving AGP nothing to strip and tripping flutter_tools'
|
||||
// post-build "libapp.so.sym or libapp.so.dbg not present" check.
|
||||
final shouldPreStripInGenSnapshot = await shorebirdFlutter
|
||||
.shouldPreStripLibappInGenSnapshot(
|
||||
platform: patcher.releaseType.releasePlatform,
|
||||
flutterRevision: release.flutterRevision,
|
||||
);
|
||||
|
||||
if (shouldPreStripInGenSnapshot) {
|
||||
// Strip unobfuscated DWARF debug info from the compiled snapshot
|
||||
// so it doesn't leak identifiers that obfuscation was meant to
|
||||
// hide. On Android 3.44+ this is handled by AGP instead; see the
|
||||
// block above.
|
||||
extraBuildArgs.add('--extra-gen-snapshot-options=--strip');
|
||||
}
|
||||
}
|
||||
// Flutter requires --split-debug-info with --obfuscate. Auto-add it
|
||||
// if --obfuscate will be in the build args (from the user or from
|
||||
|
||||
@@ -85,7 +85,7 @@ class AarReleaser extends Releaser {
|
||||
final base64PublicKey = await getEncodedPublicKey();
|
||||
final buildArgs = [...argResults.forwardedArgs];
|
||||
addSplitDebugInfoDefault(buildArgs);
|
||||
addObfuscationMapArgs(buildArgs);
|
||||
await addObfuscationMapArgs(buildArgs);
|
||||
await artifactBuilder.buildAar(
|
||||
buildNumber: buildNumber,
|
||||
targetPlatforms: architectures,
|
||||
|
||||
@@ -113,7 +113,7 @@ Please comment and upvote ${link(uri: Uri.parse('https://github.com/shorebirdtec
|
||||
final base64PublicKey = await getEncodedPublicKey();
|
||||
final buildArgs = [...argResults.forwardedArgs];
|
||||
addSplitDebugInfoDefault(buildArgs);
|
||||
addObfuscationMapArgs(buildArgs);
|
||||
await addObfuscationMapArgs(buildArgs);
|
||||
final aab = await artifactBuilder.buildAppBundle(
|
||||
flavor: flavor,
|
||||
target: target,
|
||||
|
||||
@@ -76,7 +76,7 @@ class IosFrameworkReleaser extends Releaser with AppleReleaserMixin {
|
||||
final base64PublicKey = await getEncodedPublicKey();
|
||||
final buildArgs = [...argResults.forwardedArgs];
|
||||
addSplitDebugInfoDefault(buildArgs);
|
||||
addObfuscationMapArgs(buildArgs);
|
||||
await addObfuscationMapArgs(buildArgs);
|
||||
await artifactBuilder.buildIosFramework(
|
||||
args: buildArgs,
|
||||
base64PublicKey: base64PublicKey,
|
||||
|
||||
@@ -101,7 +101,7 @@ If left checked, Xcode will rewrite the build number in the uploaded IPA, so the
|
||||
|
||||
final buildArgs = [...argResults.forwardedArgs];
|
||||
addSplitDebugInfoDefault(buildArgs);
|
||||
addObfuscationMapArgs(buildArgs);
|
||||
await addObfuscationMapArgs(buildArgs);
|
||||
|
||||
await artifactBuilder.buildIpa(
|
||||
codesign: codesign,
|
||||
|
||||
@@ -75,7 +75,7 @@ To change the version of this release, change your app's version in your pubspec
|
||||
final base64PublicKey = await getEncodedPublicKey();
|
||||
final buildArgs = [...argResults.forwardedArgs];
|
||||
addSplitDebugInfoDefault(buildArgs);
|
||||
addObfuscationMapArgs(buildArgs);
|
||||
await addObfuscationMapArgs(buildArgs);
|
||||
await artifactBuilder.buildLinuxApp(
|
||||
target: target,
|
||||
args: buildArgs,
|
||||
|
||||
@@ -71,7 +71,7 @@ class MacosReleaser extends Releaser with AppleReleaserMixin {
|
||||
|
||||
final buildArgs = [...argResults.forwardedArgs];
|
||||
addSplitDebugInfoDefault(buildArgs);
|
||||
addObfuscationMapArgs(buildArgs);
|
||||
await addObfuscationMapArgs(buildArgs);
|
||||
|
||||
await artifactBuilder.buildMacos(
|
||||
codesign: codesign,
|
||||
|
||||
@@ -144,18 +144,49 @@ abstract class Releaser {
|
||||
|
||||
/// Adds obfuscation-related gen_snapshot options to [buildArgs].
|
||||
///
|
||||
/// When obfuscation is enabled, passes --save-obfuscation-map to capture the
|
||||
/// mapping and --strip to remove unobfuscated DWARF debugging information
|
||||
/// from the compiled snapshot (the DWARF sections would otherwise leak
|
||||
/// identifiers that obfuscation was meant to hide).
|
||||
void addObfuscationMapArgs(List<String> buildArgs) {
|
||||
/// When obfuscation is enabled, passes --save-obfuscation-map to capture
|
||||
/// the mapping. Conditionally passes --strip to remove unobfuscated DWARF
|
||||
/// debugging information from the compiled snapshot, since the DWARF
|
||||
/// sections would otherwise leak identifiers that obfuscation was meant
|
||||
/// to hide.
|
||||
///
|
||||
/// On Android, --strip is only passed for Flutter versions older than
|
||||
/// 3.44. From 3.44 onward, upstream Flutter PR
|
||||
/// https://github.com/flutter/flutter/pull/181275 (merged 2026-01-26)
|
||||
/// made AGP responsible for stripping `libapp.so` and emitting the
|
||||
/// matching `.sym` companion into the AAB's BUNDLE-METADATA. Passing
|
||||
/// --strip to gen_snapshot on 3.44+ pre-strips the snapshot, leaving AGP
|
||||
/// with nothing to strip. flutter_tools then fails the build with
|
||||
/// "libapp.so.sym or libapp.so.dbg not present when checking final
|
||||
/// appbundle for debug symbols." Letting AGP do the stripping preserves
|
||||
/// the obfuscation protection in the user-shipped APK (AGP uses
|
||||
/// `llvm-strip --strip-unneeded`, which removes the same DWARF sections
|
||||
/// that gen_snapshot --strip would have), while restoring the `.sym`
|
||||
/// companion that 3.44 requires. The `.sym` file containing the
|
||||
/// pre-strip DWARF is only retained in BUNDLE-METADATA, which Play
|
||||
/// strips before delivery to end-user devices, so the only readers are
|
||||
/// the developer's own Play Console crash dashboard.
|
||||
///
|
||||
/// On non-Android platforms (iOS, macOS, Linux, Windows, iOS framework,
|
||||
/// AAR), AGP is not in the pipeline, so --strip is always passed
|
||||
/// regardless of Flutter version.
|
||||
Future<void> addObfuscationMapArgs(List<String> buildArgs) async {
|
||||
if (!useObfuscation) return;
|
||||
final mapDir = Directory(p.dirname(obfuscationMapPath));
|
||||
if (!mapDir.existsSync()) mapDir.createSync(recursive: true);
|
||||
buildArgs.addAll([
|
||||
buildArgs.add(
|
||||
'--extra-gen-snapshot-options=--save-obfuscation-map=$obfuscationMapPath',
|
||||
'--extra-gen-snapshot-options=--strip',
|
||||
]);
|
||||
);
|
||||
|
||||
final shouldPreStripInGenSnapshot = await shorebirdFlutter
|
||||
.shouldPreStripLibappInGenSnapshot(
|
||||
platform: releaseType.releasePlatform,
|
||||
flutterRevision: shorebirdEnv.flutterRevision,
|
||||
);
|
||||
|
||||
if (shouldPreStripInGenSnapshot) {
|
||||
buildArgs.add('--extra-gen-snapshot-options=--strip');
|
||||
}
|
||||
}
|
||||
|
||||
/// Platform subdirectory for the supplement directory (e.g. 'android',
|
||||
|
||||
@@ -78,7 +78,7 @@ To change the version of this release, change your app's version in your pubspec
|
||||
final base64PublicKey = await getEncodedPublicKey();
|
||||
final buildArgs = [...argResults.forwardedArgs];
|
||||
addSplitDebugInfoDefault(buildArgs);
|
||||
addObfuscationMapArgs(buildArgs);
|
||||
await addObfuscationMapArgs(buildArgs);
|
||||
final result = await artifactBuilder.buildWindowsApp(
|
||||
target: target,
|
||||
args: buildArgs,
|
||||
|
||||
@@ -19,6 +19,7 @@ class Doctor {
|
||||
/// Validators that verify shorebird will work on Android.
|
||||
final List<Validator> androidCommandValidators = [
|
||||
AndroidInternetPermissionValidator(),
|
||||
LegacyKeepDebugSymbolsValidator(),
|
||||
];
|
||||
|
||||
/// Validators that verify shorebird will work on iOS.
|
||||
@@ -41,6 +42,7 @@ class Doctor {
|
||||
List<Validator> initAndDoctorValidators = [
|
||||
ShorebirdVersionValidator(),
|
||||
AndroidInternetPermissionValidator(),
|
||||
LegacyKeepDebugSymbolsValidator(),
|
||||
XcodeprojFlutterOverrideValidator(),
|
||||
MacosEntitlementsValidator(),
|
||||
ShorebirdYamlAssetValidator(),
|
||||
|
||||
@@ -93,3 +93,20 @@ final buildTraceSupportConstraint = FlutterSupportConstraint(
|
||||
'3b10eecea184bb381f1045a878eeff36548ed11e',
|
||||
},
|
||||
);
|
||||
|
||||
/// Flutter versions where the Android Gradle Plugin (AGP) is the entity
|
||||
/// responsible for stripping `libapp.so` and emitting the matching
|
||||
/// `libapp.so.sym` companion into the AAB's BUNDLE-METADATA.
|
||||
///
|
||||
/// Background: upstream Flutter PR
|
||||
/// https://github.com/flutter/flutter/pull/181275 (merged 2026-01-26, first
|
||||
/// shipped in 3.44) inverted the strip responsibility. Before 3.44, Flutter
|
||||
/// stripped `libapp.so` itself before bundling. From 3.44 onward, Flutter
|
||||
/// leaves debug symbols in `libapp.so` and expects AGP's
|
||||
/// `stripReleaseDebugSymbols` task to strip them and produce a `.sym`
|
||||
/// companion file used by Play Console for native crash symbolication.
|
||||
/// flutter_tools adds a post-build verification that fatal-errors when the
|
||||
/// `.sym` companion is missing.
|
||||
final libappStrippedByAgpConstraint = FlutterSupportConstraint(
|
||||
minVersion: Version(3, 44, 0),
|
||||
);
|
||||
|
||||
@@ -7,10 +7,12 @@ import 'package:pub_semver/pub_semver.dart';
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
import 'package:shorebird_cli/src/executables/executables.dart';
|
||||
import 'package:shorebird_cli/src/extensions/version.dart';
|
||||
import 'package:shorebird_cli/src/flutter_version_constraints.dart';
|
||||
import 'package:shorebird_cli/src/logging/logging.dart';
|
||||
import 'package:shorebird_cli/src/platform.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_env.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
|
||||
|
||||
/// A reference to a [ShorebirdFlutter] instance.
|
||||
final shorebirdFlutterRef = create(ShorebirdFlutter.new);
|
||||
@@ -268,6 +270,33 @@ class ShorebirdFlutter {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether `gen_snapshot` should be invoked with `--strip` for a build
|
||||
/// targeting [platform] on the Flutter pin identified by [flutterRevision].
|
||||
///
|
||||
/// On non-Android platforms (iOS, macOS, Linux, Windows, iOS framework,
|
||||
/// AAR), AGP is not in the pipeline, so we always pre-strip in gen_snapshot.
|
||||
///
|
||||
/// On Android, the answer depends on the Flutter version: from 3.44 onward
|
||||
/// AGP performs the strip and emits the matching `.sym` companion;
|
||||
/// pre-stripping in gen_snapshot on those versions leaves AGP with nothing
|
||||
/// to strip and trips flutter_tools' post-build verification. See
|
||||
/// [libappStrippedByAgpConstraint].
|
||||
///
|
||||
/// An unresolvable [flutterRevision] (e.g. a development branch) is treated
|
||||
/// as satisfying the constraint, since the alternative — pre-stripping —
|
||||
/// would fail the post-build check on any 3.44+ pin.
|
||||
Future<bool> shouldPreStripLibappInGenSnapshot({
|
||||
required ReleasePlatform platform,
|
||||
required String flutterRevision,
|
||||
}) async {
|
||||
if (platform != ReleasePlatform.android) return true;
|
||||
final version = await resolveFlutterVersion(flutterRevision);
|
||||
return !libappStrippedByAgpConstraint.isSatisfiedBy(
|
||||
version: version ?? libappStrippedByAgpConstraint.minVersion,
|
||||
revision: flutterRevision,
|
||||
);
|
||||
}
|
||||
|
||||
/// Fetches the latest remote refs for the Flutter clone so that
|
||||
/// release branch pointers (e.g. `flutter_release/3.38.5`) are up to date.
|
||||
Future<void> fetchRemoteRefs() async {
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:shorebird_cli/src/flutter_version_constraints.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_env.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_flutter.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
|
||||
/// Warns when `android/app/build.gradle.kts` (or its Groovy sibling) contains
|
||||
/// a legacy `packaging.jniLibs.keepDebugSymbols.add("**/libapp.so")` line on
|
||||
/// projects that target Flutter 3.44 or newer.
|
||||
///
|
||||
/// Background:
|
||||
///
|
||||
/// Upstream Flutter PR https://github.com/flutter/flutter/pull/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, so users (and the e2e test fixture) added the AGP-level
|
||||
/// `keepDebugSymbols` exclusion to keep `libapp.so` byte-stable for Shorebird
|
||||
/// patch diffing. From 3.44 onward, Flutter expects the Android Gradle Plugin
|
||||
/// (AGP) to strip the library and produce the matching `libapp.so.sym`
|
||||
/// companion in the AAB's BUNDLE-METADATA, and flutter_tools adds a
|
||||
/// post-build verification that fatal-errors when that companion is missing.
|
||||
///
|
||||
/// The legacy line tells AGP to skip stripping `libapp.so`, which prevents
|
||||
/// the `.sym` from being produced and causes the new verification to fail.
|
||||
/// On 3.44+ the line is unnecessary as well as actively harmful, so we surface
|
||||
/// a warning that points the user at the exact file and substring to remove.
|
||||
///
|
||||
/// On older Flutter versions the line is still appropriate and this validator
|
||||
/// is a no-op.
|
||||
class LegacyKeepDebugSymbolsValidator extends Validator {
|
||||
/// The substring we treat as a match. Catches `add(...)` and `+=` forms,
|
||||
/// single-quoted and double-quoted, with arbitrary whitespace around the
|
||||
/// `(` or `+=`, since users may have hand-edited the line.
|
||||
static final _legacyKeepDebugSymbolsPattern = RegExp(
|
||||
r'''keepDebugSymbols\s*(?:\.add\s*\(|\+=)\s*['"][^'"]*\*\*/libapp\.so['"]''',
|
||||
);
|
||||
|
||||
@override
|
||||
String get description =>
|
||||
'android/app/build.gradle(.kts) does not contain a legacy '
|
||||
'keepDebugSymbols line for libapp.so';
|
||||
|
||||
@override
|
||||
bool canRunInCurrentContext() {
|
||||
if (_androidAppDirectory == null) return false;
|
||||
return _gradleFiles.any((f) => f.existsSync());
|
||||
}
|
||||
|
||||
// coverage:ignore-start
|
||||
@override
|
||||
String? get incorrectContextMessage =>
|
||||
'No android/app directory was found, so this validator does not apply.';
|
||||
// coverage:ignore-end
|
||||
|
||||
@override
|
||||
Future<List<ValidationIssue>> validate() async {
|
||||
final flutterRevision = shorebirdEnv.flutterRevision;
|
||||
final flutterVersion = await shorebirdFlutter.resolveFlutterVersion(
|
||||
flutterRevision,
|
||||
);
|
||||
final agpStripsLibapp = libappStrippedByAgpConstraint.isSatisfiedBy(
|
||||
// Treat unknown versions as satisfying the constraint so users who
|
||||
// are pinned to a development revision (where resolveFlutterVersion
|
||||
// returns null) still get the migration nudge. False positives on
|
||||
// unrelated dev branches are preferable to silently missing a real
|
||||
// breakage on the upgrade path.
|
||||
version: flutterVersion ?? libappStrippedByAgpConstraint.minVersion,
|
||||
revision: flutterRevision,
|
||||
);
|
||||
if (!agpStripsLibapp) return [];
|
||||
|
||||
final issues = <ValidationIssue>[];
|
||||
for (final gradleFile in _gradleFiles) {
|
||||
if (!gradleFile.existsSync()) continue;
|
||||
final contents = gradleFile.readAsStringSync();
|
||||
if (!_legacyKeepDebugSymbolsPattern.hasMatch(contents)) continue;
|
||||
final relativePath = p.relative(
|
||||
gradleFile.path,
|
||||
from: shorebirdEnv.getFlutterProjectRoot()!.path,
|
||||
);
|
||||
issues.add(
|
||||
ValidationIssue(
|
||||
severity: ValidationIssueSeverity.warning,
|
||||
message:
|
||||
'$relativePath contains a legacy '
|
||||
'`packaging.jniLibs.keepDebugSymbols.add("**/libapp.so")` '
|
||||
'line. Flutter 3.44 (PR flutter/flutter#181275) requires '
|
||||
'AGP to strip libapp.so and emit a `libapp.so.sym` '
|
||||
'companion. The legacy line blocks AGP from stripping, '
|
||||
'which causes builds to fail or skips Play Console crash '
|
||||
'symbols for Dart code. Remove the line.',
|
||||
),
|
||||
);
|
||||
}
|
||||
return issues;
|
||||
}
|
||||
|
||||
Directory? get _androidAppDirectory {
|
||||
final root = shorebirdEnv.getFlutterProjectRoot();
|
||||
if (root == null) return null;
|
||||
return Directory(p.join(root.path, 'android', 'app'));
|
||||
}
|
||||
|
||||
List<File> get _gradleFiles {
|
||||
final appDir = _androidAppDirectory;
|
||||
if (appDir == null) return const [];
|
||||
return [
|
||||
File(p.join(appDir.path, 'build.gradle.kts')),
|
||||
File(p.join(appDir.path, 'build.gradle')),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
|
||||
export 'android_internet_permission_validator.dart';
|
||||
export 'flavor_validator.dart';
|
||||
export 'legacy_keep_debug_symbols_validator.dart';
|
||||
export 'macos_network_entitlement_validator.dart';
|
||||
export 'shorebird_version_validator.dart';
|
||||
export 'shorebird_yaml_asset_validator.dart';
|
||||
|
||||
@@ -588,6 +588,76 @@ void main() {
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when the supplement contains an obfuscation map', () {
|
||||
// Wire up extractZip to actually write the obfuscation_map.json
|
||||
// so PatchCommand's `if (obfuscationMapFile != null)` block
|
||||
// fires. The flutter-version gating that drops --strip on
|
||||
// Android 3.44+ depends on `release.flutterRevision` resolving
|
||||
// to a Version, so we mock that per-test.
|
||||
setUp(() {
|
||||
when(
|
||||
() => artifactManager.extractZip(
|
||||
zipFile: any(named: 'zipFile'),
|
||||
outputDirectory: any(named: 'outputDirectory'),
|
||||
),
|
||||
).thenAnswer((invocation) async {
|
||||
final outputDirectory =
|
||||
invocation.namedArguments[#outputDirectory] as Directory;
|
||||
File(
|
||||
p.join(outputDirectory.path, 'obfuscation_map.json'),
|
||||
).writeAsStringSync('{}');
|
||||
});
|
||||
});
|
||||
|
||||
test(
|
||||
'''on Android with Flutter < 3.44, passes --strip in extraBuildArgs''',
|
||||
() async {
|
||||
when(
|
||||
() => shorebirdFlutter.shouldPreStripLibappInGenSnapshot(
|
||||
platform: any(named: 'platform'),
|
||||
flutterRevision: any(named: 'flutterRevision'),
|
||||
),
|
||||
).thenAnswer((_) async => true);
|
||||
|
||||
await runWithOverrides(() => command.createPatch(patcher));
|
||||
|
||||
final captured =
|
||||
verify(
|
||||
() => patcher.extraBuildArgs = captureAny(),
|
||||
).captured.last
|
||||
as List<String>;
|
||||
expect(
|
||||
captured,
|
||||
contains('--extra-gen-snapshot-options=--strip'),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'''on Android with Flutter 3.44+, omits --strip in extraBuildArgs''',
|
||||
() async {
|
||||
when(
|
||||
() => shorebirdFlutter.shouldPreStripLibappInGenSnapshot(
|
||||
platform: any(named: 'platform'),
|
||||
flutterRevision: any(named: 'flutterRevision'),
|
||||
),
|
||||
).thenAnswer((_) async => false);
|
||||
|
||||
await runWithOverrides(() => command.createPatch(patcher));
|
||||
|
||||
final captured =
|
||||
verify(
|
||||
() => patcher.extraBuildArgs = captureAny(),
|
||||
).captured.last
|
||||
as List<String>;
|
||||
expect(
|
||||
captured,
|
||||
isNot(contains('--extra-gen-snapshot-options=--strip')),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group(
|
||||
|
||||
@@ -479,6 +479,19 @@ void main() {
|
||||
setUp(() {
|
||||
when(() => argResults['obfuscate']).thenReturn(true);
|
||||
when(() => argResults.wasParsed('obfuscate')).thenReturn(true);
|
||||
when(() => shorebirdEnv.flutterRevision).thenReturn('deadbeef');
|
||||
when(
|
||||
() => shorebirdFlutter.resolveFlutterVersion(any()),
|
||||
).thenAnswer((_) async => Version(3, 41, 2));
|
||||
// AAR is an Android pipeline target; addObfuscationMapArgs
|
||||
// consults this helper to decide whether to pass --strip.
|
||||
// On pre-3.44 Flutter we still pre-strip in gen_snapshot.
|
||||
when(
|
||||
() => shorebirdFlutter.shouldPreStripLibappInGenSnapshot(
|
||||
platform: any(named: 'platform'),
|
||||
flutterRevision: any(named: 'flutterRevision'),
|
||||
),
|
||||
).thenAnswer((_) async => true);
|
||||
// Simulate the build creating the obfuscation map.
|
||||
when(
|
||||
() => artifactBuilder.buildAar(
|
||||
|
||||
@@ -342,6 +342,89 @@ To change the version of this release, change your app's version in your pubspec
|
||||
});
|
||||
});
|
||||
|
||||
group('addObfuscationMapArgs', () {
|
||||
// Flutter PR https://github.com/flutter/flutter/pull/181275 (3.44)
|
||||
// moved libapp.so stripping responsibility from gen_snapshot to AGP.
|
||||
// Passing --strip on 3.44+ pre-strips the snapshot, blocks AGP from
|
||||
// emitting the `.sym` companion, and trips flutter_tools' new
|
||||
// post-build verification. The releaser must therefore conditionally
|
||||
// omit --strip on Android for 3.44+ pins.
|
||||
setUp(() {
|
||||
when(() => argResults['obfuscate']).thenReturn(true);
|
||||
when(() => argResults.wasParsed('obfuscate')).thenReturn(true);
|
||||
when(() => shorebirdEnv.flutterRevision).thenReturn('deadbeef');
|
||||
});
|
||||
|
||||
test(
|
||||
'on Flutter < 3.44, passes --strip so gen_snapshot strips libapp.so',
|
||||
() async {
|
||||
when(
|
||||
() => shorebirdFlutter.shouldPreStripLibappInGenSnapshot(
|
||||
platform: any(named: 'platform'),
|
||||
flutterRevision: any(named: 'flutterRevision'),
|
||||
),
|
||||
).thenAnswer((_) async => true);
|
||||
|
||||
final buildArgs = <String>[];
|
||||
await runWithOverrides(
|
||||
() => androidReleaser.addObfuscationMapArgs(buildArgs),
|
||||
);
|
||||
|
||||
expect(
|
||||
buildArgs,
|
||||
containsAll([
|
||||
startsWith(
|
||||
'--extra-gen-snapshot-options=--save-obfuscation-map=',
|
||||
),
|
||||
'--extra-gen-snapshot-options=--strip',
|
||||
]),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'''on Flutter 3.44+ Android, omits --strip so AGP can strip libapp.so and emit libapp.so.sym''',
|
||||
() async {
|
||||
when(
|
||||
() => shorebirdFlutter.shouldPreStripLibappInGenSnapshot(
|
||||
platform: any(named: 'platform'),
|
||||
flutterRevision: any(named: 'flutterRevision'),
|
||||
),
|
||||
).thenAnswer((_) async => false);
|
||||
|
||||
final buildArgs = <String>[];
|
||||
await runWithOverrides(
|
||||
() => androidReleaser.addObfuscationMapArgs(buildArgs),
|
||||
);
|
||||
|
||||
expect(
|
||||
buildArgs,
|
||||
contains(
|
||||
startsWith(
|
||||
'--extra-gen-snapshot-options=--save-obfuscation-map=',
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
buildArgs,
|
||||
isNot(contains('--extra-gen-snapshot-options=--strip')),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test('is a no-op when --obfuscate is not set', () async {
|
||||
when(() => argResults['obfuscate']).thenReturn(false);
|
||||
when(() => argResults.wasParsed('obfuscate')).thenReturn(false);
|
||||
|
||||
final buildArgs = <String>[];
|
||||
await runWithOverrides(
|
||||
() => androidReleaser.addObfuscationMapArgs(buildArgs),
|
||||
);
|
||||
|
||||
expect(buildArgs, isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
group('buildReleaseArtifacts', () {
|
||||
late File aabFile;
|
||||
|
||||
|
||||
@@ -485,6 +485,14 @@ void main() {
|
||||
setUp(() {
|
||||
when(() => argResults['obfuscate']).thenReturn(true);
|
||||
when(() => argResults.wasParsed('obfuscate')).thenReturn(true);
|
||||
when(() => shorebirdEnv.flutterRevision).thenReturn('deadbeef');
|
||||
// Non-Android pipelines always pre-strip in gen_snapshot.
|
||||
when(
|
||||
() => shorebirdFlutter.shouldPreStripLibappInGenSnapshot(
|
||||
platform: any(named: 'platform'),
|
||||
flutterRevision: any(named: 'flutterRevision'),
|
||||
),
|
||||
).thenAnswer((_) async => true);
|
||||
// Simulate the build creating the obfuscation map.
|
||||
when(
|
||||
() => artifactBuilder.buildIosFramework(
|
||||
|
||||
@@ -358,6 +358,41 @@ $body
|
||||
});
|
||||
});
|
||||
|
||||
group('addObfuscationMapArgs', () {
|
||||
// The libapp.so strip gating only applies to Android; on iOS AGP is
|
||||
// not in the pipeline. iOS must continue to pre-strip the snapshot in
|
||||
// gen_snapshot regardless of the Flutter version to prevent the
|
||||
// DWARF debug sections from leaking the identifiers obfuscation is
|
||||
// meant to hide.
|
||||
setUp(() {
|
||||
when(() => argResults['obfuscate']).thenReturn(true);
|
||||
when(() => argResults.wasParsed('obfuscate')).thenReturn(true);
|
||||
when(() => shorebirdEnv.flutterRevision).thenReturn('deadbeef');
|
||||
when(
|
||||
() => shorebirdEnv.getShorebirdProjectRoot(),
|
||||
).thenReturn(projectRoot);
|
||||
});
|
||||
|
||||
test('passes --strip on Flutter 3.44+ for iOS', () async {
|
||||
when(
|
||||
() => shorebirdFlutter.shouldPreStripLibappInGenSnapshot(
|
||||
platform: any(named: 'platform'),
|
||||
flutterRevision: any(named: 'flutterRevision'),
|
||||
),
|
||||
).thenAnswer((_) async => true);
|
||||
|
||||
final buildArgs = <String>[];
|
||||
await runWithOverrides(
|
||||
() => iosReleaser.addObfuscationMapArgs(buildArgs),
|
||||
);
|
||||
|
||||
expect(
|
||||
buildArgs,
|
||||
contains('--extra-gen-snapshot-options=--strip'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('buildReleaseArtifacts', () {
|
||||
const flutterVersionAndRevision = '3.10.6 (83305b5088)';
|
||||
const base64PublicKey = 'base64PublicKey';
|
||||
@@ -629,6 +664,14 @@ $body
|
||||
setUp(() {
|
||||
when(() => argResults['obfuscate']).thenReturn(true);
|
||||
when(() => argResults.wasParsed('obfuscate')).thenReturn(true);
|
||||
when(() => shorebirdEnv.flutterRevision).thenReturn('deadbeef');
|
||||
// iOS always pre-strips in gen_snapshot (AGP isn't in the pipeline).
|
||||
when(
|
||||
() => shorebirdFlutter.shouldPreStripLibappInGenSnapshot(
|
||||
platform: any(named: 'platform'),
|
||||
flutterRevision: any(named: 'flutterRevision'),
|
||||
),
|
||||
).thenAnswer((_) async => true);
|
||||
// By default, simulate the build creating the obfuscation map.
|
||||
when(
|
||||
() => artifactBuilder.buildIpa(
|
||||
|
||||
@@ -410,6 +410,14 @@ To change the version of this release, change your app's version in your pubspec
|
||||
setUp(() {
|
||||
when(() => argResults['obfuscate']).thenReturn(true);
|
||||
when(() => argResults.wasParsed('obfuscate')).thenReturn(true);
|
||||
when(() => shorebirdEnv.flutterRevision).thenReturn('deadbeef');
|
||||
// Non-Android pipelines always pre-strip in gen_snapshot.
|
||||
when(
|
||||
() => shorebirdFlutter.shouldPreStripLibappInGenSnapshot(
|
||||
platform: any(named: 'platform'),
|
||||
flutterRevision: any(named: 'flutterRevision'),
|
||||
),
|
||||
).thenAnswer((_) async => true);
|
||||
// Simulate the build creating the obfuscation map.
|
||||
when(
|
||||
() => artifactBuilder.buildLinuxApp(
|
||||
|
||||
@@ -68,6 +68,10 @@ void main() {
|
||||
);
|
||||
}
|
||||
|
||||
setUpAll(() {
|
||||
registerFallbackValue(ReleasePlatform.macos);
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
argResults = MockArgResults();
|
||||
artifactBuilder = MockArtifactBuilder();
|
||||
@@ -455,6 +459,14 @@ To change the version of this release, change your app's version in your pubspec
|
||||
setUp(() {
|
||||
when(() => argResults['obfuscate']).thenReturn(true);
|
||||
when(() => argResults.wasParsed('obfuscate')).thenReturn(true);
|
||||
when(() => shorebirdEnv.flutterRevision).thenReturn('deadbeef');
|
||||
// Non-Android pipelines always pre-strip in gen_snapshot.
|
||||
when(
|
||||
() => shorebirdFlutter.shouldPreStripLibappInGenSnapshot(
|
||||
platform: any(named: 'platform'),
|
||||
flutterRevision: any(named: 'flutterRevision'),
|
||||
),
|
||||
).thenAnswer((_) async => true);
|
||||
// By default, simulate the build creating the obfuscation map.
|
||||
when(
|
||||
() => artifactBuilder.buildMacos(
|
||||
|
||||
@@ -426,6 +426,14 @@ To change the version of this release, change your app's version in your pubspec
|
||||
setUp(() {
|
||||
when(() => argResults['obfuscate']).thenReturn(true);
|
||||
when(() => argResults.wasParsed('obfuscate')).thenReturn(true);
|
||||
when(() => shorebirdEnv.flutterRevision).thenReturn('deadbeef');
|
||||
// Non-Android pipelines always pre-strip in gen_snapshot.
|
||||
when(
|
||||
() => shorebirdFlutter.shouldPreStripLibappInGenSnapshot(
|
||||
platform: any(named: 'platform'),
|
||||
flutterRevision: any(named: 'flutterRevision'),
|
||||
),
|
||||
).thenAnswer((_) async => true);
|
||||
// Simulate the build creating the obfuscation map.
|
||||
when(
|
||||
() => artifactBuilder.buildWindowsApp(
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:shorebird_cli/src/platform.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_env.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_flutter.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'mocks.dart';
|
||||
@@ -455,6 +456,90 @@ Tools • Dart 3.0.6 • DevTools 2.23.1''');
|
||||
});
|
||||
});
|
||||
|
||||
group('shouldPreStripLibappInGenSnapshot', () {
|
||||
test('returns true on iOS regardless of Flutter version', () async {
|
||||
when(
|
||||
() => git.forEachRef(
|
||||
directory: any(named: 'directory'),
|
||||
contains: any(named: 'contains'),
|
||||
format: any(named: 'format'),
|
||||
pattern: any(named: 'pattern'),
|
||||
),
|
||||
).thenAnswer((_) async => 'origin/flutter_release/3.44.0');
|
||||
|
||||
final result = await runWithOverrides(
|
||||
() => shorebirdFlutter.shouldPreStripLibappInGenSnapshot(
|
||||
platform: ReleasePlatform.ios,
|
||||
flutterRevision: 'deadbeef',
|
||||
),
|
||||
);
|
||||
expect(result, isTrue);
|
||||
});
|
||||
|
||||
test('returns true on Android when Flutter is older than 3.44', () async {
|
||||
when(
|
||||
() => git.forEachRef(
|
||||
directory: any(named: 'directory'),
|
||||
contains: any(named: 'contains'),
|
||||
format: any(named: 'format'),
|
||||
pattern: any(named: 'pattern'),
|
||||
),
|
||||
).thenAnswer((_) async => 'origin/flutter_release/3.43.0');
|
||||
|
||||
final result = await runWithOverrides(
|
||||
() => shorebirdFlutter.shouldPreStripLibappInGenSnapshot(
|
||||
platform: ReleasePlatform.android,
|
||||
flutterRevision: 'deadbeef',
|
||||
),
|
||||
);
|
||||
expect(result, isTrue);
|
||||
});
|
||||
|
||||
test('returns false on Android when Flutter is 3.44 or newer', () async {
|
||||
when(
|
||||
() => git.forEachRef(
|
||||
directory: any(named: 'directory'),
|
||||
contains: any(named: 'contains'),
|
||||
format: any(named: 'format'),
|
||||
pattern: any(named: 'pattern'),
|
||||
),
|
||||
).thenAnswer((_) async => 'origin/flutter_release/3.44.0');
|
||||
|
||||
final result = await runWithOverrides(
|
||||
() => shorebirdFlutter.shouldPreStripLibappInGenSnapshot(
|
||||
platform: ReleasePlatform.android,
|
||||
flutterRevision: 'deadbeef',
|
||||
),
|
||||
);
|
||||
expect(result, isFalse);
|
||||
});
|
||||
|
||||
test(
|
||||
'''returns false on Android when the version cannot be resolved (development pin)''',
|
||||
() async {
|
||||
// Unresolvable revisions (e.g. development branches) fall back to the
|
||||
// constraint's min version, so users on bleeding-edge pins get the
|
||||
// 3.44+ AGP-stripped behavior rather than the pre-strip path.
|
||||
when(
|
||||
() => git.forEachRef(
|
||||
directory: any(named: 'directory'),
|
||||
contains: any(named: 'contains'),
|
||||
format: any(named: 'format'),
|
||||
pattern: any(named: 'pattern'),
|
||||
),
|
||||
).thenAnswer((_) async => '');
|
||||
|
||||
final result = await runWithOverrides(
|
||||
() => shorebirdFlutter.shouldPreStripLibappInGenSnapshot(
|
||||
platform: ReleasePlatform.android,
|
||||
flutterRevision: 'deadbeef',
|
||||
),
|
||||
);
|
||||
expect(result, isFalse);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('fetchRemoteRefs', () {
|
||||
test('fetches from remote', () async {
|
||||
when(
|
||||
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_env.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_flutter.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../mocks.dart';
|
||||
|
||||
void main() {
|
||||
group(LegacyKeepDebugSymbolsValidator, () {
|
||||
const flutterRevision = 'aaaa1111bbbb2222cccc3333dddd4444eeee5555';
|
||||
|
||||
late Directory projectRoot;
|
||||
late ShorebirdEnv shorebirdEnv;
|
||||
late ShorebirdFlutter shorebirdFlutter;
|
||||
|
||||
String kotlinGradleWithLegacyLine = '''
|
||||
android {
|
||||
buildTypes {
|
||||
release {
|
||||
packaging.jniLibs.keepDebugSymbols.add("**/libapp.so")
|
||||
}
|
||||
}
|
||||
}
|
||||
''';
|
||||
|
||||
String groovyGradleWithLegacyPlusEquals = '''
|
||||
android {
|
||||
buildTypes {
|
||||
release {
|
||||
packaging.jniLibs.keepDebugSymbols += '**/libapp.so'
|
||||
}
|
||||
}
|
||||
}
|
||||
''';
|
||||
|
||||
String cleanGradle = '''
|
||||
android {
|
||||
buildTypes {
|
||||
release {
|
||||
// Nothing about libapp.so here.
|
||||
}
|
||||
}
|
||||
}
|
||||
''';
|
||||
|
||||
void writeGradle(String filename, String contents) {
|
||||
final appDir = Directory(p.join(projectRoot.path, 'android', 'app'))
|
||||
..createSync(recursive: true);
|
||||
File(p.join(appDir.path, filename)).writeAsStringSync(contents);
|
||||
}
|
||||
|
||||
R runWithOverrides<R>(R Function() body) {
|
||||
return runScoped(
|
||||
body,
|
||||
values: {
|
||||
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
|
||||
shorebirdFlutterRef.overrideWith(() => shorebirdFlutter),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
projectRoot = Directory.systemTemp.createTempSync();
|
||||
shorebirdEnv = MockShorebirdEnv();
|
||||
shorebirdFlutter = MockShorebirdFlutter();
|
||||
|
||||
when(() => shorebirdEnv.getFlutterProjectRoot()).thenReturn(projectRoot);
|
||||
when(() => shorebirdEnv.flutterRevision).thenReturn(flutterRevision);
|
||||
when(
|
||||
() => shorebirdFlutter.resolveFlutterVersion(any()),
|
||||
).thenAnswer((_) async => Version(3, 44, 0));
|
||||
});
|
||||
|
||||
test('has a non-empty description', () {
|
||||
expect(LegacyKeepDebugSymbolsValidator().description, isNotEmpty);
|
||||
});
|
||||
|
||||
group('canRunInCurrentContext', () {
|
||||
test('returns false when no android/app directory exists', () {
|
||||
expect(
|
||||
runWithOverrides(
|
||||
() => LegacyKeepDebugSymbolsValidator().canRunInCurrentContext(),
|
||||
),
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
|
||||
test('returns true when build.gradle.kts exists', () {
|
||||
writeGradle('build.gradle.kts', cleanGradle);
|
||||
expect(
|
||||
runWithOverrides(
|
||||
() => LegacyKeepDebugSymbolsValidator().canRunInCurrentContext(),
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
test('returns true when only build.gradle (Groovy) exists', () {
|
||||
writeGradle('build.gradle', cleanGradle);
|
||||
expect(
|
||||
runWithOverrides(
|
||||
() => LegacyKeepDebugSymbolsValidator().canRunInCurrentContext(),
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('on Flutter < 3.44', () {
|
||||
setUp(() {
|
||||
// The keepDebugSymbols line was the intended configuration before
|
||||
// 3.44, so the validator must be a no-op on older Flutter to avoid
|
||||
// false-positive warnings.
|
||||
when(
|
||||
() => shorebirdFlutter.resolveFlutterVersion(any()),
|
||||
).thenAnswer((_) async => Version(3, 41, 9));
|
||||
});
|
||||
|
||||
test('returns no issues even when the legacy line is present', () async {
|
||||
writeGradle('build.gradle.kts', kotlinGradleWithLegacyLine);
|
||||
|
||||
final issues = await runWithOverrides(
|
||||
LegacyKeepDebugSymbolsValidator().validate,
|
||||
);
|
||||
|
||||
expect(issues, isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
group('on Flutter >= 3.44', () {
|
||||
test(
|
||||
'returns no issues when neither gradle file contains the line',
|
||||
() async {
|
||||
writeGradle('build.gradle.kts', cleanGradle);
|
||||
|
||||
final issues = await runWithOverrides(
|
||||
LegacyKeepDebugSymbolsValidator().validate,
|
||||
);
|
||||
|
||||
expect(issues, isEmpty);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'returns a warning when build.gradle.kts has the .add(...) form',
|
||||
() async {
|
||||
writeGradle('build.gradle.kts', kotlinGradleWithLegacyLine);
|
||||
|
||||
final issues = await runWithOverrides(
|
||||
LegacyKeepDebugSymbolsValidator().validate,
|
||||
);
|
||||
|
||||
expect(issues, hasLength(1));
|
||||
final issue = issues.single;
|
||||
expect(issue.severity, ValidationIssueSeverity.warning);
|
||||
expect(issue.message, contains('build.gradle.kts'));
|
||||
expect(issue.message, contains('keepDebugSymbols'));
|
||||
expect(issue.message, contains('libapp.so'));
|
||||
expect(issue.message, contains('flutter/flutter#181275'));
|
||||
},
|
||||
);
|
||||
|
||||
test('returns a warning when build.gradle has the += form', () async {
|
||||
writeGradle('build.gradle', groovyGradleWithLegacyPlusEquals);
|
||||
|
||||
final issues = await runWithOverrides(
|
||||
LegacyKeepDebugSymbolsValidator().validate,
|
||||
);
|
||||
|
||||
expect(issues, hasLength(1));
|
||||
expect(issues.single.message, contains('build.gradle'));
|
||||
});
|
||||
|
||||
test(
|
||||
'returns two warnings when both files contain the legacy line',
|
||||
() async {
|
||||
writeGradle('build.gradle.kts', kotlinGradleWithLegacyLine);
|
||||
writeGradle('build.gradle', groovyGradleWithLegacyPlusEquals);
|
||||
|
||||
final issues = await runWithOverrides(
|
||||
LegacyKeepDebugSymbolsValidator().validate,
|
||||
);
|
||||
|
||||
expect(issues, hasLength(2));
|
||||
expect(
|
||||
issues.map((i) => i.severity),
|
||||
everyElement(ValidationIssueSeverity.warning),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'treats an unknown Flutter version as satisfying the constraint',
|
||||
() async {
|
||||
// resolveFlutterVersion returns null for development pins. The
|
||||
// validator should still surface the warning in that case rather
|
||||
// than silently skipping; users on bleeding-edge pins are exactly
|
||||
// the ones most likely to be on a 3.44-equivalent fork.
|
||||
when(
|
||||
() => shorebirdFlutter.resolveFlutterVersion(any()),
|
||||
).thenAnswer((_) async => null);
|
||||
writeGradle('build.gradle.kts', kotlinGradleWithLegacyLine);
|
||||
|
||||
final issues = await runWithOverrides(
|
||||
LegacyKeepDebugSymbolsValidator().validate,
|
||||
);
|
||||
|
||||
expect(issues, hasLength(1));
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user