[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)
This commit is contained in:
parent
dd0c881275
commit
059de1537e
@ -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" \
|
|||||||
-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 {
|
|||||||
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
|
|||||||
'-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
|
|||||||
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
|
|||||||
-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> {
|
|||||||
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> {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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-
|
|||||||
/// 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({
|
|||||||
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({
|
|||||||
? '' // 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({
|
|||||||
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()].
|
||||||
|
@ -4,11 +4,12 @@
|
|||||||
|
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:vm_snapshot_analysis/treemap.dart';
|
import 'package:vm_snapshot_analysis/treemap.dart';
|
||||||
|
import 'package:archive/archive.dart';
|
||||||
|
import 'package:archive/archive_io.dart';
|
||||||
|
|
||||||
import '../base/file_system.dart';
|
import '../base/file_system.dart';
|
||||||
import '../convert.dart';
|
import '../convert.dart';
|
||||||
import 'logger.dart';
|
import 'logger.dart';
|
||||||
import 'process.dart';
|
|
||||||
import 'terminal.dart';
|
import 'terminal.dart';
|
||||||
|
|
||||||
/// A class to analyze APK and AOT snapshot and generate a breakdown of the data.
|
/// A class to analyze APK and AOT snapshot and generate a breakdown of the data.
|
||||||
@ -16,13 +17,11 @@ class SizeAnalyzer {
|
|||||||
SizeAnalyzer({
|
SizeAnalyzer({
|
||||||
@required this.fileSystem,
|
@required this.fileSystem,
|
||||||
@required this.logger,
|
@required this.logger,
|
||||||
@required this.processUtils,
|
|
||||||
this.appFilenamePattern = 'libapp.so',
|
this.appFilenamePattern = 'libapp.so',
|
||||||
});
|
});
|
||||||
|
|
||||||
final FileSystem fileSystem;
|
final FileSystem fileSystem;
|
||||||
final Logger logger;
|
final Logger logger;
|
||||||
final ProcessUtils processUtils;
|
|
||||||
final Pattern appFilenamePattern;
|
final Pattern appFilenamePattern;
|
||||||
String _appFilename;
|
String _appFilename;
|
||||||
|
|
||||||
@ -30,44 +29,24 @@ class SizeAnalyzer {
|
|||||||
|
|
||||||
static const int tableWidth = 80;
|
static const int tableWidth = 80;
|
||||||
|
|
||||||
/// Analyzes [apk] and [aotSnapshot] to output a [Map] object that includes
|
static const int _kAotSizeMaxDepth = 2;
|
||||||
/// the breakdown of the both files, where the breakdown of [aotSnapshot] is placed
|
static const int _kZipSizeMaxDepth = 1;
|
||||||
/// under 'lib/arm64-v8a/$_appFilename'.
|
|
||||||
///
|
/// Analyze the [aotSnapshot] in an uncompressed output directory.
|
||||||
/// The [aotSnapshot] can be either instruction sizes snapshot or v8 snapshot.
|
Future<Map<String, dynamic>> analyzeAotSnapshot({
|
||||||
Future<Map<String, dynamic>> analyzeApkSizeAndAotSnapshot({
|
@required Directory outputDirectory,
|
||||||
@required File apk,
|
|
||||||
@required File aotSnapshot,
|
@required File aotSnapshot,
|
||||||
|
@required File precompilerTrace,
|
||||||
|
@required String type,
|
||||||
|
String excludePath,
|
||||||
}) async {
|
}) async {
|
||||||
logger.printStatus('▒' * tableWidth);
|
logger.printStatus('▒' * tableWidth);
|
||||||
_printEntitySize(
|
|
||||||
'${apk.basename} (total compressed)',
|
|
||||||
byteSize: apk.lengthSync(),
|
|
||||||
level: 0,
|
|
||||||
showColor: false,
|
|
||||||
);
|
|
||||||
logger.printStatus('━' * tableWidth);
|
logger.printStatus('━' * tableWidth);
|
||||||
final Directory tempApkContent = fileSystem.systemTempDirectory.createTempSync('flutter_tools.');
|
final _SymbolNode aotAnalysisJson = _parseDirectory(
|
||||||
// TODO(peterdjlee): Implement a way to unzip the APK for Windows. See issue #62603.
|
outputDirectory,
|
||||||
String unzipOut;
|
outputDirectory.parent.path,
|
||||||
try {
|
excludePath,
|
||||||
// TODO(peterdjlee): Use zipinfo instead of unzip.
|
);
|
||||||
unzipOut = (await processUtils.run(<String>[
|
|
||||||
'unzip',
|
|
||||||
'-o',
|
|
||||||
'-v',
|
|
||||||
apk.path,
|
|
||||||
'-d',
|
|
||||||
tempApkContent.path
|
|
||||||
])).stdout;
|
|
||||||
} on Exception catch (e) {
|
|
||||||
logger.printError(e.toString());
|
|
||||||
} finally {
|
|
||||||
// We just want the the stdout printout. We don't need the files.
|
|
||||||
tempApkContent.deleteSync(recursive: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
final _SymbolNode apkAnalysisRoot = _parseUnzipFile(unzipOut);
|
|
||||||
|
|
||||||
// Convert an AOT snapshot file into a map.
|
// Convert an AOT snapshot file into a map.
|
||||||
final Map<String, dynamic> processedAotSnapshotJson = treemapFromJson(
|
final Map<String, dynamic> processedAotSnapshotJson = treemapFromJson(
|
||||||
@ -75,66 +54,122 @@ class SizeAnalyzer {
|
|||||||
);
|
);
|
||||||
final _SymbolNode aotSnapshotJsonRoot = _parseAotSnapshot(processedAotSnapshotJson);
|
final _SymbolNode aotSnapshotJsonRoot = _parseAotSnapshot(processedAotSnapshotJson);
|
||||||
|
|
||||||
for (final _SymbolNode firstLevelPath in apkAnalysisRoot.children) {
|
for (final _SymbolNode firstLevelPath in aotAnalysisJson.children) {
|
||||||
_printEntitySize(
|
_printEntitySize(
|
||||||
firstLevelPath.name,
|
firstLevelPath.name,
|
||||||
byteSize: firstLevelPath.byteSize,
|
byteSize: firstLevelPath.byteSize,
|
||||||
level: 1,
|
level: 1,
|
||||||
);
|
);
|
||||||
// Print the expansion of lib directory to show more info for `appFilename`.
|
// Print the expansion of lib directory to show more info for `appFilename`.
|
||||||
if (firstLevelPath.name == 'lib') {
|
if (firstLevelPath.name == fileSystem.path.basename(outputDirectory.path)) {
|
||||||
_printLibChildrenPaths(firstLevelPath, '', aotSnapshotJsonRoot);
|
_printLibChildrenPaths(firstLevelPath, '', aotSnapshotJsonRoot, _kAotSizeMaxDepth, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.printStatus('▒' * tableWidth);
|
logger.printStatus('▒' * tableWidth);
|
||||||
|
|
||||||
|
Map<String, dynamic> apkAnalysisJson = aotAnalysisJson.toJson();
|
||||||
|
|
||||||
|
apkAnalysisJson['type'] = type; // one of apk, aab, ios, macos, windows, or linux.
|
||||||
|
|
||||||
|
apkAnalysisJson = _addAotSnapshotDataToAnalysis(
|
||||||
|
apkAnalysisJson: apkAnalysisJson,
|
||||||
|
path: _locatedAotFilePath,
|
||||||
|
aotSnapshotJson: processedAotSnapshotJson,
|
||||||
|
precompilerTrace: json.decode(precompilerTrace.readAsStringSync()) as Map<String, Object>,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(_appFilename != null);
|
||||||
|
return apkAnalysisJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Analyzes [apk] and [aotSnapshot] to output a [Map] object that includes
|
||||||
|
/// the breakdown of the both files, where the breakdown of [aotSnapshot] is placed
|
||||||
|
/// under 'lib/arm64-v8a/$_appFilename'.
|
||||||
|
///
|
||||||
|
/// [kind] must be one of 'apk' or 'aab'.
|
||||||
|
/// The [aotSnapshot] can be either instruction sizes snapshot or a v8 snapshot.
|
||||||
|
Future<Map<String, dynamic>> analyzeZipSizeAndAotSnapshot({
|
||||||
|
@required File zipFile,
|
||||||
|
@required File aotSnapshot,
|
||||||
|
@required File precompilerTrace,
|
||||||
|
@required String kind,
|
||||||
|
}) async {
|
||||||
|
assert(kind == 'apk' || kind == 'aab');
|
||||||
|
logger.printStatus('▒' * tableWidth);
|
||||||
|
_printEntitySize(
|
||||||
|
'${zipFile.basename} (total compressed)',
|
||||||
|
byteSize: zipFile.lengthSync(),
|
||||||
|
level: 0,
|
||||||
|
showColor: false,
|
||||||
|
);
|
||||||
|
logger.printStatus('━' * tableWidth);
|
||||||
|
|
||||||
|
final _SymbolNode apkAnalysisRoot = _parseUnzipFile(zipFile);
|
||||||
|
|
||||||
|
// Convert an AOT snapshot file into a map.
|
||||||
|
final Map<String, dynamic> processedAotSnapshotJson = treemapFromJson(
|
||||||
|
json.decode(aotSnapshot.readAsStringSync()),
|
||||||
|
);
|
||||||
|
final _SymbolNode aotSnapshotJsonRoot = _parseAotSnapshot(processedAotSnapshotJson);
|
||||||
|
for (final _SymbolNode firstLevelPath in apkAnalysisRoot.children) {
|
||||||
|
_printLibChildrenPaths(firstLevelPath, '', aotSnapshotJsonRoot, _kZipSizeMaxDepth, 0);
|
||||||
|
}
|
||||||
|
logger.printStatus('▒' * tableWidth);
|
||||||
|
|
||||||
Map<String, dynamic> apkAnalysisJson = apkAnalysisRoot.toJson();
|
Map<String, dynamic> apkAnalysisJson = apkAnalysisRoot.toJson();
|
||||||
|
|
||||||
apkAnalysisJson['type'] = 'apk';
|
apkAnalysisJson['type'] = kind;
|
||||||
|
|
||||||
// TODO(peterdjlee): Add aot snapshot for all platforms.
|
|
||||||
assert(_appFilename != null);
|
assert(_appFilename != null);
|
||||||
apkAnalysisJson = _addAotSnapshotDataToApkAnalysis(
|
apkAnalysisJson = _addAotSnapshotDataToAnalysis(
|
||||||
apkAnalysisJson: apkAnalysisJson,
|
apkAnalysisJson: apkAnalysisJson,
|
||||||
path: 'lib/arm64-v8a/$_appFilename (Dart AOT)'.split('/'), // Pass in a list of paths by splitting with '/'.
|
path: _locatedAotFilePath,
|
||||||
aotSnapshotJson: processedAotSnapshotJson,
|
aotSnapshotJson: processedAotSnapshotJson,
|
||||||
|
precompilerTrace: json.decode(precompilerTrace.readAsStringSync()) as Map<String, Object>,
|
||||||
);
|
);
|
||||||
|
|
||||||
return apkAnalysisJson;
|
return apkAnalysisJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_SymbolNode _parseUnzipFile(File zipFile) {
|
||||||
// Expression to match 'Size' column to group 1 and 'Name' column to group 2.
|
final Archive archive = ZipDecoder().decodeBytes(zipFile.readAsBytesSync());
|
||||||
final RegExp _parseUnzipOutput = RegExp(r'^\s*\d+\s+[\w|:]+\s+(\d+)\s+.* (.+)$');
|
|
||||||
|
|
||||||
// Parse the output of unzip -v which shows the zip's contents' compressed sizes.
|
|
||||||
// Example output of unzip -v:
|
|
||||||
// Length Method Size Cmpr Date Time CRC-32 Name
|
|
||||||
// -------- ------ ------- ---- ---------- ----- -------- ----
|
|
||||||
// 11708 Defl:N 2592 78% 00-00-1980 00:00 07733eef AndroidManifest.xml
|
|
||||||
// 1399 Defl:N 1092 22% 00-00-1980 00:00 f53d952a META-INF/CERT.RSA
|
|
||||||
// 46298 Defl:N 14530 69% 00-00-1980 00:00 17df02b8 META-INF/CERT.SF
|
|
||||||
_SymbolNode _parseUnzipFile(String unzipOut) {
|
|
||||||
final Map<List<String>, int> pathsToSize = <List<String>, int>{};
|
final Map<List<String>, int> pathsToSize = <List<String>, int>{};
|
||||||
|
|
||||||
// Parse each path into pathsToSize so that the key is a list of
|
for (final ArchiveFile archiveFile in archive.files) {
|
||||||
// path parts and the value is the size.
|
pathsToSize[fileSystem.path.split(archiveFile.name)] = archiveFile.rawContent.length;
|
||||||
// For example:
|
}
|
||||||
// 'path/to/file' where file = 1500 => pathsToSize[['path', 'to', 'file']] = 1500
|
return _buildSymbolTree(pathsToSize);
|
||||||
for (final String line in const LineSplitter().convert(unzipOut)) {
|
}
|
||||||
final RegExpMatch match = _parseUnzipOutput.firstMatch(line);
|
|
||||||
if (match == null) {
|
_SymbolNode _parseDirectory(Directory directory, String relativeTo, String excludePath) {
|
||||||
|
final Map<List<String>, int> pathsToSize = <List<String>, int>{};
|
||||||
|
for (final File file in directory.listSync(recursive: true).whereType<File>()) {
|
||||||
|
if (excludePath != null && file.uri.pathSegments.contains(excludePath)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const int sizeGroupIndex = 1;
|
final List<String> path = fileSystem.path.split(
|
||||||
const int nameGroupIndex = 2;
|
fileSystem.path.relative(file.path, from: relativeTo));
|
||||||
pathsToSize[match.group(nameGroupIndex).split('/')] = int.parse(match.group(sizeGroupIndex));
|
pathsToSize[path] = file.lengthSync();
|
||||||
}
|
}
|
||||||
|
return _buildSymbolTree(pathsToSize);
|
||||||
|
}
|
||||||
|
|
||||||
final _SymbolNode rootNode = _SymbolNode('Root');
|
List<String> _locatedAotFilePath;
|
||||||
|
|
||||||
|
List<String> _buildNodeName(_SymbolNode start, _SymbolNode parent) {
|
||||||
|
final List<String> results = <String>[start.name];
|
||||||
|
while (parent != null && parent.name != 'Root') {
|
||||||
|
results.insert(0, parent.name);
|
||||||
|
parent = parent.parent;
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
_SymbolNode _buildSymbolTree(Map<List<String>, int> pathsToSize) {
|
||||||
|
final _SymbolNode rootNode = _SymbolNode('Root');
|
||||||
_SymbolNode currentNode = rootNode;
|
_SymbolNode currentNode = rootNode;
|
||||||
|
|
||||||
for (final List<String> paths in pathsToSize.keys) {
|
for (final List<String> paths in pathsToSize.keys) {
|
||||||
for (final String path in paths) {
|
for (final String path in paths) {
|
||||||
_SymbolNode childWithPathAsName = currentNode.childByName(path);
|
_SymbolNode childWithPathAsName = currentNode.childByName(path);
|
||||||
@ -144,6 +179,7 @@ class SizeAnalyzer {
|
|||||||
if (matchesPattern(path, pattern: appFilenamePattern) != null) {
|
if (matchesPattern(path, pattern: appFilenamePattern) != null) {
|
||||||
_appFilename = path;
|
_appFilename = path;
|
||||||
childWithPathAsName.name += ' (Dart AOT)';
|
childWithPathAsName.name += ' (Dart AOT)';
|
||||||
|
_locatedAotFilePath = _buildNodeName(childWithPathAsName, currentNode);
|
||||||
} else if (path == 'libflutter.so') {
|
} else if (path == 'libflutter.so') {
|
||||||
childWithPathAsName.name += ' (Flutter Engine)';
|
childWithPathAsName.name += ' (Flutter Engine)';
|
||||||
}
|
}
|
||||||
@ -154,7 +190,6 @@ class SizeAnalyzer {
|
|||||||
}
|
}
|
||||||
currentNode = rootNode;
|
currentNode = rootNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
return rootNode;
|
return rootNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,52 +200,79 @@ class SizeAnalyzer {
|
|||||||
_SymbolNode currentNode,
|
_SymbolNode currentNode,
|
||||||
String totalPath,
|
String totalPath,
|
||||||
_SymbolNode aotSnapshotJsonRoot,
|
_SymbolNode aotSnapshotJsonRoot,
|
||||||
|
int maxDepth,
|
||||||
|
int currentDepth,
|
||||||
) {
|
) {
|
||||||
totalPath += currentNode.name;
|
totalPath += currentNode.name;
|
||||||
|
|
||||||
assert(_appFilename != null);
|
assert(_appFilename != null);
|
||||||
if (currentNode.children.isNotEmpty
|
if (currentNode.children.isNotEmpty
|
||||||
&& currentNode.name != '$_appFilename (Dart AOT)') {
|
&& currentNode.name != '$_appFilename (Dart AOT)'
|
||||||
|
&& currentDepth < maxDepth
|
||||||
|
&& currentNode.byteSize >= 1000) {
|
||||||
for (final _SymbolNode child in currentNode.children) {
|
for (final _SymbolNode child in currentNode.children) {
|
||||||
_printLibChildrenPaths(child, '$totalPath/', aotSnapshotJsonRoot);
|
_printLibChildrenPaths(child, '$totalPath/', aotSnapshotJsonRoot, maxDepth, currentDepth + 1);
|
||||||
}
|
}
|
||||||
|
_leadingPaths = totalPath.split('/')
|
||||||
|
..removeLast();
|
||||||
} else {
|
} else {
|
||||||
// Print total path and size if currentNode does not have any chilren.
|
// Print total path and size if currentNode does not have any children and is
|
||||||
_printEntitySize(totalPath, byteSize: currentNode.byteSize, level: 2);
|
// larger than 1KB
|
||||||
|
final bool isAotSnapshotPath = _locatedAotFilePath.join('/').contains(totalPath);
|
||||||
// We picked this file because arm64-v8a is likely the most popular
|
if (currentNode.byteSize >= 1000 || isAotSnapshotPath) {
|
||||||
// architecture. Other architecture sizes should be similar.
|
_printEntitySize(totalPath, byteSize: currentNode.byteSize, level: 1, emphasis: currentNode.children.isNotEmpty);
|
||||||
final String libappPath = 'lib/arm64-v8a/$_appFilename';
|
if (isAotSnapshotPath) {
|
||||||
// TODO(peterdjlee): Analyze aot size for all platforms.
|
_printAotSnapshotSummary(aotSnapshotJsonRoot, level: totalPath.split('/').length);
|
||||||
if (totalPath.contains(libappPath)) {
|
}
|
||||||
_printAotSnapshotSummary(aotSnapshotJsonRoot);
|
_leadingPaths = totalPath.split('/')
|
||||||
|
..removeLast();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Go through the AOT gen snapshot size JSON and print out a collapsed summary
|
/// Go through the AOT gen snapshot size JSON and print out a collapsed summary
|
||||||
/// for the first package level.
|
/// for the first package level.
|
||||||
void _printAotSnapshotSummary(_SymbolNode aotSnapshotRoot, {int maxDirectoriesShown = 10}) {
|
void _printAotSnapshotSummary(_SymbolNode aotSnapshotRoot, {int maxDirectoriesShown = 20, @required int level}) {
|
||||||
_printEntitySize(
|
_printEntitySize(
|
||||||
'Dart AOT symbols accounted decompressed size',
|
'Dart AOT symbols accounted decompressed size',
|
||||||
byteSize: aotSnapshotRoot.byteSize,
|
byteSize: aotSnapshotRoot.byteSize,
|
||||||
level: 3,
|
level: level,
|
||||||
|
emphasis: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
final List<_SymbolNode> sortedSymbols = aotSnapshotRoot.children.toList()
|
final List<_SymbolNode> sortedSymbols = aotSnapshotRoot.children.toList()
|
||||||
|
// Remove entries like @unknown, @shared, and @stubs as well as private dart libraries
|
||||||
|
// which are not interpretable by end users.
|
||||||
|
..removeWhere((_SymbolNode node) => node.name.startsWith('@') || node.name.startsWith('dart:_'))
|
||||||
..sort((_SymbolNode a, _SymbolNode b) => b.byteSize.compareTo(a.byteSize));
|
..sort((_SymbolNode a, _SymbolNode b) => b.byteSize.compareTo(a.byteSize));
|
||||||
for (final _SymbolNode node in sortedSymbols.take(maxDirectoriesShown)) {
|
for (final _SymbolNode node in sortedSymbols.take(maxDirectoriesShown)) {
|
||||||
_printEntitySize(node.name, byteSize: node.byteSize, level: 4);
|
// Node names will have an extra leading `package:*` name, remove it to
|
||||||
|
// avoid extra nesting.
|
||||||
|
_printEntitySize(_formatExtraLeadingPackages(node.name), byteSize: node.byteSize, level: level + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _formatExtraLeadingPackages(String name) {
|
||||||
|
if (!name.startsWith('package')) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
final List<String> chunks = name.split('/');
|
||||||
|
if (chunks.length < 2) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
chunks.removeAt(0);
|
||||||
|
return chunks.join('/');
|
||||||
|
}
|
||||||
|
|
||||||
/// Adds breakdown of aot snapshot data as the children of the node at the given path.
|
/// Adds breakdown of aot snapshot data as the children of the node at the given path.
|
||||||
Map<String, dynamic> _addAotSnapshotDataToApkAnalysis({
|
Map<String, dynamic> _addAotSnapshotDataToAnalysis({
|
||||||
@required Map<String, dynamic> apkAnalysisJson,
|
@required Map<String, dynamic> apkAnalysisJson,
|
||||||
@required List<String> path,
|
@required List<String> path,
|
||||||
@required Map<String, dynamic> aotSnapshotJson,
|
@required Map<String, dynamic> aotSnapshotJson,
|
||||||
|
@required Map<String, dynamic> precompilerTrace,
|
||||||
}) {
|
}) {
|
||||||
Map<String, dynamic> currentLevel = apkAnalysisJson;
|
Map<String, dynamic> currentLevel = apkAnalysisJson;
|
||||||
|
currentLevel['precompiler-trace'] = precompilerTrace;
|
||||||
while (path.isNotEmpty) {
|
while (path.isNotEmpty) {
|
||||||
final List<Map<String, dynamic>> children = currentLevel['children'] as List<Map<String, dynamic>>;
|
final List<Map<String, dynamic>> children = currentLevel['children'] as List<Map<String, dynamic>>;
|
||||||
final Map<String, dynamic> childWithPathAsName = children.firstWhere(
|
final Map<String, dynamic> childWithPathAsName = children.firstWhere(
|
||||||
@ -223,14 +285,16 @@ class SizeAnalyzer {
|
|||||||
return apkAnalysisJson;
|
return apkAnalysisJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<String> _leadingPaths = <String>[];
|
||||||
|
|
||||||
/// Print an entity's name with its size on the same line.
|
/// Print an entity's name with its size on the same line.
|
||||||
void _printEntitySize(
|
void _printEntitySize(
|
||||||
String entityName, {
|
String entityName, {
|
||||||
@required int byteSize,
|
@required int byteSize,
|
||||||
@required int level,
|
@required int level,
|
||||||
bool showColor = true,
|
bool showColor = true,
|
||||||
|
bool emphasis = false,
|
||||||
}) {
|
}) {
|
||||||
final bool emphasis = level <= 1;
|
|
||||||
final String formattedSize = _prettyPrintBytes(byteSize);
|
final String formattedSize = _prettyPrintBytes(byteSize);
|
||||||
|
|
||||||
TerminalColor color = TerminalColor.green;
|
TerminalColor color = TerminalColor.green;
|
||||||
@ -240,12 +304,32 @@ class SizeAnalyzer {
|
|||||||
color = TerminalColor.yellow;
|
color = TerminalColor.yellow;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int spaceInBetween = tableWidth - level * 2 - entityName.length - formattedSize.length;
|
// Compute any preceeding directories, and compare this to the stored
|
||||||
|
// directoried (in _leadingPaths) for the last entity that was printed. The
|
||||||
|
// similary determines whether or not leading directory information needs to
|
||||||
|
// be printed.
|
||||||
|
final List<String> localSegments = entityName.split('/')
|
||||||
|
..removeLast();
|
||||||
|
int i = 0;
|
||||||
|
while (i < _leadingPaths.length && i < localSegments.length && _leadingPaths[i] == localSegments[i]) {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
for (; i < localSegments.length; i += 1) {
|
||||||
|
logger.printStatus(
|
||||||
|
localSegments[i] + '/',
|
||||||
|
indent: (level + i) * 2,
|
||||||
|
emphasis: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_leadingPaths = localSegments;
|
||||||
|
|
||||||
|
final String baseName = fileSystem.path.basename(entityName);
|
||||||
|
final int spaceInBetween = tableWidth - (level + i) * 2 - baseName.length - formattedSize.length;
|
||||||
logger.printStatus(
|
logger.printStatus(
|
||||||
entityName + ' ' * spaceInBetween,
|
baseName + ' ' * spaceInBetween,
|
||||||
newline: false,
|
newline: false,
|
||||||
emphasis: emphasis,
|
emphasis: emphasis,
|
||||||
indent: level * 2,
|
indent: (level + i) * 2,
|
||||||
);
|
);
|
||||||
logger.printStatus(formattedSize, color: showColor ? color : null);
|
logger.printStatus(formattedSize, color: showColor ? color : null);
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
|||||||
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 {
|
|||||||
/// 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 {
|
|||||||
'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) {
|
|||||||
/// 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 {
|
|||||||
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';
|
|||||||
/// 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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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';
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
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 {
|
|||||||
|
|
||||||
@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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
// 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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 @@
|
|||||||
|
|
||||||
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 {
|
|||||||
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 {
|
|||||||
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 @@
|
|||||||
|
|
||||||
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 {
|
|||||||
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 {
|
|||||||
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';
|
|||||||
|
|
||||||
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 {
|
|||||||
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 {
|
|||||||
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';
|
|||||||
|
|
||||||
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 {
|
|||||||
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 {
|
|||||||
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 @@
|
|||||||
// 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';
|
|||||||
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(
|
|||||||
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(
|
|||||||
} 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 @@
|
|||||||
|
|
||||||
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({
|
|||||||
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({
|
|||||||
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> {
|
|||||||
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> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
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> {
|
|||||||
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 @@
|
|||||||
// 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';
|
|||||||
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';
|
|||||||
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, {
|
|||||||
} 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 {
|
||||||
|
@ -2,34 +2,13 @@
|
|||||||
// 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:archive/archive.dart';
|
||||||
import 'package:file/memory.dart';
|
import 'package:file/memory.dart';
|
||||||
import 'package:flutter_tools/src/base/analyze_size.dart';
|
import 'package:flutter_tools/src/base/analyze_size.dart';
|
||||||
import 'package:flutter_tools/src/base/file_system.dart';
|
import 'package:flutter_tools/src/base/file_system.dart';
|
||||||
import 'package:flutter_tools/src/base/logger.dart';
|
import 'package:flutter_tools/src/base/logger.dart';
|
||||||
import 'package:flutter_tools/src/base/process.dart';
|
|
||||||
|
|
||||||
import '../../src/common.dart';
|
import '../../src/common.dart';
|
||||||
import '../../src/context.dart';
|
|
||||||
|
|
||||||
const FakeCommand unzipCommmand = FakeCommand(
|
|
||||||
command: <String>[
|
|
||||||
'unzip',
|
|
||||||
'-o',
|
|
||||||
'-v',
|
|
||||||
'test.apk',
|
|
||||||
'-d',
|
|
||||||
'/.tmp_rand0/flutter_tools.rand0'
|
|
||||||
],
|
|
||||||
stdout: '''
|
|
||||||
Length Method Size Cmpr Date Time CRC-32 Name
|
|
||||||
-------- ------ ------- ---- ---------- ----- -------- ----
|
|
||||||
11708 Defl:N 2592 78% 00-00-1980 00:00 07733eef AndroidManifest.xml
|
|
||||||
1399 Defl:N 1092 22% 00-00-1980 00:00 f53d952a META-INF/CERT.RSA
|
|
||||||
46298 Defl:N 14530 69% 00-00-1980 00:00 17df02b8 META-INF/CERT.SF
|
|
||||||
46298 Defl:N 14530 69% 00-00-1980 00:00 17df02b8 lib/arm64-v8a/libxyzzyapp.so
|
|
||||||
46298 Defl:N 14530 69% 00-00-1980 00:00 17df02b8 lib/arm64-v8a/libflutter.so
|
|
||||||
''',
|
|
||||||
);
|
|
||||||
|
|
||||||
const String aotSizeOutput = '''[
|
const String aotSizeOutput = '''[
|
||||||
{
|
{
|
||||||
@ -62,12 +41,10 @@ const String aotSizeOutput = '''[
|
|||||||
void main() {
|
void main() {
|
||||||
MemoryFileSystem fileSystem;
|
MemoryFileSystem fileSystem;
|
||||||
BufferLogger logger;
|
BufferLogger logger;
|
||||||
FakeProcessManager processManager;
|
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
fileSystem = MemoryFileSystem.test();
|
fileSystem = MemoryFileSystem.test();
|
||||||
logger = BufferLogger.test();
|
logger = BufferLogger.test();
|
||||||
processManager = FakeProcessManager.list(<FakeCommand>[unzipCommmand]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('matchesPattern matches only entire strings', () {
|
test('matchesPattern matches only entire strings', () {
|
||||||
@ -85,45 +62,56 @@ void main() {
|
|||||||
final SizeAnalyzer sizeAnalyzer = SizeAnalyzer(
|
final SizeAnalyzer sizeAnalyzer = SizeAnalyzer(
|
||||||
fileSystem: fileSystem,
|
fileSystem: fileSystem,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
processUtils: ProcessUtils(
|
|
||||||
processManager: processManager,
|
|
||||||
logger: logger,
|
|
||||||
),
|
|
||||||
appFilenamePattern: RegExp(r'lib.*app\.so'),
|
appFilenamePattern: RegExp(r'lib.*app\.so'),
|
||||||
);
|
);
|
||||||
|
|
||||||
final File apk = fileSystem.file('test.apk')..createSync();
|
final Archive archive = Archive()
|
||||||
|
..addFile(ArchiveFile('AndroidManifest.xml', 100, List<int>.filled(100, 0)))
|
||||||
|
..addFile(ArchiveFile('META-INF/CERT.RSA', 10, List<int>.filled(10, 0)))
|
||||||
|
..addFile(ArchiveFile('META-INF/CERT.SF', 10, List<int>.filled(10, 0)))
|
||||||
|
..addFile(ArchiveFile('lib/arm64-v8a/libxyzzyapp.so', 50, List<int>.filled(50, 0)))
|
||||||
|
..addFile(ArchiveFile('lib/arm64-v8a/libflutter.so', 50, List<int>.filled(50, 0)));
|
||||||
|
|
||||||
|
final File apk = fileSystem.file('test.apk')
|
||||||
|
..writeAsBytesSync(ZipEncoder().encode(archive));
|
||||||
final File aotSizeJson = fileSystem.file('test.json')
|
final File aotSizeJson = fileSystem.file('test.json')
|
||||||
..createSync()
|
..createSync()
|
||||||
..writeAsStringSync(aotSizeOutput);
|
..writeAsStringSync(aotSizeOutput);
|
||||||
final Map<String, dynamic> result = await sizeAnalyzer.analyzeApkSizeAndAotSnapshot(apk: apk, aotSnapshot: aotSizeJson);
|
final File precompilerTrace = fileSystem.file('trace.json')
|
||||||
|
..writeAsStringSync('{}');
|
||||||
|
final Map<String, dynamic> result = await sizeAnalyzer.analyzeZipSizeAndAotSnapshot(
|
||||||
|
zipFile: apk,
|
||||||
|
aotSnapshot: aotSizeJson,
|
||||||
|
precompilerTrace: precompilerTrace,
|
||||||
|
kind: 'apk',
|
||||||
|
);
|
||||||
|
|
||||||
expect(result['type'], contains('apk'));
|
expect(result['type'], 'apk');
|
||||||
|
|
||||||
final Map<String, dynamic> androidManifestMap = result['children'][0] as Map<String, dynamic>;
|
final Map<String, dynamic> androidManifestMap = result['children'][0] as Map<String, dynamic>;
|
||||||
expect(androidManifestMap['n'], equals('AndroidManifest.xml'));
|
expect(androidManifestMap['n'], 'AndroidManifest.xml');
|
||||||
expect(androidManifestMap['value'], equals(2592));
|
expect(androidManifestMap['value'], 6);
|
||||||
|
|
||||||
final Map<String, dynamic> metaInfMap = result['children'][1] as Map<String, dynamic>;
|
final Map<String, dynamic> metaInfMap = result['children'][1] as Map<String, dynamic>;
|
||||||
expect(metaInfMap['n'], equals('META-INF'));
|
expect(metaInfMap['n'], 'META-INF');
|
||||||
expect(metaInfMap['value'], equals(15622));
|
expect(metaInfMap['value'], 10);
|
||||||
final Map<String, dynamic> certRsaMap = metaInfMap['children'][0] as Map<String, dynamic>;
|
final Map<String, dynamic> certRsaMap = metaInfMap['children'][0] as Map<String, dynamic>;
|
||||||
expect(certRsaMap['n'], equals('CERT.RSA'));
|
expect(certRsaMap['n'], 'CERT.RSA');
|
||||||
expect(certRsaMap['value'], equals(1092));
|
expect(certRsaMap['value'], 5);
|
||||||
final Map<String, dynamic> certSfMap = metaInfMap['children'][1] as Map<String, dynamic>;
|
final Map<String, dynamic> certSfMap = metaInfMap['children'][1] as Map<String, dynamic>;
|
||||||
expect(certSfMap['n'], equals('CERT.SF'));
|
expect(certSfMap['n'], 'CERT.SF');
|
||||||
expect(certSfMap['value'], equals(14530));
|
expect(certSfMap['value'], 5);
|
||||||
|
|
||||||
final Map<String, dynamic> libMap = result['children'][2] as Map<String, dynamic>;
|
final Map<String, dynamic> libMap = result['children'][2] as Map<String, dynamic>;
|
||||||
expect(libMap['n'], equals('lib'));
|
expect(libMap['n'], 'lib');
|
||||||
expect(libMap['value'], equals(29060));
|
expect(libMap['value'], 12);
|
||||||
final Map<String, dynamic> arm64Map = libMap['children'][0] as Map<String, dynamic>;
|
final Map<String, dynamic> arm64Map = libMap['children'][0] as Map<String, dynamic>;
|
||||||
expect(arm64Map['n'], equals('arm64-v8a'));
|
expect(arm64Map['n'], 'arm64-v8a');
|
||||||
expect(arm64Map['value'], equals(29060));
|
expect(arm64Map['value'], 12);
|
||||||
final Map<String, dynamic> libAppMap = arm64Map['children'][0] as Map<String, dynamic>;
|
final Map<String, dynamic> libAppMap = arm64Map['children'][0] as Map<String, dynamic>;
|
||||||
expect(libAppMap['n'], equals('libxyzzyapp.so (Dart AOT)'));
|
expect(libAppMap['n'], 'libxyzzyapp.so (Dart AOT)');
|
||||||
expect(libAppMap['value'], equals(14530));
|
expect(libAppMap['value'], 6);
|
||||||
expect(libAppMap['children'].length, equals(3));
|
expect(libAppMap['children'].length, 3);
|
||||||
final Map<String, dynamic> internalMap = libAppMap['children'][0] as Map<String, dynamic>;
|
final Map<String, dynamic> internalMap = libAppMap['children'][0] as Map<String, dynamic>;
|
||||||
final Map<String, dynamic> skipMap = internalMap['children'][0] as Map<String, dynamic>;
|
final Map<String, dynamic> skipMap = internalMap['children'][0] as Map<String, dynamic>;
|
||||||
expect(skipMap['n'], 'skip');
|
expect(skipMap['n'], 'skip');
|
||||||
@ -140,41 +128,92 @@ void main() {
|
|||||||
expect(allocateMap['n'], 'Allocate ArgumentError');
|
expect(allocateMap['n'], 'Allocate ArgumentError');
|
||||||
expect(allocateMap['value'], 4650);
|
expect(allocateMap['value'], 4650);
|
||||||
final Map<String, dynamic> libFlutterMap = arm64Map['children'][1] as Map<String, dynamic>;
|
final Map<String, dynamic> libFlutterMap = arm64Map['children'][1] as Map<String, dynamic>;
|
||||||
expect(libFlutterMap['n'], equals('libflutter.so (Flutter Engine)'));
|
expect(libFlutterMap['n'], 'libflutter.so (Flutter Engine)');
|
||||||
expect(libFlutterMap['value'], equals(14530));
|
expect(libFlutterMap['value'], 6);
|
||||||
|
|
||||||
|
expect(result['precompiler-trace'], <String, Object>{});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('outputs summary to command line correctly', () async {
|
test('outputs summary to command line correctly', () async {
|
||||||
final SizeAnalyzer sizeAnalyzer = SizeAnalyzer(
|
final SizeAnalyzer sizeAnalyzer = SizeAnalyzer(
|
||||||
fileSystem: fileSystem,
|
fileSystem: fileSystem,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
processUtils: ProcessUtils(
|
|
||||||
processManager: processManager,
|
|
||||||
logger: logger,
|
|
||||||
),
|
|
||||||
appFilenamePattern: RegExp(r'lib.*app\.so'),
|
appFilenamePattern: RegExp(r'lib.*app\.so'),
|
||||||
);
|
);
|
||||||
|
|
||||||
final File apk = fileSystem.file('test.apk')..createSync();
|
final Archive archive = Archive()
|
||||||
|
..addFile(ArchiveFile('AndroidManifest.xml', 100, List<int>.filled(100, 0)))
|
||||||
|
..addFile(ArchiveFile('META-INF/CERT.RSA', 10, List<int>.filled(10, 0)))
|
||||||
|
..addFile(ArchiveFile('META-INF/CERT.SF', 10, List<int>.filled(10, 0)))
|
||||||
|
..addFile(ArchiveFile('lib/arm64-v8a/libxyzzyapp.so', 50, List<int>.filled(50, 0)))
|
||||||
|
..addFile(ArchiveFile('lib/arm64-v8a/libflutter.so', 50, List<int>.filled(50, 0)));
|
||||||
|
|
||||||
|
final File apk = fileSystem.file('test.apk')
|
||||||
|
..writeAsBytesSync(ZipEncoder().encode(archive));
|
||||||
final File aotSizeJson = fileSystem.file('test.json')
|
final File aotSizeJson = fileSystem.file('test.json')
|
||||||
..createSync()
|
|
||||||
..writeAsStringSync(aotSizeOutput);
|
..writeAsStringSync(aotSizeOutput);
|
||||||
await sizeAnalyzer.analyzeApkSizeAndAotSnapshot(apk: apk, aotSnapshot: aotSizeJson);
|
final File precompilerTrace = fileSystem.file('trace.json')
|
||||||
|
..writeAsStringSync('{}');
|
||||||
|
await sizeAnalyzer.analyzeZipSizeAndAotSnapshot(
|
||||||
|
zipFile: apk,
|
||||||
|
aotSnapshot: aotSizeJson,
|
||||||
|
precompilerTrace: precompilerTrace,
|
||||||
|
kind: 'apk',
|
||||||
|
);
|
||||||
|
|
||||||
final List<String> stdout = logger.statusText.split('\n');
|
final List<String> stdout = logger.statusText.split('\n');
|
||||||
expect(
|
expect(
|
||||||
stdout,
|
stdout,
|
||||||
containsAll(<String>[
|
containsAll(<String>[
|
||||||
' AndroidManifest.xml 3 KB',
|
'test.apk (total compressed) 644 B',
|
||||||
' META-INF 15 KB',
|
'━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
|
||||||
' lib 28 KB',
|
' lib 12 B',
|
||||||
' lib/arm64-v8a/libxyzzyapp.so (Dart AOT) 14 KB',
|
' Dart AOT symbols accounted decompressed size 14 KB',
|
||||||
' Dart AOT symbols accounted decompressed size 14 KB',
|
' dart:core/',
|
||||||
' dart:_internal/SubListIterable 6 KB',
|
' RangeError 4 KB',
|
||||||
' @stubs/allocation-stubs/dart:core/ArgumentError 5 KB',
|
|
||||||
' dart:core/RangeError 4 KB',
|
|
||||||
' lib/arm64-v8a/libflutter.so (Flutter Engine) 14 KB',
|
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('can analyze contents of output directory', () async {
|
||||||
|
final SizeAnalyzer sizeAnalyzer = SizeAnalyzer(
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
logger: logger,
|
||||||
|
appFilenamePattern: RegExp(r'lib.*app\.so'),
|
||||||
|
);
|
||||||
|
|
||||||
|
final Directory outputDirectory = fileSystem.directory('example/out/foo.app')
|
||||||
|
..createSync(recursive: true);
|
||||||
|
outputDirectory.childFile('a.txt')
|
||||||
|
..createSync()
|
||||||
|
..writeAsStringSync('hello');
|
||||||
|
outputDirectory.childFile('libapp.so')
|
||||||
|
..createSync()
|
||||||
|
..writeAsStringSync('goodbye');
|
||||||
|
final File aotSizeJson = fileSystem.file('test.json')
|
||||||
|
..writeAsStringSync(aotSizeOutput);
|
||||||
|
final File precompilerTrace = fileSystem.file('trace.json')
|
||||||
|
..writeAsStringSync('{}');
|
||||||
|
|
||||||
|
final Map<String, Object> result = await sizeAnalyzer.analyzeAotSnapshot(
|
||||||
|
outputDirectory: outputDirectory,
|
||||||
|
aotSnapshot: aotSizeJson,
|
||||||
|
precompilerTrace: precompilerTrace,
|
||||||
|
type: 'linux',
|
||||||
|
);
|
||||||
|
|
||||||
|
final List<String> stdout = logger.statusText.split('\n');
|
||||||
|
expect(
|
||||||
|
stdout,
|
||||||
|
containsAll(<String>[
|
||||||
|
' foo.app 12 B',
|
||||||
|
' foo.app 12 B',
|
||||||
|
' Dart AOT symbols accounted decompressed size 14 KB',
|
||||||
|
' dart:core/',
|
||||||
|
' RangeError 4 KB',
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
expect(result['type'], 'linux');
|
||||||
|
expect(result['precompiler-trace'], <String, Object>{});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
|||||||
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() {
|
|||||||
|
|
||||||
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() {
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
'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() {
|
|||||||
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';
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
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,134 +2,133 @@
|
|||||||
// 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/memory.dart';
|
||||||
import 'package:file_testing/file_testing.dart';
|
import 'package:file_testing/file_testing.dart';
|
||||||
import 'package:flutter_tools/src/artifacts.dart';
|
import 'package:flutter_tools/src/artifacts.dart';
|
||||||
import 'package:flutter_tools/src/base/build.dart';
|
|
||||||
import 'package:flutter_tools/src/base/file_system.dart';
|
import 'package:flutter_tools/src/base/file_system.dart';
|
||||||
import 'package:flutter_tools/src/base/io.dart';
|
import 'package:flutter_tools/src/base/logger.dart';
|
||||||
import 'package:flutter_tools/src/base/platform.dart';
|
import 'package:flutter_tools/src/build_info.dart';
|
||||||
import 'package:flutter_tools/src/build_system/build_system.dart';
|
import 'package:flutter_tools/src/build_system/build_system.dart';
|
||||||
import 'package:flutter_tools/src/build_system/targets/assets.dart';
|
import 'package:flutter_tools/src/build_system/targets/assets.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/macos.dart';
|
import 'package:flutter_tools/src/build_system/targets/macos.dart';
|
||||||
import 'package:flutter_tools/src/convert.dart';
|
import 'package:flutter_tools/src/convert.dart';
|
||||||
import 'package:flutter_tools/src/globals.dart' as globals;
|
|
||||||
import 'package:flutter_tools/src/macos/xcode.dart';
|
|
||||||
import 'package:mockito/mockito.dart';
|
|
||||||
import 'package:process/process.dart';
|
|
||||||
|
|
||||||
import '../../../src/common.dart';
|
import '../../../src/common.dart';
|
||||||
|
import '../../../src/context.dart';
|
||||||
import '../../../src/fake_process_manager.dart';
|
import '../../../src/fake_process_manager.dart';
|
||||||
import '../../../src/testbed.dart';
|
|
||||||
|
|
||||||
const String _kInputPrefix = 'bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework';
|
|
||||||
const String _kOutputPrefix = 'FlutterMacOS.framework';
|
|
||||||
|
|
||||||
final List<String> inputs = <String>[
|
|
||||||
'$_kInputPrefix/FlutterMacOS',
|
|
||||||
// Headers
|
|
||||||
'$_kInputPrefix/Headers/FlutterDartProject.h',
|
|
||||||
'$_kInputPrefix/Headers/FlutterEngine.h',
|
|
||||||
'$_kInputPrefix/Headers/FlutterViewController.h',
|
|
||||||
'$_kInputPrefix/Headers/FlutterBinaryMessenger.h',
|
|
||||||
'$_kInputPrefix/Headers/FlutterChannels.h',
|
|
||||||
'$_kInputPrefix/Headers/FlutterCodecs.h',
|
|
||||||
'$_kInputPrefix/Headers/FlutterMacros.h',
|
|
||||||
'$_kInputPrefix/Headers/FlutterPluginMacOS.h',
|
|
||||||
'$_kInputPrefix/Headers/FlutterPluginRegistrarMacOS.h',
|
|
||||||
'$_kInputPrefix/Headers/FlutterMacOS.h',
|
|
||||||
// Modules
|
|
||||||
'$_kInputPrefix/Modules/module.modulemap',
|
|
||||||
// Resources
|
|
||||||
'$_kInputPrefix/Resources/icudtl.dat',
|
|
||||||
'$_kInputPrefix/Resources/Info.plist',
|
|
||||||
// Ignore Versions folder for now
|
|
||||||
'packages/flutter_tools/lib/src/build_system/targets/macos.dart',
|
|
||||||
];
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
Testbed testbed;
|
|
||||||
Environment environment;
|
Environment environment;
|
||||||
Platform platform;
|
FileSystem fileSystem;
|
||||||
|
Artifacts artifacts;
|
||||||
|
FakeProcessManager processManager;
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
platform = FakePlatform(operatingSystem: 'macos', environment: <String, String>{});
|
processManager = FakeProcessManager.any();
|
||||||
testbed = Testbed(setup: () {
|
artifacts = Artifacts.test();
|
||||||
environment = Environment.test(
|
fileSystem = MemoryFileSystem.test();
|
||||||
globals.fs.currentDirectory,
|
environment = Environment.test(
|
||||||
defines: <String, String>{
|
fileSystem.currentDirectory,
|
||||||
kBuildMode: 'debug',
|
defines: <String, String>{
|
||||||
kTargetPlatform: 'darwin-x64',
|
kBuildMode: 'debug',
|
||||||
},
|
kTargetPlatform: 'darwin-x64',
|
||||||
inputs: <String, String>{},
|
},
|
||||||
artifacts: MockArtifacts(),
|
inputs: <String, String>{},
|
||||||
processManager: FakeProcessManager.any(),
|
artifacts: artifacts,
|
||||||
logger: globals.logger,
|
processManager: processManager,
|
||||||
fileSystem: globals.fs,
|
logger: BufferLogger.test(),
|
||||||
engineVersion: '2'
|
fileSystem: fileSystem,
|
||||||
);
|
engineVersion: '2'
|
||||||
environment.buildDir.createSync(recursive: true);
|
);
|
||||||
}, overrides: <Type, Generator>{
|
|
||||||
ProcessManager: () => MockProcessManager(),
|
|
||||||
Platform: () => platform,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Copies files to correct cache directory', () => testbed.run(() async {
|
testUsingContext('Copies files to correct cache directory', () async {
|
||||||
for (final String input in inputs) {
|
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||||
globals.fs.file(input).createSync(recursive: true);
|
const FakeCommand(
|
||||||
}
|
command: <String>[
|
||||||
// Create output directory so we can test that it is deleted.
|
'cp',
|
||||||
environment.outputDir.childDirectory(_kOutputPrefix)
|
'-R',
|
||||||
.createSync(recursive: true);
|
'Artifact.flutterMacOSFramework.debug',
|
||||||
|
'/FlutterMacOS.framework',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
environment = Environment.test(
|
||||||
|
fileSystem.currentDirectory,
|
||||||
|
defines: <String, String>{
|
||||||
|
kBuildMode: 'debug',
|
||||||
|
kTargetPlatform: 'darwin-x64',
|
||||||
|
},
|
||||||
|
inputs: <String, String>{},
|
||||||
|
artifacts: artifacts,
|
||||||
|
processManager: processManager,
|
||||||
|
logger: BufferLogger.test(),
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
engineVersion: '2'
|
||||||
|
);
|
||||||
|
|
||||||
when(globals.processManager.run(any)).thenAnswer((Invocation invocation) async {
|
final Directory cacheDirectory = fileSystem.directory(
|
||||||
final List<String> arguments = invocation.positionalArguments.first as List<String>;
|
artifacts.getArtifactPath(
|
||||||
final String sourcePath = arguments[arguments.length - 2];
|
Artifact.flutterMacOSFramework,
|
||||||
final String targetPath = arguments.last;
|
mode: BuildMode.debug,
|
||||||
final Directory source = globals.fs.directory(sourcePath);
|
))
|
||||||
final Directory target = globals.fs.directory(targetPath);
|
..createSync();
|
||||||
|
cacheDirectory.childFile('dummy').createSync();
|
||||||
|
environment.buildDir.createSync(recursive: true);
|
||||||
|
environment.outputDir.createSync(recursive: true);
|
||||||
|
|
||||||
for (final FileSystemEntity entity in source.listSync(recursive: true)) {
|
|
||||||
if (entity is File) {
|
|
||||||
final String relative = globals.fs.path.relative(entity.path, from: source.path);
|
|
||||||
final String destination = globals.fs.path.join(target.path, relative);
|
|
||||||
if (!globals.fs.file(destination).parent.existsSync()) {
|
|
||||||
globals.fs.file(destination).parent.createSync();
|
|
||||||
}
|
|
||||||
entity.copySync(destination);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return FakeProcessResult()..exitCode = 0;
|
|
||||||
});
|
|
||||||
await const DebugUnpackMacOS().build(environment);
|
await const DebugUnpackMacOS().build(environment);
|
||||||
|
|
||||||
expect(globals.fs.directory(_kOutputPrefix).existsSync(), true);
|
expect(processManager.hasRemainingExpectations, false);
|
||||||
for (final String path in inputs) {
|
}, overrides: <Type, Generator>{
|
||||||
expect(globals.fs.file(path.replaceFirst(_kInputPrefix, _kOutputPrefix)), exists);
|
FileSystem: () => fileSystem,
|
||||||
}
|
ProcessManager: () => processManager,
|
||||||
}));
|
});
|
||||||
|
|
||||||
test('debug macOS application fails if App.framework missing', () => testbed.run(() async {
|
testUsingContext('debug macOS application fails if App.framework missing', () async {
|
||||||
final String inputKernel = globals.fs.path.join(environment.buildDir.path, 'app.dill');
|
fileSystem.directory(
|
||||||
globals.fs.file(inputKernel)
|
artifacts.getArtifactPath(
|
||||||
|
Artifact.flutterMacOSFramework,
|
||||||
|
mode: BuildMode.debug,
|
||||||
|
))
|
||||||
|
.createSync();
|
||||||
|
final String inputKernel = fileSystem.path.join(environment.buildDir.path, 'app.dill');
|
||||||
|
fileSystem.file(inputKernel)
|
||||||
..createSync(recursive: true)
|
..createSync(recursive: true)
|
||||||
..writeAsStringSync('testing');
|
..writeAsStringSync('testing');
|
||||||
|
|
||||||
expect(() async => await const DebugMacOSBundleFlutterAssets().build(environment),
|
expect(() async => await const DebugMacOSBundleFlutterAssets().build(environment),
|
||||||
throwsException);
|
throwsException);
|
||||||
}));
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => fileSystem,
|
||||||
|
ProcessManager: () => processManager,
|
||||||
|
});
|
||||||
|
|
||||||
test('debug macOS application creates correctly structured framework', () => testbed.run(() async {
|
testUsingContext('debug macOS application creates correctly structured framework', () async {
|
||||||
|
fileSystem.directory(
|
||||||
|
artifacts.getArtifactPath(
|
||||||
|
Artifact.flutterMacOSFramework,
|
||||||
|
mode: BuildMode.debug,
|
||||||
|
))
|
||||||
|
.createSync();
|
||||||
environment.inputs[kBundleSkSLPath] = 'bundle.sksl';
|
environment.inputs[kBundleSkSLPath] = 'bundle.sksl';
|
||||||
globals.fs.file('bin/cache/artifacts/engine/darwin-x64/vm_isolate_snapshot.bin')
|
fileSystem.file(
|
||||||
.createSync(recursive: true);
|
artifacts.getArtifactPath(
|
||||||
globals.fs.file('bin/cache/artifacts/engine/darwin-x64/isolate_snapshot.bin')
|
Artifact.vmSnapshotData,
|
||||||
.createSync(recursive: true);
|
platform: TargetPlatform.darwin_x64,
|
||||||
globals.fs.file('${environment.buildDir.path}/App.framework/App')
|
mode: BuildMode.debug,
|
||||||
|
)).createSync(recursive: true);
|
||||||
|
fileSystem.file(
|
||||||
|
artifacts.getArtifactPath(
|
||||||
|
Artifact.isolateSnapshotData,
|
||||||
|
platform: TargetPlatform.darwin_x64,
|
||||||
|
mode: BuildMode.debug,
|
||||||
|
)).createSync(recursive: true);
|
||||||
|
fileSystem.file('${environment.buildDir.path}/App.framework/App')
|
||||||
.createSync(recursive: true);
|
.createSync(recursive: true);
|
||||||
// sksl bundle
|
// sksl bundle
|
||||||
globals.fs.file('bundle.sksl').writeAsStringSync(json.encode(
|
fileSystem.file('bundle.sksl').writeAsStringSync(json.encode(
|
||||||
<String, Object>{
|
<String, Object>{
|
||||||
'engineRevision': '2',
|
'engineRevision': '2',
|
||||||
'platform': 'ios',
|
'platform': 'ios',
|
||||||
@ -140,70 +139,76 @@ void main() {
|
|||||||
));
|
));
|
||||||
|
|
||||||
final String inputKernel = '${environment.buildDir.path}/app.dill';
|
final String inputKernel = '${environment.buildDir.path}/app.dill';
|
||||||
globals.fs.file(inputKernel)
|
fileSystem.file(inputKernel)
|
||||||
..createSync(recursive: true)
|
..createSync(recursive: true)
|
||||||
..writeAsStringSync('testing');
|
..writeAsStringSync('testing');
|
||||||
|
|
||||||
await const DebugMacOSBundleFlutterAssets().build(environment);
|
await const DebugMacOSBundleFlutterAssets().build(environment);
|
||||||
|
|
||||||
expect(globals.fs.file(
|
expect(fileSystem.file(
|
||||||
'App.framework/Versions/A/Resources/flutter_assets/kernel_blob.bin').readAsStringSync(),
|
'App.framework/Versions/A/Resources/flutter_assets/kernel_blob.bin').readAsStringSync(),
|
||||||
'testing',
|
'testing',
|
||||||
);
|
);
|
||||||
expect(globals.fs.file(
|
expect(fileSystem.file(
|
||||||
'App.framework/Versions/A/Resources/Info.plist').readAsStringSync(),
|
'App.framework/Versions/A/Resources/Info.plist').readAsStringSync(),
|
||||||
contains('io.flutter.flutter.app'),
|
contains('io.flutter.flutter.app'),
|
||||||
);
|
);
|
||||||
expect(globals.fs.file(
|
expect(fileSystem.file(
|
||||||
'App.framework/Versions/A/Resources/flutter_assets/vm_snapshot_data'),
|
'App.framework/Versions/A/Resources/flutter_assets/vm_snapshot_data'),
|
||||||
exists,
|
exists,
|
||||||
);
|
);
|
||||||
expect(globals.fs.file(
|
expect(fileSystem.file(
|
||||||
'App.framework/Versions/A/Resources/flutter_assets/isolate_snapshot_data'),
|
'App.framework/Versions/A/Resources/flutter_assets/isolate_snapshot_data'),
|
||||||
exists,
|
exists,
|
||||||
);
|
);
|
||||||
|
|
||||||
final File skslFile = globals.fs.file('App.framework/Versions/A/Resources/flutter_assets/io.flutter.shaders.json');
|
final File skslFile = fileSystem.file('App.framework/Versions/A/Resources/flutter_assets/io.flutter.shaders.json');
|
||||||
|
|
||||||
expect(skslFile, exists);
|
expect(skslFile, exists);
|
||||||
expect(skslFile.readAsStringSync(), '{"data":{"A":"B"}}');
|
expect(skslFile.readAsStringSync(), '{"data":{"A":"B"}}');
|
||||||
}));
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => fileSystem,
|
||||||
|
ProcessManager: () => processManager,
|
||||||
|
});
|
||||||
|
|
||||||
test('release/profile macOS application has no blob or precompiled runtime', () => testbed.run(() async {
|
testUsingContext('release/profile macOS application has no blob or precompiled runtime', () async {
|
||||||
globals.fs.file('bin/cache/artifacts/engine/darwin-x64/vm_isolate_snapshot.bin')
|
fileSystem.file('bin/cache/artifacts/engine/darwin-x64/vm_isolate_snapshot.bin')
|
||||||
.createSync(recursive: true);
|
.createSync(recursive: true);
|
||||||
globals.fs.file('bin/cache/artifacts/engine/darwin-x64/isolate_snapshot.bin')
|
fileSystem.file('bin/cache/artifacts/engine/darwin-x64/isolate_snapshot.bin')
|
||||||
.createSync(recursive: true);
|
.createSync(recursive: true);
|
||||||
globals.fs.file('${environment.buildDir.path}/App.framework/App')
|
fileSystem.file('${environment.buildDir.path}/App.framework/App')
|
||||||
.createSync(recursive: true);
|
.createSync(recursive: true);
|
||||||
|
|
||||||
await const ProfileMacOSBundleFlutterAssets().build(environment..defines[kBuildMode] = 'profile');
|
await const ProfileMacOSBundleFlutterAssets().build(environment..defines[kBuildMode] = 'profile');
|
||||||
|
|
||||||
expect(globals.fs.file(
|
expect(fileSystem.file(
|
||||||
'App.framework/Versions/A/Resources/flutter_assets/kernel_blob.bin'),
|
'App.framework/Versions/A/Resources/flutter_assets/kernel_blob.bin'),
|
||||||
isNot(exists),
|
isNot(exists),
|
||||||
);
|
);
|
||||||
expect(globals.fs.file(
|
expect(fileSystem.file(
|
||||||
'App.framework/Versions/A/Resources/flutter_assets/vm_snapshot_data'),
|
'App.framework/Versions/A/Resources/flutter_assets/vm_snapshot_data'),
|
||||||
isNot(exists),
|
isNot(exists),
|
||||||
);
|
);
|
||||||
expect(globals.fs.file(
|
expect(fileSystem.file(
|
||||||
'App.framework/Versions/A/Resources/flutter_assets/isolate_snapshot_data'),
|
'App.framework/Versions/A/Resources/flutter_assets/isolate_snapshot_data'),
|
||||||
isNot(exists),
|
isNot(exists),
|
||||||
);
|
);
|
||||||
}));
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => fileSystem,
|
||||||
|
ProcessManager: () => processManager,
|
||||||
|
});
|
||||||
|
|
||||||
test('release/profile macOS application updates when App.framework updates', () => testbed.run(() async {
|
testUsingContext('release/profile macOS application updates when App.framework updates', () async {
|
||||||
globals.fs.file('bin/cache/artifacts/engine/darwin-x64/vm_isolate_snapshot.bin')
|
fileSystem.file('bin/cache/artifacts/engine/darwin-x64/vm_isolate_snapshot.bin')
|
||||||
.createSync(recursive: true);
|
.createSync(recursive: true);
|
||||||
globals.fs.file('bin/cache/artifacts/engine/darwin-x64/isolate_snapshot.bin')
|
fileSystem.file('bin/cache/artifacts/engine/darwin-x64/isolate_snapshot.bin')
|
||||||
.createSync(recursive: true);
|
.createSync(recursive: true);
|
||||||
final File inputFramework = globals.fs.file(globals.fs.path.join(environment.buildDir.path, 'App.framework', 'App'))
|
final File inputFramework = fileSystem.file(fileSystem.path.join(environment.buildDir.path, 'App.framework', 'App'))
|
||||||
..createSync(recursive: true)
|
..createSync(recursive: true)
|
||||||
..writeAsStringSync('ABC');
|
..writeAsStringSync('ABC');
|
||||||
|
|
||||||
await const ProfileMacOSBundleFlutterAssets().build(environment..defines[kBuildMode] = 'profile');
|
await const ProfileMacOSBundleFlutterAssets().build(environment..defines[kBuildMode] = 'profile');
|
||||||
final File outputFramework = globals.fs.file(globals.fs.path.join(environment.outputDir.path, 'App.framework', 'App'));
|
final File outputFramework = fileSystem.file(fileSystem.path.join(environment.outputDir.path, 'App.framework', 'App'));
|
||||||
|
|
||||||
expect(outputFramework.readAsStringSync(), 'ABC');
|
expect(outputFramework.readAsStringSync(), 'ABC');
|
||||||
|
|
||||||
@ -211,23 +216,8 @@ void main() {
|
|||||||
await const ProfileMacOSBundleFlutterAssets().build(environment..defines[kBuildMode] = 'profile');
|
await const ProfileMacOSBundleFlutterAssets().build(environment..defines[kBuildMode] = 'profile');
|
||||||
|
|
||||||
expect(outputFramework.readAsStringSync(), 'DEF');
|
expect(outputFramework.readAsStringSync(), 'DEF');
|
||||||
}));
|
}, overrides: <Type, Generator>{
|
||||||
}
|
FileSystem: () => fileSystem,
|
||||||
|
ProcessManager: () => processManager,
|
||||||
class MockProcessManager extends Mock implements ProcessManager {}
|
});
|
||||||
class MockGenSnapshot extends Mock implements GenSnapshot {}
|
|
||||||
class MockXcode extends Mock implements Xcode {}
|
|
||||||
class MockArtifacts extends Mock implements Artifacts {}
|
|
||||||
class FakeProcessResult implements ProcessResult {
|
|
||||||
@override
|
|
||||||
int exitCode;
|
|
||||||
|
|
||||||
@override
|
|
||||||
int pid = 0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String stderr = '';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String stdout = '';
|
|
||||||
}
|
}
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'package:flutter_tools/src/base/io.dart';
|
|
||||||
import 'package:flutter_tools/src/base/platform.dart';
|
|
||||||
import 'package:process/process.dart';
|
|
||||||
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: ';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
test('--analyze-size flag produces expected output on hello_world', () async {
|
|
||||||
final String flutterBin = globals.fs.path.join(getFlutterRoot(), 'bin', 'flutter');
|
|
||||||
final ProcessResult result = await const LocalProcessManager().run(<String>[
|
|
||||||
flutterBin,
|
|
||||||
'build',
|
|
||||||
'apk',
|
|
||||||
'--analyze-size',
|
|
||||||
], workingDirectory: globals.fs.path.join(getFlutterRoot(), 'examples', 'hello_world'));
|
|
||||||
|
|
||||||
print(result.stdout);
|
|
||||||
print(result.stderr);
|
|
||||||
expect(result.stdout.toString(), contains('app-release.apk (total compressed)'));
|
|
||||||
|
|
||||||
final String line = result.stdout.toString()
|
|
||||||
.split('\n')
|
|
||||||
.firstWhere((String line) => line.contains(debugMessage));
|
|
||||||
|
|
||||||
expect(globals.fs.file(globals.fs.path.join(line.split(debugMessage).last.trim())).existsSync(), true);
|
|
||||||
expect(result.exitCode, 0);
|
|
||||||
}, skip: const LocalPlatform().isWindows); // Not yet supported on Windows
|
|
||||||
}
|
|
@ -0,0 +1,82 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// 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';
|
||||||
|
import 'package:flutter_tools/src/globals.dart' as globals;
|
||||||
|
|
||||||
|
import '../src/common.dart';
|
||||||
|
|
||||||
|
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 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);
|
||||||
|
print(result.stderr);
|
||||||
|
expect(result.stdout.toString(), contains('app-release.apk (total compressed)'));
|
||||||
|
|
||||||
|
final String line = result.stdout.toString()
|
||||||
|
.split('\n')
|
||||||
|
.firstWhere((String line) => line.contains(apkDebugMessage));
|
||||||
|
|
||||||
|
final String outputFilePath = line.split(apkDebugMessage).last.trim();
|
||||||
|
expect(globals.fs.file(globals.fs.path.join(woringDirectory, outputFilePath)), exists);
|
||||||
|
expect(result.exitCode, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user