diff --git a/dev/devicelab/bin/tasks/build_aar_module_test.dart b/dev/devicelab/bin/tasks/build_aar_module_test.dart index b7ea856f98e..37b4c25b510 100644 --- a/dev/devicelab/bin/tasks/build_aar_module_test.dart +++ b/dev/devicelab/bin/tasks/build_aar_module_test.dart @@ -21,6 +21,11 @@ Future main() async { } print('\nUsing JAVA_HOME=$javaHome'); + // TODO(matanlurey): Remove after default. + // https://github.com/flutter/flutter/issues/160257 + section('Opt-in to --explicit-package-dependencies'); + await flutter('config', options: ['--explicit-package-dependencies']); + final Directory tempDir = Directory.systemTemp.createTempSync('flutter_module_test.'); final Directory projectDir = Directory(path.join(tempDir.path, 'hello')); try { diff --git a/packages/flutter_tools/lib/src/android/android_builder.dart b/packages/flutter_tools/lib/src/android/android_builder.dart index 262ab18f18f..0c7a9e22021 100644 --- a/packages/flutter_tools/lib/src/android/android_builder.dart +++ b/packages/flutter_tools/lib/src/android/android_builder.dart @@ -19,6 +19,7 @@ abstract class AndroidBuilder { required FlutterProject project, required Set androidBuildInfo, required String target, + required Future Function(FlutterProject, {required bool releaseMode}) generateTooling, String? outputDirectoryPath, required String buildNumber, }); diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index 832c80f6a23..6cca0e60c7e 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -191,6 +191,7 @@ class AndroidGradleBuilder implements AndroidBuilder { required FlutterProject project, required Set androidBuildInfo, required String target, + required Future Function(FlutterProject, {required bool releaseMode}) generateTooling, String? outputDirectoryPath, required String buildNumber, }) async { @@ -208,6 +209,7 @@ class AndroidGradleBuilder implements AndroidBuilder { _logger.printWarning(androidX86DeprecationWarning); } for (final AndroidBuildInfo androidBuildInfo in androidBuildInfo) { + await generateTooling(project, releaseMode: androidBuildInfo.buildInfo.isRelease); await buildGradleAar( project: project, androidBuildInfo: androidBuildInfo, diff --git a/packages/flutter_tools/lib/src/commands/build_aar.dart b/packages/flutter_tools/lib/src/commands/build_aar.dart index aabf0f41fc7..9174c1f5e77 100644 --- a/packages/flutter_tools/lib/src/commands/build_aar.dart +++ b/packages/flutter_tools/lib/src/commands/build_aar.dart @@ -111,6 +111,9 @@ class BuildAarCommand extends BuildSubCommand { await super.validateCommand(); } + @override + bool get regeneratePlatformSpecificToolingDurifyVerify => false; + @override Future runCommand() async { if (_androidSdk == null) { @@ -149,10 +152,12 @@ class BuildAarCommand extends BuildSubCommand { } displayNullSafetyMode(androidBuildInfo.first.buildInfo); + await androidBuilder?.buildAar( project: project, target: targetFile.path, androidBuildInfo: androidBuildInfo, + generateTooling: regeneratePlatformSpecificToolingIfApplicable, outputDirectoryPath: stringArg('output'), buildNumber: buildNumber, ); diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index 019d2fd74cb..cea6e30505c 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -1893,22 +1893,15 @@ Run 'flutter -h' (or 'flutter -h') for available flutter commands and buildSystem: globals.buildSystem, buildTargets: globals.buildTargets, ); - - // null implicitly means all plugins are allowed - List? allowedPlugins; - if (stringArg(FlutterGlobalOptions.kDeviceIdOption, global: true) == 'preview') { - // The preview device does not currently support any plugins. - allowedPlugins = PreviewDevice.supportedPubPlugins; - } - await project.regeneratePlatformSpecificTooling( - allowedPlugins: allowedPlugins, - releaseMode: featureFlags.isExplicitPackageDependenciesEnabled && getBuildMode().isRelease, - ); if (reportNullSafety) { await _sendNullSafetyAnalyticsEvents(project); } } + if (regeneratePlatformSpecificToolingDurifyVerify) { + await regeneratePlatformSpecificToolingIfApplicable(project); + } + setupApplicationPackages(); if (commandPath != null) { @@ -1918,6 +1911,66 @@ Run 'flutter -h' (or 'flutter -h') for available flutter commands and return runCommand(); } + /// Whether to run [regeneratePlatformSpecificTooling] in [verifyThenRunCommand]. + /// + /// By default `true`, but sub-commands that do _meta_ builds (make multiple different + /// builds sequentially in one-go) may choose to override this and provide `false`, instead + /// calling [regeneratePlatformSpecificTooling] manually when applicable. + @visibleForOverriding + bool get regeneratePlatformSpecificToolingDurifyVerify => true; + + /// Runs [FlutterProject.regeneratePlatformSpecificTooling] for [project] with appropriate configuration. + /// + /// By default, this uses [getBuildMode] to determine and provide whether a release build is being made, + /// but sub-commands (such as commands that do _meta_ builds, or builds that make multiple different builds + /// sequentially in one-go) may choose to overide this and make the call at a different point in time. + /// + /// This method should only be called when [shouldRunPub] is `true`: + /// ```dart + /// if (shouldRunPub) { + /// await regeneratePlatformSpecificTooling(project); + /// } + /// ``` + /// + /// See also: + /// + /// - . + @protected + @nonVirtual + Future regeneratePlatformSpecificToolingIfApplicable( + FlutterProject project, { + bool? releaseMode, + }) async { + if (!shouldRunPub) { + return; + } + + // TODO(matanlurey): Determine if PreviewDevice should be kept. + // https://github.com/flutter/flutter/issues/162693 + final List? allowedPlugins; + if (stringArg(FlutterGlobalOptions.kDeviceIdOption, global: true) == 'preview') { + // The preview device does not currently support any plugins. + allowedPlugins = PreviewDevice.supportedPubPlugins; + } else { + // null means all plugins are allowed + allowedPlugins = null; + } + + await project.regeneratePlatformSpecificTooling( + allowedPlugins: allowedPlugins, + // TODO(matanlurey): Move this up, i.e. releaseMode ??= getBuildMode().release. + // + // As it stands, this is a breaking change until https://github.com/flutter/flutter/issues/162704 is + // implemented, as the build_ios_framework command (and similar) will start querying + // for getBuildMode(), causing an error (meta-build commands like build ios-framework do not have + // a single build mode). Once ios-framework and macos-framework are migrated, then this can be + // cleaned up. + releaseMode: + featureFlags.isExplicitPackageDependenciesEnabled && + (releaseMode ?? getBuildMode().isRelease), + ); + } + Future _sendNullSafetyAnalyticsEvents(FlutterProject project) async { final BuildInfo buildInfo = await getBuildInfo(); NullSafetyAnalysisEvent( diff --git a/packages/flutter_tools/test/commands.shard/permeable/build_aar_test.dart b/packages/flutter_tools/test/commands.shard/permeable/build_aar_test.dart index 05712d5b925..ed63eafdc02 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/build_aar_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/build_aar_test.dart @@ -13,6 +13,7 @@ import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/build_aar.dart'; +import 'package:flutter_tools/src/dart/pub.dart'; import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/project.dart'; @@ -24,24 +25,42 @@ import '../../src/android_common.dart'; import '../../src/common.dart'; import '../../src/context.dart'; import '../../src/fake_process_manager.dart'; +import '../../src/fake_pub_deps.dart'; import '../../src/fakes.dart' hide FakeFlutterProjectFactory; import '../../src/test_flutter_command_runner.dart'; void main() { Cache.disableLocking(); - Future runCommandIn(String target, {List? arguments}) async { + /// Runs the equivalent of `flutter build aar`. + /// + /// If [arguments] are provided, they are appended to the end, i.e.: + /// ```sh + /// flutter build aar [arguments] + /// ``` + /// + /// If [androidSdk] is provided, it is used, otherwise defaults to [FakeAndroidSdk]. + Future runBuildAar( + String target, { + AndroidSdk? androidSdk = const _FakeAndroidSdk(), + List? arguments, + }) async { final BuildAarCommand command = BuildAarCommand( - androidSdk: FakeAndroidSdk(), + androidSdk: androidSdk, fileSystem: globals.fs, logger: BufferLogger.test(), verboseHelp: false, ); final CommandRunner runner = createTestCommandRunner(command); - await runner.run(['aar', '--no-pub', ...?arguments, target]); + await runner.run(['aar', ...?arguments, target]); return command; } + // TODO(matanlurey): Remove after `explicit-package-dependencies` is enabled by default. + FeatureFlags enableExplicitPackageDependencies() { + return TestFeatureFlags(isExplicitPackageDependenciesEnabled: true); + } + group('Usage', () { late Directory tempDir; late FakeAnalytics analytics; @@ -66,7 +85,7 @@ void main() { arguments: ['--no-pub', '--template=module'], ); - await runCommandIn(projectPath); + await runBuildAar(projectPath, arguments: ['--no-pub']); expect( analytics.sentEvents, contains( @@ -80,7 +99,7 @@ void main() { ); }, overrides: { - AndroidBuilder: () => FakeAndroidBuilder(), + AndroidBuilder: () => _CapturingFakeAndroidBuilder(), Analytics: () => analytics, }, ); @@ -93,7 +112,10 @@ void main() { arguments: ['--no-pub', '--template=module'], ); - await runCommandIn(projectPath, arguments: ['--target-platform=android-arm']); + await runBuildAar( + projectPath, + arguments: ['--no-pub', '--target-platform=android-arm'], + ); expect( analytics.sentEvents, contains( @@ -107,11 +129,36 @@ void main() { ); }, overrides: { - AndroidBuilder: () => FakeAndroidBuilder(), + AndroidBuilder: () => _CapturingFakeAndroidBuilder(), Analytics: () => analytics, }, ); + // Regression test for https://github.com/flutter/flutter/issues/162649. + testUsingContext( + 'triggers builds even with --pub', + () async { + final String projectPath = await createProject( + tempDir, + arguments: ['--no-pub', '--template=module'], + ); + + await runBuildAar( + projectPath, + arguments: ['--target-platform=android-arm'], + // If we use --no-pub, it bypasses validation that occurs only on a + // build with --pub, which as a consequence means that we aren't + // testing every code branch. + ); + }, + overrides: { + AndroidBuilder: () => _CapturingFakeAndroidBuilder(), + Analytics: () => analytics, + FeatureFlags: enableExplicitPackageDependencies, + Pub: () => FakePubWithPrimedDeps(allowGet: true), + }, + ); + testUsingContext( 'logs success', () async { @@ -120,7 +167,10 @@ void main() { arguments: ['--no-pub', '--template=module'], ); - await runCommandIn(projectPath, arguments: ['--target-platform=android-arm']); + await runBuildAar( + projectPath, + arguments: ['--no-pub', '--target-platform=android-arm'], + ); final Iterable successEvent = analytics.sentEvents.where( (Event e) => @@ -131,7 +181,7 @@ void main() { expect(successEvent, isNotEmpty, reason: 'Tool should send create success event'); }, overrides: { - AndroidBuilder: () => FakeAndroidBuilder(), + AndroidBuilder: () => _CapturingFakeAndroidBuilder(), Analytics: () => analytics, }, ); @@ -139,10 +189,10 @@ void main() { group('flag parsing', () { late Directory tempDir; - late FakeAndroidBuilder fakeAndroidBuilder; + late _CapturingFakeAndroidBuilder fakeAndroidBuilder; setUp(() { - fakeAndroidBuilder = FakeAndroidBuilder(); + fakeAndroidBuilder = _CapturingFakeAndroidBuilder(); tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_tools_build_aar_test.'); }); @@ -155,13 +205,19 @@ void main() { tempDir, arguments: ['--no-pub', '--template=module'], ); - await runCommandIn(projectPath); + await runBuildAar(projectPath, arguments: ['--no-pub']); - expect(fakeAndroidBuilder.buildNumber, '1.0'); - expect(fakeAndroidBuilder.androidBuildInfo.length, 3); + expect( + fakeAndroidBuilder.capturedBuildAarCalls, + hasLength(1), + reason: 'A single call to buildAar was expected.', + ); + final Invocation buildAarCall = fakeAndroidBuilder.capturedBuildAarCalls.single; + expect(buildAarCall.namedArguments[#buildNumber], '1.0'); final List buildModes = []; - for (final AndroidBuildInfo androidBuildInfo in fakeAndroidBuilder.androidBuildInfo) { + for (final AndroidBuildInfo androidBuildInfo + in buildAarCall.namedArguments[#androidBuildInfo] as Set) { final BuildInfo buildInfo = androidBuildInfo.buildInfo; buildModes.add(buildInfo.mode); if (buildInfo.mode.isPrecompiled) { @@ -180,7 +236,7 @@ void main() { AndroidArch.x86_64, ]); } - expect(buildModes.length, 3); + expect(buildModes, hasLength(3)); expect( buildModes, containsAll([BuildMode.debug, BuildMode.profile, BuildMode.release]), @@ -192,9 +248,10 @@ void main() { tempDir, arguments: ['--no-pub', '--template=module'], ); - await runCommandIn( + await runBuildAar( projectPath, arguments: [ + '--no-pub', '--no-debug', '--no-profile', '--target-platform', @@ -211,9 +268,16 @@ void main() { ], ); - expect(fakeAndroidBuilder.buildNumber, '200'); + expect( + fakeAndroidBuilder.capturedBuildAarCalls, + hasLength(1), + reason: 'A single call to buildAar was expected.', + ); + final Invocation buildAarCall = fakeAndroidBuilder.capturedBuildAarCalls.single; + expect(buildAarCall.namedArguments[#buildNumber], '200'); - final AndroidBuildInfo androidBuildInfo = fakeAndroidBuilder.androidBuildInfo.single; + final AndroidBuildInfo androidBuildInfo = + (buildAarCall.namedArguments[#androidBuildInfo] as Set).single; expect(androidBuildInfo.targetArchs, [AndroidArch.x86]); final BuildInfo buildInfo = androidBuildInfo.buildInfo; @@ -229,7 +293,6 @@ void main() { group('Gradle', () { late Directory tempDir; - late AndroidSdk mockAndroidSdk; late String gradlew; late FakeProcessManager processManager; late String flutterRoot; @@ -241,7 +304,6 @@ void main() { fs: MemoryFileSystem.test(), fakeFlutterVersion: FakeFlutterVersion(), ); - mockAndroidSdk = FakeAndroidSdk(); gradlew = globals.fs.path.join( tempDir.path, 'flutter_project', @@ -267,7 +329,7 @@ void main() { await expectLater( () async { - await runBuildAarCommand(projectPath, null, arguments: ['--no-pub']); + await runBuildAar(projectPath, androidSdk: null, arguments: ['--no-pub']); }, throwsToolExit( message: 'No Android SDK found. Try setting the ANDROID_HOME environment variable', @@ -284,17 +346,19 @@ void main() { group('throws ToolExit', () { testUsingContext('main.dart not found', () async { await expectLater(() async { - await runBuildAarCommand( + await runBuildAar( 'missing_project', - mockAndroidSdk, - arguments: ['--no-pub'], + arguments: [ + '--no-pub', + globals.fs.path.join('missing_project', 'lib', 'main.dart'), + ], ); }, throwsToolExit(message: 'main.dart does not exist')); }); testUsingContext('flutter project not valid', () async { await expectLater(() async { - await runCommandIn(tempDir.path, arguments: ['--no-pub']); + await runBuildAar(tempDir.path, arguments: ['--no-pub']); }, throwsToolExit(message: 'is not a valid flutter project')); }); }); @@ -330,10 +394,10 @@ void main() { ); await expectLater( - () => runBuildAarCommand( + () => runBuildAar( projectPath, - mockAndroidSdk, arguments: [ + '--no-pub', '--no-debug', '--no-profile', '--extra-front-end-options=foo', @@ -349,7 +413,7 @@ void main() { Java: () => null, ProcessManager: () => processManager, FeatureFlags: () => TestFeatureFlags(isIOSEnabled: false), - AndroidStudio: () => FakeAndroidStudio(), + AndroidStudio: () => _FakeAndroidStudio(), }, ); @@ -393,7 +457,7 @@ void main() { arguments: ['--no-pub', '--template=module'], ); - await runBuildAarCommand(projectPath, mockAndroidSdk); + await runBuildAar(projectPath, arguments: ['--no-pub']); expect( fakeAnalytics.sentEvents, @@ -404,7 +468,7 @@ void main() { }, overrides: { Analytics: () => fakeAnalytics, - AndroidBuilder: () => FakeAndroidBuilder(), + AndroidBuilder: () => _CapturingFakeAndroidBuilder(), FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir), }, ); @@ -427,7 +491,7 @@ void main() { value: 'true', ); - await runBuildAarCommand(projectPath, mockAndroidSdk); + await runBuildAar(projectPath, arguments: ['--no-pub']); expect( fakeAnalytics.sentEvents, @@ -438,7 +502,7 @@ void main() { }, overrides: { Analytics: () => fakeAnalytics, - AndroidBuilder: () => FakeAndroidBuilder(), + AndroidBuilder: () => _CapturingFakeAndroidBuilder(), FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir), }, ); @@ -461,7 +525,7 @@ void main() { value: 'false', ); - await runBuildAarCommand(projectPath, mockAndroidSdk); + await runBuildAar(projectPath, arguments: ['--no-pub']); expect( fakeAnalytics.sentEvents, @@ -472,7 +536,7 @@ void main() { }, overrides: { Analytics: () => fakeAnalytics, - AndroidBuilder: () => FakeAndroidBuilder(), + AndroidBuilder: () => _CapturingFakeAndroidBuilder(), FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir), }, ); @@ -480,53 +544,27 @@ void main() { }); } -Future runBuildAarCommand( - String target, - AndroidSdk? androidSdk, { - List? arguments, -}) async { - final BuildAarCommand command = BuildAarCommand( - androidSdk: androidSdk, - fileSystem: globals.fs, - logger: BufferLogger.test(), - verboseHelp: false, - ); - final CommandRunner runner = createTestCommandRunner(command); - await runner.run([ - 'aar', - '--no-pub', - ...?arguments, - globals.fs.path.join(target, 'lib', 'main.dart'), - ]); - return command; -} - -class FakeAndroidBuilder extends Fake implements AndroidBuilder { - late FlutterProject project; - late Set androidBuildInfo; - late String target; - String? outputDirectoryPath; - late String buildNumber; +/// A fake implementation of [AndroidBuilder] that allows [buildAar] calls. +/// +/// Calls to [buildAar] are stored as [capturedBuildAarCalls], other calls are rejected. +final class _CapturingFakeAndroidBuilder extends Fake implements AndroidBuilder { + final List capturedBuildAarCalls = []; @override - Future buildAar({ - required FlutterProject project, - required Set androidBuildInfo, - required String target, - String? outputDirectoryPath, - required String buildNumber, - }) async { - this.project = project; - this.androidBuildInfo = androidBuildInfo; - this.target = target; - this.outputDirectoryPath = outputDirectoryPath; - this.buildNumber = buildNumber; + Object? noSuchMethod(Invocation invocation) { + if (invocation.memberName != #buildAar) { + return super.noSuchMethod(invocation); + } + capturedBuildAarCalls.add(invocation); + return Future.value(); } } -class FakeAndroidSdk extends Fake implements AndroidSdk {} +final class _FakeAndroidSdk with Fake implements AndroidSdk { + const _FakeAndroidSdk(); +} -class FakeAndroidStudio extends Fake implements AndroidStudio { +final class _FakeAndroidStudio extends Fake implements AndroidStudio { @override String get javaPath => 'java'; } diff --git a/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart b/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart index ff01b8f081e..e20c3d6e067 100644 --- a/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart @@ -20,6 +20,7 @@ import 'package:flutter_tools/src/base/process.dart'; import 'package:flutter_tools/src/base/user_messages.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; +import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/project.dart'; import 'package:test/fake.dart'; import 'package:unified_analytics/unified_analytics.dart'; @@ -41,6 +42,11 @@ const String minimalV2EmbeddingManifest = r''' '''; void main() { + // TODO(matanlurey): Remove after `explicit-package-dependencies` is enabled by default. + FeatureFlags enableExplicitPackageDependencies() { + return TestFeatureFlags(isExplicitPackageDependenciesEnabled: true); + } + group('gradle build', () { late BufferLogger logger; late FakeAnalytics fakeAnalytics; @@ -1566,6 +1572,141 @@ Gradle Crashed }, ); + // Regression test for https://github.com/flutter/flutter/issues/162649. + testUsingContext( + 'buildAar generates tooling for each sub-build for AARs', + () async { + addTearDown(() { + printOnFailure(logger.statusText); + printOnFailure(logger.errorText); + }); + final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), + logger: logger, + processManager: processManager, + fileSystem: fileSystem, + artifacts: Artifacts.test(), + analytics: fakeAnalytics, + gradleUtils: FakeGradleUtils(), + platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), + ); + processManager.addCommands(const [ + FakeCommand( + command: [ + 'gradlew', + '-I=/packages/flutter_tools/gradle/aar_init_script.gradle', + '-Pflutter-root=/', + '-Poutput-dir=/build/host', + '-Pis-plugin=false', + '-PbuildNumber=1.0', + '-q', + '-Pdart-obfuscation=false', + '-Ptrack-widget-creation=false', + '-Ptree-shake-icons=false', + '-Ptarget-platform=android-arm,android-arm64,android-x64', + 'assembleAarDebug', + ], + ), + FakeCommand( + command: [ + 'gradlew', + '-I=/packages/flutter_tools/gradle/aar_init_script.gradle', + '-Pflutter-root=/', + '-Poutput-dir=/build/host', + '-Pis-plugin=false', + '-PbuildNumber=1.0', + '-q', + '-Pdart-obfuscation=false', + '-Ptrack-widget-creation=false', + '-Ptree-shake-icons=false', + '-Ptarget-platform=android-arm,android-arm64,android-x64', + 'assembleAarProfile', + ], + ), + FakeCommand( + command: [ + 'gradlew', + '-I=/packages/flutter_tools/gradle/aar_init_script.gradle', + '-Pflutter-root=/', + '-Poutput-dir=/build/host', + '-Pis-plugin=false', + '-PbuildNumber=1.0', + '-q', + '-Pdart-obfuscation=false', + '-Ptrack-widget-creation=false', + '-Ptree-shake-icons=false', + '-Ptarget-platform=android-arm,android-arm64,android-x64', + 'assembleAarRelease', + ], + ), + ]); + + final File manifestFile = fileSystem.file('pubspec.yaml'); + manifestFile.createSync(recursive: true); + manifestFile.writeAsStringSync(''' + flutter: + module: + androidPackage: com.example.test + '''); + + fileSystem.file('.android/gradlew').createSync(recursive: true); + fileSystem.file('.android/gradle.properties').writeAsStringSync('irrelevant'); + fileSystem.file('.android/build.gradle').createSync(recursive: true); + fileSystem.directory('build/host/outputs/repo').createSync(recursive: true); + + final List<(FlutterProject, bool)> generateToolingCalls = <(FlutterProject, bool)>[]; + await builder.buildAar( + project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), + androidBuildInfo: const { + AndroidBuildInfo( + BuildInfo( + BuildMode.debug, + null, + treeShakeIcons: false, + packageConfigPath: '.dart_tool/package_config.json', + ), + ), + AndroidBuildInfo( + BuildInfo( + BuildMode.profile, + null, + treeShakeIcons: false, + packageConfigPath: '.dart_tool/package_config.json', + ), + ), + AndroidBuildInfo( + BuildInfo( + BuildMode.release, + null, + treeShakeIcons: false, + packageConfigPath: '.dart_tool/package_config.json', + ), + ), + }, + target: '', + buildNumber: '1.0', + generateTooling: (FlutterProject project, {required bool releaseMode}) async { + generateToolingCalls.add((project, releaseMode)); + }, + ); + expect(processManager, hasNoRemainingExpectations); + + // Ideally, this should be checked before each invocation to the process, + // but instead we'll assume it was invoked in the same order as the calls + // to gradle to keep the scope of this test light. + expect(generateToolingCalls, hasLength(3)); + expect( + generateToolingCalls.map(((FlutterProject, bool) call) { + return call.$2; + }), + [false, false, true], + reason: 'generateTooling should omit debug metadata for release builds', + ); + }, + overrides: {FeatureFlags: enableExplicitPackageDependencies}, + ); + testUsingContext( 'Verbose mode for AARs includes Gradle stacktrace and sets debug log level', () async { diff --git a/packages/flutter_tools/test/src/android_common.dart b/packages/flutter_tools/test/src/android_common.dart index 600a4f01ad3..8040e629faa 100644 --- a/packages/flutter_tools/test/src/android_common.dart +++ b/packages/flutter_tools/test/src/android_common.dart @@ -20,6 +20,7 @@ class FakeAndroidBuilder implements AndroidBuilder { required FlutterProject project, required Set androidBuildInfo, required String target, + required Future Function(FlutterProject, {required bool releaseMode}) generateTooling, String? outputDirectoryPath, required String buildNumber, }) async {} diff --git a/packages/flutter_tools/test/src/fake_pub_deps.dart b/packages/flutter_tools/test/src/fake_pub_deps.dart index 53dc61dcbfe..5718f3f1fff 100644 --- a/packages/flutter_tools/test/src/fake_pub_deps.dart +++ b/packages/flutter_tools/test/src/fake_pub_deps.dart @@ -16,10 +16,15 @@ final class FakePubWithPrimedDeps implements Pub { /// dev-dependencies ([dependencies]) of any package to a set of any other /// packages. A resulting valid `dart pub deps --json` response is implicitly /// created. + /// + /// If [allowGet] is `true`, [Pub.get] can be invoked (all the parameters are + /// ignored and it is considered a success); otherwise an error is thrown to + /// reject an unexpected call. factory FakePubWithPrimedDeps({ String rootPackageName = 'app_name', Set devDependencies = const {}, Map> dependencies = const >{}, + bool allowGet = false, }) { // Start the packages: [ ... ] list with the root package. final List packages = [ @@ -56,11 +61,34 @@ final class FakePubWithPrimedDeps implements Pub { return FakePubWithPrimedDeps._({ 'root': rootPackageName, 'packages': packages, - }); + }, allowGetToSucceed: allowGet); } - const FakePubWithPrimedDeps._(this._deps); + const FakePubWithPrimedDeps._(this._deps, {required bool allowGetToSucceed}) + : _allowGetToSucceed = allowGetToSucceed; final Map _deps; + final bool _allowGetToSucceed; + + @override + Future get({ + required PubContext context, + required FlutterProject project, + bool upgrade = false, + bool offline = false, + String? flutterRootOverride, + bool checkUpToDate = false, + bool shouldSkipThirdPartyGenerator = true, + PubOutputMode outputMode = PubOutputMode.all, + }) async { + if (_allowGetToSucceed) { + return; + } + throw UnsupportedError( + 'Instance did not expect .get to be invoked. If this was intentional, ' + 'change the constructor of FakePubWithPrimeDeps to include the parameter ' + 'allowGet: true.', + ); + } @override Future> deps(FlutterProject project) async => _deps;