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
bundle_sksl_path="-iBundleSkSLPath=${BUNDLE_SKSL_PATH}"
fi
code_size_directory=""
if [[ -n "$CODE_SIZE_DIRECTORY" ]]; then
code_size_directory="-dCodeSizeDirectory=${CODE_SIZE_DIRECTORY}"
fi
RunCommand "${FLUTTER_ROOT}/bin/flutter" \
${verbose_flag} \
${flutter_engine_flag} \
......@@ -86,6 +91,7 @@ RunCommand "${FLUTTER_ROOT}/bin/flutter" \
-dSplitDebugInfo="${SPLIT_DEBUG_INFO}" \
-dTrackWidgetCreation="${TRACK_WIDGET_CREATION}" \
${bundle_sksl_path} \
${code_size_directory} \
--DartDefines="${DART_DEFINES}" \
--ExtraGenSnapshotOptions="${EXTRA_GEN_SNAPSHOT_OPTIONS}" \
--ExtraFrontEndOptions="${EXTRA_FRONT_END_OPTIONS}" \
......
......@@ -19,6 +19,7 @@ Future<void> main(List<String> arguments) async {
final String flutterRoot = Platform.environment['FLUTTER_ROOT'];
final String flutterTarget = Platform.environment['FLUTTER_TARGET']
?? pathJoin(<String>['lib', 'main.dart']);
final String codeSizeDirectory = Platform.environment['CODE_SIZE_DIRECTORY'];
final String localEngine = Platform.environment['LOCAL_ENGINE'];
final String projectDirectory = Platform.environment['PROJECT_DIR'];
final String splitDebugInfo = Platform.environment['SPLIT_DEBUG_INFO'];
......@@ -70,6 +71,8 @@ or
'-dDartObfuscation=$dartObfuscation',
if (bundleSkSLPath != null)
'-iBundleSkSLPath=$bundleSkSLPath',
if (codeSizeDirectory != null)
'-dCodeSizeDirectory=$codeSizeDirectory',
if (splitDebugInfo != null)
'-dSplitDebugInfo=$splitDebugInfo',
if (dartDefines != null)
......
......@@ -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}"
fi
local code_size_directory=""
if [[ -n "$CODE_SIZE_DIRECTORY" ]]; then
code_size_directory="-dCodeSizeDirectory=${CODE_SIZE_DIRECTORY}"
fi
RunCommand "${FLUTTER_ROOT}/bin/flutter" \
${verbose_flag} \
${flutter_engine_flag} \
......@@ -172,6 +177,7 @@ is set to release or run \"flutter build ios --release\", then re-run Archive fr
-dDartObfuscation="${DART_OBFUSCATION}" \
-dEnableBitcode="${bitcode_flag}" \
${bundle_sksl_path} \
${code_size_directory} \
--ExtraGenSnapshotOptions="${EXTRA_GEN_SNAPSHOT_OPTIONS}" \
--DartDefines="${DART_DEFINES}" \
--ExtraFrontEndOptions="${EXTRA_FRONT_END_OPTIONS}" \
......
......@@ -628,6 +628,10 @@ class FlutterPlugin implements Plugin<Project> {
if (project.hasProperty('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 addFlutterDeps = { variant ->
if (shouldSplitPerAbi()) {
......@@ -668,6 +672,7 @@ class FlutterPlugin implements Plugin<Project> {
dartDefines dartDefinesValue
bundleSkSLPath bundleSkSLPathValue
performanceMeasurementFile performanceMeasurementFileValue
codeSizeDirectory codeSizeDirectoryValue
doLast {
project.exec {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
......@@ -862,6 +867,8 @@ abstract class BaseFlutterTask extends DefaultTask {
String dartDefines
@Optional @Input
String bundleSkSLPath
@Optional @Input
String codeSizeDirectory;
String performanceMeasurementFile;
@OutputFiles
......@@ -938,6 +945,9 @@ abstract class BaseFlutterTask extends DefaultTask {
if (bundleSkSLPath != null) {
args "-iBundleSkSLPath=${bundleSkSLPath}"
}
if (codeSizeDirectory != null) {
args "-dCodeSizeDirectory=${codeSizeDirectory}"
}
if (extraGenSnapshotOptions != null) {
args "--ExtraGenSnapshotOptions=${extraGenSnapshotOptions}"
}
......
......@@ -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.
void validateBuild(AndroidBuildInfo androidBuildInfo) {
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)) {
throwToolExit(
'Cannot build ${androidBuildInfo.buildInfo.mode.name} mode for x86 ABI.\n'
......
......@@ -357,6 +357,9 @@ Future<void> buildGradleApp({
if (androidBuildInfo.buildInfo.performanceMeasurementFile != null) {
command.add('-Pperformance-measurement-file=${androidBuildInfo.buildInfo.performanceMeasurementFile}');
}
if (buildInfo.codeSizeDirectory != null) {
command.add('-Pcode-size-directory=${buildInfo.codeSizeDirectory}');
}
command.add(assembleTask);
GradleHandledError detectedGradleError;
......@@ -467,6 +470,10 @@ Future<void> buildGradleApp({
? '' // Don't display the size when building a debug variant.
: ' (${getSizeAsMB(bundleFile.lengthSync())})';
if (buildInfo.codeSizeDirectory != null) {
await _performCodeSizeAnalysis('aab', bundleFile, androidBuildInfo);
}
globals.printStatus(
'$successMark Built ${globals.fs.path.relative(bundleFile.path)}$appSize.',
color: TerminalColor.green,
......@@ -502,24 +509,39 @@ Future<void> buildGradleApp({
color: TerminalColor.green,
);
// Call size analyzer if --analyze-size flag was provided.
if (buildInfo.analyzeSize != null && !globals.platform.isWindows) {
if (buildInfo.codeSizeDirectory != null) {
await _performCodeSizeAnalysis('apk', apkFile, androidBuildInfo);
}
}
Future<void> _performCodeSizeAnalysis(
String kind,
File zipFile,
AndroidBuildInfo androidBuildInfo,
) async {
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 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.currentDirectory, 'apk-analysis', 'json')
..writeAsStringSync(jsonEncode(output));
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 APK analysis can be found at: ${outputFile.path}',
'A summary of your ${kind.toUpperCase()} analysis can be found at: ${outputFile.path}',
);
}
}
/// Builds AAR and POM files.
......
......@@ -105,7 +105,23 @@ class FileSystemUtils {
if (!file.existsSync()) {
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 {
this.performanceMeasurementFile,
this.packagesPath = '.packages',
this.nullSafetyMode = NullSafetyMode.autodetect,
this.analyzeSize,
this.codeSizeDirectory,
});
final BuildMode mode;
......@@ -114,9 +114,9 @@ class BuildInfo {
/// rerun tasks.
final String performanceMeasurementFile;
/// If provided, an output file where a v8-style heapsnapshot will be written for size
/// profiling.
final String analyzeSize;
/// If provided, an output directory where one or more v8-style heapsnapshots
/// will be written for code size profiling.
final String codeSizeDirectory;
static const BuildInfo debug = BuildInfo(BuildMode.debug, null, treeShakeIcons: false);
static const BuildInfo profile = BuildInfo(BuildMode.profile, null, treeShakeIcons: kIconTreeShakerEnabledDefault);
......@@ -178,6 +178,8 @@ class BuildInfo {
'BUNDLE_SKSL_PATH': bundleSkSLPath,
if (packagesPath != null)
'PACKAGE_CONFIG': packagesPath,
if (codeSizeDirectory != null)
'CODE_SIZE_DIRECTORY': codeSizeDirectory,
};
}
}
......@@ -698,7 +700,7 @@ String encodeDartDefines(List<String> defines) {
/// Dart defines are encoded inside [environmentDefines] as a comma-separated list.
List<String> decodeDartDefines(Map<String, String> environmentDefines, String key) {
if (!environmentDefines.containsKey(key) || environmentDefines[key].isEmpty) {
return const <String>[];
return <String>[];
}
return environmentDefines[key]
.split(',')
......
......@@ -232,6 +232,19 @@ class AndroidAot extends AotElfBase {
final List<String> extraGenSnapshotOptions = decodeDartDefines(environment.defines, kExtraGenSnapshotOptions);
final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
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(
platform: targetPlatform,
buildMode: buildMode,
......
......@@ -68,6 +68,9 @@ const String kIosArchs = 'IosArchs';
/// Whether to enable Dart obfuscation and where to save the symbol map.
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.
// This is a one-off rule for implementing build bundle in terms of assemble.
class CopyFlutterBundle extends Target {
......@@ -295,6 +298,19 @@ abstract class AotElfBase extends Target {
final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]);
final String splitDebugInfo = environment.defines[kSplitDebugInfo];
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(
platform: targetPlatform,
buildMode: buildMode,
......
......@@ -52,7 +52,7 @@ abstract class AotAssemblyBase extends Target {
final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]);
final String splitDebugInfo = environment.defines[kSplitDebugInfo];
final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true';
final List<DarwinArch> iosArchs = environment.defines[kIosArchs]
final List<DarwinArch> darwinArchs = environment.defines[kIosArchs]
?.split(' ')
?.map(getIOSArchForName)
?.toList()
......@@ -60,29 +60,41 @@ abstract class AotAssemblyBase extends Target {
if (targetPlatform != TargetPlatform.ios) {
throw Exception('aot_assembly is only supported for iOS applications.');
}
if (iosArchs.contains(DarwinArch.x86_64)) {
if (darwinArchs.contains(DarwinArch.x86_64)) {
throw Exception(
'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
// together.
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(
platform: targetPlatform,
buildMode: buildMode,
mainPath: environment.buildDir.childFile('app.dill').path,
packagesPath: environment.projectDir.childFile('.packages').path,
outputPath: environment.fileSystem.path.join(buildOutputPath, getNameForDarwinArch(iosArch)),
darwinArch: iosArch,
outputPath: environment.fileSystem.path.join(buildOutputPath, getNameForDarwinArch(darwinArch)),
darwinArch: darwinArch,
bitcode: bitcode,
quiet: true,
splitDebugInfo: splitDebugInfo,
dartObfuscation: dartObfuscation,
extraGenSnapshotOptions: extraGenSnapshotOptions,
extraGenSnapshotOptions: archExtraGenSnapshotOptions,
));
}
final List<int> results = await Future.wait(pending);
......@@ -93,7 +105,7 @@ abstract class AotAssemblyBase extends Target {
environment.fileSystem.directory(resultPath).parent.createSync(recursive: true);
final ProcessResult result = await environment.processManager.run(<String>[
'lipo',
...iosArchs.map((DarwinArch iosArch) =>
...darwinArchs.map((DarwinArch iosArch) =>
environment.fileSystem.path.join(buildOutputPath, getNameForDarwinArch(iosArch), 'App.framework', 'App')),
'-create',
'-output',
......
......@@ -8,7 +8,7 @@ import '../../base/file_system.dart';
import '../../base/io.dart';
import '../../base/process.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 '../depfile.dart';
import '../exceptions.dart';
......@@ -52,7 +52,7 @@ abstract class UnpackMacOS extends Target {
throw MissingDefineException(kBuildMode, 'unpack_macos');
}
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
.outputDir
.childDirectory('FlutterMacOS.framework');
......@@ -61,15 +61,15 @@ abstract class UnpackMacOS extends Target {
if (targetDirectory.existsSync()) {
targetDirectory.deleteSync(recursive: true);
}
final List<File> inputs = globals.fs.directory(basePath)
final List<File> inputs = environment.fileSystem.directory(basePath)
.listSync(recursive: true)
.whereType<File>()
.toList();
final List<File> outputs = inputs.map((File file) {
final String relativePath = globals.fs.path.relative(file.path, from: basePath);
return globals.fs.file(globals.fs.path.join(targetDirectory.path, relativePath));
final String relativePath = environment.fileSystem.path.relative(file.path, from: basePath);
return environment.fileSystem.file(environment.fileSystem.path.join(targetDirectory.path, relativePath));
}).toList();
final ProcessResult result = await globals.processManager
final ProcessResult result = await environment.processManager
.run(<String>['cp', '-R', basePath, targetDirectory.path]);
if (result.exitCode != 0) {
throw Exception(
......@@ -78,8 +78,8 @@ abstract class UnpackMacOS extends Target {
);
}
final DepfileService depfileService = DepfileService(
logger: globals.logger,
fileSystem: globals.fs,
logger: environment.logger,
fileSystem: environment.fileSystem,
);
depfileService.writeToFile(
Depfile(inputs, outputs),
......@@ -143,7 +143,7 @@ class DebugMacOSFramework extends Target {
@override
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'));
outputFile.createSync(recursive: true);
final File debugApp = environment.buildDir.childFile('debug_app.cc')
......@@ -195,16 +195,30 @@ class CompileMacOSFramework extends Target {
if (buildMode == BuildMode.debug) {
throw Exception('precompiled macOS framework only supported in release/profile builds.');
}
final String codeSizeDirectory = environment.defines[kCodeSizeDirectory];
final String splitDebugInfo = environment.defines[kSplitDebugInfo];
final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true';
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(
reportTimings: false,
fileSystem: globals.fs,
logger: globals.logger,
fileSystem: environment.fileSystem,
logger: environment.logger,
xcode: globals.xcode,
artifacts: globals.artifacts,
processManager: globals.processManager
artifacts: environment.artifacts,
processManager: environment.processManager
);
final int result = await snapshotter.build(
bitcode: false,
......@@ -299,8 +313,8 @@ abstract class MacOSBundleFlutterAssets extends Target {
targetPlatform: TargetPlatform.darwin_x64,
);
final DepfileService depfileService = DepfileService(
fileSystem: globals.fs,
logger: globals.logger,
fileSystem: environment.fileSystem,
logger: environment.logger,
);
depfileService.writeToFile(
assetDepfile,
......@@ -345,13 +359,13 @@ abstract class MacOSBundleFlutterAssets extends Target {
}
// Copy precompiled runtimes.
try {
final String vmSnapshotData = globals.artifacts.getArtifactPath(Artifact.vmSnapshotData,
final String vmSnapshotData = environment.artifacts.getArtifactPath(Artifact.vmSnapshotData,
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);
globals.fs.file(vmSnapshotData).copySync(
environment.fileSystem.file(vmSnapshotData).copySync(
assetDirectory.childFile('vm_snapshot_data').path);
globals.fs.file(isolateSnapshotData).copySync(
environment.fileSystem.file(isolateSnapshotData).copySync(
assetDirectory.childFile('isolate_snapshot_data').path);
} on Exception catch (err) {
throw Exception('Failed to copy precompiled runtimes: $err');
......@@ -364,7 +378,7 @@ abstract class MacOSBundleFlutterAssets extends Target {
final Link currentVersion = outputDirectory.parent
.childLink('Current');
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);
currentVersion.createSync(linkPath);
}
......@@ -372,7 +386,7 @@ abstract class MacOSBundleFlutterAssets extends Target {
final Link currentResources = frameworkRootDirectory
.childLink('Resources');
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);
currentResources.createSync(linkPath);
}
......@@ -380,7 +394,7 @@ abstract class MacOSBundleFlutterAssets extends Target {
final Link currentFramework = frameworkRootDirectory
.childLink('App');
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);
currentFramework.createSync(linkPath);
}
......
......@@ -34,6 +34,7 @@ class BuildAppBundleCommand extends BuildSubCommand {
usesTrackWidgetCreation(verboseHelp: verboseHelp);
addNullSafetyModeOptions(hide: !verboseHelp);
addEnableExperimentation(hide: !verboseHelp);
usesAnalyzeSizeFlag();
argParser.addMultiOption('target-platform',
splitCommas: true,
defaultsTo: <String>['android-arm', 'android-arm64', 'android-x64'],
......
......@@ -4,12 +4,15 @@
import 'dart:async';
import 'package:file/file.dart';
import 'package:meta/meta.dart';
import '../application_package.dart';
import '../base/analyze_size.dart';
import '../base/common.dart';
import '../base/utils.dart';
import '../build_info.dart';
import '../convert.dart';
import '../globals.dart' as globals;
import '../ios/mac.dart';
import '../runner/flutter_command.dart' show DevelopmentArtifact, FlutterCommandResult;
......@@ -35,6 +38,7 @@ class BuildIOSCommand extends BuildSubCommand {
addBuildPerformanceFile(hide: !verboseHelp);
addBundleSkSLPathOption(hide: !verboseHelp);
addNullSafetyModeOptions(hide: !verboseHelp);
usesAnalyzeSizeFlag();
argParser
..addFlag('simulator',
help: 'Build for the iOS simulator instead of the device. This changes '
......@@ -103,6 +107,44 @@ class BuildIOSCommand extends BuildSubCommand {
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) {
globals.printStatus('Built ${result.output}.');
}
......
......@@ -4,6 +4,7 @@
import 'dart:async';
import '../base/analyze_size.dart';
import '../base/common.dart';
import '../build_info.dart';
import '../cache.dart';
......@@ -30,6 +31,7 @@ class BuildLinuxCommand extends BuildSubCommand {
addBuildPerformanceFile(hide: !verboseHelp);
addBundleSkSLPathOption(hide: !verboseHelp);
addNullSafetyModeOptions(hide: !verboseHelp);
usesAnalyzeSizeFlag();
}
@override
......@@ -56,7 +58,15 @@ class BuildLinuxCommand extends BuildSubCommand {
if (!globals.platform.isLinux) {
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();
}
}
......@@ -6,6 +6,7 @@ import 'dart:async';
import 'package:meta/meta.dart';
import '../base/analyze_size.dart';
import '../base/common.dart';
import '../build_info.dart';
import '../cache.dart';
......@@ -31,6 +32,7 @@ class BuildMacosCommand extends BuildSubCommand {
addBuildPerformanceFile(hide: !verboseHelp);
addBundleSkSLPathOption(hide: !verboseHelp);
addNullSafetyModeOptions(hide: !verboseHelp);
usesAnalyzeSizeFlag();
}
@override
......@@ -62,6 +64,11 @@ class BuildMacosCommand extends BuildSubCommand {
buildInfo: buildInfo,
targetOverride: targetFile,
verboseLogging: globals.logger.isVerbose,
sizeAnalyzer: SizeAnalyzer(
fileSystem: globals.fs,
logger: globals.logger,
appFilenamePattern: 'App',
),
);
return FlutterCommandResult.success();
}
......
......@@ -6,6 +6,7 @@ import 'dart:async';
import 'package:meta/meta.dart';
import '../base/analyze_size.dart';
import '../base/common.dart';
import '../build_info.dart';
import '../cache.dart';
......@@ -33,6 +34,7 @@ class BuildWindowsCommand extends BuildSubCommand {
addBuildPerformanceFile(hide: !verboseHelp);
addBundleSkSLPathOption(hide: !verboseHelp);
addNullSafetyModeOptions(hide: !verboseHelp);
usesAnalyzeSizeFlag();
}
@override
......@@ -67,6 +69,11 @@ class BuildWindowsCommand extends BuildSubCommand {
buildInfo,
target: targetFile,
visualStudioOverride: visualStudioOverride,
sizeAnalyzer: SizeAnalyzer(
fileSystem: globals.fs,
logger: globals.logger,
appFilenamePattern: 'app.so',
),
);
return FlutterCommandResult.success();
}
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import '../artifacts.dart';
import '../base/analyze_size.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
......@@ -11,6 +12,7 @@ import '../base/utils.dart';
import '../build_info.dart';
import '../cache.dart';
import '../cmake.dart';
import '../convert.dart';
import '../globals.dart' as globals;
import '../plugins.dart';
import '../project.dart';
......@@ -20,6 +22,7 @@ Future<void> buildLinux(
LinuxProject linuxProject,
BuildInfo buildInfo, {
String target = 'lib/main.dart',
SizeAnalyzer sizeAnalyzer,
}) async {
if (!linuxProject.cmakeFile.existsSync()) {
throwToolExit('No Linux desktop project configured. See '
......@@ -53,6 +56,29 @@ Future<void> buildLinux(
} finally {
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 {
......
......@@ -4,11 +4,13 @@
import 'package:meta/meta.dart';
import '../base/analyze_size.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../base/process.dart';
import '../build_info.dart';
import '../convert.dart';
import '../globals.dart' as globals;
import '../ios/xcodeproj.dart';
import '../project.dart';
......@@ -25,6 +27,7 @@ Future<void> buildMacOS({
BuildInfo buildInfo,
String targetOverride,
@required bool verboseLogging,
SizeAnalyzer sizeAnalyzer,
}) async {
if (!flutterProject.macos.xcodeWorkspace.existsSync()) {
throwToolExit('No macOS desktop project configured. '
......@@ -106,5 +109,37 @@ Future<void> buildMacOS({
if (result != 0) {
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));
}
......@@ -609,7 +609,9 @@ abstract class FlutterCommand extends Command<void> {
FlutterOptions.kAnalyzeSize,
defaultsTo: false,
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> {
}
}
String analyzeSize;
if (argParser.options.containsKey(FlutterOptions.kAnalyzeSize)
&& boolArg(FlutterOptions.kAnalyzeSize)
&& !globals.platform.isWindows) {
final File file = globals.fsUtils.getUniqueFile(globals.fs.currentDirectory, 'flutter_size', 'json');
extraGenSnapshotOptions.add('--write-v8-snapshot-profile-to=${file.path}');
analyzeSize = file.path;
String codeSizeDirectory;
if (argParser.options.containsKey(FlutterOptions.kAnalyzeSize) && boolArg(FlutterOptions.kAnalyzeSize)) {
final Directory directory = globals.fsUtils.getUniqueDirectory(
globals.fs.directory(getBuildDirectory()),
'flutter_size',
);
directory.createSync(recursive: true);
codeSizeDirectory = directory.path;
}
NullSafetyMode nullSafetyMode = NullSafetyMode.unsound;
......@@ -688,7 +691,7 @@ abstract class FlutterCommand extends Command<void> {
);
}
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.');
}
......@@ -736,7 +739,7 @@ abstract class FlutterCommand extends Command<void> {
performanceMeasurementFile: performanceMeasurementFile,
packagesPath: globalResults['packages'] as String ?? '.packages',
nullSafetyMode: nullSafetyMode,
analyzeSize: analyzeSize,
codeSizeDirectory: codeSizeDirectory,
);
}
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import '../artifacts.dart';
import '../base/analyze_size.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
......@@ -11,6 +12,7 @@ import '../base/utils.dart';
import '../build_info.dart';
import '../cache.dart';
import '../cmake.dart';
import '../convert.dart';
import '../globals.dart' as globals;
import '../plugins.dart';
import '../project.dart';
......@@ -25,6 +27,7 @@ const String _cmakeVisualStudioGeneratorIdentifier = 'Visual Studio 16 2019';
Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, {
String target,
VisualStudio visualStudioOverride,
SizeAnalyzer sizeAnalyzer,
}) async {
if (!windowsProject.cmakeFile.existsSync()) {
throwToolExit(
......@@ -75,6 +78,29 @@ Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, {
} finally {
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 {
......
......@@ -15,10 +15,8 @@ import 'package:mockito/mockito.dart';
import '../../src/common.dart';
import '../../src/context.dart';
class MockPlatform extends Mock implements Platform {}
void main() {
group('ensureDirectoryExists', () {
group('fsUtils', () {
MemoryFileSystem fs;
FileSystemUtils fsUtils;
......@@ -26,19 +24,37 @@ void main() {
fs = MemoryFileSystem();
fsUtils = FileSystemUtils(
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');
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();
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', () {
......@@ -61,7 +77,7 @@ void main() {
final FileSystemUtils fsUtils = FileSystemUtils(
fileSystem: sourceMemoryFs,
platform: MockPlatform(),
platform: FakePlatform(),
);
fsUtils.copyDirectorySync(sourceDirectory, targetDirectory);
......@@ -81,7 +97,7 @@ void main() {
final MemoryFileSystem fileSystem = MemoryFileSystem();
final FileSystemUtils fsUtils = FileSystemUtils(
fileSystem: fileSystem,
platform: MockPlatform(),
platform: FakePlatform(),
);
final Directory origin = fileSystem.directory('/origin');
origin.createSync();
......
......@@ -107,6 +107,7 @@ void main() {
extraGenSnapshotOptions: <String>['--enable-experiment=non-nullable', 'fizz'],
bundleSkSLPath: 'foo/bar/baz.sksl.json',
packagesPath: 'foo/.packages',
codeSizeDirectory: 'foo/code-size',
);
expect(buildInfo.toEnvironmentConfig(), <String, String>{
......@@ -119,6 +120,7 @@ void main() {
'EXTRA_GEN_SNAPSHOT_OPTIONS': '--enable-experiment%3Dnon-nullable,fizz',
'BUNDLE_SKSL_PATH': 'foo/bar/baz.sksl.json',
'PACKAGE_CONFIG': 'foo/.packages',
'CODE_SIZE_DIRECTORY': 'foo/code-size',
});
});
......
......@@ -209,6 +209,50 @@ void main() {
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 {
processManager = FakeProcessManager.list(<FakeCommand>[]);
final Environment environment = Environment.test(
......
......@@ -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/targets/common.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 '../../../src/common.dart';
......@@ -32,10 +31,6 @@ void main() {
FileSystem fileSystem;
Logger logger;
setUpAll(() {
Cache.disableLocking();
});
setUp(() {
processManager = FakeProcessManager.list(<FakeCommand>[]);
logger = BufferLogger.test();
......@@ -47,6 +42,7 @@ void main() {
kBuildMode: getNameForBuildMode(BuildMode.profile),
kTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm),
},
inputs: <String, String>{},
artifacts: artifacts,
processManager: processManager,
fileSystem: fileSystem,
......@@ -59,6 +55,7 @@ void main() {
kBuildMode: getNameForBuildMode(BuildMode.profile),
kTargetPlatform: getNameForTargetPlatform(TargetPlatform.ios),
},
inputs: <String, String>{},
artifacts: artifacts,
processManager: processManager,
fileSystem: fileSystem,
......@@ -357,6 +354,36 @@ void main() {
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 {
androidEnvironment.defines.remove(kBuildMode);
......@@ -611,6 +638,88 @@ void main() {
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 {
androidEnvironment.defines[kExtraGenSnapshotOptions] = 'foo,bar,baz=2';
androidEnvironment.defines[kBuildMode] = getNameForBuildMode(BuildMode.profile);
......
......@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// 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/platform.dart';
import 'package:process/process.dart';
......@@ -9,16 +10,19 @@ import 'package:flutter_tools/src/globals.dart' as globals;
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() {
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 ProcessResult result = await const LocalProcessManager().run(<String>[
flutterBin,
'build',
'apk',
'--analyze-size',
'--target-platform=android-arm64'
], workingDirectory: globals.fs.path.join(getFlutterRoot(), 'examples', 'hello_world'));
print(result.stdout);
......@@ -27,9 +31,52 @@ void main() {
final String line = result.stdout.toString()
.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);
}, 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