Unverified Commit 059de153 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] support code size tooling on iOS, linux, windows, macOS, and...

[flutter_tools] support code size tooling on iOS, linux, windows, macOS, and Android on Windows (#63610)

Adds support for size analysis on iOS, macOS, linux, and Windows - using an uncompressed directory based approach. The output format is not currently specified.

Adds support for size analysis on android on windows, switching to package:archive

Updates the console format to display as a tree, allowing longer paths. Increases the number of dart libraries shown (to avoid only ever printing the flutter/dart:ui libraries, which dominate the size)
parent dd0c8812
...@@ -72,6 +72,11 @@ if [[ -n "$BUNDLE_SKSL_PATH" ]]; then ...@@ -72,6 +72,11 @@ if [[ -n "$BUNDLE_SKSL_PATH" ]]; then
bundle_sksl_path="-iBundleSkSLPath=${BUNDLE_SKSL_PATH}" bundle_sksl_path="-iBundleSkSLPath=${BUNDLE_SKSL_PATH}"
fi fi
code_size_directory=""
if [[ -n "$CODE_SIZE_DIRECTORY" ]]; then
code_size_directory="-dCodeSizeDirectory=${CODE_SIZE_DIRECTORY}"
fi
RunCommand "${FLUTTER_ROOT}/bin/flutter" \ RunCommand "${FLUTTER_ROOT}/bin/flutter" \
${verbose_flag} \ ${verbose_flag} \
${flutter_engine_flag} \ ${flutter_engine_flag} \
...@@ -86,6 +91,7 @@ RunCommand "${FLUTTER_ROOT}/bin/flutter" \ ...@@ -86,6 +91,7 @@ RunCommand "${FLUTTER_ROOT}/bin/flutter" \
-dSplitDebugInfo="${SPLIT_DEBUG_INFO}" \ -dSplitDebugInfo="${SPLIT_DEBUG_INFO}" \
-dTrackWidgetCreation="${TRACK_WIDGET_CREATION}" \ -dTrackWidgetCreation="${TRACK_WIDGET_CREATION}" \
${bundle_sksl_path} \ ${bundle_sksl_path} \
${code_size_directory} \
--DartDefines="${DART_DEFINES}" \ --DartDefines="${DART_DEFINES}" \
--ExtraGenSnapshotOptions="${EXTRA_GEN_SNAPSHOT_OPTIONS}" \ --ExtraGenSnapshotOptions="${EXTRA_GEN_SNAPSHOT_OPTIONS}" \
--ExtraFrontEndOptions="${EXTRA_FRONT_END_OPTIONS}" \ --ExtraFrontEndOptions="${EXTRA_FRONT_END_OPTIONS}" \
......
...@@ -19,6 +19,7 @@ Future<void> main(List<String> arguments) async { ...@@ -19,6 +19,7 @@ Future<void> main(List<String> arguments) async {
final String flutterRoot = Platform.environment['FLUTTER_ROOT']; final String flutterRoot = Platform.environment['FLUTTER_ROOT'];
final String flutterTarget = Platform.environment['FLUTTER_TARGET'] final String flutterTarget = Platform.environment['FLUTTER_TARGET']
?? pathJoin(<String>['lib', 'main.dart']); ?? pathJoin(<String>['lib', 'main.dart']);
final String codeSizeDirectory = Platform.environment['CODE_SIZE_DIRECTORY'];
final String localEngine = Platform.environment['LOCAL_ENGINE']; final String localEngine = Platform.environment['LOCAL_ENGINE'];
final String projectDirectory = Platform.environment['PROJECT_DIR']; final String projectDirectory = Platform.environment['PROJECT_DIR'];
final String splitDebugInfo = Platform.environment['SPLIT_DEBUG_INFO']; final String splitDebugInfo = Platform.environment['SPLIT_DEBUG_INFO'];
...@@ -70,6 +71,8 @@ or ...@@ -70,6 +71,8 @@ or
'-dDartObfuscation=$dartObfuscation', '-dDartObfuscation=$dartObfuscation',
if (bundleSkSLPath != null) if (bundleSkSLPath != null)
'-iBundleSkSLPath=$bundleSkSLPath', '-iBundleSkSLPath=$bundleSkSLPath',
if (codeSizeDirectory != null)
'-dCodeSizeDirectory=$codeSizeDirectory',
if (splitDebugInfo != null) if (splitDebugInfo != null)
'-dSplitDebugInfo=$splitDebugInfo', '-dSplitDebugInfo=$splitDebugInfo',
if (dartDefines != null) if (dartDefines != null)
......
...@@ -155,6 +155,11 @@ is set to release or run \"flutter build ios --release\", then re-run Archive fr ...@@ -155,6 +155,11 @@ is set to release or run \"flutter build ios --release\", then re-run Archive fr
performance_measurement_option="--performance-measurement-file=${PERFORMANCE_MEASUREMENT_FILE}" performance_measurement_option="--performance-measurement-file=${PERFORMANCE_MEASUREMENT_FILE}"
fi fi
local code_size_directory=""
if [[ -n "$CODE_SIZE_DIRECTORY" ]]; then
code_size_directory="-dCodeSizeDirectory=${CODE_SIZE_DIRECTORY}"
fi
RunCommand "${FLUTTER_ROOT}/bin/flutter" \ RunCommand "${FLUTTER_ROOT}/bin/flutter" \
${verbose_flag} \ ${verbose_flag} \
${flutter_engine_flag} \ ${flutter_engine_flag} \
...@@ -172,6 +177,7 @@ is set to release or run \"flutter build ios --release\", then re-run Archive fr ...@@ -172,6 +177,7 @@ is set to release or run \"flutter build ios --release\", then re-run Archive fr
-dDartObfuscation="${DART_OBFUSCATION}" \ -dDartObfuscation="${DART_OBFUSCATION}" \
-dEnableBitcode="${bitcode_flag}" \ -dEnableBitcode="${bitcode_flag}" \
${bundle_sksl_path} \ ${bundle_sksl_path} \
${code_size_directory} \
--ExtraGenSnapshotOptions="${EXTRA_GEN_SNAPSHOT_OPTIONS}" \ --ExtraGenSnapshotOptions="${EXTRA_GEN_SNAPSHOT_OPTIONS}" \
--DartDefines="${DART_DEFINES}" \ --DartDefines="${DART_DEFINES}" \
--ExtraFrontEndOptions="${EXTRA_FRONT_END_OPTIONS}" \ --ExtraFrontEndOptions="${EXTRA_FRONT_END_OPTIONS}" \
......
...@@ -628,6 +628,10 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -628,6 +628,10 @@ class FlutterPlugin implements Plugin<Project> {
if (project.hasProperty('performance-measurement-file')) { if (project.hasProperty('performance-measurement-file')) {
performanceMeasurementFileValue = project.property('performance-measurement-file') performanceMeasurementFileValue = project.property('performance-measurement-file')
} }
String codeSizeDirectoryValue;
if (project.hasProperty('code-size-directory')) {
codeSizeDirectoryValue = project.property('code-size-directory')
}
def targetPlatforms = getTargetPlatforms() def targetPlatforms = getTargetPlatforms()
def addFlutterDeps = { variant -> def addFlutterDeps = { variant ->
if (shouldSplitPerAbi()) { if (shouldSplitPerAbi()) {
...@@ -668,6 +672,7 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -668,6 +672,7 @@ class FlutterPlugin implements Plugin<Project> {
dartDefines dartDefinesValue dartDefines dartDefinesValue
bundleSkSLPath bundleSkSLPathValue bundleSkSLPath bundleSkSLPathValue
performanceMeasurementFile performanceMeasurementFileValue performanceMeasurementFile performanceMeasurementFileValue
codeSizeDirectory codeSizeDirectoryValue
doLast { doLast {
project.exec { project.exec {
if (Os.isFamily(Os.FAMILY_WINDOWS)) { if (Os.isFamily(Os.FAMILY_WINDOWS)) {
...@@ -862,6 +867,8 @@ abstract class BaseFlutterTask extends DefaultTask { ...@@ -862,6 +867,8 @@ abstract class BaseFlutterTask extends DefaultTask {
String dartDefines String dartDefines
@Optional @Input @Optional @Input
String bundleSkSLPath String bundleSkSLPath
@Optional @Input
String codeSizeDirectory;
String performanceMeasurementFile; String performanceMeasurementFile;
@OutputFiles @OutputFiles
...@@ -938,6 +945,9 @@ abstract class BaseFlutterTask extends DefaultTask { ...@@ -938,6 +945,9 @@ abstract class BaseFlutterTask extends DefaultTask {
if (bundleSkSLPath != null) { if (bundleSkSLPath != null) {
args "-iBundleSkSLPath=${bundleSkSLPath}" args "-iBundleSkSLPath=${bundleSkSLPath}"
} }
if (codeSizeDirectory != null) {
args "-dCodeSizeDirectory=${codeSizeDirectory}"
}
if (extraGenSnapshotOptions != null) { if (extraGenSnapshotOptions != null) {
args "--ExtraGenSnapshotOptions=${extraGenSnapshotOptions}" args "--ExtraGenSnapshotOptions=${extraGenSnapshotOptions}"
} }
......
...@@ -11,6 +11,13 @@ const String kSupportedAbis = 'https://flutter.dev/docs/deployment/android#what- ...@@ -11,6 +11,13 @@ const String kSupportedAbis = 'https://flutter.dev/docs/deployment/android#what-
/// Validates that the build mode and build number are valid for a given build. /// Validates that the build mode and build number are valid for a given build.
void validateBuild(AndroidBuildInfo androidBuildInfo) { void validateBuild(AndroidBuildInfo androidBuildInfo) {
final BuildInfo buildInfo = androidBuildInfo.buildInfo; final BuildInfo buildInfo = androidBuildInfo.buildInfo;
if (buildInfo.codeSizeDirectory != null && androidBuildInfo.targetArchs.length > 1) {
throwToolExit(
'Cannot perform code size analysis when building for multiple ABIs. '
'Specify one of android-arm, android-arm64, or android-x64 in the '
'--target-plaform flag.'
);
}
if (buildInfo.mode.isPrecompiled && androidBuildInfo.targetArchs.contains(AndroidArch.x86)) { if (buildInfo.mode.isPrecompiled && androidBuildInfo.targetArchs.contains(AndroidArch.x86)) {
throwToolExit( throwToolExit(
'Cannot build ${androidBuildInfo.buildInfo.mode.name} mode for x86 ABI.\n' 'Cannot build ${androidBuildInfo.buildInfo.mode.name} mode for x86 ABI.\n'
......
...@@ -357,6 +357,9 @@ Future<void> buildGradleApp({ ...@@ -357,6 +357,9 @@ Future<void> buildGradleApp({
if (androidBuildInfo.buildInfo.performanceMeasurementFile != null) { if (androidBuildInfo.buildInfo.performanceMeasurementFile != null) {
command.add('-Pperformance-measurement-file=${androidBuildInfo.buildInfo.performanceMeasurementFile}'); command.add('-Pperformance-measurement-file=${androidBuildInfo.buildInfo.performanceMeasurementFile}');
} }
if (buildInfo.codeSizeDirectory != null) {
command.add('-Pcode-size-directory=${buildInfo.codeSizeDirectory}');
}
command.add(assembleTask); command.add(assembleTask);
GradleHandledError detectedGradleError; GradleHandledError detectedGradleError;
...@@ -467,6 +470,10 @@ Future<void> buildGradleApp({ ...@@ -467,6 +470,10 @@ Future<void> buildGradleApp({
? '' // Don't display the size when building a debug variant. ? '' // Don't display the size when building a debug variant.
: ' (${getSizeAsMB(bundleFile.lengthSync())})'; : ' (${getSizeAsMB(bundleFile.lengthSync())})';
if (buildInfo.codeSizeDirectory != null) {
await _performCodeSizeAnalysis('aab', bundleFile, androidBuildInfo);
}
globals.printStatus( globals.printStatus(
'$successMark Built ${globals.fs.path.relative(bundleFile.path)}$appSize.', '$successMark Built ${globals.fs.path.relative(bundleFile.path)}$appSize.',
color: TerminalColor.green, color: TerminalColor.green,
...@@ -502,26 +509,41 @@ Future<void> buildGradleApp({ ...@@ -502,26 +509,41 @@ Future<void> buildGradleApp({
color: TerminalColor.green, color: TerminalColor.green,
); );
// Call size analyzer if --analyze-size flag was provided. if (buildInfo.codeSizeDirectory != null) {
if (buildInfo.analyzeSize != null && !globals.platform.isWindows) { await _performCodeSizeAnalysis('apk', apkFile, androidBuildInfo);
final SizeAnalyzer sizeAnalyzer = SizeAnalyzer(
fileSystem: globals.fs,
logger: globals.logger,
processUtils: ProcessUtils.instance,
);
final Map<String, Object> output = await sizeAnalyzer.analyzeApkSizeAndAotSnapshot(
apk: apkFile,
aotSnapshot: globals.fs.file(buildInfo.analyzeSize),
);
final File outputFile = globals.fsUtils.getUniqueFile(globals.fs.currentDirectory, 'apk-analysis', 'json')
..writeAsStringSync(jsonEncode(output));
// This message is used as a sentinel in analyze_apk_size_test.dart
globals.printStatus(
'A summary of your APK analysis can be found at: ${outputFile.path}',
);
} }
} }
Future<void> _performCodeSizeAnalysis(
String kind,
File zipFile,
AndroidBuildInfo androidBuildInfo,
) async {
final SizeAnalyzer sizeAnalyzer = SizeAnalyzer(
fileSystem: globals.fs,
logger: globals.logger,
);
final String archName = getNameForAndroidArch(androidBuildInfo.targetArchs.single);
final BuildInfo buildInfo = androidBuildInfo.buildInfo;
final File aotSnapshot = globals.fs.directory(buildInfo.codeSizeDirectory)
.childFile('snapshot.$archName.json');
final File precompilerTrace = globals.fs.directory(buildInfo.codeSizeDirectory)
.childFile('trace.$archName.json');
final Map<String, Object> output = await sizeAnalyzer.analyzeZipSizeAndAotSnapshot(
zipFile: zipFile,
aotSnapshot: aotSnapshot,
precompilerTrace: precompilerTrace,
kind: kind,
);
final File outputFile = globals.fsUtils.getUniqueFile(
globals.fs.directory(getBuildDirectory()),'$kind-code-size-analysis', 'json',
)..writeAsStringSync(jsonEncode(output));
// This message is used as a sentinel in analyze_apk_size_test.dart
globals.printStatus(
'A summary of your ${kind.toUpperCase()} analysis can be found at: ${outputFile.path}',
);
}
/// Builds AAR and POM files. /// Builds AAR and POM files.
/// ///
/// * [project] is typically [FlutterProject.current()]. /// * [project] is typically [FlutterProject.current()].
......
...@@ -105,7 +105,23 @@ class FileSystemUtils { ...@@ -105,7 +105,23 @@ class FileSystemUtils {
if (!file.existsSync()) { if (!file.existsSync()) {
return file; return file;
} }
i++; i += 1;
}
}
/// Appends a number to a directory name in order to make it unique under a
/// directory.
Directory getUniqueDirectory(Directory dir, String baseName) {
final FileSystem fs = dir.fileSystem;
int i = 1;
while (true) {
final String name = '${baseName}_${i.toString().padLeft(2, '0')}';
final Directory directory = fs.directory(_fileSystem.path.join(dir.path, name));
if (!directory.existsSync()) {
return directory;
}
i += 1;
} }
} }
......
...@@ -33,7 +33,7 @@ class BuildInfo { ...@@ -33,7 +33,7 @@ class BuildInfo {
this.performanceMeasurementFile, this.performanceMeasurementFile,
this.packagesPath = '.packages', this.packagesPath = '.packages',
this.nullSafetyMode = NullSafetyMode.autodetect, this.nullSafetyMode = NullSafetyMode.autodetect,
this.analyzeSize, this.codeSizeDirectory,
}); });
final BuildMode mode; final BuildMode mode;
...@@ -114,9 +114,9 @@ class BuildInfo { ...@@ -114,9 +114,9 @@ class BuildInfo {
/// rerun tasks. /// rerun tasks.
final String performanceMeasurementFile; final String performanceMeasurementFile;
/// If provided, an output file where a v8-style heapsnapshot will be written for size /// If provided, an output directory where one or more v8-style heapsnapshots
/// profiling. /// will be written for code size profiling.
final String analyzeSize; final String codeSizeDirectory;
static const BuildInfo debug = BuildInfo(BuildMode.debug, null, treeShakeIcons: false); static const BuildInfo debug = BuildInfo(BuildMode.debug, null, treeShakeIcons: false);
static const BuildInfo profile = BuildInfo(BuildMode.profile, null, treeShakeIcons: kIconTreeShakerEnabledDefault); static const BuildInfo profile = BuildInfo(BuildMode.profile, null, treeShakeIcons: kIconTreeShakerEnabledDefault);
...@@ -178,6 +178,8 @@ class BuildInfo { ...@@ -178,6 +178,8 @@ class BuildInfo {
'BUNDLE_SKSL_PATH': bundleSkSLPath, 'BUNDLE_SKSL_PATH': bundleSkSLPath,
if (packagesPath != null) if (packagesPath != null)
'PACKAGE_CONFIG': packagesPath, 'PACKAGE_CONFIG': packagesPath,
if (codeSizeDirectory != null)
'CODE_SIZE_DIRECTORY': codeSizeDirectory,
}; };
} }
} }
...@@ -698,7 +700,7 @@ String encodeDartDefines(List<String> defines) { ...@@ -698,7 +700,7 @@ String encodeDartDefines(List<String> defines) {
/// Dart defines are encoded inside [environmentDefines] as a comma-separated list. /// Dart defines are encoded inside [environmentDefines] as a comma-separated list.
List<String> decodeDartDefines(Map<String, String> environmentDefines, String key) { List<String> decodeDartDefines(Map<String, String> environmentDefines, String key) {
if (!environmentDefines.containsKey(key) || environmentDefines[key].isEmpty) { if (!environmentDefines.containsKey(key) || environmentDefines[key].isEmpty) {
return const <String>[]; return <String>[];
} }
return environmentDefines[key] return environmentDefines[key]
.split(',') .split(',')
......
...@@ -232,6 +232,19 @@ class AndroidAot extends AotElfBase { ...@@ -232,6 +232,19 @@ class AndroidAot extends AotElfBase {
final List<String> extraGenSnapshotOptions = decodeDartDefines(environment.defines, kExtraGenSnapshotOptions); final List<String> extraGenSnapshotOptions = decodeDartDefines(environment.defines, kExtraGenSnapshotOptions);
final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]); final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true'; final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true';
final String codeSizeDirectory = environment.defines[kCodeSizeDirectory];
if (codeSizeDirectory != null) {
final File codeSizeFile = environment.fileSystem
.directory(codeSizeDirectory)
.childFile('snapshot.$_androidAbiName.json');
final File precompilerTraceFile = environment.fileSystem
.directory(codeSizeDirectory)
.childFile('trace.$_androidAbiName.json');
extraGenSnapshotOptions.add('--write-v8-snapshot-profile-to=${codeSizeFile.path}');
extraGenSnapshotOptions.add('--trace-precompiler-to=${precompilerTraceFile.path}');
}
final int snapshotExitCode = await snapshotter.build( final int snapshotExitCode = await snapshotter.build(
platform: targetPlatform, platform: targetPlatform,
buildMode: buildMode, buildMode: buildMode,
......
...@@ -68,6 +68,9 @@ const String kIosArchs = 'IosArchs'; ...@@ -68,6 +68,9 @@ const String kIosArchs = 'IosArchs';
/// Whether to enable Dart obfuscation and where to save the symbol map. /// Whether to enable Dart obfuscation and where to save the symbol map.
const String kDartObfuscation = 'DartObfuscation'; const String kDartObfuscation = 'DartObfuscation';
/// An output directory where one or more code-size measurements may be written.
const String kCodeSizeDirectory = 'CodeSizeDirectory';
/// Copies the pre-built flutter bundle. /// Copies the pre-built flutter bundle.
// This is a one-off rule for implementing build bundle in terms of assemble. // This is a one-off rule for implementing build bundle in terms of assemble.
class CopyFlutterBundle extends Target { class CopyFlutterBundle extends Target {
...@@ -295,6 +298,19 @@ abstract class AotElfBase extends Target { ...@@ -295,6 +298,19 @@ abstract class AotElfBase extends Target {
final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]); final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]);
final String splitDebugInfo = environment.defines[kSplitDebugInfo]; final String splitDebugInfo = environment.defines[kSplitDebugInfo];
final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true'; final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true';
final String codeSizeDirectory = environment.defines[kCodeSizeDirectory];
if (codeSizeDirectory != null) {
final File codeSizeFile = environment.fileSystem
.directory(codeSizeDirectory)
.childFile('snapshot.${environment.defines[kTargetPlatform]}.json');
final File precompilerTraceFile = environment.fileSystem
.directory(codeSizeDirectory)
.childFile('trace.${environment.defines[kTargetPlatform]}.json');
extraGenSnapshotOptions.add('--write-v8-snapshot-profile-to=${codeSizeFile.path}');
extraGenSnapshotOptions.add('--trace-precompiler-to=${precompilerTraceFile.path}');
}
final int snapshotExitCode = await snapshotter.build( final int snapshotExitCode = await snapshotter.build(
platform: targetPlatform, platform: targetPlatform,
buildMode: buildMode, buildMode: buildMode,
......
...@@ -52,7 +52,7 @@ abstract class AotAssemblyBase extends Target { ...@@ -52,7 +52,7 @@ abstract class AotAssemblyBase extends Target {
final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]); final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]);
final String splitDebugInfo = environment.defines[kSplitDebugInfo]; final String splitDebugInfo = environment.defines[kSplitDebugInfo];
final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true'; final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true';
final List<DarwinArch> iosArchs = environment.defines[kIosArchs] final List<DarwinArch> darwinArchs = environment.defines[kIosArchs]
?.split(' ') ?.split(' ')
?.map(getIOSArchForName) ?.map(getIOSArchForName)
?.toList() ?.toList()
...@@ -60,29 +60,41 @@ abstract class AotAssemblyBase extends Target { ...@@ -60,29 +60,41 @@ abstract class AotAssemblyBase extends Target {
if (targetPlatform != TargetPlatform.ios) { if (targetPlatform != TargetPlatform.ios) {
throw Exception('aot_assembly is only supported for iOS applications.'); throw Exception('aot_assembly is only supported for iOS applications.');
} }
if (iosArchs.contains(DarwinArch.x86_64)) { if (darwinArchs.contains(DarwinArch.x86_64)) {
throw Exception( throw Exception(
'release/profile builds are only supported for physical devices. ' 'release/profile builds are only supported for physical devices. '
'attempted to build for $iosArchs.' 'attempted to build for $darwinArchs.'
); );
} }
final String codeSizeDirectory = environment.defines[kCodeSizeDirectory];
// If we're building multiple iOS archs the binaries need to be lipo'd // If we're building multiple iOS archs the binaries need to be lipo'd
// together. // together.
final List<Future<int>> pending = <Future<int>>[]; final List<Future<int>> pending = <Future<int>>[];
for (final DarwinArch iosArch in iosArchs) { for (final DarwinArch darwinArch in darwinArchs) {
final List<String> archExtraGenSnapshotOptions = List<String>.of(extraGenSnapshotOptions);
if (codeSizeDirectory != null) {
final File codeSizeFile = environment.fileSystem
.directory(codeSizeDirectory)
.childFile('snapshot.${getNameForDarwinArch(darwinArch)}.json');
final File precompilerTraceFile = environment.fileSystem
.directory(codeSizeDirectory)
.childFile('trace.${getNameForDarwinArch(darwinArch)}.json');
archExtraGenSnapshotOptions.add('--write-v8-snapshot-profile-to=${codeSizeFile.path}');
archExtraGenSnapshotOptions.add('--trace-precompiler-to=${precompilerTraceFile.path}');
}
pending.add(snapshotter.build( pending.add(snapshotter.build(
platform: targetPlatform, platform: targetPlatform,
buildMode: buildMode, buildMode: buildMode,
mainPath: environment.buildDir.childFile('app.dill').path, mainPath: environment.buildDir.childFile('app.dill').path,
packagesPath: environment.projectDir.childFile('.packages').path, packagesPath: environment.projectDir.childFile('.packages').path,
outputPath: environment.fileSystem.path.join(buildOutputPath, getNameForDarwinArch(iosArch)), outputPath: environment.fileSystem.path.join(buildOutputPath, getNameForDarwinArch(darwinArch)),
darwinArch: iosArch, darwinArch: darwinArch,
bitcode: bitcode, bitcode: bitcode,
quiet: true, quiet: true,
splitDebugInfo: splitDebugInfo, splitDebugInfo: splitDebugInfo,
dartObfuscation: dartObfuscation, dartObfuscation: dartObfuscation,
extraGenSnapshotOptions: extraGenSnapshotOptions, extraGenSnapshotOptions: archExtraGenSnapshotOptions,
)); ));
} }
final List<int> results = await Future.wait(pending); final List<int> results = await Future.wait(pending);
...@@ -93,7 +105,7 @@ abstract class AotAssemblyBase extends Target { ...@@ -93,7 +105,7 @@ abstract class AotAssemblyBase extends Target {
environment.fileSystem.directory(resultPath).parent.createSync(recursive: true); environment.fileSystem.directory(resultPath).parent.createSync(recursive: true);
final ProcessResult result = await environment.processManager.run(<String>[ final ProcessResult result = await environment.processManager.run(<String>[
'lipo', 'lipo',
...iosArchs.map((DarwinArch iosArch) => ...darwinArchs.map((DarwinArch iosArch) =>
environment.fileSystem.path.join(buildOutputPath, getNameForDarwinArch(iosArch), 'App.framework', 'App')), environment.fileSystem.path.join(buildOutputPath, getNameForDarwinArch(iosArch), 'App.framework', 'App')),
'-create', '-create',
'-output', '-output',
......
...@@ -8,7 +8,7 @@ import '../../base/file_system.dart'; ...@@ -8,7 +8,7 @@ import '../../base/file_system.dart';
import '../../base/io.dart'; import '../../base/io.dart';
import '../../base/process.dart'; import '../../base/process.dart';
import '../../build_info.dart'; import '../../build_info.dart';
import '../../globals.dart' as globals; import '../../globals.dart' as globals hide fs, logger, artifacts, processManager;
import '../build_system.dart'; import '../build_system.dart';
import '../depfile.dart'; import '../depfile.dart';
import '../exceptions.dart'; import '../exceptions.dart';
...@@ -52,7 +52,7 @@ abstract class UnpackMacOS extends Target { ...@@ -52,7 +52,7 @@ abstract class UnpackMacOS extends Target {
throw MissingDefineException(kBuildMode, 'unpack_macos'); throw MissingDefineException(kBuildMode, 'unpack_macos');
} }
final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]); final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
final String basePath = globals.artifacts.getArtifactPath(Artifact.flutterMacOSFramework, mode: buildMode); final String basePath = environment.artifacts.getArtifactPath(Artifact.flutterMacOSFramework, mode: buildMode);
final Directory targetDirectory = environment final Directory targetDirectory = environment
.outputDir .outputDir
.childDirectory('FlutterMacOS.framework'); .childDirectory('FlutterMacOS.framework');
...@@ -61,15 +61,15 @@ abstract class UnpackMacOS extends Target { ...@@ -61,15 +61,15 @@ abstract class UnpackMacOS extends Target {
if (targetDirectory.existsSync()) { if (targetDirectory.existsSync()) {
targetDirectory.deleteSync(recursive: true); targetDirectory.deleteSync(recursive: true);
} }
final List<File> inputs = globals.fs.directory(basePath) final List<File> inputs = environment.fileSystem.directory(basePath)
.listSync(recursive: true) .listSync(recursive: true)
.whereType<File>() .whereType<File>()
.toList(); .toList();
final List<File> outputs = inputs.map((File file) { final List<File> outputs = inputs.map((File file) {
final String relativePath = globals.fs.path.relative(file.path, from: basePath); final String relativePath = environment.fileSystem.path.relative(file.path, from: basePath);
return globals.fs.file(globals.fs.path.join(targetDirectory.path, relativePath)); return environment.fileSystem.file(environment.fileSystem.path.join(targetDirectory.path, relativePath));
}).toList(); }).toList();
final ProcessResult result = await globals.processManager final ProcessResult result = await environment.processManager
.run(<String>['cp', '-R', basePath, targetDirectory.path]); .run(<String>['cp', '-R', basePath, targetDirectory.path]);
if (result.exitCode != 0) { if (result.exitCode != 0) {
throw Exception( throw Exception(
...@@ -78,8 +78,8 @@ abstract class UnpackMacOS extends Target { ...@@ -78,8 +78,8 @@ abstract class UnpackMacOS extends Target {
); );
} }
final DepfileService depfileService = DepfileService( final DepfileService depfileService = DepfileService(
logger: globals.logger, logger: environment.logger,
fileSystem: globals.fs, fileSystem: environment.fileSystem,
); );
depfileService.writeToFile( depfileService.writeToFile(
Depfile(inputs, outputs), Depfile(inputs, outputs),
...@@ -143,7 +143,7 @@ class DebugMacOSFramework extends Target { ...@@ -143,7 +143,7 @@ class DebugMacOSFramework extends Target {
@override @override
Future<void> build(Environment environment) async { Future<void> build(Environment environment) async {
final File outputFile = globals.fs.file(globals.fs.path.join( final File outputFile = environment.fileSystem.file(environment.fileSystem.path.join(
environment.buildDir.path, 'App.framework', 'App')); environment.buildDir.path, 'App.framework', 'App'));
outputFile.createSync(recursive: true); outputFile.createSync(recursive: true);
final File debugApp = environment.buildDir.childFile('debug_app.cc') final File debugApp = environment.buildDir.childFile('debug_app.cc')
...@@ -195,16 +195,30 @@ class CompileMacOSFramework extends Target { ...@@ -195,16 +195,30 @@ class CompileMacOSFramework extends Target {
if (buildMode == BuildMode.debug) { if (buildMode == BuildMode.debug) {
throw Exception('precompiled macOS framework only supported in release/profile builds.'); throw Exception('precompiled macOS framework only supported in release/profile builds.');
} }
final String codeSizeDirectory = environment.defines[kCodeSizeDirectory];
final String splitDebugInfo = environment.defines[kSplitDebugInfo]; final String splitDebugInfo = environment.defines[kSplitDebugInfo];
final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true'; final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true';
final List<String> extraGenSnapshotOptions = decodeDartDefines(environment.defines, kExtraGenSnapshotOptions); final List<String> extraGenSnapshotOptions = decodeDartDefines(environment.defines, kExtraGenSnapshotOptions);
if (codeSizeDirectory != null) {
final File codeSizeFile = environment.fileSystem
.directory(codeSizeDirectory)
.childFile('snapshot.${getNameForDarwinArch(DarwinArch.x86_64)}.json');
extraGenSnapshotOptions.add('--write-v8-snapshot-profile-to=${codeSizeFile.path}');
final File precompilerTraceFile = environment.fileSystem
.directory(codeSizeDirectory)
.childFile('trace.${getNameForDarwinArch(DarwinArch.x86_64)}.json');
extraGenSnapshotOptions.add('--write-v8-snapshot-profile-to=${codeSizeFile.path}');
extraGenSnapshotOptions.add('--trace-precompiler-to=${precompilerTraceFile.path}');
}
final AOTSnapshotter snapshotter = AOTSnapshotter( final AOTSnapshotter snapshotter = AOTSnapshotter(
reportTimings: false, reportTimings: false,
fileSystem: globals.fs, fileSystem: environment.fileSystem,
logger: globals.logger, logger: environment.logger,
xcode: globals.xcode, xcode: globals.xcode,
artifacts: globals.artifacts, artifacts: environment.artifacts,
processManager: globals.processManager processManager: environment.processManager
); );
final int result = await snapshotter.build( final int result = await snapshotter.build(
bitcode: false, bitcode: false,
...@@ -299,8 +313,8 @@ abstract class MacOSBundleFlutterAssets extends Target { ...@@ -299,8 +313,8 @@ abstract class MacOSBundleFlutterAssets extends Target {
targetPlatform: TargetPlatform.darwin_x64, targetPlatform: TargetPlatform.darwin_x64,
); );
final DepfileService depfileService = DepfileService( final DepfileService depfileService = DepfileService(
fileSystem: globals.fs, fileSystem: environment.fileSystem,
logger: globals.logger, logger: environment.logger,
); );
depfileService.writeToFile( depfileService.writeToFile(
assetDepfile, assetDepfile,
...@@ -345,13 +359,13 @@ abstract class MacOSBundleFlutterAssets extends Target { ...@@ -345,13 +359,13 @@ abstract class MacOSBundleFlutterAssets extends Target {
} }
// Copy precompiled runtimes. // Copy precompiled runtimes.
try { try {
final String vmSnapshotData = globals.artifacts.getArtifactPath(Artifact.vmSnapshotData, final String vmSnapshotData = environment.artifacts.getArtifactPath(Artifact.vmSnapshotData,
platform: TargetPlatform.darwin_x64, mode: BuildMode.debug); platform: TargetPlatform.darwin_x64, mode: BuildMode.debug);
final String isolateSnapshotData = globals.artifacts.getArtifactPath(Artifact.isolateSnapshotData, final String isolateSnapshotData = environment.artifacts.getArtifactPath(Artifact.isolateSnapshotData,
platform: TargetPlatform.darwin_x64, mode: BuildMode.debug); platform: TargetPlatform.darwin_x64, mode: BuildMode.debug);
globals.fs.file(vmSnapshotData).copySync( environment.fileSystem.file(vmSnapshotData).copySync(
assetDirectory.childFile('vm_snapshot_data').path); assetDirectory.childFile('vm_snapshot_data').path);
globals.fs.file(isolateSnapshotData).copySync( environment.fileSystem.file(isolateSnapshotData).copySync(
assetDirectory.childFile('isolate_snapshot_data').path); assetDirectory.childFile('isolate_snapshot_data').path);
} on Exception catch (err) { } on Exception catch (err) {
throw Exception('Failed to copy precompiled runtimes: $err'); throw Exception('Failed to copy precompiled runtimes: $err');
...@@ -364,7 +378,7 @@ abstract class MacOSBundleFlutterAssets extends Target { ...@@ -364,7 +378,7 @@ abstract class MacOSBundleFlutterAssets extends Target {
final Link currentVersion = outputDirectory.parent final Link currentVersion = outputDirectory.parent
.childLink('Current'); .childLink('Current');
if (!currentVersion.existsSync()) { if (!currentVersion.existsSync()) {
final String linkPath = globals.fs.path.relative(outputDirectory.path, final String linkPath = environment.fileSystem.path.relative(outputDirectory.path,
from: outputDirectory.parent.path); from: outputDirectory.parent.path);
currentVersion.createSync(linkPath); currentVersion.createSync(linkPath);
} }
...@@ -372,7 +386,7 @@ abstract class MacOSBundleFlutterAssets extends Target { ...@@ -372,7 +386,7 @@ abstract class MacOSBundleFlutterAssets extends Target {
final Link currentResources = frameworkRootDirectory final Link currentResources = frameworkRootDirectory
.childLink('Resources'); .childLink('Resources');
if (!currentResources.existsSync()) { if (!currentResources.existsSync()) {
final String linkPath = globals.fs.path.relative(globals.fs.path.join(currentVersion.path, 'Resources'), final String linkPath = environment.fileSystem.path.relative(environment.fileSystem.path.join(currentVersion.path, 'Resources'),
from: frameworkRootDirectory.path); from: frameworkRootDirectory.path);
currentResources.createSync(linkPath); currentResources.createSync(linkPath);
} }
...@@ -380,7 +394,7 @@ abstract class MacOSBundleFlutterAssets extends Target { ...@@ -380,7 +394,7 @@ abstract class MacOSBundleFlutterAssets extends Target {
final Link currentFramework = frameworkRootDirectory final Link currentFramework = frameworkRootDirectory
.childLink('App'); .childLink('App');
if (!currentFramework.existsSync()) { if (!currentFramework.existsSync()) {
final String linkPath = globals.fs.path.relative(globals.fs.path.join(currentVersion.path, 'App'), final String linkPath = environment.fileSystem.path.relative(environment.fileSystem.path.join(currentVersion.path, 'App'),
from: frameworkRootDirectory.path); from: frameworkRootDirectory.path);
currentFramework.createSync(linkPath); currentFramework.createSync(linkPath);
} }
......
...@@ -34,6 +34,7 @@ class BuildAppBundleCommand extends BuildSubCommand { ...@@ -34,6 +34,7 @@ class BuildAppBundleCommand extends BuildSubCommand {
usesTrackWidgetCreation(verboseHelp: verboseHelp); usesTrackWidgetCreation(verboseHelp: verboseHelp);
addNullSafetyModeOptions(hide: !verboseHelp); addNullSafetyModeOptions(hide: !verboseHelp);
addEnableExperimentation(hide: !verboseHelp); addEnableExperimentation(hide: !verboseHelp);
usesAnalyzeSizeFlag();
argParser.addMultiOption('target-platform', argParser.addMultiOption('target-platform',
splitCommas: true, splitCommas: true,
defaultsTo: <String>['android-arm', 'android-arm64', 'android-x64'], defaultsTo: <String>['android-arm', 'android-arm64', 'android-x64'],
......
...@@ -4,12 +4,15 @@ ...@@ -4,12 +4,15 @@
import 'dart:async'; import 'dart:async';
import 'package:file/file.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import '../application_package.dart'; import '../application_package.dart';
import '../base/analyze_size.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../convert.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../ios/mac.dart'; import '../ios/mac.dart';
import '../runner/flutter_command.dart' show DevelopmentArtifact, FlutterCommandResult; import '../runner/flutter_command.dart' show DevelopmentArtifact, FlutterCommandResult;
...@@ -35,6 +38,7 @@ class BuildIOSCommand extends BuildSubCommand { ...@@ -35,6 +38,7 @@ class BuildIOSCommand extends BuildSubCommand {
addBuildPerformanceFile(hide: !verboseHelp); addBuildPerformanceFile(hide: !verboseHelp);
addBundleSkSLPathOption(hide: !verboseHelp); addBundleSkSLPathOption(hide: !verboseHelp);
addNullSafetyModeOptions(hide: !verboseHelp); addNullSafetyModeOptions(hide: !verboseHelp);
usesAnalyzeSizeFlag();
argParser argParser
..addFlag('simulator', ..addFlag('simulator',
help: 'Build for the iOS simulator instead of the device. This changes ' help: 'Build for the iOS simulator instead of the device. This changes '
...@@ -103,6 +107,44 @@ class BuildIOSCommand extends BuildSubCommand { ...@@ -103,6 +107,44 @@ class BuildIOSCommand extends BuildSubCommand {
throwToolExit('Encountered error while building for $logTarget.'); throwToolExit('Encountered error while building for $logTarget.');
} }
if (buildInfo.codeSizeDirectory != null) {
final SizeAnalyzer sizeAnalyzer = SizeAnalyzer(
fileSystem: globals.fs,
logger: globals.logger,
appFilenamePattern: 'App'
);
// Only support 64bit iOS code size analysis.
final String arch = getNameForDarwinArch(DarwinArch.arm64);
final File aotSnapshot = globals.fs.directory(buildInfo.codeSizeDirectory)
.childFile('snapshot.$arch.json');
final File precompilerTrace = globals.fs.directory(buildInfo.codeSizeDirectory)
.childFile('trace.$arch.json');
// This analysis is only supported for release builds, which also excludes the simulator.
// Attempt to guess the correct .app by picking the first one.
final Directory candidateDirectory = globals.fs.directory(
globals.fs.path.join(getIosBuildDirectory(), 'Release-iphoneos'),
);
final Directory appDirectory = candidateDirectory.listSync()
.whereType<Directory>()
.firstWhere((Directory directory) {
return globals.fs.path.extension(directory.path) == '.app';
});
final Map<String, Object> output = await sizeAnalyzer.analyzeAotSnapshot(
aotSnapshot: aotSnapshot,
precompilerTrace: precompilerTrace,
outputDirectory: appDirectory,
type: 'ios',
);
final File outputFile = globals.fsUtils.getUniqueFile(
globals.fs.directory(getBuildDirectory()),'ios-code-size-analysis', 'json',
)..writeAsStringSync(jsonEncode(output));
// This message is used as a sentinel in analyze_apk_size_test.dart
globals.printStatus(
'A summary of your iOS bundle analysis can be found at: ${outputFile.path}',
);
}
if (result.output != null) { if (result.output != null) {
globals.printStatus('Built ${result.output}.'); globals.printStatus('Built ${result.output}.');
} }
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'dart:async'; import 'dart:async';
import '../base/analyze_size.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../cache.dart'; import '../cache.dart';
...@@ -30,6 +31,7 @@ class BuildLinuxCommand extends BuildSubCommand { ...@@ -30,6 +31,7 @@ class BuildLinuxCommand extends BuildSubCommand {
addBuildPerformanceFile(hide: !verboseHelp); addBuildPerformanceFile(hide: !verboseHelp);
addBundleSkSLPathOption(hide: !verboseHelp); addBundleSkSLPathOption(hide: !verboseHelp);
addNullSafetyModeOptions(hide: !verboseHelp); addNullSafetyModeOptions(hide: !verboseHelp);
usesAnalyzeSizeFlag();
} }
@override @override
...@@ -56,7 +58,15 @@ class BuildLinuxCommand extends BuildSubCommand { ...@@ -56,7 +58,15 @@ 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.');
} }
await buildLinux(flutterProject.linux, buildInfo, target: targetFile); await buildLinux(
flutterProject.linux,
buildInfo,
target: targetFile,
sizeAnalyzer: SizeAnalyzer(
fileSystem: globals.fs,
logger: globals.logger,
),
);
return FlutterCommandResult.success(); return FlutterCommandResult.success();
} }
} }
...@@ -6,6 +6,7 @@ import 'dart:async'; ...@@ -6,6 +6,7 @@ import 'dart:async';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import '../base/analyze_size.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../cache.dart'; import '../cache.dart';
...@@ -31,6 +32,7 @@ class BuildMacosCommand extends BuildSubCommand { ...@@ -31,6 +32,7 @@ class BuildMacosCommand extends BuildSubCommand {
addBuildPerformanceFile(hide: !verboseHelp); addBuildPerformanceFile(hide: !verboseHelp);
addBundleSkSLPathOption(hide: !verboseHelp); addBundleSkSLPathOption(hide: !verboseHelp);
addNullSafetyModeOptions(hide: !verboseHelp); addNullSafetyModeOptions(hide: !verboseHelp);
usesAnalyzeSizeFlag();
} }
@override @override
...@@ -62,6 +64,11 @@ class BuildMacosCommand extends BuildSubCommand { ...@@ -62,6 +64,11 @@ class BuildMacosCommand extends BuildSubCommand {
buildInfo: buildInfo, buildInfo: buildInfo,
targetOverride: targetFile, targetOverride: targetFile,
verboseLogging: globals.logger.isVerbose, verboseLogging: globals.logger.isVerbose,
sizeAnalyzer: SizeAnalyzer(
fileSystem: globals.fs,
logger: globals.logger,
appFilenamePattern: 'App',
),
); );
return FlutterCommandResult.success(); return FlutterCommandResult.success();
} }
......
...@@ -6,6 +6,7 @@ import 'dart:async'; ...@@ -6,6 +6,7 @@ import 'dart:async';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import '../base/analyze_size.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../cache.dart'; import '../cache.dart';
...@@ -33,6 +34,7 @@ class BuildWindowsCommand extends BuildSubCommand { ...@@ -33,6 +34,7 @@ class BuildWindowsCommand extends BuildSubCommand {
addBuildPerformanceFile(hide: !verboseHelp); addBuildPerformanceFile(hide: !verboseHelp);
addBundleSkSLPathOption(hide: !verboseHelp); addBundleSkSLPathOption(hide: !verboseHelp);
addNullSafetyModeOptions(hide: !verboseHelp); addNullSafetyModeOptions(hide: !verboseHelp);
usesAnalyzeSizeFlag();
} }
@override @override
...@@ -67,6 +69,11 @@ class BuildWindowsCommand extends BuildSubCommand { ...@@ -67,6 +69,11 @@ class BuildWindowsCommand extends BuildSubCommand {
buildInfo, buildInfo,
target: targetFile, target: targetFile,
visualStudioOverride: visualStudioOverride, visualStudioOverride: visualStudioOverride,
sizeAnalyzer: SizeAnalyzer(
fileSystem: globals.fs,
logger: globals.logger,
appFilenamePattern: 'app.so',
),
); );
return FlutterCommandResult.success(); return FlutterCommandResult.success();
} }
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import '../artifacts.dart'; import '../artifacts.dart';
import '../base/analyze_size.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/logger.dart'; import '../base/logger.dart';
...@@ -11,6 +12,7 @@ import '../base/utils.dart'; ...@@ -11,6 +12,7 @@ import '../base/utils.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../cache.dart'; import '../cache.dart';
import '../cmake.dart'; import '../cmake.dart';
import '../convert.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../plugins.dart'; import '../plugins.dart';
import '../project.dart'; import '../project.dart';
...@@ -20,6 +22,7 @@ Future<void> buildLinux( ...@@ -20,6 +22,7 @@ Future<void> buildLinux(
LinuxProject linuxProject, LinuxProject linuxProject,
BuildInfo buildInfo, { BuildInfo buildInfo, {
String target = 'lib/main.dart', String target = 'lib/main.dart',
SizeAnalyzer sizeAnalyzer,
}) async { }) async {
if (!linuxProject.cmakeFile.existsSync()) { if (!linuxProject.cmakeFile.existsSync()) {
throwToolExit('No Linux desktop project configured. See ' throwToolExit('No Linux desktop project configured. See '
...@@ -53,6 +56,29 @@ Future<void> buildLinux( ...@@ -53,6 +56,29 @@ Future<void> buildLinux(
} finally { } finally {
status.cancel(); status.cancel();
} }
if (buildInfo.codeSizeDirectory != null && sizeAnalyzer != null) {
final String arch = getNameForTargetPlatform(TargetPlatform.linux_x64);
final File codeSizeFile = globals.fs.directory(buildInfo.codeSizeDirectory)
.childFile('snapshot.$arch.json');
final File precompilerTrace = globals.fs.directory(buildInfo.codeSizeDirectory)
.childFile('trace.$arch.json');
final Map<String, Object> output = await sizeAnalyzer.analyzeAotSnapshot(
aotSnapshot: codeSizeFile,
// This analysis is only supported for release builds.
outputDirectory: globals.fs.directory(
globals.fs.path.join(getLinuxBuildDirectory(), 'release', 'bundle'),
),
precompilerTrace: precompilerTrace,
type: 'linux',
);
final File outputFile = globals.fsUtils.getUniqueFile(
globals.fs.directory(getBuildDirectory()),'linux-code-size-analysis', 'json',
)..writeAsStringSync(jsonEncode(output));
// This message is used as a sentinel in analyze_apk_size_test.dart
globals.printStatus(
'A summary of your Linux bundle analysis can be found at: ${outputFile.path}',
);
}
} }
Future<void> _runCmake(String buildModeName, Directory sourceDir, Directory buildDir) async { Future<void> _runCmake(String buildModeName, Directory sourceDir, Directory buildDir) async {
......
...@@ -4,11 +4,13 @@ ...@@ -4,11 +4,13 @@
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import '../base/analyze_size.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../convert.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../ios/xcodeproj.dart'; import '../ios/xcodeproj.dart';
import '../project.dart'; import '../project.dart';
...@@ -25,6 +27,7 @@ Future<void> buildMacOS({ ...@@ -25,6 +27,7 @@ Future<void> buildMacOS({
BuildInfo buildInfo, BuildInfo buildInfo,
String targetOverride, String targetOverride,
@required bool verboseLogging, @required bool verboseLogging,
SizeAnalyzer sizeAnalyzer,
}) async { }) async {
if (!flutterProject.macos.xcodeWorkspace.existsSync()) { if (!flutterProject.macos.xcodeWorkspace.existsSync()) {
throwToolExit('No macOS desktop project configured. ' throwToolExit('No macOS desktop project configured. '
...@@ -106,5 +109,37 @@ Future<void> buildMacOS({ ...@@ -106,5 +109,37 @@ Future<void> buildMacOS({
if (result != 0) { if (result != 0) {
throwToolExit('Build process failed'); throwToolExit('Build process failed');
} }
if (buildInfo.codeSizeDirectory != null && sizeAnalyzer != null) {
final String arch = getNameForDarwinArch(DarwinArch.x86_64);
final File aotSnapshot = globals.fs.directory(buildInfo.codeSizeDirectory)
.childFile('snapshot.$arch.json');
final File precompilerTrace = globals.fs.directory(buildInfo.codeSizeDirectory)
.childFile('trace.$arch.json');
// This analysis is only supported for release builds.
// Attempt to guess the correct .app by picking the first one.
final Directory candidateDirectory = globals.fs.directory(
globals.fs.path.join(getMacOSBuildDirectory(), 'Build', 'Products', 'Release'),
);
final Directory appDirectory = candidateDirectory.listSync()
.whereType<Directory>()
.firstWhere((Directory directory) {
return globals.fs.path.extension(directory.path) == '.app';
});
final Map<String, Object> output = await sizeAnalyzer.analyzeAotSnapshot(
aotSnapshot: aotSnapshot,
precompilerTrace: precompilerTrace,
outputDirectory: appDirectory,
type: 'macos',
excludePath: 'Versions', // Avoid double counting caused by symlinks
);
final File outputFile = globals.fsUtils.getUniqueFile(
globals.fs.directory(getBuildDirectory()),'macos-code-size-analysis', 'json',
)..writeAsStringSync(jsonEncode(output));
// This message is used as a sentinel in analyze_apk_size_test.dart
globals.printStatus(
'A summary of your macOS bundle analysis can be found at: ${outputFile.path}',
);
}
globals.flutterUsage.sendTiming('build', 'xcode-macos', Duration(milliseconds: sw.elapsedMilliseconds)); globals.flutterUsage.sendTiming('build', 'xcode-macos', Duration(milliseconds: sw.elapsedMilliseconds));
} }
...@@ -609,7 +609,9 @@ abstract class FlutterCommand extends Command<void> { ...@@ -609,7 +609,9 @@ abstract class FlutterCommand extends Command<void> {
FlutterOptions.kAnalyzeSize, FlutterOptions.kAnalyzeSize,
defaultsTo: false, defaultsTo: false,
help: 'Whether to produce additional profile information for artifact output size. ' help: 'Whether to produce additional profile information for artifact output size. '
'This flag is only supported on release builds on macOS/Linux hosts.' 'This flag is only supported on release builds. When building for Android, a single '
'ABI must be specified at a time with the --target-platform flag. When building for iOS, '
'only the symbols from the arm64 architecture are used to analyze code size.'
); );
} }
...@@ -648,13 +650,14 @@ abstract class FlutterCommand extends Command<void> { ...@@ -648,13 +650,14 @@ abstract class FlutterCommand extends Command<void> {
} }
} }
String analyzeSize; String codeSizeDirectory;
if (argParser.options.containsKey(FlutterOptions.kAnalyzeSize) if (argParser.options.containsKey(FlutterOptions.kAnalyzeSize) && boolArg(FlutterOptions.kAnalyzeSize)) {
&& boolArg(FlutterOptions.kAnalyzeSize) final Directory directory = globals.fsUtils.getUniqueDirectory(
&& !globals.platform.isWindows) { globals.fs.directory(getBuildDirectory()),
final File file = globals.fsUtils.getUniqueFile(globals.fs.currentDirectory, 'flutter_size', 'json'); 'flutter_size',
extraGenSnapshotOptions.add('--write-v8-snapshot-profile-to=${file.path}'); );
analyzeSize = file.path; directory.createSync(recursive: true);
codeSizeDirectory = directory.path;
} }
NullSafetyMode nullSafetyMode = NullSafetyMode.unsound; NullSafetyMode nullSafetyMode = NullSafetyMode.unsound;
...@@ -688,7 +691,7 @@ abstract class FlutterCommand extends Command<void> { ...@@ -688,7 +691,7 @@ abstract class FlutterCommand extends Command<void> {
); );
} }
final BuildMode buildMode = forcedBuildMode ?? getBuildMode(); final BuildMode buildMode = forcedBuildMode ?? getBuildMode();
if (buildMode != BuildMode.release && analyzeSize != null) { if (buildMode != BuildMode.release && codeSizeDirectory != null) {
throwToolExit('--analyze-size can only be used on release builds.'); throwToolExit('--analyze-size can only be used on release builds.');
} }
...@@ -736,7 +739,7 @@ abstract class FlutterCommand extends Command<void> { ...@@ -736,7 +739,7 @@ abstract class FlutterCommand extends Command<void> {
performanceMeasurementFile: performanceMeasurementFile, performanceMeasurementFile: performanceMeasurementFile,
packagesPath: globalResults['packages'] as String ?? '.packages', packagesPath: globalResults['packages'] as String ?? '.packages',
nullSafetyMode: nullSafetyMode, nullSafetyMode: nullSafetyMode,
analyzeSize: analyzeSize, codeSizeDirectory: codeSizeDirectory,
); );
} }
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import '../artifacts.dart'; import '../artifacts.dart';
import '../base/analyze_size.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/logger.dart'; import '../base/logger.dart';
...@@ -11,6 +12,7 @@ import '../base/utils.dart'; ...@@ -11,6 +12,7 @@ import '../base/utils.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../cache.dart'; import '../cache.dart';
import '../cmake.dart'; import '../cmake.dart';
import '../convert.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../plugins.dart'; import '../plugins.dart';
import '../project.dart'; import '../project.dart';
...@@ -25,6 +27,7 @@ const String _cmakeVisualStudioGeneratorIdentifier = 'Visual Studio 16 2019'; ...@@ -25,6 +27,7 @@ const String _cmakeVisualStudioGeneratorIdentifier = 'Visual Studio 16 2019';
Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, { Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, {
String target, String target,
VisualStudio visualStudioOverride, VisualStudio visualStudioOverride,
SizeAnalyzer sizeAnalyzer,
}) async { }) async {
if (!windowsProject.cmakeFile.existsSync()) { if (!windowsProject.cmakeFile.existsSync()) {
throwToolExit( throwToolExit(
...@@ -75,6 +78,29 @@ Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, { ...@@ -75,6 +78,29 @@ Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, {
} finally { } finally {
status.cancel(); status.cancel();
} }
if (buildInfo.codeSizeDirectory != null && sizeAnalyzer != null) {
final String arch = getNameForTargetPlatform(TargetPlatform.windows_x64);
final File codeSizeFile = globals.fs.directory(buildInfo.codeSizeDirectory)
.childFile('snapshot.$arch.json');
final File precompilerTrace = globals.fs.directory(buildInfo.codeSizeDirectory)
.childFile('trace.$arch.json');
final Map<String, Object> output = await sizeAnalyzer.analyzeAotSnapshot(
aotSnapshot: codeSizeFile,
// This analysis is only supported for release builds.
outputDirectory: globals.fs.directory(
globals.fs.path.join(getWindowsBuildDirectory(), 'runner', 'Release'),
),
precompilerTrace: precompilerTrace,
type: 'windows',
);
final File outputFile = globals.fsUtils.getUniqueFile(
globals.fs.directory(getBuildDirectory()),'windows-code-size-analysis', 'json',
)..writeAsStringSync(jsonEncode(output));
// This message is used as a sentinel in analyze_apk_size_test.dart
globals.printStatus(
'A summary of your Windows bundle analysis can be found at: ${outputFile.path}',
);
}
} }
Future<void> _runCmakeGeneration(String cmakePath, Directory buildDir, Directory sourceDir) async { Future<void> _runCmakeGeneration(String cmakePath, Directory buildDir, Directory sourceDir) async {
......
...@@ -15,10 +15,8 @@ import 'package:mockito/mockito.dart'; ...@@ -15,10 +15,8 @@ import 'package:mockito/mockito.dart';
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/context.dart'; import '../../src/context.dart';
class MockPlatform extends Mock implements Platform {}
void main() { void main() {
group('ensureDirectoryExists', () { group('fsUtils', () {
MemoryFileSystem fs; MemoryFileSystem fs;
FileSystemUtils fsUtils; FileSystemUtils fsUtils;
...@@ -26,19 +24,37 @@ void main() { ...@@ -26,19 +24,37 @@ void main() {
fs = MemoryFileSystem(); fs = MemoryFileSystem();
fsUtils = FileSystemUtils( fsUtils = FileSystemUtils(
fileSystem: fs, fileSystem: fs,
platform: MockPlatform(), platform: FakePlatform(),
); );
}); });
testWithoutContext('recursively creates a directory if it does not exist', () async { testWithoutContext('ensureDirectoryExists recursively creates a directory if it does not exist', () async {
fsUtils.ensureDirectoryExists('foo/bar/baz.flx'); fsUtils.ensureDirectoryExists('foo/bar/baz.flx');
expect(fs.isDirectorySync('foo/bar'), true); expect(fs.isDirectorySync('foo/bar'), true);
}); });
testWithoutContext('throws tool exit on failure to create', () async { testWithoutContext('ensureDirectoryExists throws tool exit on failure to create', () async {
fs.file('foo').createSync(); fs.file('foo').createSync();
expect(() => fsUtils.ensureDirectoryExists('foo/bar.flx'), throwsToolExit()); expect(() => fsUtils.ensureDirectoryExists('foo/bar.flx'), throwsToolExit());
}); });
testWithoutContext('getUniqueFile creates a unique file name', () async {
final File fileA = fsUtils.getUniqueFile(fs.currentDirectory, 'foo', 'json')
..createSync();
final File fileB = fsUtils.getUniqueFile(fs.currentDirectory, 'foo', 'json');
expect(fileA.path, '/foo_01.json');
expect(fileB.path, '/foo_02.json');
});
testWithoutContext('getUniqueDirectory creates a unique directory name', () async {
final Directory directoryA = fsUtils.getUniqueDirectory(fs.currentDirectory, 'foo')
..createSync();
final Directory directoryB = fsUtils.getUniqueDirectory(fs.currentDirectory, 'foo');
expect(directoryA.path, '/foo_01');
expect(directoryB.path, '/foo_02');
});
}); });
group('copyDirectorySync', () { group('copyDirectorySync', () {
...@@ -61,7 +77,7 @@ void main() { ...@@ -61,7 +77,7 @@ void main() {
final FileSystemUtils fsUtils = FileSystemUtils( final FileSystemUtils fsUtils = FileSystemUtils(
fileSystem: sourceMemoryFs, fileSystem: sourceMemoryFs,
platform: MockPlatform(), platform: FakePlatform(),
); );
fsUtils.copyDirectorySync(sourceDirectory, targetDirectory); fsUtils.copyDirectorySync(sourceDirectory, targetDirectory);
...@@ -81,7 +97,7 @@ void main() { ...@@ -81,7 +97,7 @@ void main() {
final MemoryFileSystem fileSystem = MemoryFileSystem(); final MemoryFileSystem fileSystem = MemoryFileSystem();
final FileSystemUtils fsUtils = FileSystemUtils( final FileSystemUtils fsUtils = FileSystemUtils(
fileSystem: fileSystem, fileSystem: fileSystem,
platform: MockPlatform(), platform: FakePlatform(),
); );
final Directory origin = fileSystem.directory('/origin'); final Directory origin = fileSystem.directory('/origin');
origin.createSync(); origin.createSync();
......
...@@ -107,6 +107,7 @@ void main() { ...@@ -107,6 +107,7 @@ void main() {
extraGenSnapshotOptions: <String>['--enable-experiment=non-nullable', 'fizz'], extraGenSnapshotOptions: <String>['--enable-experiment=non-nullable', 'fizz'],
bundleSkSLPath: 'foo/bar/baz.sksl.json', bundleSkSLPath: 'foo/bar/baz.sksl.json',
packagesPath: 'foo/.packages', packagesPath: 'foo/.packages',
codeSizeDirectory: 'foo/code-size',
); );
expect(buildInfo.toEnvironmentConfig(), <String, String>{ expect(buildInfo.toEnvironmentConfig(), <String, String>{
...@@ -119,6 +120,7 @@ void main() { ...@@ -119,6 +120,7 @@ void main() {
'EXTRA_GEN_SNAPSHOT_OPTIONS': '--enable-experiment%3Dnon-nullable,fizz', 'EXTRA_GEN_SNAPSHOT_OPTIONS': '--enable-experiment%3Dnon-nullable,fizz',
'BUNDLE_SKSL_PATH': 'foo/bar/baz.sksl.json', 'BUNDLE_SKSL_PATH': 'foo/bar/baz.sksl.json',
'PACKAGE_CONFIG': 'foo/.packages', 'PACKAGE_CONFIG': 'foo/.packages',
'CODE_SIZE_DIRECTORY': 'foo/code-size',
}); });
}); });
......
...@@ -209,6 +209,50 @@ void main() { ...@@ -209,6 +209,50 @@ void main() {
ProcessManager: () => processManager, ProcessManager: () => processManager,
}); });
testUsingContext('AndroidAot provide code size information.', () async {
processManager = FakeProcessManager.list(<FakeCommand>[]);
final Environment environment = Environment.test(
fileSystem.currentDirectory,
outputDir: fileSystem.directory('out')..createSync(),
defines: <String, String>{
kBuildMode: 'release',
kCodeSizeDirectory: 'code_size_1',
},
artifacts: artifacts,
processManager: processManager,
fileSystem: fileSystem,
logger: logger,
);
processManager.addCommand(FakeCommand(command: <String>[
artifacts.getArtifactPath(
Artifact.genSnapshot,
platform: TargetPlatform.android_arm64,
mode: BuildMode.release,
),
'--deterministic',
'--write-v8-snapshot-profile-to=code_size_1/snapshot.arm64-v8a.json',
'--trace-precompiler-to=code_size_1/trace.arm64-v8a.json',
'--snapshot_kind=app-aot-elf',
'--elf=${environment.buildDir.childDirectory('arm64-v8a').childFile('app.so').path}',
'--strip',
'--no-causal-async-stacks',
'--lazy-async-stacks',
environment.buildDir.childFile('app.dill').path,
],
));
environment.buildDir.createSync(recursive: true);
environment.buildDir.childFile('app.dill').createSync();
environment.projectDir.childFile('.packages').writeAsStringSync('\n');
const AndroidAot androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release);
await androidAot.build(environment);
expect(processManager.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('kExtraGenSnapshotOptions passes values to gen_snapshot', () async { testUsingContext('kExtraGenSnapshotOptions passes values to gen_snapshot', () async {
processManager = FakeProcessManager.list(<FakeCommand>[]); processManager = FakeProcessManager.list(<FakeCommand>[]);
final Environment environment = Environment.test( final Environment environment = Environment.test(
......
...@@ -12,7 +12,6 @@ import 'package:flutter_tools/src/build_system/build_system.dart'; ...@@ -12,7 +12,6 @@ import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/build_system/exceptions.dart'; import 'package:flutter_tools/src/build_system/exceptions.dart';
import 'package:flutter_tools/src/build_system/targets/common.dart'; import 'package:flutter_tools/src/build_system/targets/common.dart';
import 'package:flutter_tools/src/build_system/targets/ios.dart'; import 'package:flutter_tools/src/build_system/targets/ios.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/compile.dart'; import 'package:flutter_tools/src/compile.dart';
import '../../../src/common.dart'; import '../../../src/common.dart';
...@@ -32,10 +31,6 @@ void main() { ...@@ -32,10 +31,6 @@ void main() {
FileSystem fileSystem; FileSystem fileSystem;
Logger logger; Logger logger;
setUpAll(() {
Cache.disableLocking();
});
setUp(() { setUp(() {
processManager = FakeProcessManager.list(<FakeCommand>[]); processManager = FakeProcessManager.list(<FakeCommand>[]);
logger = BufferLogger.test(); logger = BufferLogger.test();
...@@ -47,6 +42,7 @@ void main() { ...@@ -47,6 +42,7 @@ void main() {
kBuildMode: getNameForBuildMode(BuildMode.profile), kBuildMode: getNameForBuildMode(BuildMode.profile),
kTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm), kTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm),
}, },
inputs: <String, String>{},
artifacts: artifacts, artifacts: artifacts,
processManager: processManager, processManager: processManager,
fileSystem: fileSystem, fileSystem: fileSystem,
...@@ -59,6 +55,7 @@ void main() { ...@@ -59,6 +55,7 @@ void main() {
kBuildMode: getNameForBuildMode(BuildMode.profile), kBuildMode: getNameForBuildMode(BuildMode.profile),
kTargetPlatform: getNameForTargetPlatform(TargetPlatform.ios), kTargetPlatform: getNameForTargetPlatform(TargetPlatform.ios),
}, },
inputs: <String, String>{},
artifacts: artifacts, artifacts: artifacts,
processManager: processManager, processManager: processManager,
fileSystem: fileSystem, fileSystem: fileSystem,
...@@ -357,6 +354,36 @@ void main() { ...@@ -357,6 +354,36 @@ void main() {
expect(processManager.hasRemainingExpectations, false); expect(processManager.hasRemainingExpectations, false);
}); });
testUsingContext('AotElfRelease configures gen_snapshot with code size directory', () async {
androidEnvironment.defines[kCodeSizeDirectory] = 'code_size_1';
final String build = androidEnvironment.buildDir.path;
processManager.addCommands(<FakeCommand>[
FakeCommand(command: <String>[
artifacts.getArtifactPath(
Artifact.genSnapshot,
platform: TargetPlatform.android_arm,
mode: BuildMode.profile,
),
'--deterministic',
'--write-v8-snapshot-profile-to=code_size_1/snapshot.android-arm.json',
'--trace-precompiler-to=code_size_1/trace.android-arm.json',
kElfAot,
'--elf=$build/app.so',
'--strip',
'--no-sim-use-hardfp',
'--no-use-integer-division',
'--no-causal-async-stacks',
'--lazy-async-stacks',
'$build/app.dill',
])
]);
androidEnvironment.buildDir.childFile('app.dill').createSync(recursive: true);
await const AotElfRelease(TargetPlatform.android_arm).build(androidEnvironment);
expect(processManager.hasRemainingExpectations, false);
});
testUsingContext('AotElfProfile throws error if missing build mode', () async { testUsingContext('AotElfProfile throws error if missing build mode', () async {
androidEnvironment.defines.remove(kBuildMode); androidEnvironment.defines.remove(kBuildMode);
...@@ -611,6 +638,88 @@ void main() { ...@@ -611,6 +638,88 @@ void main() {
ProcessManager: () => processManager, ProcessManager: () => processManager,
}); });
testUsingContext('AotAssemblyRelease configures gen_snapshot with code size directory', () async {
iosEnvironment.defines[kCodeSizeDirectory] = 'code_size_1';
iosEnvironment.defines[kIosArchs] = 'arm64';
iosEnvironment.defines[kBitcodeFlag] = 'true';
final String build = iosEnvironment.buildDir.path;
processManager.addCommands(<FakeCommand>[
FakeCommand(command: <String>[
// This path is not known by the cache due to the iOS gen_snapshot split.
'Artifact.genSnapshot.TargetPlatform.ios.profile_arm64',
'--deterministic',
'--write-v8-snapshot-profile-to=code_size_1/snapshot.arm64.json',
'--trace-precompiler-to=code_size_1/trace.arm64.json',
kAssemblyAot,
'--assembly=$build/arm64/snapshot_assembly.S',
'--strip',
'--no-causal-async-stacks',
'--lazy-async-stacks',
'$build/app.dill',
]),
const FakeCommand(command: <String>[
'xcrun',
'--sdk',
'iphoneos',
'--show-sdk-path',
]),
FakeCommand(command: <String>[
'xcrun',
'cc',
'-arch',
'arm64',
'-isysroot',
'',
// Contains bitcode flag.
'-fembed-bitcode',
'-c',
'$build/arm64/snapshot_assembly.S',
'-o',
'$build/arm64/snapshot_assembly.o',
]),
FakeCommand(command: <String>[
'xcrun',
'clang',
'-arch',
'arm64',
'-miphoneos-version-min=9.0',
'-dynamiclib',
'-Xlinker',
'-rpath',
'-Xlinker',
'@executable_path/Frameworks',
'-Xlinker',
'-rpath',
'-Xlinker',
'@loader_path/Frameworks',
'-install_name',
'@rpath/App.framework/App',
// Contains bitcode flag.
'-fembed-bitcode',
'-isysroot',
'',
'-o',
'$build/arm64/App.framework/App',
'$build/arm64/snapshot_assembly.o',
]),
FakeCommand(command: <String>[
'lipo',
'$build/arm64/App.framework/App',
'-create',
'-output',
'$build/App.framework/App',
]),
]);
await const AotAssemblyProfile().build(iosEnvironment);
expect(processManager.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
Platform: () => macPlatform,
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('kExtraGenSnapshotOptions passes values to gen_snapshot', () async { testUsingContext('kExtraGenSnapshotOptions passes values to gen_snapshot', () async {
androidEnvironment.defines[kExtraGenSnapshotOptions] = 'foo,bar,baz=2'; androidEnvironment.defines[kExtraGenSnapshotOptions] = 'foo,bar,baz=2';
androidEnvironment.defines[kBuildMode] = getNameForBuildMode(BuildMode.profile); androidEnvironment.defines[kBuildMode] = getNameForBuildMode(BuildMode.profile);
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// 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:file_testing/file_testing.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
...@@ -9,16 +10,19 @@ import 'package:flutter_tools/src/globals.dart' as globals; ...@@ -9,16 +10,19 @@ import 'package:flutter_tools/src/globals.dart' as globals;
import '../src/common.dart'; import '../src/common.dart';
const String debugMessage = 'A summary of your APK analysis can be found at: '; const String apkDebugMessage = 'A summary of your APK analysis can be found at: ';
const String iosDebugMessage = 'A summary of your iOS bundle analysis can be found at: ';
void main() { void main() {
test('--analyze-size flag produces expected output on hello_world', () async { test('--analyze-size flag produces expected output on hello_world for Android', () async {
final String woringDirectory = globals.fs.path.join(getFlutterRoot(), 'examples', 'hello_world');
final String flutterBin = globals.fs.path.join(getFlutterRoot(), 'bin', 'flutter'); final String flutterBin = globals.fs.path.join(getFlutterRoot(), 'bin', 'flutter');
final ProcessResult result = await const LocalProcessManager().run(<String>[ final ProcessResult result = await const LocalProcessManager().run(<String>[
flutterBin, flutterBin,
'build', 'build',
'apk', 'apk',
'--analyze-size', '--analyze-size',
'--target-platform=android-arm64'
], workingDirectory: globals.fs.path.join(getFlutterRoot(), 'examples', 'hello_world')); ], workingDirectory: globals.fs.path.join(getFlutterRoot(), 'examples', 'hello_world'));
print(result.stdout); print(result.stdout);
...@@ -27,9 +31,52 @@ void main() { ...@@ -27,9 +31,52 @@ void main() {
final String line = result.stdout.toString() final String line = result.stdout.toString()
.split('\n') .split('\n')
.firstWhere((String line) => line.contains(debugMessage)); .firstWhere((String line) => line.contains(apkDebugMessage));
expect(globals.fs.file(globals.fs.path.join(line.split(debugMessage).last.trim())).existsSync(), true); final String outputFilePath = line.split(apkDebugMessage).last.trim();
expect(globals.fs.file(globals.fs.path.join(woringDirectory, outputFilePath)), exists);
expect(result.exitCode, 0); expect(result.exitCode, 0);
}, skip: const LocalPlatform().isWindows); // Not yet supported on Windows });
test('--analyze-size flag produces expected output on hello_world for iOS', () async {
final String woringDirectory = globals.fs.path.join(getFlutterRoot(), 'examples', 'hello_world');
final String flutterBin = globals.fs.path.join(getFlutterRoot(), 'bin', 'flutter');
final ProcessResult result = await const LocalProcessManager().run(<String>[
flutterBin,
'build',
'ios',
'--analyze-size',
'--no-codesign',
], workingDirectory: woringDirectory);
print(result.stdout);
print(result.stderr);
expect(result.stdout.toString(), contains('Dart AOT symbols accounted decompressed size'));
final String line = result.stdout.toString()
.split('\n')
.firstWhere((String line) => line.contains(iosDebugMessage));
final String outputFilePath = line.split(iosDebugMessage).last.trim();
expect(globals.fs.file(globals.fs.path.join(woringDirectory, outputFilePath)), exists);
expect(result.exitCode, 0);
}, skip: !const LocalPlatform().isMacOS); // Only supported on macOS
test('--analyze-size is only supported in release mode', () async {
final String flutterBin = globals.fs.path.join(getFlutterRoot(), 'bin', 'flutter');
final ProcessResult result = await const LocalProcessManager().run(<String>[
flutterBin,
'build',
'apk',
'--analyze-size',
'--target-platform=android-arm64',
'--debug',
], workingDirectory: globals.fs.path.join(getFlutterRoot(), 'examples', 'hello_world'));
print(result.stdout);
print(result.stderr);
expect(result.stderr.toString(), contains('--analyze-size can only be used on release builds'));
expect(result.exitCode, 1);
});
} }
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