Unverified Commit 2a5690f0 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] fix iOS build inconsistencies and pipe through performance file (#56094)

Ensure iOS and android builds can be correctly cached. Use the performance-measurement-file to verify that all targets were skipped on the second invocation. This is only run on the flutter_gallery build.
parent ac2dfcf3
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math' as math;
......@@ -282,15 +283,18 @@ Future<void> _runToolTests() async {
await selectSubshard(subshards);
}
/// Verifies that AOT, APK, and IPA (if on macOS) builds the examples apps
/// Verifies that APK, and IPA (if on macOS) builds the examples apps
/// without crashing. It does not actually launch the apps. That happens later
/// in the devicelab. This is just a smoke-test. In particular, this will verify
/// we can build when there are spaces in the path name for the Flutter SDK and
/// target app.
Future<void> _runBuildTests() async {
final List<FileSystemEntity> exampleDirectories = Directory(path.join(flutterRoot, 'examples')).listSync()
..add(Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'non_nullable')));
..add(Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'non_nullable')))
..add(Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'flutter_gallery')));
for (final FileSystemEntity fileEntity in exampleDirectories) {
// Only verify caching with flutter gallery.
final bool verifyCaching = fileEntity.path.contains('flutter_gallery');
if (fileEntity is! Directory) {
continue;
}
......@@ -300,15 +304,15 @@ Future<void> _runBuildTests() async {
? <String>['--enable-experiment', 'non-nullable']
: <String>[];
if (Directory(path.join(examplePath, 'android')).existsSync()) {
await _flutterBuildApk(examplePath, release: false, additionalArgs: additionalArgs);
await _flutterBuildApk(examplePath, release: true, additionalArgs: additionalArgs);
await _flutterBuildApk(examplePath, release: false, additionalArgs: additionalArgs, verifyCaching: verifyCaching);
await _flutterBuildApk(examplePath, release: true, additionalArgs: additionalArgs, verifyCaching: verifyCaching);
} else {
print('Example project ${path.basename(examplePath)} has no android directory, skipping apk');
}
if (Platform.isMacOS) {
if (Directory(path.join(examplePath, 'ios')).existsSync()) {
await _flutterBuildIpa(examplePath, release: false, additionalArgs: additionalArgs);
await _flutterBuildIpa(examplePath, release: true, additionalArgs: additionalArgs);
await _flutterBuildIpa(examplePath, release: false, additionalArgs: additionalArgs, verifyCaching: verifyCaching);
await _flutterBuildIpa(examplePath, release: true, additionalArgs: additionalArgs, verifyCaching: verifyCaching);
} else {
print('Example project ${path.basename(examplePath)} has no ios directory, skipping ipa');
}
......@@ -332,9 +336,10 @@ Future<void> _runBuildTests() async {
Future<void> _flutterBuildApk(String relativePathToApplication, {
@required bool release,
bool verifyCaching = false,
List<String> additionalArgs = const <String>[],
}) async {
print('${green}Testing APK --debug build$reset for $cyan$relativePathToApplication$reset...');
print('${green}Testing APK build$reset for $cyan$relativePathToApplication$reset...');
await runCommand(flutter,
<String>[
'build',
......@@ -348,11 +353,36 @@ Future<void> _flutterBuildApk(String relativePathToApplication, {
],
workingDirectory: path.join(flutterRoot, relativePathToApplication),
);
if (verifyCaching) {
print('${green}Testing APK cache$reset for $cyan$relativePathToApplication$reset...');
await runCommand(flutter,
<String>[
'build',
'apk',
'--performance-measurement-file=perf.json',
...additionalArgs,
if (release)
'--release'
else
'--debug',
'-v',
],
workingDirectory: path.join(flutterRoot, relativePathToApplication),
);
final File file = File(path.join(flutterRoot, relativePathToApplication, 'perf.json'));
if (!_allTargetsCached(file)) {
print('${red}Not all build targets cached after second run.$reset');
print('The target performance data was: ${file.readAsStringSync()}');
exit(1);
}
}
}
Future<void> _flutterBuildIpa(String relativePathToApplication, {
@required bool release,
List<String> additionalArgs = const <String>[],
bool verifyCaching = false,
}) async {
assert(Platform.isMacOS);
print('${green}Testing IPA build$reset for $cyan$relativePathToApplication$reset...');
......@@ -382,6 +412,38 @@ Future<void> _flutterBuildIpa(String relativePathToApplication, {
],
workingDirectory: path.join(flutterRoot, relativePathToApplication),
);
if (verifyCaching) {
print('${green}Testing IPA cache$reset for $cyan$relativePathToApplication$reset...');
await runCommand(flutter,
<String>[
'build',
'ios',
'--performance-measurement-file=perf.json',
...additionalArgs,
'--no-codesign',
if (release)
'--release'
else
'--debug',
'-v',
],
workingDirectory: path.join(flutterRoot, relativePathToApplication),
);
final File file = File(path.join(flutterRoot, relativePathToApplication, 'perf.json'));
if (!_allTargetsCached(file)) {
print('${red}Not all build targets cached after second run.$reset');
print('The target performance data was: ${file.readAsStringSync()}');
exit(1);
}
}
}
bool _allTargetsCached(File performanceFile) {
final Map<String, Object> data = json.decode(performanceFile.readAsStringSync())
as Map<String, Object>;
final List<Map<String, Object>> targets = (data['targets'] as List<Object>)
.cast<Map<String, Object>>();
return targets.every((Map<String, Object> element) => element['skipped'] == true);
}
Future<void> _flutterBuildDart2js(String relativePathToApplication, String target, { bool expectNonZeroExit = false }) async {
......
......@@ -62,14 +62,9 @@ ephemeral_dir="${SOURCE_ROOT}/Flutter/ephemeral"
build_inputs_path="${ephemeral_dir}/FlutterInputs.xcfilelist"
build_outputs_path="${ephemeral_dir}/FlutterOutputs.xcfilelist"
icon_tree_shaker_flag="false"
if [[ -n "$TREE_SHAKE_ICONS" ]]; then
icon_tree_shaker_flag="true"
fi
dart_obfuscation_flag="false"
if [[ -n "$DART_OBFUSCATION" ]]; then
dart_obfuscation_flag="true"
local performance_measurement_option=""
if [[ -n "$PERFORMANCE_MEASUREMENT_FILE" ]]; then
performance_measurement_option="--performance-measurement-file=${PERFORMANCE_MEASUREMENT_FILE}"
fi
RunCommand "${FLUTTER_ROOT}/bin/flutter" \
......@@ -77,11 +72,12 @@ RunCommand "${FLUTTER_ROOT}/bin/flutter" \
${flutter_engine_flag} \
${local_engine_flag} \
assemble \
${performance_measurement_option} \
-dTargetPlatform=darwin-x64 \
-dTargetFile="${target_path}" \
-dBuildMode="${build_mode}" \
-dTreeShakeIcons="${icon_tree_shaker_flag}" \
-dDartObfuscation="${dart_obfuscation_flag}" \
-dTreeShakeIcons="${TREE_SHAKE_ICONS}" \
-dDartObfuscation="${DART_OBFUSCATION}" \
-dSplitDebugInfo="${SPLIT_DEBUG_INFO}" \
--DartDefines="${DART_DEFINES}" \
--ExtraGenSnapshotOptions="${EXTRA_GEN_SNAPSHOT_OPTIONS}" \
......
......@@ -54,10 +54,6 @@ BuildApp() {
derived_dir="${project_path}/.ios/Flutter"
fi
RunCommand mkdir -p -- "$derived_dir"
AssertExists "$derived_dir"
RunCommand rm -rf -- "${derived_dir}/App.framework"
# Default value of assets_path is flutter_assets
local assets_path="flutter_assets"
# The value of assets_path can set by add FLTAssetsPath to
......@@ -156,19 +152,9 @@ BuildApp() {
verbose_flag="--verbose"
fi
local track_widget_creation_flag=""
if [[ -n "$TRACK_WIDGET_CREATION" ]]; then
track_widget_creation_flag="true"
fi
icon_tree_shaker_flag="false"
if [[ -n "$TREE_SHAKE_ICONS" ]]; then
icon_tree_shaker_flag="true"
fi
dart_obfuscation_flag="false"
if [[ -n "$DART_OBFUSCATION" ]]; then
dart_obfuscation_flag="true"
local performance_measurement_option=""
if [[ -n "$PERFORMANCE_MEASUREMENT_FILE" ]]; then
performance_measurement_option="--performance-measurement-file=${PERFORMANCE_MEASUREMENT_FILE}"
fi
RunCommand "${FLUTTER_ROOT}/bin/flutter" \
......@@ -177,14 +163,15 @@ BuildApp() {
${local_engine_flag} \
assemble \
--output="${derived_dir}/" \
${performance_measurement_option} \
-dTargetPlatform=ios \
-dTargetFile="${target_path}" \
-dBuildMode=${build_mode} \
-dIosArchs="${ARCHS}" \
-dSplitDebugInfo="${SPLIT_DEBUG_INFO}" \
-dTreeShakeIcons="${icon_tree_shaker_flag}" \
-dTrackWidgetCreation="${track_widget_creation_flag}" \
-dDartObfuscation="${dart_obfuscation_flag}" \
-dTreeShakeIcons="${TREE_SHAKE_ICONS}" \
-dTrackWidgetCreation="${TRACK_WIDGET_CREATION}" \
-dDartObfuscation="${DART_OBFUSCATION}" \
-dEnableBitcode="${bitcode_flag}" \
--ExtraGenSnapshotOptions="${EXTRA_GEN_SNAPSHOT_OPTIONS}" \
--DartDefines="${DART_DEFINES}" \
......
......@@ -623,6 +623,10 @@ class FlutterPlugin implements Plugin<Project> {
if (project.hasProperty('bundle-sksl-path')) {
bundleSkSLPathValue = project.property('bundle-sksl-path')
}
String performanceMeasurementFileValue;
if (project.hasProperty('performance-measurement-file')) {
performanceMeasurementFileValue = project.property('performance-measurement-file')
}
def targetPlatforms = getTargetPlatforms()
def addFlutterDeps = { variant ->
if (shouldSplitPerAbi()) {
......@@ -662,6 +666,7 @@ class FlutterPlugin implements Plugin<Project> {
dartObfuscation dartObfuscationValue
dartDefines dartDefinesValue
bundleSkSLPath bundleSkSLPathValue
performanceMeasurementFile performanceMeasurementFileValue
doLast {
project.exec {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
......@@ -856,6 +861,7 @@ abstract class BaseFlutterTask extends DefaultTask {
String dartDefines
@Optional @Input
String bundleSkSLPath
String performanceMeasurementFile;
@OutputFiles
FileCollection getDependenciesFiles() {
......@@ -903,6 +909,9 @@ abstract class BaseFlutterTask extends DefaultTask {
args "assemble"
args "--depfile", "${intermediateDir}/flutter_build.d"
args "--output", "${intermediateDir}"
if (performanceMeasurementFile != null) {
args "--performance-measurement-file=${performanceMeasurementFile}"
}
if (!fastStart || buildMode != "debug") {
args "-dTargetFile=${targetPath}"
} else {
......
......@@ -354,6 +354,9 @@ Future<void> buildGradleApp({
if (androidBuildInfo.buildInfo.bundleSkSLPath != null) {
command.add('-Pbundle-sksl-path=${androidBuildInfo.buildInfo.bundleSkSLPath}');
}
if (androidBuildInfo.buildInfo.performanceMeasurementFile != null) {
command.add('-Pperformance-measurement-file=${androidBuildInfo.buildInfo.performanceMeasurementFile}');
}
command.add(assembleTask);
GradleHandledError detectedGradleError;
......
......@@ -27,6 +27,7 @@ class BuildInfo {
this.bundleSkSLPath,
this.dartExperiments = const <String>[],
@required this.treeShakeIcons,
this.performanceMeasurementFile,
});
final BuildMode mode;
......@@ -89,6 +90,13 @@ class BuildInfo {
/// A list of Dart experiments.
final List<String> dartExperiments;
/// The name of a file where flutter assemble will output performance
/// information in a JSON format.
///
/// This is not considered a build input and will not force assemble to
/// rerun tasks.
final String performanceMeasurementFile;
static const BuildInfo debug = BuildInfo(BuildMode.debug, null, treeShakeIcons: false);
static const BuildInfo profile = BuildInfo(BuildMode.profile, null, treeShakeIcons: kIconTreeShakerEnabledDefault);
static const BuildInfo jitRelease = BuildInfo(BuildMode.jitRelease, null, treeShakeIcons: kIconTreeShakerEnabledDefault);
......@@ -143,6 +151,8 @@ class BuildInfo {
'TRACK_WIDGET_CREATION': trackWidgetCreation.toString(),
if (treeShakeIcons != null)
'TREE_SHAKE_ICONS': treeShakeIcons.toString(),
if (performanceMeasurementFile != null)
'PERFORMANCE_MEASUREMENT_FILE': performanceMeasurementFile,
};
}
}
......
......@@ -119,9 +119,9 @@ class DebugAndroidApplication extends AndroidAssetBundle {
@override
List<Source> get outputs => <Source>[
...super.outputs,
const Source.pattern('{OUTPUT_DIR}/vm_snapshot_data'),
const Source.pattern('{OUTPUT_DIR}/isolate_snapshot_data'),
const Source.pattern('{OUTPUT_DIR}/kernel_blob.bin'),
const Source.pattern('{OUTPUT_DIR}/flutter_assets/vm_snapshot_data'),
const Source.pattern('{OUTPUT_DIR}/flutter_assets/isolate_snapshot_data'),
const Source.pattern('{OUTPUT_DIR}/flutter_assets/kernel_blob.bin'),
];
}
......
......@@ -187,7 +187,7 @@ class DebugUniveralFramework extends Target {
@override
List<Source> get outputs => const <Source>[
Source.pattern('{BUILD_DIR}/App')
Source.pattern('{BUILD_DIR}/App.framework/App'),
];
@override
......@@ -200,7 +200,10 @@ class DebugUniveralFramework extends Target {
?? <DarwinArch>{DarwinArch.arm64};
final File iphoneFile = environment.buildDir.childFile('iphone_framework');
final File simulatorFile = environment.buildDir.childFile('simulator_framework');
final File lipoOutputFile = environment.buildDir.childFile('App');
final File lipoOutputFile = environment.buildDir
.childDirectory('App.framework')
.childFile('App');
lipoOutputFile.parent.createSync(recursive: true);
final RunResult iphoneResult = await createStubAppFramework(
iphoneFile,
SdkType.iPhone,
......@@ -250,7 +253,7 @@ abstract class IosAssetBundle extends Target {
@override
List<Source> get inputs => const <Source>[
Source.pattern('{BUILD_DIR}/App'),
Source.pattern('{BUILD_DIR}/App.framework/App'),
Source.pattern('{PROJECT_DIR}/pubspec.yaml'),
...IconTreeShaker.inputs,
];
......@@ -280,7 +283,9 @@ abstract class IosAssetBundle extends Target {
// Only copy the prebuilt runtimes and kernel blob in debug mode.
if (buildMode == BuildMode.debug) {
// Copy the App.framework to the output directory.
environment.buildDir.childFile('App')
environment.buildDir
.childDirectory('App.framework')
.childFile('App')
.copySync(frameworkDirectory.childFile('App').path);
final String vmSnapshotData = globals.artifacts.getArtifactPath(Artifact.vmSnapshotData, mode: BuildMode.debug);
......
......@@ -32,6 +32,7 @@ class BuildApkCommand extends BuildSubCommand {
usesExtraFrontendOptions();
addBundleSkSLPathOption(hide: !verboseHelp);
addEnableExperimentation(hide: !verboseHelp);
addBuildPerformanceFile(hide: !verboseHelp);
argParser
..addFlag('split-per-abi',
negatable: false,
......
......@@ -29,9 +29,9 @@ class BuildAppBundleCommand extends BuildSubCommand {
usesDartDefineOption();
usesExtraFrontendOptions();
addBundleSkSLPathOption(hide: !verboseHelp);
argParser
..addFlag('track-widget-creation', negatable: false, hide: !verboseHelp)
..addMultiOption('target-platform',
addBuildPerformanceFile(hide: !verboseHelp);
usesTrackWidgetCreation(verboseHelp: verboseHelp);
argParser.addMultiOption('target-platform',
splitCommas: true,
defaultsTo: <String>['android-arm', 'android-arm64', 'android-x64'],
allowed: <String>['android-arm', 'android-arm64', 'android-x64'],
......
......@@ -32,6 +32,7 @@ class BuildIOSCommand extends BuildSubCommand {
usesDartDefineOption();
usesExtraFrontendOptions();
addEnableExperimentation(hide: !verboseHelp);
addBuildPerformanceFile(hide: !verboseHelp);
argParser
..addFlag('simulator',
help: 'Build for the iOS simulator instead of the device.',
......
......@@ -27,6 +27,7 @@ class BuildLinuxCommand extends BuildSubCommand {
usesExtraFrontendOptions();
addEnableExperimentation(hide: !verboseHelp);
usesTrackWidgetCreation(verboseHelp: verboseHelp);
addBuildPerformanceFile(hide: !verboseHelp);
}
@override
......
......@@ -28,6 +28,7 @@ class BuildMacosCommand extends BuildSubCommand {
usesBuildNumberOption();
usesBuildNameOption();
addEnableExperimentation(hide: !verboseHelp);
addBuildPerformanceFile(hide: !verboseHelp);
}
@override
......
......@@ -30,6 +30,7 @@ class BuildWindowsCommand extends BuildSubCommand {
usesExtraFrontendOptions();
addEnableExperimentation(hide: !verboseHelp);
usesTrackWidgetCreation(verboseHelp: verboseHelp);
addBuildPerformanceFile(hide: !verboseHelp);
}
@override
......
......@@ -166,16 +166,6 @@ List<String> _xcodeBuildSettingsLines({
xcodeBuildSettings.add('FLUTTER_TARGET=$targetOverride');
}
// This is an optional path to split debug info
if (buildInfo.splitDebugInfoPath != null) {
xcodeBuildSettings.add('SPLIT_DEBUG_INFO=${buildInfo.splitDebugInfoPath}');
}
// This is an optional path to obfuscate and output a mapping.
if (buildInfo.dartObfuscation) {
xcodeBuildSettings.add('DART_OBFUSCATION=true');
}
// The build outputs directory, relative to FLUTTER_APPLICATION_PATH.
xcodeBuildSettings.add('FLUTTER_BUILD_DIR=${buildDirOverride ?? getBuildDirectory()}');
......@@ -226,31 +216,9 @@ List<String> _xcodeBuildSettingsLines({
}
}
if (buildInfo.trackWidgetCreation) {
xcodeBuildSettings.add('TRACK_WIDGET_CREATION=true');
}
if (buildInfo.treeShakeIcons) {
xcodeBuildSettings.add('TREE_SHAKE_ICONS=true');
}
if (buildInfo.dartDefines?.isNotEmpty ?? false) {
xcodeBuildSettings.add('DART_DEFINES=${buildInfo.dartDefines.join(',')}');
}
if (buildInfo.extraFrontEndOptions?.isNotEmpty ?? false) {
xcodeBuildSettings.add(
'EXTRA_FRONT_END_OPTIONS='
'${buildInfo.extraFrontEndOptions.join(',')}',
);
for (final MapEntry<String, String> config in buildInfo.toEnvironmentConfig().entries) {
xcodeBuildSettings.add('${config.key}=${config.value}');
}
if (buildInfo.extraGenSnapshotOptions?.isNotEmpty ?? false) {
xcodeBuildSettings.add(
'EXTRA_GEN_SNAPSHOT_OPTIONS='
'${buildInfo.extraGenSnapshotOptions.join(',')}',
);
}
return xcodeBuildSettings;
}
......
......@@ -109,6 +109,7 @@ class FlutterOptions {
static const String kDartObfuscationOption = 'obfuscate';
static const String kDartDefinesOption = 'dart-define';
static const String kBundleSkSLPathOption = 'bundle-sksl-path';
static const String kPerformanceMeasurementFile = 'performance-measurement-file';
}
abstract class FlutterCommand extends Command<void> {
......@@ -495,6 +496,15 @@ abstract class FlutterCommand extends Command<void> {
);
}
void addBuildPerformanceFile({ bool hide = false }) {
argParser.addOption(
FlutterOptions.kPerformanceMeasurementFile,
help:
'The name of a file where flutter assemble performance and '
'cachedness information will be written in a JSON format.'
);
}
set defaultBuildMode(BuildMode value) {
_defaultBuildMode = value;
}
......@@ -602,6 +612,10 @@ abstract class FlutterCommand extends Command<void> {
? stringArg(FlutterOptions.kBundleSkSLPathOption)
: null;
final String performanceMeasurementFile = argParser.options.containsKey(FlutterOptions.kPerformanceMeasurementFile)
? stringArg(FlutterOptions.kPerformanceMeasurementFile)
: null;
return BuildInfo(buildMode,
argParser.options.containsKey('flavor')
? stringArg('flavor')
......@@ -631,6 +645,7 @@ abstract class FlutterCommand extends Command<void> {
: const <String>[],
bundleSkSLPath: bundleSkSLPath,
dartExperiments: experiments,
performanceMeasurementFile: performanceMeasurementFile,
);
}
......
......@@ -100,7 +100,7 @@ void main() {
environment.buildDir.childFile('iphone_framework').path,
environment.buildDir.childFile('simulator_framework').path,
'-output',
environment.buildDir.childFile('App').path,
environment.buildDir.childDirectory('App.framework').childFile('App').path,
]),
]);
......@@ -127,7 +127,10 @@ void main() {
// App kernel
environment.buildDir.childFile('app.dill').createSync(recursive: true);
// Stub framework
environment.buildDir.childFile('App').createSync();
environment.buildDir
.childDirectory('App.framework')
.childFile('App')
.createSync(recursive: true);
await const DebugIosApplicationBundle().build(environment);
......
......@@ -223,7 +223,7 @@ void main() {
'-q',
'-Ptarget-platform=android-arm,android-arm64,android-x64',
'-Ptarget=${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
'-Ptrack-widget-creation=false',
'-Ptrack-widget-creation=true',
'-Pshrink=true',
'bundleRelease',
],
......@@ -256,7 +256,7 @@ void main() {
'-q',
'-Ptarget-platform=android-arm,android-arm64,android-x64',
'-Ptarget=${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
'-Ptrack-widget-creation=false',
'-Ptrack-widget-creation=true',
'bundleRelease',
],
workingDirectory: anyNamed('workingDirectory'),
......@@ -279,7 +279,7 @@ void main() {
'-q',
'-Ptarget-platform=android-arm,android-arm64,android-x64',
'-Ptarget=${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
'-Ptrack-widget-creation=false',
'-Ptrack-widget-creation=true',
'-Pshrink=true',
'bundleRelease',
],
......@@ -346,7 +346,7 @@ void main() {
'-q',
'-Ptarget-platform=android-arm,android-arm64,android-x64',
'-Ptarget=${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
'-Ptrack-widget-creation=false',
'-Ptrack-widget-creation=true',
'-Pshrink=true',
'assembleRelease',
],
......@@ -402,7 +402,7 @@ void main() {
'-q',
'-Ptarget-platform=android-arm,android-arm64,android-x64',
'-Ptarget=${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
'-Ptrack-widget-creation=false',
'-Ptrack-widget-creation=true',
'-Pshrink=true',
'assembleRelease',
],
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment