Unverified Commit ed66037f authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] describe current null safety build mode (#73426)

parent 5a4df0ad
...@@ -2,6 +2,9 @@ ...@@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:meta/meta.dart';
import '../build_info.dart';
import '../commands/build_linux.dart'; import '../commands/build_linux.dart';
import '../commands/build_macos.dart'; import '../commands/build_macos.dart';
import '../commands/build_windows.dart'; import '../commands/build_windows.dart';
...@@ -52,4 +55,25 @@ abstract class BuildSubCommand extends FlutterCommand { ...@@ -52,4 +55,25 @@ abstract class BuildSubCommand extends FlutterCommand {
@override @override
bool get reportNullSafety => true; bool get reportNullSafety => true;
/// Display a message describing the current null safety runtime mode
/// that was selected.
///
/// This is similar to the run message in run_hot.dart
@protected
void displayNullSafetyMode(BuildInfo buildInfo) {
globals.printStatus('');
if (buildInfo.nullSafetyMode == NullSafetyMode.sound) {
globals.printStatus('💪 Building with sound null safety 💪', emphasis: true);
} else {
globals.printStatus(
'Building with unsound null safety',
emphasis: true,
);
globals.printStatus(
'For more information see https://dart.dev/null-safety/unsound-null-safety',
);
}
globals.printStatus('');
}
} }
...@@ -126,6 +126,7 @@ class BuildAarCommand extends BuildSubCommand { ...@@ -126,6 +126,7 @@ class BuildAarCommand extends BuildSubCommand {
throwToolExit('Please specify a build mode and try again.'); throwToolExit('Please specify a build mode and try again.');
} }
displayNullSafetyMode(androidBuildInfo.first.buildInfo);
await androidBuilder.buildAar( await androidBuilder.buildAar(
project: _getProject(), project: _getProject(),
target: '', // Not needed because this command only builds Android's code. target: '', // Not needed because this command only builds Android's code.
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
import '../android/android_builder.dart'; import '../android/android_builder.dart';
import '../android/build_validation.dart'; import '../android/build_validation.dart';
import '../android/gradle_utils.dart'; import '../android/gradle_utils.dart';
import '../base/terminal.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../cache.dart'; import '../cache.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
...@@ -61,7 +60,10 @@ class BuildApkCommand extends BuildSubCommand { ...@@ -61,7 +60,10 @@ class BuildApkCommand extends BuildSubCommand {
final String description = 'Build an Android APK file from your app.\n\n' final String description = 'Build an Android APK file from your app.\n\n'
"This command can build debug and release versions of your application. 'debug' builds support " "This command can build debug and release versions of your application. 'debug' builds support "
"debugging and a quick development cycle. 'release' builds don't support debugging and are " "debugging and a quick development cycle. 'release' builds don't support debugging and are "
'suitable for deploying to app stores.'; 'suitable for deploying to app stores. If you are deploying the app to the Play Store, '
'it\'s recommended to use app bundles or split the APK to reduce the APK size. Learn more at:\n\n'
' * https://developer.android.com/guide/app-bundle\n'
' * https://developer.android.com/studio/build/configure-apk-splits#configure-abi-split';
@override @override
Future<Map<CustomDimensions, String>> get usageValues async { Future<Map<CustomDimensions, String>> get usageValues async {
...@@ -97,24 +99,7 @@ class BuildApkCommand extends BuildSubCommand { ...@@ -97,24 +99,7 @@ class BuildApkCommand extends BuildSubCommand {
targetArchs: stringsArg('target-platform').map<AndroidArch>(getAndroidArchForName), targetArchs: stringsArg('target-platform').map<AndroidArch>(getAndroidArchForName),
); );
validateBuild(androidBuildInfo); validateBuild(androidBuildInfo);
displayNullSafetyMode(androidBuildInfo.buildInfo);
if (buildInfo.isRelease && !androidBuildInfo.splitPerAbi && androidBuildInfo.targetArchs.length > 1) {
final String targetPlatforms = stringsArg('target-platform').join(', ');
globals.printStatus('You are building a fat APK that includes binaries for '
'$targetPlatforms.', emphasis: true, color: TerminalColor.green);
globals.printStatus('If you are deploying the app to the Play Store, '
"it's recommended to use app bundles or split the APK to reduce the APK size.", emphasis: true);
globals.printStatus('To generate an app bundle, run:', emphasis: true, indent: 4);
globals.printStatus('flutter build appbundle '
'--target-platform ${targetPlatforms.replaceAll(' ', '')}',indent: 8);
globals.printStatus('Learn more: https://developer.android.com/guide/app-bundle',indent: 8);
globals.printStatus('To split the APKs per ABI, run:', emphasis: true, indent: 4);
globals.printStatus('flutter build apk '
'--target-platform ${targetPlatforms.replaceAll(' ', '')} '
'--split-per-abi', indent: 8);
globals.printStatus('Learn more: https://developer.android.com/studio/build/configure-apk-splits#configure-abi-split',indent: 8);
}
await androidBuilder.buildApk( await androidBuilder.buildApk(
project: FlutterProject.current(), project: FlutterProject.current(),
target: targetFile, target: targetFile,
......
...@@ -86,6 +86,7 @@ class BuildAppBundleCommand extends BuildSubCommand { ...@@ -86,6 +86,7 @@ class BuildAppBundleCommand extends BuildSubCommand {
targetArchs: stringsArg('target-platform').map<AndroidArch>(getAndroidArchForName), targetArchs: stringsArg('target-platform').map<AndroidArch>(getAndroidArchForName),
); );
validateBuild(androidBuildInfo); validateBuild(androidBuildInfo);
displayNullSafetyMode(androidBuildInfo.buildInfo);
await androidBuilder.buildAab( await androidBuilder.buildAab(
project: FlutterProject.current(), project: FlutterProject.current(),
target: targetFile, target: targetFile,
......
...@@ -97,6 +97,7 @@ class BuildBundleCommand extends BuildSubCommand { ...@@ -97,6 +97,7 @@ class BuildBundleCommand extends BuildSubCommand {
} }
final BuildInfo buildInfo = await getBuildInfo(); final BuildInfo buildInfo = await getBuildInfo();
displayNullSafetyMode(buildInfo);
await bundleBuilder.build( await bundleBuilder.build(
platform: platform, platform: platform,
......
...@@ -72,6 +72,7 @@ class BuildFuchsiaCommand extends BuildSubCommand { ...@@ -72,6 +72,7 @@ class BuildFuchsiaCommand extends BuildSubCommand {
if (!flutterProject.fuchsia.existsSync()) { if (!flutterProject.fuchsia.existsSync()) {
throwToolExit('No Fuchsia project is configured.'); throwToolExit('No Fuchsia project is configured.');
} }
displayNullSafetyMode(buildInfo);
await buildFuchsia( await buildFuchsia(
fuchsiaProject: flutterProject.fuchsia, fuchsiaProject: flutterProject.fuchsia,
target: targetFile, target: targetFile,
......
...@@ -110,6 +110,7 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand { ...@@ -110,6 +110,7 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand {
} }
final FlutterCommandResult xcarchiveResult = await super.runCommand(); final FlutterCommandResult xcarchiveResult = await super.runCommand();
final BuildInfo buildInfo = await getBuildInfo(); final BuildInfo buildInfo = await getBuildInfo();
displayNullSafetyMode(buildInfo);
if (exportOptionsPlist == null) { if (exportOptionsPlist == null) {
return xcarchiveResult; return xcarchiveResult;
......
...@@ -127,7 +127,7 @@ class BuildIOSFrameworkCommand extends BuildSubCommand { ...@@ -127,7 +127,7 @@ class BuildIOSFrameworkCommand extends BuildSubCommand {
FlutterProject _project; FlutterProject _project;
Future<List<BuildInfo>> get buildInfos async { Future<List<BuildInfo>> getBuildInfos() async {
final List<BuildInfo> buildInfos = <BuildInfo>[]; final List<BuildInfo> buildInfos = <BuildInfo>[];
if (boolArg('debug')) { if (boolArg('debug')) {
...@@ -154,7 +154,7 @@ class BuildIOSFrameworkCommand extends BuildSubCommand { ...@@ -154,7 +154,7 @@ class BuildIOSFrameworkCommand extends BuildSubCommand {
if (boolArg('universal')) { if (boolArg('universal')) {
throwToolExit('--universal has been deprecated, only XCFrameworks are supported.'); throwToolExit('--universal has been deprecated, only XCFrameworks are supported.');
} }
if ((await buildInfos).isEmpty) { if ((await getBuildInfos()).isEmpty) {
throwToolExit('At least one of "--debug" or "--profile", or "--release" is required.'); throwToolExit('At least one of "--debug" or "--profile", or "--release" is required.');
} }
} }
...@@ -173,8 +173,9 @@ class BuildIOSFrameworkCommand extends BuildSubCommand { ...@@ -173,8 +173,9 @@ class BuildIOSFrameworkCommand extends BuildSubCommand {
} }
final Directory outputDirectory = globals.fs.directory(globals.fs.path.absolute(globals.fs.path.normalize(outputArgument))); final Directory outputDirectory = globals.fs.directory(globals.fs.path.absolute(globals.fs.path.normalize(outputArgument)));
final List<BuildInfo> buildInfos = await getBuildInfos();
for (final BuildInfo buildInfo in await buildInfos) { displayNullSafetyMode(buildInfos.first);
for (final BuildInfo buildInfo in buildInfos) {
final String productBundleIdentifier = await _project.ios.productBundleIdentifier(buildInfo); final String productBundleIdentifier = await _project.ios.productBundleIdentifier(buildInfo);
globals.printStatus('Building frameworks for $productBundleIdentifier in ${getNameForBuildMode(buildInfo.mode)} mode...'); globals.printStatus('Building frameworks for $productBundleIdentifier in ${getNameForBuildMode(buildInfo.mode)} mode...');
final String xcodeBuildConfiguration = toTitleCase(getNameForBuildMode(buildInfo.mode)); final String xcodeBuildConfiguration = toTitleCase(getNameForBuildMode(buildInfo.mode));
......
...@@ -43,6 +43,7 @@ class BuildLinuxCommand extends BuildSubCommand { ...@@ -43,6 +43,7 @@ class BuildLinuxCommand extends BuildSubCommand {
if (!globals.platform.isLinux) { if (!globals.platform.isLinux) {
throwToolExit('"build linux" only supported on Linux hosts.'); throwToolExit('"build linux" only supported on Linux hosts.');
} }
displayNullSafetyMode(buildInfo);
await buildLinux( await buildLinux(
flutterProject.linux, flutterProject.linux,
buildInfo, buildInfo,
......
...@@ -47,6 +47,7 @@ class BuildMacosCommand extends BuildSubCommand { ...@@ -47,6 +47,7 @@ class BuildMacosCommand extends BuildSubCommand {
if (!globals.platform.isMacOS) { if (!globals.platform.isMacOS) {
throwToolExit('"build macos" only supported on macOS hosts.'); throwToolExit('"build macos" only supported on macOS hosts.');
} }
displayNullSafetyMode(buildInfo);
await buildMacOS( await buildMacOS(
flutterProject: flutterProject, flutterProject: flutterProject,
buildInfo: buildInfo, buildInfo: buildInfo,
......
...@@ -84,6 +84,7 @@ class BuildWebCommand extends BuildSubCommand { ...@@ -84,6 +84,7 @@ class BuildWebCommand extends BuildSubCommand {
if (buildInfo.isDebug) { if (buildInfo.isDebug) {
throwToolExit('debug builds cannot be built directly for the web. Try using "flutter run"'); throwToolExit('debug builds cannot be built directly for the web. Try using "flutter run"');
} }
displayNullSafetyMode(buildInfo);
await buildWeb( await buildWeb(
flutterProject, flutterProject,
target, target,
......
...@@ -49,6 +49,7 @@ class BuildWindowsCommand extends BuildSubCommand { ...@@ -49,6 +49,7 @@ class BuildWindowsCommand extends BuildSubCommand {
if (!globals.platform.isWindows) { if (!globals.platform.isWindows) {
throwToolExit('"build windows" only supported on Windows hosts.'); throwToolExit('"build windows" only supported on Windows hosts.');
} }
displayNullSafetyMode(buildInfo);
await buildWindows( await buildWindows(
flutterProject.windows, flutterProject.windows,
buildInfo, buildInfo,
......
...@@ -661,7 +661,8 @@ abstract class FlutterCommand extends Command<void> { ...@@ -661,7 +661,8 @@ abstract class FlutterCommand extends Command<void> {
FlutterOptions.kPerformanceMeasurementFile, FlutterOptions.kPerformanceMeasurementFile,
help: help:
'The name of a file where flutter assemble performance and ' 'The name of a file where flutter assemble performance and '
'cached-ness information will be written in a JSON format.' 'cached-ness information will be written in a JSON format.',
hide: hide,
); );
} }
...@@ -674,6 +675,7 @@ abstract class FlutterCommand extends Command<void> { ...@@ -674,6 +675,7 @@ abstract class FlutterCommand extends Command<void> {
"'--no-daemon' to the gradle wrapper script. This flag will cause the daemon " "'--no-daemon' to the gradle wrapper script. This flag will cause the daemon "
'process to terminate after the build is completed', 'process to terminate after the build is completed',
defaultsTo: true, defaultsTo: true,
hide: hide,
); );
} }
......
...@@ -3,7 +3,9 @@ ...@@ -3,7 +3,9 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:args/args.dart'; import 'package:args/args.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/commands/attach.dart'; import 'package:flutter_tools/src/commands/attach.dart';
import 'package:flutter_tools/src/commands/build.dart';
import 'package:flutter_tools/src/commands/build_aar.dart'; import 'package:flutter_tools/src/commands/build_aar.dart';
import 'package:flutter_tools/src/commands/build_apk.dart'; import 'package:flutter_tools/src/commands/build_apk.dart';
import 'package:flutter_tools/src/commands/build_appbundle.dart'; import 'package:flutter_tools/src/commands/build_appbundle.dart';
...@@ -47,4 +49,45 @@ void main() { ...@@ -47,4 +49,45 @@ void main() {
expect(results.wasParsed('enable-experiment'), true); expect(results.wasParsed('enable-experiment'), true);
} }
}); });
testUsingContext('BuildSubCommand displays current null safety mode', () async {
const BuildInfo unsound = BuildInfo(
BuildMode.debug,
'',
trackWidgetCreation: false,
nullSafetyMode: NullSafetyMode.unsound,
treeShakeIcons: false,
);
const BuildInfo sound = BuildInfo(
BuildMode.debug,
'',
trackWidgetCreation: false,
nullSafetyMode: NullSafetyMode.sound,
treeShakeIcons: false,
);
FakeBuildSubCommand().test(unsound);
expect(testLogger.statusText, contains('Building with unsound null safety'));
testLogger.clear();
FakeBuildSubCommand().test(sound);
expect(testLogger.statusText, contains('💪 Building with sound null safety 💪'));
});
}
class FakeBuildSubCommand extends BuildSubCommand {
@override
String get description => throw UnimplementedError();
@override
String get name => throw UnimplementedError();
void test(BuildInfo buildInfo) {
displayNullSafetyMode(buildInfo);
}
@override
Future<FlutterCommandResult> runCommand() {
throw UnimplementedError();
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment