diff --git a/packages/flutter_tools/bin/macos_assemble.sh b/packages/flutter_tools/bin/macos_assemble.sh index 1139ac6dbde..c2205853e1a 100755 --- a/packages/flutter_tools/bin/macos_assemble.sh +++ b/packages/flutter_tools/bin/macos_assemble.sh @@ -84,6 +84,7 @@ RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics \ -dDartObfuscation="${dart_obfuscation_flag}" \ -dSplitDebugInfo="${SPLIT_DEBUG_INFO}" \ -dDartDefines="${DART_DEFINES}" \ + -dExtraFrontEndOptions="${EXTRA_FRONT_END_OPTIONS}" \ --build-inputs="${build_inputs_path}" \ --build-outputs="${build_outputs_path}" \ --output="${ephemeral_dir}" \ diff --git a/packages/flutter_tools/bin/xcode_backend.sh b/packages/flutter_tools/bin/xcode_backend.sh index b8b007d8464..47f408e6af7 100755 --- a/packages/flutter_tools/bin/xcode_backend.sh +++ b/packages/flutter_tools/bin/xcode_backend.sh @@ -187,6 +187,7 @@ BuildApp() { -dDartObfuscation="${dart_obfuscation_flag}" \ -dEnableBitcode="${bitcode_flag}" \ -dDartDefines="${DART_DEFINES}" \ + -dExtraFrontEndOptions="${EXTRA_FRONT_END_OPTIONS}" \ "${build_mode}_ios_bundle_flutter_assets" if [[ $? -ne 0 ]]; then diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index 5fadda7504d..a91159ace3a 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -313,7 +313,7 @@ Future buildGradleApp({ command.add('-Ptrack-widget-creation=${buildInfo.trackWidgetCreation}'); if (buildInfo.extraFrontEndOptions != null) { - command.add('-Pextra-front-end-options=${buildInfo.extraFrontEndOptions}'); + command.add('-Pextra-front-end-options=${buildInfo.extraFrontEndOptions.join(',')}'); } if (buildInfo.extraGenSnapshotOptions != null) { command.add('-Pextra-gen-snapshot-options=${buildInfo.extraGenSnapshotOptions}'); diff --git a/packages/flutter_tools/lib/src/build_system/targets/dart.dart b/packages/flutter_tools/lib/src/build_system/targets/dart.dart index e675a43747e..0ef9c0d0873 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/dart.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/dart.dart @@ -204,7 +204,10 @@ class KernelSnapshot extends Target { final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]); // This configuration is all optional. - final List extraFrontEndOptions = environment.defines[kExtraFrontEndOptions]?.split(','); + final String rawFrontEndOption = environment.defines[kExtraFrontEndOptions]; + final List extraFrontEndOptions = (rawFrontEndOption?.isNotEmpty ?? false) + ? rawFrontEndOption?.split(',') + : null; final List fileSystemRoots = environment.defines[kFileSystemRoots]?.split(','); final String fileSystemScheme = environment.defines[kFileSystemScheme]; diff --git a/packages/flutter_tools/lib/src/commands/build_aot.dart b/packages/flutter_tools/lib/src/commands/build_aot.dart index 353cdddecfd..26e0799ec0c 100644 --- a/packages/flutter_tools/lib/src/commands/build_aot.dart +++ b/packages/flutter_tools/lib/src/commands/build_aot.dart @@ -20,6 +20,7 @@ class BuildAotCommand extends BuildSubCommand with TargetPlatformBasedDevelopmen addBuildModeFlags(); usesPubOption(); usesDartDefineOption(); + usesExtraFrontendOptions(); argParser ..addOption('output-dir', defaultsTo: getAotBuildDirectory()) ..addOption('target-platform', @@ -33,10 +34,6 @@ class BuildAotCommand extends BuildSubCommand with TargetPlatformBasedDevelopmen allowed: DarwinArch.values.map(getNameForDarwinArch), help: 'iOS architectures to build.', ) - ..addMultiOption(FlutterOptions.kExtraFrontEndOptions, - splitCommas: true, - hide: true, - ) ..addMultiOption(FlutterOptions.kExtraGenSnapshotOptions, splitCommas: true, hide: true, diff --git a/packages/flutter_tools/lib/src/commands/build_apk.dart b/packages/flutter_tools/lib/src/commands/build_apk.dart index 3bf4576895a..b24e78368df 100644 --- a/packages/flutter_tools/lib/src/commands/build_apk.dart +++ b/packages/flutter_tools/lib/src/commands/build_apk.dart @@ -29,6 +29,7 @@ class BuildApkCommand extends BuildSubCommand { addSplitDebugInfoOption(); addDartObfuscationOption(); usesDartDefineOption(); + usesExtraFrontendOptions(); argParser ..addFlag('split-per-abi', negatable: false, diff --git a/packages/flutter_tools/lib/src/commands/build_appbundle.dart b/packages/flutter_tools/lib/src/commands/build_appbundle.dart index e6d7dbcf904..431f5cacd30 100644 --- a/packages/flutter_tools/lib/src/commands/build_appbundle.dart +++ b/packages/flutter_tools/lib/src/commands/build_appbundle.dart @@ -27,6 +27,7 @@ class BuildAppBundleCommand extends BuildSubCommand { addSplitDebugInfoOption(); addDartObfuscationOption(); usesDartDefineOption(); + usesExtraFrontendOptions(); argParser ..addFlag('track-widget-creation', negatable: false, hide: !verboseHelp) ..addMultiOption('target-platform', diff --git a/packages/flutter_tools/lib/src/commands/build_bundle.dart b/packages/flutter_tools/lib/src/commands/build_bundle.dart index ec0e37374f5..d85fa23bd9e 100644 --- a/packages/flutter_tools/lib/src/commands/build_bundle.dart +++ b/packages/flutter_tools/lib/src/commands/build_bundle.dart @@ -21,6 +21,7 @@ class BuildBundleCommand extends BuildSubCommand { usesFilesystemOptions(hide: !verboseHelp); usesBuildNumberOption(); addBuildModeFlags(verboseHelp: verboseHelp); + usesExtraFrontendOptions(); argParser ..addFlag( 'precompiled', @@ -49,10 +50,6 @@ class BuildBundleCommand extends BuildSubCommand { 'windows-x64', ], ) - ..addMultiOption(FlutterOptions.kExtraFrontEndOptions, - splitCommas: true, - hide: true, - ) ..addOption('asset-dir', defaultsTo: getAssetBuildDirectory()) ..addMultiOption(FlutterOptions.kExtraGenSnapshotOptions, splitCommas: true, diff --git a/packages/flutter_tools/lib/src/commands/build_ios.dart b/packages/flutter_tools/lib/src/commands/build_ios.dart index d6f48b2ee30..a5342ee144e 100644 --- a/packages/flutter_tools/lib/src/commands/build_ios.dart +++ b/packages/flutter_tools/lib/src/commands/build_ios.dart @@ -28,6 +28,7 @@ class BuildIOSCommand extends BuildSubCommand { usesBuildNameOption(); addDartObfuscationOption(); usesDartDefineOption(); + usesExtraFrontendOptions(); argParser ..addFlag('simulator', help: 'Build for the iOS simulator instead of the device.', diff --git a/packages/flutter_tools/lib/src/commands/build_ios_framework.dart b/packages/flutter_tools/lib/src/commands/build_ios_framework.dart index 1784a906d34..1171766adac 100644 --- a/packages/flutter_tools/lib/src/commands/build_ios_framework.dart +++ b/packages/flutter_tools/lib/src/commands/build_ios_framework.dart @@ -51,6 +51,7 @@ class BuildIOSFrameworkCommand extends BuildSubCommand { usesDartDefineOption(); addSplitDebugInfoOption(); addDartObfuscationOption(); + usesExtraFrontendOptions(); argParser ..addFlag('debug', negatable: true, diff --git a/packages/flutter_tools/lib/src/commands/build_macos.dart b/packages/flutter_tools/lib/src/commands/build_macos.dart index 96230b768a0..8830260e752 100644 --- a/packages/flutter_tools/lib/src/commands/build_macos.dart +++ b/packages/flutter_tools/lib/src/commands/build_macos.dart @@ -22,6 +22,7 @@ class BuildMacosCommand extends BuildSubCommand { usesTargetOption(); addBuildModeFlags(); addDartObfuscationOption(); + usesExtraFrontendOptions(); } @override diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart index 09081ecf286..b8e243282b7 100644 --- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart +++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart @@ -239,6 +239,10 @@ List _xcodeBuildSettingsLines({ xcodeBuildSettings.add('DART_DEFINES=${jsonEncode(buildInfo.dartDefines)}'); } + if (buildInfo.extraFrontEndOptions?.isNotEmpty ?? false) { + xcodeBuildSettings.add('EXTRA_FRONT_END_OPTIONS=${buildInfo.extraFrontEndOptions.join(',')}'); + } + return xcodeBuildSettings; } diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index 208a34ba8b7..a6d7d2beb68 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -429,6 +429,13 @@ abstract class FlutterCommand extends Command { ); } + void usesExtraFrontendOptions() { + argParser.addMultiOption(FlutterOptions.kExtraFrontEndOptions, + splitCommas: true, + hide: true, + ); + } + void usesFuchsiaOptions({ bool hide = false }) { argParser.addOption( 'target-model', diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/dart_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/dart_test.dart index cac3fbb362a..5ee0fdb78d4 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/dart_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/dart_test.dart @@ -130,6 +130,68 @@ void main() { expect(processManager.hasRemainingExpectations, false); })); + test('KernelSnapshot correctly handles an empty string in ExtraFrontEndOptions', () => testbed.run(() async { + globals.fs.file('.packages').writeAsStringSync('\n'); + final String build = androidEnvironment.buildDir.path; + processManager = FakeProcessManager.list([ + FakeCommand(command: [ + artifacts.getArtifactPath(Artifact.engineDartBinary), + artifacts.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk), + '--sdk-root', + artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath) + '/', + '--target=flutter', + '-Ddart.developer.causal_async_stacks=false', + ...buildModeOptions(BuildMode.profile), + '--aot', + '--tfa', + '--packages', + '/.packages', + '--output-dill', + '$build/app.dill', + '--depfile', + '$build/kernel_snapshot.d', + '/lib/main.dart', + ], stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey $build/app.dill 0\n'), + ]); + + await const KernelSnapshot() + .build(androidEnvironment..defines[kExtraFrontEndOptions] = ''); + + expect(processManager.hasRemainingExpectations, false); + })); + + test('KernelSnapshot correctly forwards ExtraFrontEndOptions', () => testbed.run(() async { + globals.fs.file('.packages').writeAsStringSync('\n'); + final String build = androidEnvironment.buildDir.path; + processManager = FakeProcessManager.list([ + FakeCommand(command: [ + artifacts.getArtifactPath(Artifact.engineDartBinary), + artifacts.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk), + '--sdk-root', + artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath) + '/', + '--target=flutter', + '-Ddart.developer.causal_async_stacks=false', + ...buildModeOptions(BuildMode.profile), + '--aot', + '--tfa', + '--packages', + '/.packages', + '--output-dill', + '$build/app.dill', + '--depfile', + '$build/kernel_snapshot.d', + 'foo', + 'bar', + '/lib/main.dart', + ], stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey $build/app.dill 0\n'), + ]); + + await const KernelSnapshot() + .build(androidEnvironment..defines[kExtraFrontEndOptions] = 'foo,bar'); + + expect(processManager.hasRemainingExpectations, false); + })); + test('KernelSnapshot can disable track-widget-creation on debug builds', () => testbed.run(() async { globals.fs.file('.packages').writeAsStringSync('\n'); final String build = androidEnvironment.buildDir.path; diff --git a/packages/flutter_tools/test/general.shard/commands/build_apk_test.dart b/packages/flutter_tools/test/general.shard/commands/build_apk_test.dart index b4752174b88..0d619d648e7 100644 --- a/packages/flutter_tools/test/general.shard/commands/build_apk_test.dart +++ b/packages/flutter_tools/test/general.shard/commands/build_apk_test.dart @@ -279,6 +279,38 @@ void main() { ProcessManager: () => mockProcessManager, }); + testUsingContext('--extra-front-end-options are provided to gradle project', () async { + final String projectPath = await createProject(tempDir, + arguments: ['--no-pub', '--template=app']); + + await expectLater(() async { + await runBuildApkCommand(projectPath, arguments: [ + '--extra-front-end-options=foo', + '--extra-front-end-options=bar', + ]); + }, throwsToolExit(message: 'Gradle task assembleRelease failed with exit code 1')); + + verify(mockProcessManager.start( + [ + gradlew, + '-q', + '-Ptarget-platform=android-arm,android-arm64,android-x64', + '-Ptarget=${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}', + '-Ptrack-widget-creation=true', + '-Pextra-front-end-options=foo,bar', + '-Pshrink=true', + 'assembleRelease', + ], + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'), + )).called(1); + }, + overrides: { + AndroidSdk: () => mockAndroidSdk, + FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir), + ProcessManager: () => mockProcessManager, + }); + testUsingContext('shrinking is disabled when --no-shrink is passed', () async { final String projectPath = await createProject(tempDir, arguments: ['--no-pub', '--template=app']);