Break up and show more progression during an Xcode build (#14715)
* Created plumbing but has stream problem
* testing with makePipe
* Trying pipe but not really getting anywhere
* works by repeatedly reading line
* Minor cleanup
* works
* Clean up pipe after use.
* Move the last status forward
* Make sure failed script commands bubble up
diff --git a/packages/flutter_tools/bin/xcode_backend.sh b/packages/flutter_tools/bin/xcode_backend.sh
index fc1539a..2d0ae18 100755
--- a/packages/flutter_tools/bin/xcode_backend.sh
+++ b/packages/flutter_tools/bin/xcode_backend.sh
@@ -11,6 +11,14 @@
return $?
}
+# When provided with a pipe by the host Flutter build process, output to the
+# pipe goes to stdout of the Flutter build process directly.
+StreamOutput() {
+ if [[ -n "$SCRIPT_OUTPUT_STREAM_FILE" ]]; then
+ echo "$1" > $SCRIPT_OUTPUT_STREAM_FILE
+ fi
+}
+
EchoError() {
echo "$@" 1>&2
}
@@ -103,18 +111,20 @@
aot_flags="--${build_mode}"
fi
+ StreamOutput " ├─Building Dart code..."
RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics build aot \
--output-dir="${build_dir}/aot" \
--target-platform=ios \
--target="${target_path}" \
${aot_flags} \
${local_engine_flag} \
- ${preview_dart_2_flag} \
+ ${preview_dart_2_flag}
if [[ $? -ne 0 ]]; then
EchoError "Failed to build ${project_path}."
exit -1
fi
+ StreamOutput "done"
RunCommand cp -r -- "${build_dir}/aot/App.framework" "${derived_dir}"
else
@@ -133,6 +143,7 @@
precompilation_flag="--precompiled"
fi
+ StreamOutput " ├─Assembling Flutter resources..."
RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics build flx \
--target="${target_path}" \
--output-file="${derived_dir}/app.flx" \
@@ -141,12 +152,14 @@
--working-dir="${derived_dir}/flutter_assets" \
${precompilation_flag} \
${local_engine_flag} \
- ${preview_dart_2_flag} \
+ ${preview_dart_2_flag}
if [[ $? -ne 0 ]]; then
EchoError "Failed to package ${project_path}."
exit -1
fi
+ StreamOutput "done"
+ StreamOutput " └─Compiling, linking and signing..."
RunCommand popd > /dev/null
diff --git a/packages/flutter_tools/lib/src/base/logger.dart b/packages/flutter_tools/lib/src/base/logger.dart
index 0c13133..0493edb 100644
--- a/packages/flutter_tools/lib/src/base/logger.dart
+++ b/packages/flutter_tools/lib/src/base/logger.dart
@@ -40,7 +40,15 @@
///
/// [message] is the message to display to the user; [progressId] provides an ID which can be
/// used to identify this type of progress (`hot.reload`, `hot.restart`, ...).
- Status startProgress(String message, { String progressId, bool expectSlowOperation: false });
+ ///
+ /// [progressIndicatorPadding] can optionally be used to specify spacing
+ /// between the [message] and the progress indicator.
+ Status startProgress(
+ String message, {
+ String progressId,
+ bool expectSlowOperation: false,
+ int progressIndicatorPadding: 52,
+ });
}
class Status {
@@ -96,13 +104,23 @@
void printTrace(String message) { }
@override
- Status startProgress(String message, { String progressId, bool expectSlowOperation: false }) {
+ Status startProgress(
+ String message, {
+ String progressId,
+ bool expectSlowOperation: false,
+ int progressIndicatorPadding: 52,
+ }) {
if (_status != null) {
// Ignore nested progresses; return a no-op status object.
return new Status();
} else {
if (supportsColor) {
- _status = new _AnsiStatus(message, expectSlowOperation, () { _status = null; });
+ _status = new _AnsiStatus(
+ message,
+ expectSlowOperation,
+ () { _status = null; },
+ progressIndicatorPadding,
+ );
return _status;
} else {
printStatus(message);
@@ -163,7 +181,12 @@
void printTrace(String message) => _trace.writeln(message);
@override
- Status startProgress(String message, { String progressId, bool expectSlowOperation: false }) {
+ Status startProgress(
+ String message, {
+ String progressId,
+ bool expectSlowOperation: false,
+ int progressIndicatorPadding: 52,
+ }) {
printStatus(message);
return new Status();
}
@@ -208,7 +231,12 @@
}
@override
- Status startProgress(String message, { String progressId, bool expectSlowOperation: false }) {
+ Status startProgress(
+ String message, {
+ String progressId,
+ bool expectSlowOperation: false,
+ int progressIndicatorPadding: 52,
+ }) {
printStatus(message);
return new Status();
}
@@ -253,10 +281,10 @@
}
class _AnsiStatus extends Status {
- _AnsiStatus(this.message, this.expectSlowOperation, this.onFinish) {
+ _AnsiStatus(this.message, this.expectSlowOperation, this.onFinish, int padding) {
stopwatch = new Stopwatch()..start();
- stdout.write('${message.padRight(52)} ');
+ stdout.write('${message.padRight(padding)} ');
stdout.write('${_progress[0]}');
timer = new Timer.periodic(const Duration(milliseconds: 100), _callback);
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index 9d16715..7c9dbba 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -763,7 +763,12 @@
}
@override
- Status startProgress(String message, { String progressId, bool expectSlowOperation: false }) {
+ Status startProgress(
+ String message, {
+ String progressId,
+ bool expectSlowOperation: false,
+ int progressIndicatorPadding: 52,
+ }) {
printStatus(message);
return new Status();
}
@@ -863,7 +868,12 @@
Status _status;
@override
- Status startProgress(String message, { String progressId, bool expectSlowOperation: false }) {
+ Status startProgress(
+ String message, {
+ String progressId,
+ bool expectSlowOperation: false,
+ int progressIndicatorPadding: 52,
+ }) {
// Ignore nested progresses; return a no-op status object.
if (_status != null)
return new Status();
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index 970def0..5a24e31 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -8,6 +8,7 @@
import '../application_package.dart';
import '../base/file_system.dart';
import '../base/io.dart';
+import '../base/logger.dart';
import '../base/platform.dart';
import '../base/port_scanner.dart';
import '../base/process.dart';
@@ -239,6 +240,8 @@
int installationResult = -1;
Uri localObservatoryUri;
+ final Status installStatus =
+ logger.startProgress('Installing and launching...', expectSlowOperation: true);
if (!debuggingOptions.debuggingEnabled) {
// If debugging is not enabled, just launch the application and continue.
printTrace('Debugging is not enabled');
@@ -247,6 +250,7 @@
mapFunction: monitorInstallationFailure,
trace: true,
);
+ installStatus.stop();
} else {
// Debugging is enabled, look for the observatory server port post launch.
printTrace('Debugging is enabled, connecting to observatory');
@@ -282,6 +286,7 @@
observatoryDiscovery.cancel();
});
}
+ installStatus.stop();
if (installationResult != 0) {
printError('Could not install ${bundle.path} on $id.');
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index 621186d..7044ef3 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -13,9 +13,11 @@
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/logger.dart';
+import '../base/os.dart';
import '../base/platform.dart';
import '../base/process.dart';
import '../base/process_manager.dart';
+import '../base/utils.dart';
import '../build_info.dart';
import '../flx.dart' as flx;
import '../globals.dart';
@@ -346,14 +348,58 @@
);
}
- final Status buildStatus =
- logger.startProgress('Running Xcode build...', expectSlowOperation: true);
+ Status buildSubStatus;
+ Status initialBuildStatus;
+ Directory scriptOutputPipeTempDirectory;
+
+ if (logger.supportsColor) {
+ scriptOutputPipeTempDirectory = fs.systemTempDirectory
+ .createTempSync('flutter_build_log_pipe');
+ final File scriptOutputPipeFile =
+ scriptOutputPipeTempDirectory.childFile('pipe_to_stdout');
+ os.makePipe(scriptOutputPipeFile.path);
+
+ Future<void> listenToScriptOutputLine() async {
+ final List<String> lines = await scriptOutputPipeFile.readAsLines();
+ for (String line in lines) {
+ if (line == 'done') {
+ buildSubStatus?.stop();
+ buildSubStatus = null;
+ } else {
+ initialBuildStatus.cancel();
+ buildSubStatus = logger.startProgress(
+ line,
+ expectSlowOperation: true,
+ progressIndicatorPadding: 45,
+ );
+ }
+ }
+ return listenToScriptOutputLine();
+ }
+
+ // Trigger the start of the pipe -> stdout loop. Ignore exceptions.
+ listenToScriptOutputLine(); // ignore: unawaited_futures
+
+ buildCommands.add('SCRIPT_OUTPUT_STREAM_FILE=${scriptOutputPipeFile.absolute.path}');
+ }
+
+ final Stopwatch buildStopwatch = new Stopwatch()..start();
+ initialBuildStatus = logger.startProgress('Starting Xcode build...');
final RunResult buildResult = await runAsync(
buildCommands,
workingDirectory: app.appDirectory,
allowReentrantFlutter: true
);
- buildStatus.stop();
+ buildSubStatus?.stop();
+ initialBuildStatus?.cancel();
+ buildStopwatch.stop();
+ // Free pipe file.
+ scriptOutputPipeTempDirectory?.deleteSync(recursive: true);
+ printStatus(
+ 'Xcode build done',
+ ansiAlternative: 'Xcode build done'.padRight(53)
+ + '${getElapsedAsSeconds(buildStopwatch.elapsed).padLeft(5)}',
+ );
// Run -showBuildSettings again but with the exact same parameters as the build.
final Map<String, String> buildSettings = parseXcodeBuildSettings(runCheckedSync(