[tool] Conditionalize color on stdout (#4436)

Refactors colorization to a centralized utility file, and makes it all conditional on `stdout` having ANSI escape support. This makes the output more readable on LUCI, which unlike the Cirrus log display doesn't handle ANSI.

Fixes https://github.com/flutter/flutter/issues/89392
This commit is contained in:
stuartmorgan
2023-07-11 15:02:59 -04:00
committed by GitHub
parent 404084cb0a
commit 28c419d2ab
35 changed files with 187 additions and 26 deletions

View File

@ -5,7 +5,7 @@
import 'package:file/file.dart';
import 'package:yaml/yaml.dart';
import 'common/core.dart';
import 'common/output_utils.dart';
import 'common/package_looping_command.dart';
import 'common/repository_package.dart';

View File

@ -6,6 +6,7 @@ import 'package:file/file.dart';
import 'package:yaml/yaml.dart';
import 'common/core.dart';
import 'common/output_utils.dart';
import 'common/package_looping_command.dart';
import 'common/plugin_utils.dart';
import 'common/repository_package.dart';

View File

@ -6,6 +6,7 @@ import 'package:file/file.dart';
import 'package:platform/platform.dart';
import 'core.dart';
import 'output_utils.dart';
import 'process_runner.dart';
const String _cacheCommandKey = 'CMAKE_COMMAND:INTERNAL';

View File

@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:colorize/colorize.dart';
import 'package:file/file.dart';
import 'package:pub_semver/pub_semver.dart';
@ -69,16 +68,6 @@ bool isPackage(FileSystemEntity entity) {
return entity.childFile('pubspec.yaml').existsSync();
}
/// Prints `successMessage` in green.
void printSuccess(String successMessage) {
print(Colorize(successMessage)..green());
}
/// Prints `errorMessage` in red.
void printError(String errorMessage) {
print(Colorize(errorMessage)..red());
}
/// Error thrown when a command needs to exit with a non-zero exit code.
///
/// While there is no specific definition of the meaning of different non-zero

View File

@ -0,0 +1,44 @@
// Copyright 2013 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 'dart:io';
import 'package:colorize/colorize.dart';
import 'package:meta/meta.dart';
export 'package:colorize/colorize.dart' show Styles;
/// True if color should be applied.
///
/// Defaults to autodetecting stdout.
@visibleForTesting
bool useColorForOutput = stdout.supportsAnsiEscapes;
String _colorizeIfAppropriate(String string, Styles color) {
if (!useColorForOutput) {
return string;
}
return Colorize(string).apply(color).toString();
}
/// Prints [message] in green, if the environment supports color.
void printSuccess(String message) {
print(_colorizeIfAppropriate(message, Styles.GREEN));
}
/// Prints [message] in yellow, if the environment supports color.
void printWarning(String message) {
print(_colorizeIfAppropriate(message, Styles.YELLOW));
}
/// Prints [message] in red, if the environment supports color.
void printError(String message) {
print(_colorizeIfAppropriate(message, Styles.RED));
}
/// Returns [message] with escapes to print it in [color], if the environment
/// supports color.
String colorizeString(String message, Styles color) {
return _colorizeIfAppropriate(message, color);
}

View File

@ -14,6 +14,7 @@ import 'package:yaml/yaml.dart';
import 'core.dart';
import 'git_version_finder.dart';
import 'output_utils.dart';
import 'process_runner.dart';
import 'repository_package.dart';

View File

@ -4,12 +4,12 @@
import 'dart:async';
import 'package:colorize/colorize.dart';
import 'package:file/file.dart';
import 'package:path/path.dart' as p;
import 'package:pub_semver/pub_semver.dart';
import 'core.dart';
import 'output_utils.dart';
import 'package_command.dart';
import 'repository_package.dart';
@ -208,7 +208,7 @@ abstract class PackageLoopingCommand extends PackageCommand {
/// messages. DO NOT RELY on someone noticing a warning; instead, use it for
/// things that might be useful to someone debugging an unexpected result.
void logWarning(String warningMessage) {
_printColorized(warningMessage, Styles.YELLOW);
printWarning(warningMessage);
if (_currentPackageEntry != null) {
_packagesWithWarnings.add(_currentPackageEntry!);
} else {
@ -467,7 +467,7 @@ abstract class PackageLoopingCommand extends PackageCommand {
}
if (!captureOutput) {
summary = (Colorize(summary)..apply(style)).toString();
summary = colorizeString(summary, style);
}
print(' ${entry.package.displayName} - $summary');
}
@ -500,7 +500,7 @@ abstract class PackageLoopingCommand extends PackageCommand {
if (captureOutput) {
print(message);
} else {
print(Colorize(message)..apply(color));
print(colorizeString(message, color));
}
}

View File

@ -7,7 +7,7 @@ import 'dart:io' as io;
import 'package:file/file.dart';
import 'core.dart';
import 'output_utils.dart';
import 'process_runner.dart';
const String _xcodeBuildCommand = 'xcodebuild';

View File

@ -11,6 +11,7 @@ import 'package:pubspec_parse/pubspec_parse.dart';
import 'common/core.dart';
import 'common/file_utils.dart';
import 'common/output_utils.dart';
import 'common/package_command.dart';
import 'common/process_runner.dart';
import 'common/pub_utils.dart';

View File

@ -5,6 +5,7 @@
import 'package:file/file.dart';
import 'common/core.dart';
import 'common/output_utils.dart';
import 'common/package_looping_command.dart';
import 'common/plugin_utils.dart';
import 'common/pub_utils.dart';

View File

@ -5,7 +5,7 @@
import 'package:file/file.dart';
import 'package:yaml/yaml.dart';
import 'common/core.dart';
import 'common/output_utils.dart';
import 'common/package_looping_command.dart';
import 'common/repository_package.dart';

View File

@ -8,6 +8,7 @@ import 'dart:io';
import 'package:file/file.dart';
import 'common/core.dart';
import 'common/output_utils.dart';
import 'common/package_looping_command.dart';
import 'common/plugin_utils.dart';
import 'common/repository_package.dart';

View File

@ -6,9 +6,9 @@ import 'package:file/file.dart';
import 'package:path/path.dart' as p;
import 'package:pub_semver/pub_semver.dart';
import 'common/core.dart';
import 'common/file_utils.dart';
import 'common/git_version_finder.dart';
import 'common/output_utils.dart';
import 'common/package_looping_command.dart';
import 'common/plugin_utils.dart';
import 'common/repository_package.dart';

View File

@ -9,6 +9,7 @@ import 'package:uuid/uuid.dart';
import 'common/core.dart';
import 'common/gradle.dart';
import 'common/output_utils.dart';
import 'common/package_looping_command.dart';
import 'common/plugin_utils.dart';
import 'common/repository_package.dart';

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'common/core.dart';
import 'common/output_utils.dart';
import 'common/package_looping_command.dart';
import 'common/repository_package.dart';

View File

@ -10,6 +10,7 @@ import 'package:http/http.dart' as http;
import 'package:meta/meta.dart';
import 'common/core.dart';
import 'common/output_utils.dart';
import 'common/package_command.dart';
/// In theory this should be 8191, but in practice that was still resulting in

View File

@ -7,6 +7,7 @@ import 'package:meta/meta.dart';
import 'package:pub_semver/pub_semver.dart';
import 'common/core.dart';
import 'common/output_utils.dart';
import 'common/package_looping_command.dart';
import 'common/plugin_utils.dart';
import 'common/repository_package.dart';

View File

@ -6,6 +6,7 @@ import 'package:file/file.dart';
import 'package:path/path.dart' as p;
import 'common/core.dart';
import 'common/output_utils.dart';
import 'common/package_command.dart';
const Set<String> _codeFileExtensions = <String>{

View File

@ -4,6 +4,7 @@
import 'common/core.dart';
import 'common/gradle.dart';
import 'common/output_utils.dart';
import 'common/package_looping_command.dart';
import 'common/plugin_utils.dart';
import 'common/repository_package.dart';

View File

@ -11,6 +11,7 @@ import 'package:yaml_edit/yaml_edit.dart';
import 'common/core.dart';
import 'common/git_version_finder.dart';
import 'common/output_utils.dart';
import 'common/package_command.dart';
import 'common/repository_package.dart';

View File

@ -8,6 +8,7 @@ import 'package:meta/meta.dart';
import 'common/cmake.dart';
import 'common/core.dart';
import 'common/gradle.dart';
import 'common/output_utils.dart';
import 'common/package_looping_command.dart';
import 'common/plugin_utils.dart';
import 'common/repository_package.dart';

View File

@ -8,6 +8,7 @@ import 'dart:io';
import 'package:file/file.dart';
import 'common/core.dart';
import 'common/output_utils.dart';
import 'common/package_looping_command.dart';
import 'common/repository_package.dart';

View File

@ -9,7 +9,7 @@ import 'dart:io' as io;
import 'package:http/http.dart' as http;
import 'package:pub_semver/pub_semver.dart';
import 'common/core.dart';
import 'common/output_utils.dart';
import 'common/package_looping_command.dart';
import 'common/pub_utils.dart';
import 'common/pub_version_finder.dart';

View File

@ -18,6 +18,7 @@ import 'package:yaml/yaml.dart';
import 'common/core.dart';
import 'common/file_utils.dart';
import 'common/git_version_finder.dart';
import 'common/output_utils.dart';
import 'common/package_command.dart';
import 'common/package_looping_command.dart';
import 'common/pub_version_finder.dart';

View File

@ -9,6 +9,7 @@ import 'package:pubspec_parse/pubspec_parse.dart';
import 'package:yaml/yaml.dart';
import 'common/core.dart';
import 'common/output_utils.dart';
import 'common/package_looping_command.dart';
import 'common/repository_package.dart';

View File

@ -6,6 +6,7 @@ import 'package:file/file.dart';
import 'package:yaml/yaml.dart';
import 'common/core.dart';
import 'common/output_utils.dart';
import 'common/package_looping_command.dart';
import 'common/repository_package.dart';

View File

@ -11,6 +11,7 @@ import 'package:pubspec_parse/pubspec_parse.dart';
import 'package:yaml_edit/yaml_edit.dart';
import 'common/core.dart';
import 'common/output_utils.dart';
import 'common/package_looping_command.dart';
import 'common/pub_utils.dart';
import 'common/pub_version_finder.dart';

View File

@ -9,7 +9,7 @@ import 'package:meta/meta.dart';
import 'package:yaml/yaml.dart';
import 'package:yaml_edit/yaml_edit.dart';
import 'common/core.dart';
import 'common/output_utils.dart';
import 'common/package_looping_command.dart';
import 'common/pub_utils.dart';
import 'common/repository_package.dart';

View File

@ -6,6 +6,7 @@ import 'package:pub_semver/pub_semver.dart';
import 'package:yaml_edit/yaml_edit.dart';
import 'common/core.dart';
import 'common/output_utils.dart';
import 'common/package_looping_command.dart';
import 'common/repository_package.dart';

View File

@ -7,8 +7,8 @@ import 'package:file/file.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:yaml_edit/yaml_edit.dart';
import 'common/core.dart';
import 'common/git_version_finder.dart';
import 'common/output_utils.dart';
import 'common/package_looping_command.dart';
import 'common/package_state_utils.dart';
import 'common/repository_package.dart';

View File

@ -8,8 +8,8 @@ import 'package:meta/meta.dart';
import 'package:path/path.dart' as p;
import 'package:pub_semver/pub_semver.dart';
import 'common/core.dart';
import 'common/git_version_finder.dart';
import 'common/output_utils.dart';
import 'common/package_looping_command.dart';
import 'common/package_state_utils.dart';
import 'common/pub_version_finder.dart';

View File

@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'common/core.dart';
import 'common/output_utils.dart';
import 'common/package_looping_command.dart';
import 'common/plugin_utils.dart';
import 'common/repository_package.dart';

View File

@ -0,0 +1,100 @@
// Copyright 2013 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 'dart:async';
import 'dart:io';
import 'package:flutter_plugin_tools/src/common/output_utils.dart';
import 'package:test/test.dart';
void main() {
group('with color support', () {
setUp(() {
useColorForOutput = true;
});
tearDown(() {
useColorForOutput = stdout.supportsAnsiEscapes;
});
test('colorize works', () async {
const String message = 'a message';
expect(
colorizeString(message, Styles.MAGENTA), '\x1B[35m$message\x1B[0m');
});
test('printSuccess is green', () async {
const String message = 'a message';
expect(await _capturePrint(() => printSuccess(message)),
'\x1B[32m$message\x1B[0m');
});
test('printWarning is yellow', () async {
const String message = 'a message';
expect(await _capturePrint(() => printWarning(message)),
'\x1B[33m$message\x1B[0m');
});
test('printError is red', () async {
const String message = 'a message';
expect(await _capturePrint(() => printError(message)),
'\x1B[31m$message\x1B[0m');
});
});
group('without color support', () {
setUp(() {
useColorForOutput = false;
});
tearDown(() {
useColorForOutput = stdout.supportsAnsiEscapes;
});
test('colorize no-ops', () async {
const String message = 'a message';
expect(colorizeString(message, Styles.MAGENTA), message);
});
test('printSuccess just prints', () async {
const String message = 'a message';
expect(await _capturePrint(() => printSuccess(message)), message);
});
test('printWarning just prints', () async {
const String message = 'a message';
expect(await _capturePrint(() => printWarning(message)), message);
});
test('printError just prints', () async {
const String message = 'a message';
expect(await _capturePrint(() => printError(message)), message);
});
});
}
/// Run the command [runner] with the given [args] and return
/// what was printed.
/// A custom [errorHandler] can be used to handle the runner error as desired without throwing.
Future<String> _capturePrint(void Function() printFunction) async {
final StringBuffer output = StringBuffer();
final ZoneSpecification spec = ZoneSpecification(
print: (_, __, ___, String message) {
output.write(message);
},
);
await Zone.current
.fork(specification: spec)
.run<Future<void>>(() async => printFunction());
return output.toString();
}

View File

@ -9,6 +9,7 @@ import 'package:args/command_runner.dart';
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_plugin_tools/src/common/core.dart';
import 'package:flutter_plugin_tools/src/common/output_utils.dart';
import 'package:flutter_plugin_tools/src/common/package_looping_command.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
@ -82,6 +83,8 @@ void main() {
late Directory thirdPartyPackagesDir;
setUp(() {
// Correct color handling is part of the behavior being tested here.
useColorForOutput = true;
fileSystem = MemoryFileSystem();
mockPlatform = MockPlatform();
packagesDir = createPackagesDirectory(fileSystem: fileSystem);
@ -90,6 +93,11 @@ void main() {
.childDirectory('packages');
});
tearDown(() {
// Restore the default behavior.
useColorForOutput = io.stdout.supportsAnsiEscapes;
});
/// Creates a TestPackageLoopingCommand instance that uses [gitDiffResponse]
/// for git diffs, and logs output to [printOutput].
TestPackageLoopingCommand createTestCommand({

View File

@ -525,8 +525,8 @@ void main() {
'Publishing all packages that have changed relative to "HEAD~"'),
contains('Running `pub publish ` in ${plugin1.path}...'),
contains('Running `pub publish ` in ${plugin2.path}...'),
contains('plugin1 - \x1B[32mpublished\x1B[0m'),
contains('plugin2/plugin2 - \x1B[32mpublished\x1B[0m'),
contains('plugin1 - published'),
contains('plugin2/plugin2 - published'),
]));
expect(
processRunner.recordedCalls,