| // 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 DateTime.now().difference(start).toString(); |
| } |
| |
| void printProgress(String action, String workingDir, String command) { |
| print('$arrow $action: cd $cyan$workingDir$reset; $yellow$command$reset'); |
| } |
| |
| Stream<String> runAndGetStdout(String executable, List<String> arguments, { |
| String workingDirectory, |
| Map<String, String> environment, |
| bool expectNonZeroExit = false, |
| int expectedExitCode, |
| String failureMessage, |
| Duration timeout = _kLongTimeout, |
| Function beforeExit, |
| }) async* { |
| final String commandDescription = '${path.relative(executable, from: workingDirectory)} ${arguments.join(' ')}'; |
| final String relativeWorkingDir = path.relative(workingDirectory); |
| |
| printProgress('RUNNING', relativeWorkingDir, commandDescription); |
| |
| final DateTime start = DateTime.now(); |
| final Process process = await Process.start(executable, arguments, |
| workingDirectory: workingDirectory, |
| environment: environment, |
| ); |
| |
| stderr.addStream(process.stderr); |
| final Stream<String> lines = process.stdout.transform(utf8.decoder).transform(const LineSplitter()); |
| await for (String line in lines) { |
| yield line; |
| } |
| |
| 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); |
| } |
| 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' |
| ); |
| beforeExit?.call(); |
| exit(1); |
| } |
| } |
| |
| Future<void> 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; |
| } |
| printProgress('RUNNING', relativeWorkingDir, commandDescription); |
| |
| final DateTime start = 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<void>(<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<int>((List<int> ints) => ints).toList())); |
| stderr.writeln(utf8.decode((await savedStderr).expand<int>((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); |
| } |
| } |