flutter build aar regenerates tooling between each build-mode step (#162705)

Closes https://github.com/flutter/flutter/issues/162703.

I still need to do a similar change for `ios|macos`-`framework` in
https://github.com/flutter/flutter/issues/162704.

I added two unit tests, as well as opted in an integration test to the
flag (it will become the default in
https://github.com/flutter/flutter/pull/160289).

/cc @reidbaker @gmackall.
This commit is contained in:
Matan Lurey 2025-02-06 09:03:02 -08:00 committed by GitHub
parent f219bbac55
commit 0e59f0f64c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 363 additions and 89 deletions

View File

@ -21,6 +21,11 @@ Future<void> main() async {
} }
print('\nUsing JAVA_HOME=$javaHome'); 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: <String>['--explicit-package-dependencies']);
final Directory tempDir = Directory.systemTemp.createTempSync('flutter_module_test.'); final Directory tempDir = Directory.systemTemp.createTempSync('flutter_module_test.');
final Directory projectDir = Directory(path.join(tempDir.path, 'hello')); final Directory projectDir = Directory(path.join(tempDir.path, 'hello'));
try { try {

View File

@ -19,6 +19,7 @@ abstract class AndroidBuilder {
required FlutterProject project, required FlutterProject project,
required Set<AndroidBuildInfo> androidBuildInfo, required Set<AndroidBuildInfo> androidBuildInfo,
required String target, required String target,
required Future<void> Function(FlutterProject, {required bool releaseMode}) generateTooling,
String? outputDirectoryPath, String? outputDirectoryPath,
required String buildNumber, required String buildNumber,
}); });

View File

