Split analysis steps out of dev/bots/test.dart into dev/bots/analyze.dart (#21174)
* Split analysis steps out of dev/bots/test.dart into dev/bots/analyze.dart.
This allows to run analysis step with command line arguments that are only applicable to flutter analyze(like --dart-sdk, needed for dart-flutter-engine head-head-head bot).
* Add forgotten dev/bots/analyze.dart
* Refactor common code from analyze.dart and test.dart into run_command.dart
* Remove comments, add header
diff --git a/dev/bots/run_command.dart b/dev/bots/run_command.dart
new file mode 100644
index 0000000..5427741
--- /dev/null
+++ b/dev/bots/run_command.dart
@@ -0,0 +1,90 @@
+// Copyright 2017 The Chromium 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 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:path/path.dart' as path;
+
+final bool hasColor = stdout.supportsAnsiEscapes;
+
+final String bold = hasColor ? '\x1B[1m' : '';
+final String red = hasColor ? '\x1B[31m' : '';
+final String green = hasColor ? '\x1B[32m' : '';
+final String yellow = hasColor ? '\x1B[33m' : '';
+final String cyan = hasColor ? '\x1B[36m' : '';
+final String reset = hasColor ? '\x1B[0m' : '';
+final String redLine = '$red━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━$reset';
+const String arrow = '⏩';
+const String clock = '🕐';
+
+const Duration _kLongTimeout = Duration(minutes: 45);
+
+String elapsedTime(DateTime start) {
+ return new DateTime.now().difference(start).toString();
+}
+
+void printProgress(String action, String workingDir, String command) {
+ print('$arrow $action: cd $cyan$workingDir$reset; $yellow$command$reset');
+}
+
+Future<Null> runCommand(String executable, List<String> arguments, {
+ String workingDirectory,
+ Map<String, String> environment,
+ bool expectNonZeroExit = false,
+ int expectedExitCode,
+ String failureMessage,
+ bool printOutput = true,
+ bool skip = false,
+ Duration timeout = _kLongTimeout,
+}) async {
+ final String commandDescription = '${path.relative(executable, from: workingDirectory)} ${arguments.join(' ')}';
+ final String relativeWorkingDir = path.relative(workingDirectory);
+ if (skip) {
+ printProgress('SKIPPING', relativeWorkingDir, commandDescription);
+ return null;
+ }
+ printProgress('RUNNING', relativeWorkingDir, commandDescription);
+
+ final DateTime start = new DateTime.now();
+ final Process process = await Process.start(executable, arguments,
+ workingDirectory: workingDirectory,
+ environment: environment,
+ );
+
+ Future<List<List<int>>> savedStdout, savedStderr;
+ if (printOutput) {
+ await Future.wait(<Future<void>>[
+ stdout.addStream(process.stdout),
+ stderr.addStream(process.stderr)
+ ]);
+ } else {
+ savedStdout = process.stdout.toList();
+ savedStderr = process.stderr.toList();
+ }
+
+ final int exitCode = await process.exitCode.timeout(timeout, onTimeout: () {
+ stderr.writeln('Process timed out after $timeout');
+ return expectNonZeroExit ? 0 : 1;
+ });
+ print('$clock ELAPSED TIME: $bold${elapsedTime(start)}$reset for $commandDescription in $relativeWorkingDir: ');
+ if ((exitCode == 0) == expectNonZeroExit || (expectedExitCode != null && exitCode != expectedExitCode)) {
+ if (failureMessage != null) {
+ print(failureMessage);
+ }
+ if (!printOutput) {
+ stdout.writeln(utf8.decode((await savedStdout).expand((List<int> ints) => ints).toList()));
+ stderr.writeln(utf8.decode((await savedStderr).expand((List<int> ints) => ints).toList()));
+ }
+ print(
+ '$redLine\n'
+ '${bold}ERROR:$red Last command exited with $exitCode (expected: ${expectNonZeroExit ? (expectedExitCode ?? 'non-zero') : 'zero'}).$reset\n'
+ '${bold}Command:$cyan $commandDescription$reset\n'
+ '${bold}Relative working directory:$red $relativeWorkingDir$reset\n'
+ '$redLine'
+ );
+ exit(1);
+ }
+}