[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}"
|
||||
fi
|
||||
|
||||
code_size_directory=""
|
||||
if [[ -n "$CODE_SIZE_DIRECTORY" ]]; then
|
||||
code_size_directory="-dCodeSizeDirectory=${CODE_SIZE_DIRECTORY}"
|
||||
fi
|
||||
|
||||
RunCommand "${FLUTTER_ROOT}/bin/flutter" \
|
||||
${verbose_flag} \
|
||||
${flutter_engine_flag} \
|
||||
@ -86,6 +91,7 @@ RunCommand "${FLUTTER_ROOT}/bin/flutter" \
|
||||
-dSplitDebugInfo="${SPLIT_DEBUG_INFO}" \
|
||||
-dTrackWidgetCreation="${TRACK_WIDGET_CREATION}" \
|
||||
${bundle_sksl_path} \
|
||||
${code_size_directory} \
|
||||
--DartDefines="${DART_DEFINES}" \
|
||||
--ExtraGenSnapshotOptions="${EXTRA_GEN_SNAPSHOT_OPTIONS}" \
|
||||
--ExtraFrontEndOptions="${EXTRA_FRONT_END_OPTIONS}" \
|
||||
|
@ -19,6 +19,7 @@ Future<void> main(List<String> arguments) async {
|
||||
final String flutterRoot = Platform.environment['FLUTTER_ROOT'];
|
||||
final String flutterTarget = Platform.environment['FLUTTER_TARGET']
|
||||
?? pathJoin(<String>['lib', 'main.dart']);
|
||||
final String codeSizeDirectory = Platform.environment['CODE_SIZE_DIRECTORY'];
|
||||
final String localEngine = Platform.environment['LOCAL_ENGINE'];
|
||||
final String projectDirectory = Platform.environment['PROJECT_DIR'];
|
||||
final String splitDebugInfo = Platform.environment['SPLIT_DEBUG_INFO'];
|
||||
@ -70,6 +71,8 @@ or
|
||||
'-dDartObfuscation=$dartObfuscation',
|
||||
if (bundleSkSLPath != null)
|
||||
'-iBundleSkSLPath=$bundleSkSLPath',
|
||||
if (codeSizeDirectory != null)
|
||||
'-dCodeSizeDirectory=$codeSizeDirectory',
|
||||
if (splitDebugInfo != null)
|
||||
'-dSplitDebugInfo=$splitDebugInfo',
|
||||
if (dartDefines != null)
|
||||
|
@ -155,6 +155,11 @@ is set to release or run \"flutter build ios --release\", then re-run Archive fr
|
||||
performance_measurement_option="--performance-measurement-file=${PERFORMANCE_MEASUREMENT_FILE}"
|
||||
fi
|
||||
|
||||
local code_size_directory=""
|
||||
if [[ -n "$CODE_SIZE_DIRECTORY" ]]; then
|
||||
code_size_directory="-dCodeSizeDirectory=${CODE_SIZE_DIRECTORY}"
|
||||
fi
|
||||
|
||||
RunCommand "${FLUTTER_ROOT}/bin/flutter" \
|
||||
${verbose_flag} \
|
||||
${flutter_engine_flag} \
|
||||
@ -172,6 +177,7 @@ is set to release or run \"flutter build ios --release\", then re-run Archive fr
|
||||
-dDartObfuscation="${DART_OBFUSCATION}" \
|
||||
-dEnableBitcode="${bitcode_flag}" \
|
||||
${bundle_sksl_path} \
|
||||
${code_size_directory} \
|
||||
--ExtraGenSnapshotOptions="${EXTRA_GEN_SNAPSHOT_OPTIONS}" \
|
||||
--DartDefines="${DART_DEFINES}" \
|
||||
--ExtraFrontEndOptions="${EXTRA_FRONT_END_OPTIONS}" \
|
||||
|
@ -628,6 +628,10 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
if (project.hasProperty('performance-measurement-file')) {
|
||||
performanceMeasurementFileValue = project.property('performance-measurement-file')
|
||||
}
|
||||
String codeSizeDirectoryValue;
|
||||
if (project.hasProperty('code-size-directory')) {
|
||||
codeSizeDirectoryValue = project.property('code-size-directory')
|
||||
}
|
||||
def targetPlatforms = getTargetPlatforms()
|
||||
def addFlutterDeps = { variant ->
|
||||
if (shouldSplitPerAbi()) {
|
||||
@ -668,6 +672,7 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
dartDefines dartDefinesValue
|
||||
bundleSkSLPath bundleSkSLPathValue
|
||||
performanceMeasurementFile performanceMeasurementFileValue
|
||||
codeSizeDirectory codeSizeDirectoryValue
|
||||
doLast {
|
||||
project.exec {
|
||||
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
||||
@ -862,6 +867,8 @@ abstract class BaseFlutterTask extends DefaultTask {
|
||||
String dartDefines
|
||||
@Optional @Input
|
||||
String bundleSkSLPath
|
||||
@Optional @Input
|
||||
String codeSizeDirectory;
|
||||
String performanceMeasurementFile;
|
||||
|
||||
@OutputFiles
|
||||
@ -938,6 +945,9 @@ abstract class BaseFlutterTask extends DefaultTask {
|
||||
if (bundleSkSLPath != null) {
|
||||
args "-iBundleSkSLPath=${bundleSkSLPath}"
|
||||
}
|
||||
if (codeSizeDirectory != null) {
|
||||
args "-dCodeSizeDirectory=${codeSizeDirectory}"
|
||||
}
|
||||
if (extraGenSnapshotOptions != null) {
|
||||
args "--ExtraGenSnapshotOptions=${extraGenSnapshotOptions}"
|
||||
}
|
||||
|
@ -11,6 +11,13 @@ const String kSupportedAbis = 'https://flutter.dev/docs/deployment/android#what-
|
||||
/// Validates that the build mode and build number are valid for a given build.
|
||||
void validateBuild(AndroidBuildInfo androidBuildInfo) {
|
||||
final BuildInfo buildInfo = androidBuildInfo.buildInfo;
|
||||
if (buildInfo.codeSizeDirectory != null && androidBuildInfo.targetArchs.length > 1) {
|
||||
throwToolExit(
|
||||
'Cannot perform code size analysis when building for multiple ABIs. '
|
||||
'Specify one of android-arm, android-arm64, or android-x64 in the '
|
||||
'--target-plaform flag.'
|
||||
);
|
||||
}
|
||||
if (buildInfo.mode.isPrecompiled && androidBuildInfo.targetArchs.contains(AndroidArch.x86)) {
|
||||
throwToolExit(
|
||||
'Cannot build ${androidBuildInfo.buildInfo.mode.name} mode for x86 ABI.\n'
|
||||
|
@ -357,6 +357,9 @@ Future<void> buildGradleApp({
|
||||
if (androidBuildInfo.buildInfo.performanceMeasurementFile != null) {
|
||||
command.add('-Pperformance-measurement-file=${androidBuildInfo.buildInfo.performanceMeasurementFile}');
|
||||
}
|
||||
if (buildInfo.codeSizeDirectory != null) {
|
||||
command.add('-Pcode-size-directory=${buildInfo.codeSizeDirectory}');
|
||||
}
|
||||
command.add(assembleTask);
|
||||
|
||||
GradleHandledError detectedGradleError;
|
||||
@ -467,6 +470,10 @@ Future<void> buildGradleApp({
|
||||
? '' // Don't display the size when building a debug variant.
|
||||
: ' (${getSizeAsMB(bundleFile.lengthSync())})';
|
||||
|
||||
if (buildInfo.codeSizeDirectory != null) {
|
||||
await _performCodeSizeAnalysis('aab', bundleFile, androidBuildInfo);
|
||||
}
|
||||
|
||||
globals.printStatus(
|
||||
'$successMark Built ${globals.fs.path.relative(bundleFile.path)}$appSize.',
|
||||
color: TerminalColor.green,
|
||||
@ -502,26 +509,41 @@ Future<void> buildGradleApp({
|
||||
color: TerminalColor.green,
|
||||
);
|
||||
|
||||
// Call size analyzer if --analyze-size flag was provided.
|
||||
if (buildInfo.analyzeSize != null && !globals.platform.isWindows) {
|
||||
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}',
|
||||
);
|
||||
if (buildInfo.codeSizeDirectory != null) {
|
||||
await _performCodeSizeAnalysis('apk', apkFile, androidBuildInfo);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _performCodeSizeAnalysis(
|
||||
String kind,
|
||||
File zipFile,
|
||||
AndroidBuildInfo androidBuildInfo,
|
||||
) async {
|
||||
final SizeAnalyzer sizeAnalyzer = SizeAnalyzer(
|
||||
fileSystem: globals.fs,
|
||||
logger: globals.logger,
|
||||
);
|
||||
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.
|
||||
///
|
||||
/// * [project] is typically [FlutterProject.current()].
|
||||
|
@ -4,11 +4,12 @@
|
||||
|
||||
import 'package:meta/meta.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 '../convert.dart';
|
||||
import 'logger.dart';
|
||||
import 'process.dart';
|
||||
import 'terminal.dart';
|
||||
|
||||
/// A class to analyze APK and AOT snapshot and generate a breakdown of the data.
|
||||
@ -16,13 +17,11 @@ class SizeAnalyzer {
|
||||
SizeAnalyzer({
|
||||
@required this.fileSystem,
|
||||
@required this.logger,
|
||||
@required this.processUtils,
|
||||
this.appFilenamePattern = 'libapp.so',
|
||||
});
|
||||
|
||||
final FileSystem fileSystem;
|
||||
final Logger logger;
|
||||
final ProcessUtils processUtils;
|
||||
final Pattern appFilenamePattern;
|
||||
String _appFilename;
|
||||
|
||||
@ -30,44 +29,24 @@ class SizeAnalyzer {
|
||||
|
||||
static const int tableWidth = 80;
|
||||
|
||||
/// 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'.
|
||||
///
|
||||
/// The [aotSnapshot] can be either instruction sizes snapshot or v8 snapshot.
|
||||
Future<Map<String, dynamic>> analyzeApkSizeAndAotSnapshot({
|
||||
@required File apk,
|
||||
static const int _kAotSizeMaxDepth = 2;
|
||||
static const int _kZipSizeMaxDepth = 1;
|
||||
|
||||
/// Analyze the [aotSnapshot] in an uncompressed output directory.
|
||||
Future<Map<String, dynamic>> analyzeAotSnapshot({
|
||||
@required Directory outputDirectory,
|
||||
@required File aotSnapshot,
|
||||
@required File precompilerTrace,
|
||||
@required String type,
|
||||
String excludePath,
|
||||
}) async {
|
||||
logger.printStatus('▒' * tableWidth);
|
||||
_printEntitySize(
|
||||
'${apk.basename} (total compressed)',
|
||||
byteSize: apk.lengthSync(),
|
||||
level: 0,
|
||||
showColor: false,
|
||||
);
|
||||
logger.printStatus('━' * tableWidth);
|
||||
final Directory tempApkContent = fileSystem.systemTempDirectory.createTempSync('flutter_tools.');
|
||||
// TODO(peterdjlee): Implement a way to unzip the APK for Windows. See issue #62603.
|
||||
String unzipOut;
|
||||
try {
|
||||
// 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);
|
||||
final _SymbolNode aotAnalysisJson = _parseDirectory(
|
||||
outputDirectory,
|
||||
outputDirectory.parent.path,
|
||||
excludePath,
|
||||
);
|
||||
|
||||
// Convert an AOT snapshot file into a map.
|
||||
final Map<String, dynamic> processedAotSnapshotJson = treemapFromJson(
|
||||
@ -75,66 +54,122 @@ class SizeAnalyzer {
|
||||
);
|
||||
final _SymbolNode aotSnapshotJsonRoot = _parseAotSnapshot(processedAotSnapshotJson);
|
||||
|
||||
for (final _SymbolNode firstLevelPath in apkAnalysisRoot.children) {
|
||||
for (final _SymbolNode firstLevelPath in aotAnalysisJson.children) {
|
||||
_printEntitySize(
|
||||
firstLevelPath.name,
|
||||
byteSize: firstLevelPath.byteSize,
|
||||
level: 1,
|
||||
);
|
||||
// Print the expansion of lib directory to show more info for `appFilename`.
|
||||
if (firstLevelPath.name == 'lib') {
|
||||
_printLibChildrenPaths(firstLevelPath, '', aotSnapshotJsonRoot);
|
||||
if (firstLevelPath.name == fileSystem.path.basename(outputDirectory.path)) {
|
||||
_printLibChildrenPaths(firstLevelPath, '', aotSnapshotJsonRoot, _kAotSizeMaxDepth, 0);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
apkAnalysisJson['type'] = 'apk';
|
||||
apkAnalysisJson['type'] = kind;
|
||||
|
||||
// TODO(peterdjlee): Add aot snapshot for all platforms.
|
||||
assert(_appFilename != null);
|
||||
apkAnalysisJson = _addAotSnapshotDataToApkAnalysis(
|
||||
apkAnalysisJson = _addAotSnapshotDataToAnalysis(
|
||||
apkAnalysisJson: apkAnalysisJson,
|
||||
path: 'lib/arm64-v8a/$_appFilename (Dart AOT)'.split('/'), // Pass in a list of paths by splitting with '/'.
|
||||
path: _locatedAotFilePath,
|
||||
aotSnapshotJson: processedAotSnapshotJson,
|
||||
precompilerTrace: json.decode(precompilerTrace.readAsStringSync()) as Map<String, Object>,
|
||||
);
|
||||
|
||||
return apkAnalysisJson;
|
||||
}
|
||||
|
||||
|
||||
// Expression to match 'Size' column to group 1 and 'Name' column to group 2.
|
||||
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) {
|
||||
_SymbolNode _parseUnzipFile(File zipFile) {
|
||||
final Archive archive = ZipDecoder().decodeBytes(zipFile.readAsBytesSync());
|
||||
final Map<List<String>, int> pathsToSize = <List<String>, int>{};
|
||||
|
||||
// Parse each path into pathsToSize so that the key is a list of
|
||||
// path parts and the value is the size.
|
||||
// For example:
|
||||
// 'path/to/file' where file = 1500 => pathsToSize[['path', 'to', 'file']] = 1500
|
||||
for (final String line in const LineSplitter().convert(unzipOut)) {
|
||||
final RegExpMatch match = _parseUnzipOutput.firstMatch(line);
|
||||
if (match == null) {
|
||||
for (final ArchiveFile archiveFile in archive.files) {
|
||||
pathsToSize[fileSystem.path.split(archiveFile.name)] = archiveFile.rawContent.length;
|
||||
}
|
||||
return _buildSymbolTree(pathsToSize);
|
||||
}
|
||||
|
||||
_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;
|
||||
}
|
||||
const int sizeGroupIndex = 1;
|
||||
const int nameGroupIndex = 2;
|
||||
pathsToSize[match.group(nameGroupIndex).split('/')] = int.parse(match.group(sizeGroupIndex));
|
||||
final List<String> path = fileSystem.path.split(
|
||||
fileSystem.path.relative(file.path, from: relativeTo));
|
||||
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;
|
||||
|
||||
for (final List<String> paths in pathsToSize.keys) {
|
||||
for (final String path in paths) {
|
||||
_SymbolNode childWithPathAsName = currentNode.childByName(path);
|
||||
@ -144,6 +179,7 @@ class SizeAnalyzer {
|
||||
if (matchesPattern(path, pattern: appFilenamePattern) != null) {
|
||||
_appFilename = path;
|
||||
childWithPathAsName.name += ' (Dart AOT)';
|
||||
_locatedAotFilePath = _buildNodeName(childWithPathAsName, currentNode);
|
||||
} else if (path == 'libflutter.so') {
|
||||
childWithPathAsName.name += ' (Flutter Engine)';
|
||||
}
|
||||
@ -154,7 +190,6 @@ class SizeAnalyzer {
|
||||
}
|
||||
currentNode = rootNode;
|
||||
}
|
||||
|
||||
return rootNode;
|
||||
}
|
||||
|
||||
@ -165,52 +200,79 @@ class SizeAnalyzer {
|
||||
_SymbolNode currentNode,
|
||||
String totalPath,
|
||||
_SymbolNode aotSnapshotJsonRoot,
|
||||
int maxDepth,
|
||||
int currentDepth,
|
||||
) {
|
||||
totalPath += currentNode.name;
|
||||
|
||||
assert(_appFilename != null);
|
||||
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) {
|
||||
_printLibChildrenPaths(child, '$totalPath/', aotSnapshotJsonRoot);
|
||||
_printLibChildrenPaths(child, '$totalPath/', aotSnapshotJsonRoot, maxDepth, currentDepth + 1);
|
||||
}
|
||||
_leadingPaths = totalPath.split('/')
|
||||
..removeLast();
|
||||
} else {
|
||||
// Print total path and size if currentNode does not have any chilren.
|
||||
_printEntitySize(totalPath, byteSize: currentNode.byteSize, level: 2);
|
||||
|
||||
// We picked this file because arm64-v8a is likely the most popular
|
||||
// architecture. Other architecture sizes should be similar.
|
||||
final String libappPath = 'lib/arm64-v8a/$_appFilename';
|
||||
// TODO(peterdjlee): Analyze aot size for all platforms.
|
||||
if (totalPath.contains(libappPath)) {
|
||||
_printAotSnapshotSummary(aotSnapshotJsonRoot);
|
||||
// Print total path and size if currentNode does not have any children and is
|
||||
// larger than 1KB
|
||||
final bool isAotSnapshotPath = _locatedAotFilePath.join('/').contains(totalPath);
|
||||
if (currentNode.byteSize >= 1000 || isAotSnapshotPath) {
|
||||
_printEntitySize(totalPath, byteSize: currentNode.byteSize, level: 1, emphasis: currentNode.children.isNotEmpty);
|
||||
if (isAotSnapshotPath) {
|
||||
_printAotSnapshotSummary(aotSnapshotJsonRoot, level: totalPath.split('/').length);
|
||||
}
|
||||
_leadingPaths = totalPath.split('/')
|
||||
..removeLast();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Go through the AOT gen snapshot size JSON and print out a collapsed summary
|
||||
/// for the first package level.
|
||||
void _printAotSnapshotSummary(_SymbolNode aotSnapshotRoot, {int maxDirectoriesShown = 10}) {
|
||||
void _printAotSnapshotSummary(_SymbolNode aotSnapshotRoot, {int maxDirectoriesShown = 20, @required int level}) {
|
||||
_printEntitySize(
|
||||
'Dart AOT symbols accounted decompressed size',
|
||||
byteSize: aotSnapshotRoot.byteSize,
|
||||
level: 3,
|
||||
level: level,
|
||||
emphasis: true,
|
||||
);
|
||||
|
||||
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));
|
||||
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.
|
||||
Map<String, dynamic> _addAotSnapshotDataToApkAnalysis({
|
||||
Map<String, dynamic> _addAotSnapshotDataToAnalysis({
|
||||
@required Map<String, dynamic> apkAnalysisJson,
|
||||
@required List<String> path,
|
||||
@required Map<String, dynamic> aotSnapshotJson,
|
||||
@required Map<String, dynamic> precompilerTrace,
|
||||
}) {
|
||||
Map<String, dynamic> currentLevel = apkAnalysisJson;
|
||||
currentLevel['precompiler-trace'] = precompilerTrace;
|
||||
while (path.isNotEmpty) {
|
||||
final List<Map<String, dynamic>> children = currentLevel['children'] as List<Map<String, dynamic>>;
|
||||
final Map<String, dynamic> childWithPathAsName = children.firstWhere(
|
||||
@ -223,14 +285,16 @@ class SizeAnalyzer {
|
||||
return apkAnalysisJson;
|
||||
}
|
||||
|
||||
List<String> _leadingPaths = <String>[];
|
||||
|
||||
/// Print an entity's name with its size on the same line.
|
||||
void _printEntitySize(
|
||||
String entityName, {
|
||||
@required int byteSize,
|
||||
@required int level,
|
||||
bool showColor = true,
|
||||
bool emphasis = false,
|
||||
}) {
|
||||
final bool emphasis = level <= 1;
|
||||
final String formattedSize = _prettyPrintBytes(byteSize);
|
||||
|
||||
TerminalColor color = TerminalColor.green;
|
||||
@ -240,12 +304,32 @@ class SizeAnalyzer {
|
||||
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(
|
||||
entityName + ' ' * spaceInBetween,
|
||||
baseName + ' ' * spaceInBetween,
|
||||
newline: false,
|
||||
emphasis: emphasis,
|
||||
indent: level * 2,
|
||||
indent: (level + i) * 2,
|
||||
);
|
||||
logger.printStatus(formattedSize, color: showColor ? color : null);
|
||||
}
|
||||
|
@ -105,7 +105,23 @@ class FileSystemUtils {
|
||||
if (!file.existsSync()) {
|
||||
return file;
|
||||
}
|
||||
i++;
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Appends a number to a directory name in order to make it unique under a
|
||||
/// directory.
|
||||
Directory getUniqueDirectory(Directory dir, String baseName) {
|
||||
final FileSystem fs = dir.fileSystem;
|
||||
int i = 1;
|
||||
|
||||
while (true) {
|
||||
final String name = '${baseName}_${i.toString().padLeft(2, '0')}';
|
||||
final Directory directory = fs.directory(_fileSystem.path.join(dir.path, name));
|
||||
if (!directory.existsSync()) {
|
||||
return directory;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ class BuildInfo {
|
||||
this.performanceMeasurementFile,
|
||||
this.packagesPath = '.packages',
|
||||
this.nullSafetyMode = NullSafetyMode.autodetect,
|
||||
this.analyzeSize,
|
||||
this.codeSizeDirectory,
|
||||
});
|
||||
|
||||
final BuildMode mode;
|
||||
@ -114,9 +114,9 @@ class BuildInfo {
|
||||
/// rerun tasks.
|
||||
final String performanceMeasurementFile;
|
||||
|
||||
/// If provided, an output file where a v8-style heapsnapshot will be written for size
|
||||
/// profiling.
|
||||
final String analyzeSize;
|
||||
/// If provided, an output directory where one or more v8-style heapsnapshots
|
||||
/// will be written for code size profiling.
|
||||
final String codeSizeDirectory;
|
||||
|
||||
static const BuildInfo debug = BuildInfo(BuildMode.debug, null, treeShakeIcons: false);
|
||||
static const BuildInfo profile = BuildInfo(BuildMode.profile, null, treeShakeIcons: kIconTreeShakerEnabledDefault);
|
||||
@ -178,6 +178,8 @@ class BuildInfo {
|
||||
'BUNDLE_SKSL_PATH': bundleSkSLPath,
|
||||
if (packagesPath != null)
|
||||
'PACKAGE_CONFIG': packagesPath,
|
||||
if (codeSizeDirectory != null)
|
||||
'CODE_SIZE_DIRECTORY': codeSizeDirectory,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -698,7 +700,7 @@ String encodeDartDefines(List<String> defines) {
|
||||
/// Dart defines are encoded inside [environmentDefines] as a comma-separated list.
|
||||
List<String> decodeDartDefines(Map<String, String> environmentDefines, String key) {
|
||||
if (!environmentDefines.containsKey(key) || environmentDefines[key].isEmpty) {
|
||||
return const <String>[];
|
||||
return <String>[];
|
||||
}
|
||||
return environmentDefines[key]
|
||||
.split(',')
|
||||
|
@ -232,6 +232,19 @@ class AndroidAot extends AotElfBase {
|
||||
final List<String> extraGenSnapshotOptions = decodeDartDefines(environment.defines, kExtraGenSnapshotOptions);
|
||||
final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
|
||||
final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true';
|
||||
final String codeSizeDirectory = environment.defines[kCodeSizeDirectory];
|
||||
|
||||
if (codeSizeDirectory != null) {
|
||||
final File codeSizeFile = environment.fileSystem
|
||||
.directory(codeSizeDirectory)
|
||||
.childFile('snapshot.$_androidAbiName.json');
|
||||
final File precompilerTraceFile = environment.fileSystem
|
||||
.directory(codeSizeDirectory)
|
||||
.childFile('trace.$_androidAbiName.json');
|
||||
extraGenSnapshotOptions.add('--write-v8-snapshot-profile-to=${codeSizeFile.path}');
|
||||
extraGenSnapshotOptions.add('--trace-precompiler-to=${precompilerTraceFile.path}');
|
||||
}
|
||||
|
||||
final int snapshotExitCode = await snapshotter.build(
|
||||
platform: targetPlatform,
|
||||
buildMode: buildMode,
|
||||
|
@ -68,6 +68,9 @@ const String kIosArchs = 'IosArchs';
|
||||
/// Whether to enable Dart obfuscation and where to save the symbol map.
|
||||
const String kDartObfuscation = 'DartObfuscation';
|
||||
|
||||
/// An output directory where one or more code-size measurements may be written.
|
||||
const String kCodeSizeDirectory = 'CodeSizeDirectory';
|
||||
|
||||
/// Copies the pre-built flutter bundle.
|
||||
// This is a one-off rule for implementing build bundle in terms of assemble.
|
||||
class CopyFlutterBundle extends Target {
|
||||
@ -295,6 +298,19 @@ abstract class AotElfBase extends Target {
|
||||
final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]);
|
||||
final String splitDebugInfo = environment.defines[kSplitDebugInfo];
|
||||
final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true';
|
||||
final String codeSizeDirectory = environment.defines[kCodeSizeDirectory];
|
||||
|
||||
if (codeSizeDirectory != null) {
|
||||
final File codeSizeFile = environment.fileSystem
|
||||
.directory(codeSizeDirectory)
|
||||
.childFile('snapshot.${environment.defines[kTargetPlatform]}.json');
|
||||
final File precompilerTraceFile = environment.fileSystem
|
||||
.directory(codeSizeDirectory)
|
||||
.childFile('trace.${environment.defines[kTargetPlatform]}.json');
|
||||
extraGenSnapshotOptions.add('--write-v8-snapshot-profile-to=${codeSizeFile.path}');
|
||||
extraGenSnapshotOptions.add('--trace-precompiler-to=${precompilerTraceFile.path}');
|
||||
}
|
||||
|
||||
final int snapshotExitCode = await snapshotter.build(
|
||||
platform: targetPlatform,
|
||||
buildMode: buildMode,
|
||||
|
@ -52,7 +52,7 @@ abstract class AotAssemblyBase extends Target {
|
||||
final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]);
|
||||
final String splitDebugInfo = environment.defines[kSplitDebugInfo];
|
||||
final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true';
|
||||
final List<DarwinArch> iosArchs = environment.defines[kIosArchs]
|
||||
final List<DarwinArch> darwinArchs = environment.defines[kIosArchs]
|
||||
?.split(' ')
|
||||
?.map(getIOSArchForName)
|
||||
?.toList()
|
||||
@ -60,29 +60,41 @@ abstract class AotAssemblyBase extends Target {
|
||||
if (targetPlatform != TargetPlatform.ios) {
|
||||
throw Exception('aot_assembly is only supported for iOS applications.');
|
||||
}
|
||||
if (iosArchs.contains(DarwinArch.x86_64)) {
|
||||
if (darwinArchs.contains(DarwinArch.x86_64)) {
|
||||
throw Exception(
|
||||
'release/profile builds are only supported for physical devices. '
|
||||
'attempted to build for $iosArchs.'
|
||||
'attempted to build for $darwinArchs.'
|
||||
);
|
||||
}
|
||||
final String codeSizeDirectory = environment.defines[kCodeSizeDirectory];
|
||||
|
||||
// If we're building multiple iOS archs the binaries need to be lipo'd
|
||||
// together.
|
||||
final List<Future<int>> pending = <Future<int>>[];
|
||||
for (final DarwinArch iosArch in iosArchs) {
|
||||
for (final DarwinArch darwinArch in darwinArchs) {
|
||||
final List<String> archExtraGenSnapshotOptions = List<String>.of(extraGenSnapshotOptions);
|
||||
if (codeSizeDirectory != null) {
|
||||
final File codeSizeFile = environment.fileSystem
|
||||
.directory(codeSizeDirectory)
|
||||
.childFile('snapshot.${getNameForDarwinArch(darwinArch)}.json');
|
||||
final File precompilerTraceFile = environment.fileSystem
|
||||
.directory(codeSizeDirectory)
|
||||
.childFile('trace.${getNameForDarwinArch(darwinArch)}.json');
|
||||
archExtraGenSnapshotOptions.add('--write-v8-snapshot-profile-to=${codeSizeFile.path}');
|
||||
archExtraGenSnapshotOptions.add('--trace-precompiler-to=${precompilerTraceFile.path}');
|
||||
}
|
||||
pending.add(snapshotter.build(
|
||||
platform: targetPlatform,
|
||||
buildMode: buildMode,
|
||||
mainPath: environment.buildDir.childFile('app.dill').path,
|
||||
packagesPath: environment.projectDir.childFile('.packages').path,
|
||||
outputPath: environment.fileSystem.path.join(buildOutputPath, getNameForDarwinArch(iosArch)),
|
||||
darwinArch: iosArch,
|
||||
outputPath: environment.fileSystem.path.join(buildOutputPath, getNameForDarwinArch(darwinArch)),
|
||||
darwinArch: darwinArch,
|
||||
bitcode: bitcode,
|
||||
quiet: true,
|
||||
splitDebugInfo: splitDebugInfo,
|
||||
dartObfuscation: dartObfuscation,
|
||||
extraGenSnapshotOptions: extraGenSnapshotOptions,
|
||||
extraGenSnapshotOptions: archExtraGenSnapshotOptions,
|
||||
));
|
||||
}
|
||||
final List<int> results = await Future.wait(pending);
|
||||
@ -93,7 +105,7 @@ abstract class AotAssemblyBase extends Target {
|
||||
environment.fileSystem.directory(resultPath).parent.createSync(recursive: true);
|
||||
final ProcessResult result = await environment.processManager.run(<String>[
|
||||
'lipo',
|
||||
...iosArchs.map((DarwinArch iosArch) =>
|
||||
...darwinArchs.map((DarwinArch iosArch) =>
|
||||
environment.fileSystem.path.join(buildOutputPath, getNameForDarwinArch(iosArch), 'App.framework', 'App')),
|
||||
'-create',
|
||||
'-output',
|
||||
|
@ -8,7 +8,7 @@ import '../../base/file_system.dart';
|
||||
import '../../base/io.dart';
|
||||
import '../../base/process.dart';
|
||||
import '../../build_info.dart';
|
||||
import '../../globals.dart' as globals;
|
||||
import '../../globals.dart' as globals hide fs, logger, artifacts, processManager;
|
||||
import '../build_system.dart';
|
||||
import '../depfile.dart';
|
||||
import '../exceptions.dart';
|
||||
@ -52,7 +52,7 @@ abstract class UnpackMacOS extends Target {
|
||||
throw MissingDefineException(kBuildMode, 'unpack_macos');
|
||||
}
|
||||
final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
|
||||
final String basePath = globals.artifacts.getArtifactPath(Artifact.flutterMacOSFramework, mode: buildMode);
|
||||
final String basePath = environment.artifacts.getArtifactPath(Artifact.flutterMacOSFramework, mode: buildMode);
|
||||
final Directory targetDirectory = environment
|
||||
.outputDir
|
||||
.childDirectory('FlutterMacOS.framework');
|
||||
@ -61,15 +61,15 @@ abstract class UnpackMacOS extends Target {
|
||||
if (targetDirectory.existsSync()) {
|
||||
targetDirectory.deleteSync(recursive: true);
|
||||
}
|
||||
final List<File> inputs = globals.fs.directory(basePath)
|
||||
final List<File> inputs = environment.fileSystem.directory(basePath)
|
||||
.listSync(recursive: true)
|
||||
.whereType<File>()
|
||||
.toList();
|
||||
final List<File> outputs = inputs.map((File file) {
|
||||
final String relativePath = globals.fs.path.relative(file.path, from: basePath);
|
||||
return globals.fs.file(globals.fs.path.join(targetDirectory.path, relativePath));
|
||||
final String relativePath = environment.fileSystem.path.relative(file.path, from: basePath);
|
||||
return environment.fileSystem.file(environment.fileSystem.path.join(targetDirectory.path, relativePath));
|
||||
}).toList();
|
||||
final ProcessResult result = await globals.processManager
|
||||
final ProcessResult result = await environment.processManager
|
||||
.run(<String>['cp', '-R', basePath, targetDirectory.path]);
|
||||
if (result.exitCode != 0) {
|
||||
throw Exception(
|
||||
@ -78,8 +78,8 @@ abstract class UnpackMacOS extends Target {
|
||||
);
|
||||
}
|
||||
final DepfileService depfileService = DepfileService(
|
||||
logger: globals.logger,
|
||||
fileSystem: globals.fs,
|
||||
logger: environment.logger,
|
||||
fileSystem: environment.fileSystem,
|
||||
);
|
||||
depfileService.writeToFile(
|
||||
Depfile(inputs, outputs),
|
||||
@ -143,7 +143,7 @@ class DebugMacOSFramework extends Target {
|
||||
|
||||
@override
|
||||
Future<void> build(Environment environment) async {
|
||||
final File outputFile = globals.fs.file(globals.fs.path.join(
|
||||
final File outputFile = environment.fileSystem.file(environment.fileSystem.path.join(
|
||||
environment.buildDir.path, 'App.framework', 'App'));
|
||||
outputFile.createSync(recursive: true);
|
||||
final File debugApp = environment.buildDir.childFile('debug_app.cc')
|
||||
@ -195,16 +195,30 @@ class CompileMacOSFramework extends Target {
|
||||
if (buildMode == BuildMode.debug) {
|
||||
throw Exception('precompiled macOS framework only supported in release/profile builds.');
|
||||
}
|
||||
final String codeSizeDirectory = environment.defines[kCodeSizeDirectory];
|
||||
final String splitDebugInfo = environment.defines[kSplitDebugInfo];
|
||||
final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true';
|
||||
final List<String> extraGenSnapshotOptions = decodeDartDefines(environment.defines, kExtraGenSnapshotOptions);
|
||||
|
||||
if (codeSizeDirectory != null) {
|
||||
final File codeSizeFile = environment.fileSystem
|
||||
.directory(codeSizeDirectory)
|
||||
.childFile('snapshot.${getNameForDarwinArch(DarwinArch.x86_64)}.json');
|
||||
extraGenSnapshotOptions.add('--write-v8-snapshot-profile-to=${codeSizeFile.path}');
|
||||
final File precompilerTraceFile = environment.fileSystem
|
||||
.directory(codeSizeDirectory)
|
||||
.childFile('trace.${getNameForDarwinArch(DarwinArch.x86_64)}.json');
|
||||
extraGenSnapshotOptions.add('--write-v8-snapshot-profile-to=${codeSizeFile.path}');
|
||||
extraGenSnapshotOptions.add('--trace-precompiler-to=${precompilerTraceFile.path}');
|
||||
}
|
||||
|
||||
final AOTSnapshotter snapshotter = AOTSnapshotter(
|
||||
reportTimings: false,
|
||||
fileSystem: globals.fs,
|
||||
logger: globals.logger,
|
||||
fileSystem: environment.fileSystem,
|
||||
logger: environment.logger,
|
||||
xcode: globals.xcode,
|
||||
artifacts: globals.artifacts,
|
||||
processManager: globals.processManager
|
||||
artifacts: environment.artifacts,
|
||||
processManager: environment.processManager
|
||||
);
|
||||
final int result = await snapshotter.build(
|
||||
bitcode: false,
|
||||
@ -299,8 +313,8 @@ abstract class MacOSBundleFlutterAssets extends Target {
|
||||
targetPlatform: TargetPlatform.darwin_x64,
|
||||
);
|
||||
final DepfileService depfileService = DepfileService(
|
||||
fileSystem: globals.fs,
|
||||
logger: globals.logger,
|
||||
fileSystem: environment.fileSystem,
|
||||
logger: environment.logger,
|
||||
);
|
||||
depfileService.writeToFile(
|
||||
assetDepfile,
|
||||
@ -345,13 +359,13 @@ abstract class MacOSBundleFlutterAssets extends Target {
|
||||
}
|
||||
// Copy precompiled runtimes.
|
||||
try {
|
||||
final String vmSnapshotData = globals.artifacts.getArtifactPath(Artifact.vmSnapshotData,
|
||||
final String vmSnapshotData = environment.artifacts.getArtifactPath(Artifact.vmSnapshotData,
|
||||
platform: TargetPlatform.darwin_x64, mode: BuildMode.debug);
|
||||
final String isolateSnapshotData = globals.artifacts.getArtifactPath(Artifact.isolateSnapshotData,
|
||||
final String isolateSnapshotData = environment.artifacts.getArtifactPath(Artifact.isolateSnapshotData,
|
||||
platform: TargetPlatform.darwin_x64, mode: BuildMode.debug);
|
||||
globals.fs.file(vmSnapshotData).copySync(
|
||||
environment.fileSystem.file(vmSnapshotData).copySync(
|
||||
assetDirectory.childFile('vm_snapshot_data').path);
|
||||
globals.fs.file(isolateSnapshotData).copySync(
|
||||
environment.fileSystem.file(isolateSnapshotData).copySync(
|
||||
assetDirectory.childFile('isolate_snapshot_data').path);
|
||||
} on Exception catch (err) {
|
||||
throw Exception('Failed to copy precompiled runtimes: $err');
|
||||
@ -364,7 +378,7 @@ abstract class MacOSBundleFlutterAssets extends Target {
|
||||
final Link currentVersion = outputDirectory.parent
|
||||
.childLink('Current');
|
||||
if (!currentVersion.existsSync()) {
|
||||
final String linkPath = globals.fs.path.relative(outputDirectory.path,
|
||||
final String linkPath = environment.fileSystem.path.relative(outputDirectory.path,
|
||||
from: outputDirectory.parent.path);
|
||||
currentVersion.createSync(linkPath);
|
||||
}
|
||||
@ -372,7 +386,7 @@ abstract class MacOSBundleFlutterAssets extends Target {
|
||||
final Link currentResources = frameworkRootDirectory
|
||||
.childLink('Resources');
|
||||
if (!currentResources.existsSync()) {
|
||||
final String linkPath = globals.fs.path.relative(globals.fs.path.join(currentVersion.path, 'Resources'),
|
||||
final String linkPath = environment.fileSystem.path.relative(environment.fileSystem.path.join(currentVersion.path, 'Resources'),
|
||||
from: frameworkRootDirectory.path);
|
||||
currentResources.createSync(linkPath);
|
||||
}
|
||||
@ -380,7 +394,7 @@ abstract class MacOSBundleFlutterAssets extends Target {
|
||||
final Link currentFramework = frameworkRootDirectory
|
||||
.childLink('App');
|
||||
if (!currentFramework.existsSync()) {
|
||||
final String linkPath = globals.fs.path.relative(globals.fs.path.join(currentVersion.path, 'App'),
|
||||
final String linkPath = environment.fileSystem.path.relative(environment.fileSystem.path.join(currentVersion.path, 'App'),
|
||||
from: frameworkRootDirectory.path);
|
||||
currentFramework.createSync(linkPath);
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ class BuildAppBundleCommand extends BuildSubCommand {
|
||||
usesTrackWidgetCreation(verboseHelp: verboseHelp);
|
||||
addNullSafetyModeOptions(hide: !verboseHelp);
|
||||
addEnableExperimentation(hide: !verboseHelp);
|
||||
usesAnalyzeSizeFlag();
|
||||
argParser.addMultiOption('target-platform',
|
||||
splitCommas: true,
|
||||
defaultsTo: <String>['android-arm', 'android-arm64', 'android-x64'],
|
||||
|
@ -4,12 +4,15 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:file/file.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../application_package.dart';
|
||||
import '../base/analyze_size.dart';
|
||||
import '../base/common.dart';
|
||||
import '../base/utils.dart';
|
||||
import '../build_info.dart';
|
||||
import '../convert.dart';
|
||||
import '../globals.dart' as globals;
|
||||
import '../ios/mac.dart';
|
||||
import '../runner/flutter_command.dart' show DevelopmentArtifact, FlutterCommandResult;
|
||||
@ -35,6 +38,7 @@ class BuildIOSCommand extends BuildSubCommand {
|
||||
addBuildPerformanceFile(hide: !verboseHelp);
|
||||
addBundleSkSLPathOption(hide: !verboseHelp);
|
||||
addNullSafetyModeOptions(hide: !verboseHelp);
|
||||
usesAnalyzeSizeFlag();
|
||||
argParser
|
||||
..addFlag('simulator',
|
||||
help: 'Build for the iOS simulator instead of the device. This changes '
|
||||
@ -103,6 +107,44 @@ class BuildIOSCommand extends BuildSubCommand {
|
||||
throwToolExit('Encountered error while building for $logTarget.');
|
||||
}
|
||||
|
||||
if (buildInfo.codeSizeDirectory != null) {
|
||||
final SizeAnalyzer sizeAnalyzer = SizeAnalyzer(
|
||||
fileSystem: globals.fs,
|
||||
logger: globals.logger,
|
||||
appFilenamePattern: 'App'
|
||||
);
|
||||
// Only support 64bit iOS code size analysis.
|
||||
final String arch = getNameForDarwinArch(DarwinArch.arm64);
|
||||
final File aotSnapshot = globals.fs.directory(buildInfo.codeSizeDirectory)
|
||||
.childFile('snapshot.$arch.json');
|
||||
final File precompilerTrace = globals.fs.directory(buildInfo.codeSizeDirectory)
|
||||
.childFile('trace.$arch.json');
|
||||
|
||||
// This analysis is only supported for release builds, which also excludes the simulator.
|
||||
// Attempt to guess the correct .app by picking the first one.
|
||||
final Directory candidateDirectory = globals.fs.directory(
|
||||
globals.fs.path.join(getIosBuildDirectory(), 'Release-iphoneos'),
|
||||
);
|
||||
final Directory appDirectory = candidateDirectory.listSync()
|
||||
.whereType<Directory>()
|
||||
.firstWhere((Directory directory) {
|
||||
return globals.fs.path.extension(directory.path) == '.app';
|
||||
});
|
||||
final Map<String, Object> output = await sizeAnalyzer.analyzeAotSnapshot(
|
||||
aotSnapshot: aotSnapshot,
|
||||
precompilerTrace: precompilerTrace,
|
||||
outputDirectory: appDirectory,
|
||||
type: 'ios',
|
||||
);
|
||||
final File outputFile = globals.fsUtils.getUniqueFile(
|
||||
globals.fs.directory(getBuildDirectory()),'ios-code-size-analysis', 'json',
|
||||
)..writeAsStringSync(jsonEncode(output));
|
||||
// This message is used as a sentinel in analyze_apk_size_test.dart
|
||||
globals.printStatus(
|
||||
'A summary of your iOS bundle analysis can be found at: ${outputFile.path}',
|
||||
);
|
||||
}
|
||||
|
||||
if (result.output != null) {
|
||||
globals.printStatus('Built ${result.output}.');
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import '../base/analyze_size.dart';
|
||||
import '../base/common.dart';
|
||||
import '../build_info.dart';
|
||||
import '../cache.dart';
|
||||
@ -30,6 +31,7 @@ class BuildLinuxCommand extends BuildSubCommand {
|
||||
addBuildPerformanceFile(hide: !verboseHelp);
|
||||
addBundleSkSLPathOption(hide: !verboseHelp);
|
||||
addNullSafetyModeOptions(hide: !verboseHelp);
|
||||
usesAnalyzeSizeFlag();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -56,7 +58,15 @@ class BuildLinuxCommand extends BuildSubCommand {
|
||||
if (!globals.platform.isLinux) {
|
||||
throwToolExit('"build linux" only supported on Linux hosts.');
|
||||
}
|
||||
await buildLinux(flutterProject.linux, buildInfo, target: targetFile);
|
||||
await buildLinux(
|
||||
flutterProject.linux,
|
||||
buildInfo,
|
||||
target: targetFile,
|
||||
sizeAnalyzer: SizeAnalyzer(
|
||||
fileSystem: globals.fs,
|
||||
logger: globals.logger,
|
||||
),
|
||||
);
|
||||
return FlutterCommandResult.success();
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../base/analyze_size.dart';
|
||||
import '../base/common.dart';
|
||||
import '../build_info.dart';
|
||||
import '../cache.dart';
|
||||
@ -31,6 +32,7 @@ class BuildMacosCommand extends BuildSubCommand {
|
||||
addBuildPerformanceFile(hide: !verboseHelp);
|
||||
addBundleSkSLPathOption(hide: !verboseHelp);
|
||||
addNullSafetyModeOptions(hide: !verboseHelp);
|
||||
usesAnalyzeSizeFlag();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -62,6 +64,11 @@ class BuildMacosCommand extends BuildSubCommand {
|
||||
buildInfo: buildInfo,
|
||||
targetOverride: targetFile,
|
||||
verboseLogging: globals.logger.isVerbose,
|
||||
sizeAnalyzer: SizeAnalyzer(
|
||||
fileSystem: globals.fs,
|
||||
logger: globals.logger,
|
||||
appFilenamePattern: 'App',
|
||||
),
|
||||
);
|
||||
return FlutterCommandResult.success();
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../base/analyze_size.dart';
|
||||
import '../base/common.dart';
|
||||
import '../build_info.dart';
|
||||
import '../cache.dart';
|
||||
@ -33,6 +34,7 @@ class BuildWindowsCommand extends BuildSubCommand {
|
||||
addBuildPerformanceFile(hide: !verboseHelp);
|
||||
addBundleSkSLPathOption(hide: !verboseHelp);
|
||||
addNullSafetyModeOptions(hide: !verboseHelp);
|
||||
usesAnalyzeSizeFlag();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -67,6 +69,11 @@ class BuildWindowsCommand extends BuildSubCommand {
|
||||
buildInfo,
|
||||
target: targetFile,
|
||||
visualStudioOverride: visualStudioOverride,
|
||||
sizeAnalyzer: SizeAnalyzer(
|
||||
fileSystem: globals.fs,
|
||||
logger: globals.logger,
|
||||
appFilenamePattern: 'app.so',
|
||||
),
|
||||
);
|
||||
return FlutterCommandResult.success();
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../artifacts.dart';
|
||||
import '../base/analyze_size.dart';
|
||||
import '../base/common.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/logger.dart';
|
||||
@ -11,6 +12,7 @@ import '../base/utils.dart';
|
||||
import '../build_info.dart';
|
||||
import '../cache.dart';
|
||||
import '../cmake.dart';
|
||||
import '../convert.dart';
|
||||
import '../globals.dart' as globals;
|
||||
import '../plugins.dart';
|
||||
import '../project.dart';
|
||||
@ -20,6 +22,7 @@ Future<void> buildLinux(
|
||||
LinuxProject linuxProject,
|
||||
BuildInfo buildInfo, {
|
||||
String target = 'lib/main.dart',
|
||||
SizeAnalyzer sizeAnalyzer,
|
||||
}) async {
|
||||
if (!linuxProject.cmakeFile.existsSync()) {
|
||||
throwToolExit('No Linux desktop project configured. See '
|
||||
@ -53,6 +56,29 @@ Future<void> buildLinux(
|
||||
} finally {
|
||||
status.cancel();
|
||||
}
|
||||
if (buildInfo.codeSizeDirectory != null && sizeAnalyzer != null) {
|
||||
final String arch = getNameForTargetPlatform(TargetPlatform.linux_x64);
|
||||
final File codeSizeFile = globals.fs.directory(buildInfo.codeSizeDirectory)
|
||||
.childFile('snapshot.$arch.json');
|
||||
final File precompilerTrace = globals.fs.directory(buildInfo.codeSizeDirectory)
|
||||
.childFile('trace.$arch.json');
|
||||
final Map<String, Object> output = await sizeAnalyzer.analyzeAotSnapshot(
|
||||
aotSnapshot: codeSizeFile,
|
||||
// This analysis is only supported for release builds.
|
||||
outputDirectory: globals.fs.directory(
|
||||
globals.fs.path.join(getLinuxBuildDirectory(), 'release', 'bundle'),
|
||||
),
|
||||
precompilerTrace: precompilerTrace,
|
||||
type: 'linux',
|
||||
);
|
||||
final File outputFile = globals.fsUtils.getUniqueFile(
|
||||
globals.fs.directory(getBuildDirectory()),'linux-code-size-analysis', 'json',
|
||||
)..writeAsStringSync(jsonEncode(output));
|
||||
// This message is used as a sentinel in analyze_apk_size_test.dart
|
||||
globals.printStatus(
|
||||
'A summary of your Linux bundle analysis can be found at: ${outputFile.path}',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _runCmake(String buildModeName, Directory sourceDir, Directory buildDir) async {
|
||||
|
@ -4,11 +4,13 @@
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../base/analyze_size.dart';
|
||||
import '../base/common.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/logger.dart';
|
||||
import '../base/process.dart';
|
||||
import '../build_info.dart';
|
||||
import '../convert.dart';
|
||||
import '../globals.dart' as globals;
|
||||
import '../ios/xcodeproj.dart';
|
||||
import '../project.dart';
|
||||
@ -25,6 +27,7 @@ Future<void> buildMacOS({
|
||||
BuildInfo buildInfo,
|
||||
String targetOverride,
|
||||
@required bool verboseLogging,
|
||||
SizeAnalyzer sizeAnalyzer,
|
||||
}) async {
|
||||
if (!flutterProject.macos.xcodeWorkspace.existsSync()) {
|
||||
throwToolExit('No macOS desktop project configured. '
|
||||
@ -106,5 +109,37 @@ Future<void> buildMacOS({
|
||||
if (result != 0) {
|
||||
throwToolExit('Build process failed');
|
||||
}
|
||||
if (buildInfo.codeSizeDirectory != null && sizeAnalyzer != null) {
|
||||
final String arch = getNameForDarwinArch(DarwinArch.x86_64);
|
||||
final File aotSnapshot = globals.fs.directory(buildInfo.codeSizeDirectory)
|
||||
.childFile('snapshot.$arch.json');
|
||||
final File precompilerTrace = globals.fs.directory(buildInfo.codeSizeDirectory)
|
||||
.childFile('trace.$arch.json');
|
||||
|
||||
// This analysis is only supported for release builds.
|
||||
// Attempt to guess the correct .app by picking the first one.
|
||||
final Directory candidateDirectory = globals.fs.directory(
|
||||
globals.fs.path.join(getMacOSBuildDirectory(), 'Build', 'Products', 'Release'),
|
||||
);
|
||||
final Directory appDirectory = candidateDirectory.listSync()
|
||||
.whereType<Directory>()
|
||||
.firstWhere((Directory directory) {
|
||||
return globals.fs.path.extension(directory.path) == '.app';
|
||||
});
|
||||
final Map<String, Object> output = await sizeAnalyzer.analyzeAotSnapshot(
|
||||
aotSnapshot: aotSnapshot,
|
||||
precompilerTrace: precompilerTrace,
|
||||
outputDirectory: appDirectory,
|
||||
type: 'macos',
|
||||
excludePath: 'Versions', // Avoid double counting caused by symlinks
|
||||
);
|
||||
final File outputFile = globals.fsUtils.getUniqueFile(
|
||||
globals.fs.directory(getBuildDirectory()),'macos-code-size-analysis', 'json',
|
||||
)..writeAsStringSync(jsonEncode(output));
|
||||
// This message is used as a sentinel in analyze_apk_size_test.dart
|
||||
globals.printStatus(
|
||||
'A summary of your macOS bundle analysis can be found at: ${outputFile.path}',
|
||||
);
|
||||
}
|
||||
globals.flutterUsage.sendTiming('build', 'xcode-macos', Duration(milliseconds: sw.elapsedMilliseconds));
|
||||
}
|
||||
|
@ -609,7 +609,9 @@ abstract class FlutterCommand extends Command<void> {
|
||||
FlutterOptions.kAnalyzeSize,
|
||||
defaultsTo: false,
|
||||
help: 'Whether to produce additional profile information for artifact output size. '
|
||||
'This flag is only supported on release builds on macOS/Linux hosts.'
|
||||
'This flag is only supported on release builds. When building for Android, a single '
|
||||
'ABI must be specified at a time with the --target-platform flag. When building for iOS, '
|
||||
'only the symbols from the arm64 architecture are used to analyze code size.'
|
||||
);
|
||||
}
|
||||
|
||||
@ -648,13 +650,14 @@ abstract class FlutterCommand extends Command<void> {
|
||||
}
|
||||
}
|
||||
|
||||
String analyzeSize;
|
||||
if (argParser.options.containsKey(FlutterOptions.kAnalyzeSize)
|
||||
&& boolArg(FlutterOptions.kAnalyzeSize)
|
||||
&& !globals.platform.isWindows) {
|
||||
final File file = globals.fsUtils.getUniqueFile(globals.fs.currentDirectory, 'flutter_size', 'json');
|
||||
extraGenSnapshotOptions.add('--write-v8-snapshot-profile-to=${file.path}');
|
||||
analyzeSize = file.path;
|
||||
String codeSizeDirectory;
|
||||
if (argParser.options.containsKey(FlutterOptions.kAnalyzeSize) && boolArg(FlutterOptions.kAnalyzeSize)) {
|
||||
final Directory directory = globals.fsUtils.getUniqueDirectory(
|
||||
globals.fs.directory(getBuildDirectory()),
|
||||
'flutter_size',
|
||||
);
|
||||
directory.createSync(recursive: true);
|
||||
codeSizeDirectory = directory.path;
|
||||
}
|
||||
|
||||
NullSafetyMode nullSafetyMode = NullSafetyMode.unsound;
|
||||
@ -688,7 +691,7 @@ abstract class FlutterCommand extends Command<void> {
|
||||
);
|
||||
}
|
||||
final BuildMode buildMode = forcedBuildMode ?? getBuildMode();
|
||||
if (buildMode != BuildMode.release && analyzeSize != null) {
|
||||
if (buildMode != BuildMode.release && codeSizeDirectory != null) {
|
||||
throwToolExit('--analyze-size can only be used on release builds.');
|
||||
}
|
||||
|
||||
@ -736,7 +739,7 @@ abstract class FlutterCommand extends Command<void> {
|
||||
performanceMeasurementFile: performanceMeasurementFile,
|
||||
packagesPath: globalResults['packages'] as String ?? '.packages',
|
||||
nullSafetyMode: nullSafetyMode,
|
||||
analyzeSize: analyzeSize,
|
||||
codeSizeDirectory: codeSizeDirectory,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../artifacts.dart';
|
||||
import '../base/analyze_size.dart';
|
||||
import '../base/common.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/logger.dart';
|
||||
@ -11,6 +12,7 @@ import '../base/utils.dart';
|
||||
import '../build_info.dart';
|
||||
import '../cache.dart';
|
||||
import '../cmake.dart';
|
||||
import '../convert.dart';
|
||||
import '../globals.dart' as globals;
|
||||
import '../plugins.dart';
|
||||
import '../project.dart';
|
||||
@ -25,6 +27,7 @@ const String _cmakeVisualStudioGeneratorIdentifier = 'Visual Studio 16 2019';
|
||||
Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, {
|
||||
String target,
|
||||
VisualStudio visualStudioOverride,
|
||||
SizeAnalyzer sizeAnalyzer,
|
||||
}) async {
|
||||
if (!windowsProject.cmakeFile.existsSync()) {
|
||||
throwToolExit(
|
||||
@ -75,6 +78,29 @@ Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, {
|
||||
} finally {
|
||||
status.cancel();
|
||||
}
|
||||
if (buildInfo.codeSizeDirectory != null && sizeAnalyzer != null) {
|
||||
final String arch = getNameForTargetPlatform(TargetPlatform.windows_x64);
|
||||
final File codeSizeFile = globals.fs.directory(buildInfo.codeSizeDirectory)
|
||||
.childFile('snapshot.$arch.json');
|
||||
final File precompilerTrace = globals.fs.directory(buildInfo.codeSizeDirectory)
|
||||
.childFile('trace.$arch.json');
|
||||
final Map<String, Object> output = await sizeAnalyzer.analyzeAotSnapshot(
|
||||
aotSnapshot: codeSizeFile,
|
||||
// This analysis is only supported for release builds.
|
||||
outputDirectory: globals.fs.directory(
|
||||
globals.fs.path.join(getWindowsBuildDirectory(), 'runner', 'Release'),
|
||||
),
|
||||
precompilerTrace: precompilerTrace,
|
||||
type: 'windows',
|
||||
);
|
||||
final File outputFile = globals.fsUtils.getUniqueFile(
|
||||
globals.fs.directory(getBuildDirectory()),'windows-code-size-analysis', 'json',
|
||||
)..writeAsStringSync(jsonEncode(output));
|
||||
// This message is used as a sentinel in analyze_apk_size_test.dart
|
||||
globals.printStatus(
|
||||
'A summary of your Windows bundle analysis can be found at: ${outputFile.path}',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _runCmakeGeneration(String cmakePath, Directory buildDir, Directory sourceDir) async {
|
||||
|
@ -2,34 +2,13 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:file/memory.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/logger.dart';
|
||||
import 'package:flutter_tools/src/base/process.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 = '''[
|
||||
{
|
||||
@ -62,12 +41,10 @@ const String aotSizeOutput = '''[
|
||||
void main() {
|
||||
MemoryFileSystem fileSystem;
|
||||
BufferLogger logger;
|
||||
FakeProcessManager processManager;
|
||||
|
||||
setUp(() {
|
||||
fileSystem = MemoryFileSystem.test();
|
||||
logger = BufferLogger.test();
|
||||
processManager = FakeProcessManager.list(<FakeCommand>[unzipCommmand]);
|
||||
});
|
||||
|
||||
test('matchesPattern matches only entire strings', () {
|
||||
@ -85,45 +62,56 @@ void main() {
|
||||
final SizeAnalyzer sizeAnalyzer = SizeAnalyzer(
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
processUtils: ProcessUtils(
|
||||
processManager: processManager,
|
||||
logger: logger,
|
||||
),
|
||||
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')
|
||||
..createSync()
|
||||
..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>;
|
||||
expect(androidManifestMap['n'], equals('AndroidManifest.xml'));
|
||||
expect(androidManifestMap['value'], equals(2592));
|
||||
expect(androidManifestMap['n'], 'AndroidManifest.xml');
|
||||
expect(androidManifestMap['value'], 6);
|
||||
|
||||
final Map<String, dynamic> metaInfMap = result['children'][1] as Map<String, dynamic>;
|
||||
expect(metaInfMap['n'], equals('META-INF'));
|
||||
expect(metaInfMap['value'], equals(15622));
|
||||
expect(metaInfMap['n'], 'META-INF');
|
||||
expect(metaInfMap['value'], 10);
|
||||
final Map<String, dynamic> certRsaMap = metaInfMap['children'][0] as Map<String, dynamic>;
|
||||
expect(certRsaMap['n'], equals('CERT.RSA'));
|
||||
expect(certRsaMap['value'], equals(1092));
|
||||
expect(certRsaMap['n'], 'CERT.RSA');
|
||||
expect(certRsaMap['value'], 5);
|
||||
final Map<String, dynamic> certSfMap = metaInfMap['children'][1] as Map<String, dynamic>;
|
||||
expect(certSfMap['n'], equals('CERT.SF'));
|
||||
expect(certSfMap['value'], equals(14530));
|
||||
expect(certSfMap['n'], 'CERT.SF');
|
||||
expect(certSfMap['value'], 5);
|
||||
|
||||
final Map<String, dynamic> libMap = result['children'][2] as Map<String, dynamic>;
|
||||
expect(libMap['n'], equals('lib'));
|
||||
expect(libMap['value'], equals(29060));
|
||||
expect(libMap['n'], 'lib');
|
||||
expect(libMap['value'], 12);
|
||||
final Map<String, dynamic> arm64Map = libMap['children'][0] as Map<String, dynamic>;
|
||||
expect(arm64Map['n'], equals('arm64-v8a'));
|
||||
expect(arm64Map['value'], equals(29060));
|
||||
expect(arm64Map['n'], 'arm64-v8a');
|
||||
expect(arm64Map['value'], 12);
|
||||
final Map<String, dynamic> libAppMap = arm64Map['children'][0] as Map<String, dynamic>;
|
||||
expect(libAppMap['n'], equals('libxyzzyapp.so (Dart AOT)'));
|
||||
expect(libAppMap['value'], equals(14530));
|
||||
expect(libAppMap['children'].length, equals(3));
|
||||
expect(libAppMap['n'], 'libxyzzyapp.so (Dart AOT)');
|
||||
expect(libAppMap['value'], 6);
|
||||
expect(libAppMap['children'].length, 3);
|
||||
final Map<String, dynamic> internalMap = libAppMap['children'][0] as Map<String, dynamic>;
|
||||
final Map<String, dynamic> skipMap = internalMap['children'][0] as Map<String, dynamic>;
|
||||
expect(skipMap['n'], 'skip');
|
||||
@ -140,41 +128,92 @@ void main() {
|
||||
expect(allocateMap['n'], 'Allocate ArgumentError');
|
||||
expect(allocateMap['value'], 4650);
|
||||
final Map<String, dynamic> libFlutterMap = arm64Map['children'][1] as Map<String, dynamic>;
|
||||
expect(libFlutterMap['n'], equals('libflutter.so (Flutter Engine)'));
|
||||
expect(libFlutterMap['value'], equals(14530));
|
||||
expect(libFlutterMap['n'], 'libflutter.so (Flutter Engine)');
|
||||
expect(libFlutterMap['value'], 6);
|
||||
|
||||
expect(result['precompiler-trace'], <String, Object>{});
|
||||
});
|
||||
|
||||
test('outputs summary to command line correctly', () async {
|
||||
final SizeAnalyzer sizeAnalyzer = SizeAnalyzer(
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
processUtils: ProcessUtils(
|
||||
processManager: processManager,
|
||||
logger: logger,
|
||||
),
|
||||
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')
|
||||
..createSync()
|
||||
..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');
|
||||
expect(
|
||||
stdout,
|
||||
containsAll(<String>[
|
||||
' AndroidManifest.xml 3 KB',
|
||||
' META-INF 15 KB',
|
||||
' lib 28 KB',
|
||||
' lib/arm64-v8a/libxyzzyapp.so (Dart AOT) 14 KB',
|
||||
' Dart AOT symbols accounted decompressed size 14 KB',
|
||||
' dart:_internal/SubListIterable 6 KB',
|
||||
' @stubs/allocation-stubs/dart:core/ArgumentError 5 KB',
|
||||
' dart:core/RangeError 4 KB',
|
||||
' lib/arm64-v8a/libflutter.so (Flutter Engine) 14 KB',
|
||||
'test.apk (total compressed) 644 B',
|
||||
'━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
|
||||
' lib 12 B',
|
||||
' Dart AOT symbols accounted decompressed size 14 KB',
|
||||
' dart:core/',
|
||||
' RangeError 4 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/context.dart';
|
||||
|
||||
class MockPlatform extends Mock implements Platform {}
|
||||
|
||||
void main() {
|
||||
group('ensureDirectoryExists', () {
|
||||
group('fsUtils', () {
|
||||
MemoryFileSystem fs;
|
||||
FileSystemUtils fsUtils;
|
||||
|
||||
@ -26,19 +24,37 @@ void main() {
|
||||
fs = MemoryFileSystem();
|
||||
fsUtils = FileSystemUtils(
|
||||
fileSystem: fs,
|
||||
platform: MockPlatform(),
|
||||
platform: FakePlatform(),
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('recursively creates a directory if it does not exist', () async {
|
||||
testWithoutContext('ensureDirectoryExists recursively creates a directory if it does not exist', () async {
|
||||
fsUtils.ensureDirectoryExists('foo/bar/baz.flx');
|
||||
expect(fs.isDirectorySync('foo/bar'), true);
|
||||
});
|
||||
|
||||
testWithoutContext('throws tool exit on failure to create', () async {
|
||||
testWithoutContext('ensureDirectoryExists throws tool exit on failure to create', () async {
|
||||
fs.file('foo').createSync();
|
||||
expect(() => fsUtils.ensureDirectoryExists('foo/bar.flx'), throwsToolExit());
|
||||
});
|
||||
|
||||
testWithoutContext('getUniqueFile creates a unique file name', () async {
|
||||
final File fileA = fsUtils.getUniqueFile(fs.currentDirectory, 'foo', 'json')
|
||||
..createSync();
|
||||
final File fileB = fsUtils.getUniqueFile(fs.currentDirectory, 'foo', 'json');
|
||||
|
||||
expect(fileA.path, '/foo_01.json');
|
||||
expect(fileB.path, '/foo_02.json');
|
||||
});
|
||||
|
||||
testWithoutContext('getUniqueDirectory creates a unique directory name', () async {
|
||||
final Directory directoryA = fsUtils.getUniqueDirectory(fs.currentDirectory, 'foo')
|
||||
..createSync();
|
||||
final Directory directoryB = fsUtils.getUniqueDirectory(fs.currentDirectory, 'foo');
|
||||
|
||||
expect(directoryA.path, '/foo_01');
|
||||
expect(directoryB.path, '/foo_02');
|
||||
});
|
||||
});
|
||||
|
||||
group('copyDirectorySync', () {
|
||||
@ -61,7 +77,7 @@ void main() {
|
||||
|
||||
final FileSystemUtils fsUtils = FileSystemUtils(
|
||||
fileSystem: sourceMemoryFs,
|
||||
platform: MockPlatform(),
|
||||
platform: FakePlatform(),
|
||||
);
|
||||
fsUtils.copyDirectorySync(sourceDirectory, targetDirectory);
|
||||
|
||||
@ -81,7 +97,7 @@ void main() {
|
||||
final MemoryFileSystem fileSystem = MemoryFileSystem();
|
||||
final FileSystemUtils fsUtils = FileSystemUtils(
|
||||
fileSystem: fileSystem,
|
||||
platform: MockPlatform(),
|
||||
platform: FakePlatform(),
|
||||
);
|
||||
final Directory origin = fileSystem.directory('/origin');
|
||||
origin.createSync();
|
||||
|
@ -107,6 +107,7 @@ void main() {
|
||||
extraGenSnapshotOptions: <String>['--enable-experiment=non-nullable', 'fizz'],
|
||||
bundleSkSLPath: 'foo/bar/baz.sksl.json',
|
||||
packagesPath: 'foo/.packages',
|
||||
codeSizeDirectory: 'foo/code-size',
|
||||
);
|
||||
|
||||
expect(buildInfo.toEnvironmentConfig(), <String, String>{
|
||||
@ -119,6 +120,7 @@ void main() {
|
||||
'EXTRA_GEN_SNAPSHOT_OPTIONS': '--enable-experiment%3Dnon-nullable,fizz',
|
||||
'BUNDLE_SKSL_PATH': 'foo/bar/baz.sksl.json',
|
||||
'PACKAGE_CONFIG': 'foo/.packages',
|
||||
'CODE_SIZE_DIRECTORY': 'foo/code-size',
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -209,6 +209,50 @@ void main() {
|
||||
ProcessManager: () => processManager,
|
||||
});
|
||||
|
||||
testUsingContext('AndroidAot provide code size information.', () async {
|
||||
processManager = FakeProcessManager.list(<FakeCommand>[]);
|
||||
final Environment environment = Environment.test(
|
||||
fileSystem.currentDirectory,
|
||||
outputDir: fileSystem.directory('out')..createSync(),
|
||||
defines: <String, String>{
|
||||
kBuildMode: 'release',
|
||||
kCodeSizeDirectory: 'code_size_1',
|
||||
},
|
||||
artifacts: artifacts,
|
||||
processManager: processManager,
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
);
|
||||
processManager.addCommand(FakeCommand(command: <String>[
|
||||
artifacts.getArtifactPath(
|
||||
Artifact.genSnapshot,
|
||||
platform: TargetPlatform.android_arm64,
|
||||
mode: BuildMode.release,
|
||||
),
|
||||
'--deterministic',
|
||||
'--write-v8-snapshot-profile-to=code_size_1/snapshot.arm64-v8a.json',
|
||||
'--trace-precompiler-to=code_size_1/trace.arm64-v8a.json',
|
||||
'--snapshot_kind=app-aot-elf',
|
||||
'--elf=${environment.buildDir.childDirectory('arm64-v8a').childFile('app.so').path}',
|
||||
'--strip',
|
||||
'--no-causal-async-stacks',
|
||||
'--lazy-async-stacks',
|
||||
environment.buildDir.childFile('app.dill').path,
|
||||
],
|
||||
));
|
||||
environment.buildDir.createSync(recursive: true);
|
||||
environment.buildDir.childFile('app.dill').createSync();
|
||||
environment.projectDir.childFile('.packages').writeAsStringSync('\n');
|
||||
const AndroidAot androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release);
|
||||
|
||||
await androidAot.build(environment);
|
||||
|
||||
expect(processManager.hasRemainingExpectations, false);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fileSystem,
|
||||
ProcessManager: () => processManager,
|
||||
});
|
||||
|
||||
testUsingContext('kExtraGenSnapshotOptions passes values to gen_snapshot', () async {
|
||||
processManager = FakeProcessManager.list(<FakeCommand>[]);
|
||||
final Environment environment = Environment.test(
|
||||
|
@ -12,7 +12,6 @@ import 'package:flutter_tools/src/build_system/build_system.dart';
|
||||
import 'package:flutter_tools/src/build_system/exceptions.dart';
|
||||
import 'package:flutter_tools/src/build_system/targets/common.dart';
|
||||
import 'package:flutter_tools/src/build_system/targets/ios.dart';
|
||||
import 'package:flutter_tools/src/cache.dart';
|
||||
import 'package:flutter_tools/src/compile.dart';
|
||||
|
||||
import '../../../src/common.dart';
|
||||
@ -32,10 +31,6 @@ void main() {
|
||||
FileSystem fileSystem;
|
||||
Logger logger;
|
||||
|
||||
setUpAll(() {
|
||||
Cache.disableLocking();
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
processManager = FakeProcessManager.list(<FakeCommand>[]);
|
||||
logger = BufferLogger.test();
|
||||
@ -47,6 +42,7 @@ void main() {
|
||||
kBuildMode: getNameForBuildMode(BuildMode.profile),
|
||||
kTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm),
|
||||
},
|
||||
inputs: <String, String>{},
|
||||
artifacts: artifacts,
|
||||
processManager: processManager,
|
||||
fileSystem: fileSystem,
|
||||
@ -59,6 +55,7 @@ void main() {
|
||||
kBuildMode: getNameForBuildMode(BuildMode.profile),
|
||||
kTargetPlatform: getNameForTargetPlatform(TargetPlatform.ios),
|
||||
},
|
||||
inputs: <String, String>{},
|
||||
artifacts: artifacts,
|
||||
processManager: processManager,
|
||||
fileSystem: fileSystem,
|
||||
@ -357,6 +354,36 @@ void main() {
|
||||
expect(processManager.hasRemainingExpectations, false);
|
||||
});
|
||||
|
||||
testUsingContext('AotElfRelease configures gen_snapshot with code size directory', () async {
|
||||
androidEnvironment.defines[kCodeSizeDirectory] = 'code_size_1';
|
||||
final String build = androidEnvironment.buildDir.path;
|
||||
processManager.addCommands(<FakeCommand>[
|
||||
FakeCommand(command: <String>[
|
||||
artifacts.getArtifactPath(
|
||||
Artifact.genSnapshot,
|
||||
platform: TargetPlatform.android_arm,
|
||||
mode: BuildMode.profile,
|
||||
),
|
||||
'--deterministic',
|
||||
'--write-v8-snapshot-profile-to=code_size_1/snapshot.android-arm.json',
|
||||
'--trace-precompiler-to=code_size_1/trace.android-arm.json',
|
||||
kElfAot,
|
||||
'--elf=$build/app.so',
|
||||
'--strip',
|
||||
'--no-sim-use-hardfp',
|
||||
'--no-use-integer-division',
|
||||
'--no-causal-async-stacks',
|
||||
'--lazy-async-stacks',
|
||||
'$build/app.dill',
|
||||
])
|
||||
]);
|
||||
androidEnvironment.buildDir.childFile('app.dill').createSync(recursive: true);
|
||||
|
||||
await const AotElfRelease(TargetPlatform.android_arm).build(androidEnvironment);
|
||||
|
||||
expect(processManager.hasRemainingExpectations, false);
|
||||
});
|
||||
|
||||
testUsingContext('AotElfProfile throws error if missing build mode', () async {
|
||||
androidEnvironment.defines.remove(kBuildMode);
|
||||
|
||||
@ -611,6 +638,88 @@ void main() {
|
||||
ProcessManager: () => processManager,
|
||||
});
|
||||
|
||||
testUsingContext('AotAssemblyRelease configures gen_snapshot with code size directory', () async {
|
||||
iosEnvironment.defines[kCodeSizeDirectory] = 'code_size_1';
|
||||
iosEnvironment.defines[kIosArchs] = 'arm64';
|
||||
iosEnvironment.defines[kBitcodeFlag] = 'true';
|
||||
final String build = iosEnvironment.buildDir.path;
|
||||
processManager.addCommands(<FakeCommand>[
|
||||
FakeCommand(command: <String>[
|
||||
// This path is not known by the cache due to the iOS gen_snapshot split.
|
||||
'Artifact.genSnapshot.TargetPlatform.ios.profile_arm64',
|
||||
'--deterministic',
|
||||
'--write-v8-snapshot-profile-to=code_size_1/snapshot.arm64.json',
|
||||
'--trace-precompiler-to=code_size_1/trace.arm64.json',
|
||||
kAssemblyAot,
|
||||
'--assembly=$build/arm64/snapshot_assembly.S',
|
||||
'--strip',
|
||||
'--no-causal-async-stacks',
|
||||
'--lazy-async-stacks',
|
||||
'$build/app.dill',
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
'xcrun',
|
||||
'--sdk',
|
||||
'iphoneos',
|
||||
'--show-sdk-path',
|
||||
]),
|
||||
FakeCommand(command: <String>[
|
||||
'xcrun',
|
||||
'cc',
|
||||
'-arch',
|
||||
'arm64',
|
||||
'-isysroot',
|
||||
'',
|
||||
// Contains bitcode flag.
|
||||
'-fembed-bitcode',
|
||||
'-c',
|
||||
'$build/arm64/snapshot_assembly.S',
|
||||
'-o',
|
||||
'$build/arm64/snapshot_assembly.o',
|
||||
]),
|
||||
FakeCommand(command: <String>[
|
||||
'xcrun',
|
||||
'clang',
|
||||
'-arch',
|
||||
'arm64',
|
||||
'-miphoneos-version-min=9.0',
|
||||
'-dynamiclib',
|
||||
'-Xlinker',
|
||||
'-rpath',
|
||||
'-Xlinker',
|
||||
'@executable_path/Frameworks',
|
||||
'-Xlinker',
|
||||
'-rpath',
|
||||
'-Xlinker',
|
||||
'@loader_path/Frameworks',
|
||||
'-install_name',
|
||||
'@rpath/App.framework/App',
|
||||
// Contains bitcode flag.
|
||||
'-fembed-bitcode',
|
||||
'-isysroot',
|
||||
'',
|
||||
'-o',
|
||||
'$build/arm64/App.framework/App',
|
||||
'$build/arm64/snapshot_assembly.o',
|
||||
]),
|
||||
FakeCommand(command: <String>[
|
||||
'lipo',
|
||||
'$build/arm64/App.framework/App',
|
||||
'-create',
|
||||
'-output',
|
||||
'$build/App.framework/App',
|
||||
]),
|
||||
]);
|
||||
|
||||
await const AotAssemblyProfile().build(iosEnvironment);
|
||||
|
||||
expect(processManager.hasRemainingExpectations, false);
|
||||
}, overrides: <Type, Generator>{
|
||||
Platform: () => macPlatform,
|
||||
FileSystem: () => fileSystem,
|
||||
ProcessManager: () => processManager,
|
||||
});
|
||||
|
||||
testUsingContext('kExtraGenSnapshotOptions passes values to gen_snapshot', () async {
|
||||
androidEnvironment.defines[kExtraGenSnapshotOptions] = 'foo,bar,baz=2';
|
||||
androidEnvironment.defines[kBuildMode] = getNameForBuildMode(BuildMode.profile);
|
||||
|
@ -2,134 +2,133 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:file_testing/file_testing.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/io.dart';
|
||||
import 'package:flutter_tools/src/base/platform.dart';
|
||||
import 'package:flutter_tools/src/base/logger.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/targets/assets.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/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/context.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() {
|
||||
Testbed testbed;
|
||||
Environment environment;
|
||||
Platform platform;
|
||||
FileSystem fileSystem;
|
||||
Artifacts artifacts;
|
||||
FakeProcessManager processManager;
|
||||
|
||||
setUp(() {
|
||||
platform = FakePlatform(operatingSystem: 'macos', environment: <String, String>{});
|
||||
testbed = Testbed(setup: () {
|
||||
environment = Environment.test(
|
||||
globals.fs.currentDirectory,
|
||||
defines: <String, String>{
|
||||
kBuildMode: 'debug',
|
||||
kTargetPlatform: 'darwin-x64',
|
||||
},
|
||||
inputs: <String, String>{},
|
||||
artifacts: MockArtifacts(),
|
||||
processManager: FakeProcessManager.any(),
|
||||
logger: globals.logger,
|
||||
fileSystem: globals.fs,
|
||||
engineVersion: '2'
|
||||
);
|
||||
environment.buildDir.createSync(recursive: true);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => MockProcessManager(),
|
||||
Platform: () => platform,
|
||||
});
|
||||
processManager = FakeProcessManager.any();
|
||||
artifacts = Artifacts.test();
|
||||
fileSystem = MemoryFileSystem.test();
|
||||
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'
|
||||
);
|
||||
});
|
||||
|
||||
test('Copies files to correct cache directory', () => testbed.run(() async {
|
||||
for (final String input in inputs) {
|
||||
globals.fs.file(input).createSync(recursive: true);
|
||||
}
|
||||
// Create output directory so we can test that it is deleted.
|
||||
environment.outputDir.childDirectory(_kOutputPrefix)
|
||||
.createSync(recursive: true);
|
||||
testUsingContext('Copies files to correct cache directory', () async {
|
||||
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||
const FakeCommand(
|
||||
command: <String>[
|
||||
'cp',
|
||||
'-R',
|
||||
'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 List<String> arguments = invocation.positionalArguments.first as List<String>;
|
||||
final String sourcePath = arguments[arguments.length - 2];
|
||||
final String targetPath = arguments.last;
|
||||
final Directory source = globals.fs.directory(sourcePath);
|
||||
final Directory target = globals.fs.directory(targetPath);
|
||||
final Directory cacheDirectory = fileSystem.directory(
|
||||
artifacts.getArtifactPath(
|
||||
Artifact.flutterMacOSFramework,
|
||||
mode: BuildMode.debug,
|
||||
))
|
||||
..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);
|
||||
|
||||
expect(globals.fs.directory(_kOutputPrefix).existsSync(), true);
|
||||
for (final String path in inputs) {
|
||||
expect(globals.fs.file(path.replaceFirst(_kInputPrefix, _kOutputPrefix)), exists);
|
||||
}
|
||||
}));
|
||||
expect(processManager.hasRemainingExpectations, false);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fileSystem,
|
||||
ProcessManager: () => processManager,
|
||||
});
|
||||
|
||||
test('debug macOS application fails if App.framework missing', () => testbed.run(() async {
|
||||
final String inputKernel = globals.fs.path.join(environment.buildDir.path, 'app.dill');
|
||||
globals.fs.file(inputKernel)
|
||||
testUsingContext('debug macOS application fails if App.framework missing', () async {
|
||||
fileSystem.directory(
|
||||
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)
|
||||
..writeAsStringSync('testing');
|
||||
|
||||
expect(() async => await const DebugMacOSBundleFlutterAssets().build(environment),
|
||||
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';
|
||||
globals.fs.file('bin/cache/artifacts/engine/darwin-x64/vm_isolate_snapshot.bin')
|
||||
.createSync(recursive: true);
|
||||
globals.fs.file('bin/cache/artifacts/engine/darwin-x64/isolate_snapshot.bin')
|
||||
.createSync(recursive: true);
|
||||
globals.fs.file('${environment.buildDir.path}/App.framework/App')
|
||||
fileSystem.file(
|
||||
artifacts.getArtifactPath(
|
||||
Artifact.vmSnapshotData,
|
||||
platform: TargetPlatform.darwin_x64,
|
||||
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);
|
||||
// sksl bundle
|
||||
globals.fs.file('bundle.sksl').writeAsStringSync(json.encode(
|
||||
fileSystem.file('bundle.sksl').writeAsStringSync(json.encode(
|
||||
<String, Object>{
|
||||
'engineRevision': '2',
|
||||
'platform': 'ios',
|
||||
@ -140,70 +139,76 @@ void main() {
|
||||
));
|
||||
|
||||
final String inputKernel = '${environment.buildDir.path}/app.dill';
|
||||
globals.fs.file(inputKernel)
|
||||
fileSystem.file(inputKernel)
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('testing');
|
||||
|
||||
await const DebugMacOSBundleFlutterAssets().build(environment);
|
||||
|
||||
expect(globals.fs.file(
|
||||
expect(fileSystem.file(
|
||||
'App.framework/Versions/A/Resources/flutter_assets/kernel_blob.bin').readAsStringSync(),
|
||||
'testing',
|
||||
);
|
||||
expect(globals.fs.file(
|
||||
expect(fileSystem.file(
|
||||
'App.framework/Versions/A/Resources/Info.plist').readAsStringSync(),
|
||||
contains('io.flutter.flutter.app'),
|
||||
);
|
||||
expect(globals.fs.file(
|
||||
expect(fileSystem.file(
|
||||
'App.framework/Versions/A/Resources/flutter_assets/vm_snapshot_data'),
|
||||
exists,
|
||||
);
|
||||
expect(globals.fs.file(
|
||||
expect(fileSystem.file(
|
||||
'App.framework/Versions/A/Resources/flutter_assets/isolate_snapshot_data'),
|
||||
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.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 {
|
||||
globals.fs.file('bin/cache/artifacts/engine/darwin-x64/vm_isolate_snapshot.bin')
|
||||
testUsingContext('release/profile macOS application has no blob or precompiled runtime', () async {
|
||||
fileSystem.file('bin/cache/artifacts/engine/darwin-x64/vm_isolate_snapshot.bin')
|
||||
.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);
|
||||
globals.fs.file('${environment.buildDir.path}/App.framework/App')
|
||||
fileSystem.file('${environment.buildDir.path}/App.framework/App')
|
||||
.createSync(recursive: true);
|
||||
|
||||
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'),
|
||||
isNot(exists),
|
||||
);
|
||||
expect(globals.fs.file(
|
||||
expect(fileSystem.file(
|
||||
'App.framework/Versions/A/Resources/flutter_assets/vm_snapshot_data'),
|
||||
isNot(exists),
|
||||
);
|
||||
expect(globals.fs.file(
|
||||
expect(fileSystem.file(
|
||||
'App.framework/Versions/A/Resources/flutter_assets/isolate_snapshot_data'),
|
||||
isNot(exists),
|
||||
);
|
||||
}));
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fileSystem,
|
||||
ProcessManager: () => processManager,
|
||||
});
|
||||
|
||||
test('release/profile macOS application updates when App.framework updates', () => testbed.run(() async {
|
||||
globals.fs.file('bin/cache/artifacts/engine/darwin-x64/vm_isolate_snapshot.bin')
|
||||
testUsingContext('release/profile macOS application updates when App.framework updates', () async {
|
||||
fileSystem.file('bin/cache/artifacts/engine/darwin-x64/vm_isolate_snapshot.bin')
|
||||
.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);
|
||||
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)
|
||||
..writeAsStringSync('ABC');
|
||||
|
||||
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');
|
||||
|
||||
@ -211,23 +216,8 @@ void main() {
|
||||
await const ProfileMacOSBundleFlutterAssets().build(environment..defines[kBuildMode] = 'profile');
|
||||
|
||||
expect(outputFramework.readAsStringSync(), 'DEF');
|
||||
}));
|
||||
}
|
||||
|
||||
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 = '';
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fileSystem,
|
||||
ProcessManager: () => processManager,
|
||||
});
|
||||
}
|
||||
|
@ -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