@ -191,6 +191,7 @@ class AndroidGradleBuilder implements AndroidBuilder {
required FlutterProject project, required FlutterProject project,
required Set<AndroidBuildInfo> androidBuildInfo, required Set<AndroidBuildInfo> androidBuildInfo,
required String target, required String target,
required Future<void> Function(FlutterProject, {required bool releaseMode}) generateTooling,
String? outputDirectoryPath, String? outputDirectoryPath,
required String buildNumber, required String buildNumber,
}) async { }) async {
@ -208,6 +209,7 @@ class AndroidGradleBuilder implements AndroidBuilder {
_logger.printWarning(androidX86DeprecationWarning); _logger.printWarning(androidX86DeprecationWarning);
} }
for (final AndroidBuildInfo androidBuildInfo in androidBuildInfo) { for (final AndroidBuildInfo androidBuildInfo in androidBuildInfo) {
await generateTooling(project, releaseMode: androidBuildInfo.buildInfo.isRelease);
await buildGradleAar( await buildGradleAar(
project: project, project: project,
androidBuildInfo: androidBuildInfo, androidBuildInfo: androidBuildInfo,

View File

@ -111,6 +111,9 @@ class BuildAarCommand extends BuildSubCommand {
await super.validateCommand(); await super.validateCommand();
} }
@override
bool get regeneratePlatformSpecificToolingDurifyVerify => false;
@override @override
Future<FlutterCommandResult> runCommand() async { Future<FlutterCommandResult> runCommand() async {
if (_androidSdk == null) { if (_androidSdk == null) {
@ -149,10 +152,12 @@ class BuildAarCommand extends BuildSubCommand {
} }
displayNullSafetyMode(androidBuildInfo.first.buildInfo); displayNullSafetyMode(androidBuildInfo.first.buildInfo);
await androidBuilder?.buildAar( await androidBuilder?.buildAar(
project: project, project: project,
target: targetFile.path, target: targetFile.path,
androidBuildInfo: androidBuildInfo, androidBuildInfo: androidBuildInfo,
generateTooling: regeneratePlatformSpecificToolingIfApplicable,
outputDirectoryPath: stringArg('output'), outputDirectoryPath: stringArg('output'),
buildNumber: buildNumber, buildNumber: buildNumber,
); );

View File

@ -1893,22 +1893,15 @@ Run 'flutter -h' (or 'flutter <command> -h') for available flutter commands and
buildSystem: globals.buildSystem, buildSystem: globals.buildSystem,
buildTargets: globals.buildTargets, buildTargets: globals.buildTargets,
); );
// null implicitly means all plugins are allowed
List<String>? 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) { if (reportNullSafety) {
await _sendNullSafetyAnalyticsEvents(project); await _sendNullSafetyAnalyticsEvents(project);
} }
} }
if (regeneratePlatformSpecificToolingDurifyVerify) {
await regeneratePlatformSpecificToolingIfApplicable(project);
}
setupApplicationPackages(); setupApplicationPackages();
if (commandPath != null) { if (commandPath != null) {
@ -1918,6 +1911,66 @@ Run 'flutter -h' (or 'flutter <command> -h') for available flutter commands and
return runCommand(); 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:
///
/// - <https://github.com/flutter/flutter/issues/162649>.
@protected
@nonVirtual
Future<void> 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<String>? 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<void> _sendNullSafetyAnalyticsEvents(FlutterProject project) async { Future<void> _sendNullSafetyAnalyticsEvents(FlutterProject project) async {
final BuildInfo buildInfo = await getBuildInfo(); final BuildInfo buildInfo = await getBuildInfo();
NullSafetyAnalysisEvent( NullSafetyAnalysisEvent(

View File

@ -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/build_info.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build_aar.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/features.dart';
import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/project.dart';
@ -24,24 +25,42 @@ import '../../src/android_common.dart';
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/context.dart'; import '../../src/context.dart';
import '../../src/fake_process_manager.dart'; import '../../src/fake_process_manager.dart';
import '../../src/fake_pub_deps.dart';
import '../../src/fakes.dart' hide FakeFlutterProjectFactory; import '../../src/fakes.dart' hide FakeFlutterProjectFactory;
import '../../src/test_flutter_command_runner.dart'; import '../../src/test_flutter_command_runner.dart';
void main() { void main() {
Cache.disableLocking(); Cache.disableLocking();
Future<BuildAarCommand> runCommandIn(String target, {List<String>? 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<BuildAarCommand> runBuildAar(
String target, {
AndroidSdk? androidSdk = const _FakeAndroidSdk(),
List<String>? arguments,
}) async {
final BuildAarCommand command = BuildAarCommand( final BuildAarCommand command = BuildAarCommand(
androidSdk: FakeAndroidSdk(), androidSdk: androidSdk,
fileSystem: globals.fs, fileSystem: globals.fs,
logger: BufferLogger.test(), logger: BufferLogger.test(),
verboseHelp: false, verboseHelp: false,
); );
final CommandRunner<void> runner = createTestCommandRunner(command); final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>['aar', '--no-pub', ...?arguments, target]); await runner.run(<String>['aar', ...?arguments, target]);
return command; return command;
} }
// TODO(matanlurey): Remove after `explicit-package-dependencies` is enabled by default.
FeatureFlags enableExplicitPackageDependencies() {
return TestFeatureFlags(isExplicitPackageDependenciesEnabled: true);
}
group('Usage', () { group('Usage', () {
late Directory tempDir; late Directory tempDir;
late FakeAnalytics analytics; late FakeAnalytics analytics;
@ -66,7 +85,7 @@ void main() {
arguments: <String>['--no-pub', '--template=module'], arguments: <String>['--no-pub', '--template=module'],
); );
await runCommandIn(projectPath); await runBuildAar(projectPath, arguments: <String>['--no-pub']);
expect( expect(
analytics.sentEvents, analytics.sentEvents,
contains( contains(
@ -80,7 +99,7 @@ void main() {
); );
}, },
overrides: <Type, Generator>{ overrides: <Type, Generator>{
AndroidBuilder: () => FakeAndroidBuilder(), AndroidBuilder: () => _CapturingFakeAndroidBuilder(),
Analytics: () => analytics, Analytics: () => analytics,
}, },
); );
@ -93,7 +112,10 @@ void main() {
arguments: <String>['--no-pub', '--template=module'], arguments: <String>['--no-pub', '--template=module'],
); );
await runCommandIn(projectPath, arguments: <String>['--target-platform=android-arm']); await runBuildAar(
projectPath,
arguments: <String>['--no-pub', '--target-platform=android-arm'],
);
expect( expect(
analytics.sentEvents, analytics.sentEvents,
contains( contains(
@ -107,11 +129,36 @@ void main() {
); );
}, },
overrides: <Type, Generator>{ overrides: <Type, Generator>{
AndroidBuilder: () => FakeAndroidBuilder(), AndroidBuilder: () => _CapturingFakeAndroidBuilder(),
Analytics: () => analytics, 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: <String>['--no-pub', '--template=module'],
);
await runBuildAar(
projectPath,
arguments: <String>['--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: <Type, Generator>{
AndroidBuilder: () => _CapturingFakeAndroidBuilder(),
Analytics: () => analytics,
FeatureFlags: enableExplicitPackageDependencies,
Pub: () => FakePubWithPrimedDeps(allowGet: true),
},
);
testUsingContext( testUsingContext(
'logs success', 'logs success',
() async { () async {
@ -120,7 +167,10 @@ void main() {
arguments: <String>['--no-pub', '--template=module'], arguments: <String>['--no-pub', '--template=module'],
); );
await runCommandIn(projectPath, arguments: <String>['--target-platform=android-arm']); await runBuildAar(
projectPath,
arguments: <String>['--no-pub', '--target-platform=android-arm'],
);
final Iterable<Event> successEvent = analytics.sentEvents.where( final Iterable<Event> successEvent = analytics.sentEvents.where(
(Event e) => (Event e) =>
@ -131,7 +181,7 @@ void main() {
expect(successEvent, isNotEmpty, reason: 'Tool should send create success event'); expect(successEvent, isNotEmpty, reason: 'Tool should send create success event');
}, },
overrides: <Type, Generator>{ overrides: <Type, Generator>{
AndroidBuilder: () => FakeAndroidBuilder(), AndroidBuilder: () => _CapturingFakeAndroidBuilder(),
Analytics: () => analytics, Analytics: () => analytics,
}, },
); );
@ -139,10 +189,10 @@ void main() {
group('flag parsing', () { group('flag parsing', () {
late Directory tempDir; late Directory tempDir;
late FakeAndroidBuilder fakeAndroidBuilder; late _CapturingFakeAndroidBuilder fakeAndroidBuilder;
setUp(() { setUp(() {
fakeAndroidBuilder = FakeAndroidBuilder(); fakeAndroidBuilder = _CapturingFakeAndroidBuilder();
tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_tools_build_aar_test.'); tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_tools_build_aar_test.');
}); });
@ -155,13 +205,19 @@ void main() {
tempDir, tempDir,
arguments: <String>['--no-pub', '--template=module'], arguments: <String>['--no-pub', '--template=module'],
); );
await runCommandIn(projectPath); await runBuildAar(projectPath, arguments: <String>['--no-pub']);
expect(fakeAndroidBuilder.buildNumber, '1.0'); expect(
expect(fakeAndroidBuilder.androidBuildInfo.length, 3); 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<BuildMode> buildModes = <BuildMode>[]; final List<BuildMode> buildModes = <BuildMode>[];
for (final AndroidBuildInfo androidBuildInfo in fakeAndroidBuilder.androidBuildInfo) { for (final AndroidBuildInfo androidBuildInfo
in buildAarCall.namedArguments[#androidBuildInfo] as Set<AndroidBuildInfo>) {
final BuildInfo buildInfo = androidBuildInfo.buildInfo; final BuildInfo buildInfo = androidBuildInfo.buildInfo;
buildModes.add(buildInfo.mode); buildModes.add(buildInfo.mode);
if (buildInfo.mode.isPrecompiled) { if (buildInfo.mode.isPrecompiled) {
@ -180,7 +236,7 @@ void main() {
AndroidArch.x86_64, AndroidArch.x86_64,
]); ]);
} }
expect(buildModes.length, 3); expect(buildModes, hasLength(3));
expect( expect(
buildModes, buildModes,
containsAll(<BuildMode>[BuildMode.debug, BuildMode.profile, BuildMode.release]), containsAll(<BuildMode>[BuildMode.debug, BuildMode.profile, BuildMode.release]),
@ -192,9 +248,10 @@ void main() {
tempDir, tempDir,
arguments: <String>['--no-pub', '--template=module'], arguments: <String>['--no-pub', '--template=module'],
); );
await runCommandIn( await runBuildAar(
projectPath, projectPath,
arguments: <String>[ arguments: <String>[
'--no-pub',
'--no-debug', '--no-debug',
'--no-profile', '--no-profile',
'--target-platform', '--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<AndroidBuildInfo>).single;
expect(androidBuildInfo.targetArchs, <AndroidArch>[AndroidArch.x86]); expect(androidBuildInfo.targetArchs, <AndroidArch>[AndroidArch.x86]);
final BuildInfo buildInfo = androidBuildInfo.buildInfo; final BuildInfo buildInfo = androidBuildInfo.buildInfo;
@ -229,7 +293,6 @@ void main() {
group('Gradle', () { group('Gradle', () {
late Directory tempDir; late Directory tempDir;
late AndroidSdk mockAndroidSdk;
late String gradlew; late String gradlew;
late FakeProcessManager processManager; late FakeProcessManager processManager;
late String flutterRoot; late String flutterRoot;
@ -241,7 +304,6 @@ void main() {
fs: MemoryFileSystem.test(), fs: MemoryFileSystem.test(),
fakeFlutterVersion: FakeFlutterVersion(), fakeFlutterVersion: FakeFlutterVersion(),
); );
mockAndroidSdk = FakeAndroidSdk();
gradlew = globals.fs.path.join( gradlew = globals.fs.path.join(
tempDir.path, tempDir.path,
'flutter_project', 'flutter_project',
@ -267,7 +329,7 @@ void main() {
await expectLater( await expectLater(
() async { () async {
await runBuildAarCommand(projectPath, null, arguments: <String>['--no-pub']); await runBuildAar(projectPath, androidSdk: null, arguments: <String>['--no-pub']);
}, },
throwsToolExit( throwsToolExit(
message: 'No Android SDK found. Try setting the ANDROID_HOME environment variable', message: 'No Android SDK found. Try setting the ANDROID_HOME environment variable',
@ -284,17 +346,19 @@ void main() {
group('throws ToolExit', () { group('throws ToolExit', () {
testUsingContext('main.dart not found', () async { testUsingContext('main.dart not found', () async {
await expectLater(() async { await expectLater(() async {
await runBuildAarCommand( await runBuildAar(
'missing_project', 'missing_project',
mockAndroidSdk, arguments: <String>[
arguments: <String>['--no-pub'], '--no-pub',
globals.fs.path.join('missing_project', 'lib', 'main.dart'),
],
); );
}, throwsToolExit(message: 'main.dart does not exist')); }, throwsToolExit(message: 'main.dart does not exist'));
}); });
testUsingContext('flutter project not valid', () async { testUsingContext('flutter project not valid', () async {
await expectLater(() async { await expectLater(() async {
await runCommandIn(tempDir.path, arguments: <String>['--no-pub']); await runBuildAar(tempDir.path, arguments: <String>['--no-pub']);
}, throwsToolExit(message: 'is not a valid flutter project')); }, throwsToolExit(message: 'is not a valid flutter project'));
}); });
}); });
@ -330,10 +394,10 @@ void main() {
); );
await expectLater( await expectLater(
() => runBuildAarCommand( () => runBuildAar(
projectPath, projectPath,
mockAndroidSdk,
arguments: <String>[ arguments: <String>[
'--no-pub',
'--no-debug', '--no-debug',
'--no-profile', '--no-profile',
'--extra-front-end-options=foo', '--extra-front-end-options=foo',
@ -349,7 +413,7 @@ void main() {
Java: () => null, Java: () => null,
ProcessManager: () => processManager, ProcessManager: () => processManager,
FeatureFlags: () => TestFeatureFlags(isIOSEnabled: false), FeatureFlags: () => TestFeatureFlags(isIOSEnabled: false),
AndroidStudio: () => FakeAndroidStudio(), AndroidStudio: () => _FakeAndroidStudio(),
}, },
); );
@ -393,7 +457,7 @@ void main() {
arguments: <String>['--no-pub', '--template=module'], arguments: <String>['--no-pub', '--template=module'],
); );
await runBuildAarCommand(projectPath, mockAndroidSdk); await runBuildAar(projectPath, arguments: <String>['--no-pub']);
expect( expect(
fakeAnalytics.sentEvents, fakeAnalytics.sentEvents,
@ -404,7 +468,7 @@ void main() {
}, },
overrides: <Type, Generator>{ overrides: <Type, Generator>{
Analytics: () => fakeAnalytics, Analytics: () => fakeAnalytics,
AndroidBuilder: () => FakeAndroidBuilder(), AndroidBuilder: () => _CapturingFakeAndroidBuilder(),
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir), FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
}, },
); );
@ -427,7 +491,7 @@ void main() {
value: 'true', value: 'true',
); );
await runBuildAarCommand(projectPath, mockAndroidSdk); await runBuildAar(projectPath, arguments: <String>['--no-pub']);
expect( expect(
fakeAnalytics.sentEvents, fakeAnalytics.sentEvents,
@ -438,7 +502,7 @@ void main() {
}, },
overrides: <Type, Generator>{ overrides: <Type, Generator>{
Analytics: () => fakeAnalytics, Analytics: () => fakeAnalytics,
AndroidBuilder: () => FakeAndroidBuilder(), AndroidBuilder: () => _CapturingFakeAndroidBuilder(),
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir), FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
}, },
); );
@ -461,7 +525,7 @@ void main() {
value: 'false', value: 'false',
); );
await runBuildAarCommand(projectPath, mockAndroidSdk); await runBuildAar(projectPath, arguments: <String>['--no-pub']);
expect( expect(
fakeAnalytics.sentEvents, fakeAnalytics.sentEvents,
@ -472,7 +536,7 @@ void main() {
}, },
overrides: <Type, Generator>{ overrides: <Type, Generator>{
Analytics: () => fakeAnalytics, Analytics: () => fakeAnalytics,
AndroidBuilder: () => FakeAndroidBuilder(), AndroidBuilder: () => _CapturingFakeAndroidBuilder(),
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir), FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
}, },
); );
@ -480,53 +544,27 @@ void main() {
}); });
} }
Future<BuildAarCommand> runBuildAarCommand( /// A fake implementation of [AndroidBuilder] that allows [buildAar] calls.
String target, ///
AndroidSdk? androidSdk, { /// Calls to [buildAar] are stored as [capturedBuildAarCalls], other calls are rejected.
List<String>? arguments, final class _CapturingFakeAndroidBuilder extends Fake implements AndroidBuilder {
}) async { final List<Invocation> capturedBuildAarCalls = <Invocation>[];
final BuildAarCommand command = BuildAarCommand(
androidSdk: androidSdk,
fileSystem: globals.fs,
logger: BufferLogger.test(),
verboseHelp: false,
);
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>[
'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> androidBuildInfo;
late String target;
String? outputDirectoryPath;
late String buildNumber;
@override @override
Future<void> buildAar({ Object? noSuchMethod(Invocation invocation) {
required FlutterProject project, if (invocation.memberName != #buildAar) {
required Set<AndroidBuildInfo> androidBuildInfo, return super.noSuchMethod(invocation);
required String target, }
String? outputDirectoryPath, capturedBuildAarCalls.add(invocation);
required String buildNumber, return Future<void>.value();
}) async {
this.project = project;
this.androidBuildInfo = androidBuildInfo;
this.target = target;
this.outputDirectoryPath = outputDirectoryPath;
this.buildNumber = buildNumber;
} }
} }
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 @override
String get javaPath => 'java'; String get javaPath => 'java';
} }

View File

@ -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/base/user_messages.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/project.dart';
import 'package:test/fake.dart'; import 'package:test/fake.dart';
import 'package:unified_analytics/unified_analytics.dart'; import 'package:unified_analytics/unified_analytics.dart';
@ -41,6 +42,11 @@ const String minimalV2EmbeddingManifest = r'''
'''; ''';
void main() { void main() {
// TODO(matanlurey): Remove after `explicit-package-dependencies` is enabled by default.
FeatureFlags enableExplicitPackageDependencies() {
return TestFeatureFlags(isExplicitPackageDependenciesEnabled: true);
}
group('gradle build', () { group('gradle build', () {
late BufferLogger logger; late BufferLogger logger;
late FakeAnalytics fakeAnalytics; 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>[
FakeCommand(
command: <String>[
'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: <String>[
'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: <String>[
'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>{
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;
}),
<bool>[false, false, true],
reason: 'generateTooling should omit debug metadata for release builds',
);
},
overrides: <Type, Generator>{FeatureFlags: enableExplicitPackageDependencies},
);
testUsingContext( testUsingContext(
'Verbose mode for AARs includes Gradle stacktrace and sets debug log level', 'Verbose mode for AARs includes Gradle stacktrace and sets debug log level',
() async { () async {

View File

@ -20,6 +20,7 @@ class FakeAndroidBuilder implements AndroidBuilder {
required FlutterProject project, required FlutterProject project,
required Set<AndroidBuildInfo> androidBuildInfo, required Set<AndroidBuildInfo> androidBuildInfo,
required String target, required String target,
required Future<void> Function(FlutterProject, {required bool releaseMode}) generateTooling,
String? outputDirectoryPath, String? outputDirectoryPath,
required String buildNumber, required String buildNumber,
}) async {} }) async {}

View File

@ -16,10 +16,15 @@ final class FakePubWithPrimedDeps implements Pub {
/// dev-dependencies ([dependencies]) of any package to a set of any other /// dev-dependencies ([dependencies]) of any package to a set of any other
/// packages. A resulting valid `dart pub deps --json` response is implicitly /// packages. A resulting valid `dart pub deps --json` response is implicitly
/// created. /// 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({ factory FakePubWithPrimedDeps({
String rootPackageName = 'app_name', String rootPackageName = 'app_name',
Set<String> devDependencies = const <String>{}, Set<String> devDependencies = const <String>{},
Map<String, Set<String>> dependencies = const <String, Set<String>>{}, Map<String, Set<String>> dependencies = const <String, Set<String>>{},
bool allowGet = false,
}) { }) {
// Start the packages: [ ... ] list with the root package. // Start the packages: [ ... ] list with the root package.
final List<Object?> packages = <Object?>[ final List<Object?> packages = <Object?>[
@ -56,11 +61,34 @@ final class FakePubWithPrimedDeps implements Pub {
return FakePubWithPrimedDeps._(<String, Object?>{ return FakePubWithPrimedDeps._(<String, Object?>{
'root': rootPackageName, 'root': rootPackageName,
'packages': packages, 'packages': packages,
}); }, allowGetToSucceed: allowGet);
} }
const FakePubWithPrimedDeps._(this._deps); const FakePubWithPrimedDeps._(this._deps, {required bool allowGetToSucceed})
: _allowGetToSucceed = allowGetToSucceed;
final Map<String, Object?> _deps; final Map<String, Object?> _deps;
final bool _allowGetToSucceed;
@override
Future<void> 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 <Pub>.get to be invoked. If this was intentional, '
'change the constructor of FakePubWithPrimeDeps to include the parameter '
'allowGet: true.',
);
}
@override @override
Future<Map<String, Object?>> deps(FlutterProject project) async => _deps; Future<Map<String, Object?>> deps(FlutterProject project) async => _deps;