Only output errors during implicit resolution (#3689)
diff --git a/lib/src/command/dependency_services.dart b/lib/src/command/dependency_services.dart
index 13c6e70..84f3921 100644
--- a/lib/src/command/dependency_services.dart
+++ b/lib/src/command/dependency_services.dart
@@ -448,7 +448,7 @@
cache.sources,
filePath: entrypoint.lockFilePath,
);
- await log.warningsOnlyUnlessTerminal(
+ await log.errorsOnlyUnlessTerminal(
() async {
final updatedPubspec = pubspecEditor.toString();
// Resolve versions, this will update transitive dependencies that were
diff --git a/lib/src/command/global_run.dart b/lib/src/command/global_run.dart
index e64e811..22f05c7 100644
--- a/lib/src/command/global_run.dart
+++ b/lib/src/command/global_run.dart
@@ -78,7 +78,7 @@
args,
vmArgs: vmArgs,
enableAsserts: argResults['enable-asserts'] || argResults['checked'],
- recompile: (executable) => log.warningsOnlyUnlessTerminal(
+ recompile: (executable) => log.errorsOnlyUnlessTerminal(
() => globalEntrypoint.precompileExecutable(executable)),
alwaysUseSubprocess: alwaysUseSubprocess,
);
diff --git a/lib/src/command/lish.dart b/lib/src/command/lish.dart
index 7090412..6ec403b 100644
--- a/lib/src/command/lish.dart
+++ b/lib/src/command/lish.dart
@@ -219,7 +219,7 @@
@override
Future runProtected() async {
if (argResults.wasParsed('server')) {
- await log.warningsOnlyUnlessTerminal(() {
+ await log.errorsOnlyUnlessTerminal(() {
log.message(
'''
The --server option is deprecated. Use `publish_to` in your pubspec.yaml or set
diff --git a/lib/src/command/outdated.dart b/lib/src/command/outdated.dart
index 0ae3ab3..50422e7 100644
--- a/lib/src/command/outdated.dart
+++ b/lib/src/command/outdated.dart
@@ -4,7 +4,6 @@
import 'dart:async';
import 'dart:convert';
-import 'dart:io';
import 'dart:math';
import 'package:collection/collection.dart' show IterableExtension;
@@ -39,7 +38,7 @@
/// Avoid showing spinning progress messages when not in a terminal, and
/// when we are outputting machine-readable json.
- bool get _shouldShowSpinner => stdout.hasTerminal && !argResults['json'];
+ bool get _shouldShowSpinner => terminalOutputForStdout && !argResults['json'];
@override
bool get takesArguments => false;
diff --git a/lib/src/command/run.dart b/lib/src/command/run.dart
index fd38930..3deaf76 100644
--- a/lib/src/command/run.dart
+++ b/lib/src/command/run.dart
@@ -53,7 +53,7 @@
@override
Future<void> runProtected() async {
if (deprecated) {
- await log.warningsOnlyUnlessTerminal(() {
+ await log.errorsOnlyUnlessTerminal(() {
log.message('Deprecated. Use `dart run` instead.');
});
}
@@ -96,7 +96,7 @@
Executable.adaptProgramName(package, executable),
args,
enableAsserts: argResults['enable-asserts'] || argResults['checked'],
- recompile: (executable) => log.warningsOnlyUnlessTerminal(
+ recompile: (executable) => log.errorsOnlyUnlessTerminal(
() => entrypoint.precompileExecutable(executable)),
vmArgs: vmArgs,
alwaysUseSubprocess: alwaysUseSubprocess,
diff --git a/lib/src/command/upgrade.dart b/lib/src/command/upgrade.dart
index defa20b..1fe147e 100644
--- a/lib/src/command/upgrade.dart
+++ b/lib/src/command/upgrade.dart
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
-import 'dart:io';
import 'package:pub_semver/pub_semver.dart';
import 'package:yaml_edit/yaml_edit.dart';
@@ -74,7 +73,7 @@
}
/// Avoid showing spinning progress messages when not in a terminal.
- bool get _shouldShowSpinner => stdout.hasTerminal;
+ bool get _shouldShowSpinner => terminalOutputForStdout;
bool get _dryRun => argResults['dry-run'];
diff --git a/lib/src/executable.dart b/lib/src/executable.dart
index 4c823d6..fecc272 100644
--- a/lib/src/executable.dart
+++ b/lib/src/executable.dart
@@ -310,7 +310,7 @@
} on DataException catch (e) {
log.fine('Resolution not up to date: ${e.message}. Redoing.');
try {
- await warningsOnlyUnlessTerminal(
+ await errorsOnlyUnlessTerminal(
() => entrypoint.acquireDependencies(
SolveType.get,
analytics: analytics,
@@ -368,7 +368,7 @@
if (!fileExists(snapshotPath) ||
entrypoint.packageGraph.isPackageMutable(package)) {
try {
- await warningsOnlyUnlessTerminal(
+ await errorsOnlyUnlessTerminal(
() => entrypoint.precompileExecutable(
executable,
additionalSources: additionalSources,
diff --git a/lib/src/io.dart b/lib/src/io.dart
index 46d910c..7252803 100644
--- a/lib/src/io.dart
+++ b/lib/src/io.dart
@@ -27,6 +27,30 @@
export 'package:http/http.dart' show ByteStream;
+/// Environment variable names that are recognized by pub.
+class EnvironmentKeys {
+ /// Overrides terminal detection for stdout.
+ ///
+ /// Supported values:
+ /// * missing or `''` (empty string): dart:io terminal detection is used.
+ /// * `"0"`: output as if no terminal is attached
+ /// - no animations
+ /// - no ANSI colors
+ /// - use unicode characters
+ /// - silent inside [log.errorsOnlyUnlessTerminal]).
+ /// * `"1"`: output as if a terminal is attached
+ /// - animations
+ /// - ANSI colors (can be overriden again with NO_COLOR)
+ /// - no unicode on Windows
+ /// - normal verbosity in output inside
+ /// [log.errorsOnlyUnlessTerminal]).
+ ///
+ /// This variable is mainly for testing, and no forward compatibility
+ /// guarantees are given.
+ static const forceTerminalOutput = '_PUB_FORCE_TERMINAL_OUTPUT';
+ // TODO(sigurdm): Add other environment keys here.
+}
+
/// The pool used for restricting access to asynchronous operations that consume
/// file descriptors.
///
@@ -639,6 +663,25 @@
}
}
+/// Returns `true` if [stdout] should be treated as a terminal.
+///
+/// The detected behaviour can be overridden with the environment variable
+/// [EnvironmentKeys.forceTerminalOutput].
+bool get terminalOutputForStdout {
+ final environmentValue =
+ Platform.environment[EnvironmentKeys.forceTerminalOutput];
+ if (environmentValue == null || environmentValue == '') {
+ return stdout.hasTerminal;
+ } else if (environmentValue == '0') {
+ return false;
+ } else if (environmentValue == '1') {
+ return true;
+ } else {
+ throw DataException(
+ 'Environment variable ${EnvironmentKeys.forceTerminalOutput} has unsupported value: $environmentValue.');
+ }
+}
+
/// Flushes the stdout and stderr streams, then exits the program with the given
/// status code.
///
diff --git a/lib/src/log.dart b/lib/src/log.dart
index 61c6768..d8937f3 100644
--- a/lib/src/log.dart
+++ b/lib/src/log.dart
@@ -410,10 +410,10 @@
/// Unless the user has overriden the verbosity,
///
/// This is useful to not pollute stdout when the output is piped somewhere.
-Future<T> warningsOnlyUnlessTerminal<T>(FutureOr<T> Function() callback) async {
+Future<T> errorsOnlyUnlessTerminal<T>(FutureOr<T> Function() callback) async {
final oldVerbosity = verbosity;
- if (verbosity == Verbosity.normal && !stdout.hasTerminal) {
- verbosity = Verbosity.warning;
+ if (verbosity == Verbosity.normal && !terminalOutputForStdout) {
+ verbosity = Verbosity.error;
}
final result = await callback();
verbosity = oldVerbosity;
diff --git a/lib/src/progress.dart b/lib/src/progress.dart
index b9c6931..a127ae5 100644
--- a/lib/src/progress.dart
+++ b/lib/src/progress.dart
@@ -5,6 +5,7 @@
import 'dart:async';
import 'dart:io';
+import 'io.dart';
import 'log.dart' as log;
import 'utils.dart';
@@ -39,9 +40,8 @@
// The animation is only shown when it would be meaningful to a human.
// That means we're writing a visible message to a TTY at normal log levels
// with non-JSON output.
- if (stdioType(stdout) != StdioType.terminal ||
+ if (terminalOutputForStdout ||
!log.verbosity.isLevelVisible(level) ||
- log.json.enabled ||
fine ||
log.verbosity.isLevelVisible(log.Level.fine)) {
// Not animating, so just log the start and wait until the task is
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index 0719bbb..72f8201 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -418,7 +418,7 @@
return false;
case ForceColorOption.auto:
return (!Platform.environment.containsKey('NO_COLOR')) &&
- stdout.hasTerminal &&
+ terminalOutputForStdout &&
stdout.supportsAnsiEscapes;
}
}
@@ -439,7 +439,7 @@
// The tests support unicode also on windows.
runningFromTest ||
// When not outputting to terminal we can also use unicode.
- !stdout.hasTerminal ||
+ !terminalOutputForStdout ||
!Platform.isWindows ||
Platform.environment.containsKey('WT_SESSION');
diff --git a/test/embedding/embedding_test.dart b/test/embedding/embedding_test.dart
index 3aaf53b..16d2e7d 100644
--- a/test/embedding/embedding_test.dart
+++ b/test/embedding/embedding_test.dart
@@ -7,6 +7,7 @@
import 'package:path/path.dart' as path;
import 'package:path/path.dart' as p;
+import 'package:pub/src/io.dart' show EnvironmentKeys;
import 'package:test/test.dart';
import 'package:test_process/test_process.dart';
@@ -314,6 +315,69 @@
),
);
});
+
+ test('`embedding run` does not have output when successful and no terminal',
+ () async {
+ await d.dir(appPath, [
+ d.pubspec({
+ 'name': 'myapp',
+ 'dependencies': {'foo': '^1.0.0'}
+ }),
+ d.dir('bin', [
+ d.file('myapp.dart', 'main() {print(42);}'),
+ ])
+ ]).create();
+
+ final server = await servePackages();
+ server.serve('foo', '1.0.0');
+
+ final buffer = StringBuffer();
+ await runEmbeddingToBuffer(
+ ['run', 'myapp'],
+ buffer,
+ workingDirectory: d.path(appPath),
+ environment: {EnvironmentKeys.forceTerminalOutput: '0'},
+ );
+
+ expect(
+ buffer.toString(),
+ allOf(
+ isNot(contains('Resolving dependencies...')),
+ contains('42'),
+ ),
+ );
+ });
+ test('`embedding run` outputs info when successful and has a terminal',
+ () async {
+ await d.dir(appPath, [
+ d.pubspec({
+ 'name': 'myapp',
+ 'dependencies': {'foo': '^1.0.0'}
+ }),
+ d.dir('bin', [
+ d.file('myapp.dart', 'main() {print(42);}'),
+ ])
+ ]).create();
+
+ final server = await servePackages();
+ server.serve('foo', '1.0.0');
+
+ final buffer = StringBuffer();
+ await runEmbeddingToBuffer(
+ ['run', 'myapp'],
+ buffer,
+ workingDirectory: d.path(appPath),
+ environment: {EnvironmentKeys.forceTerminalOutput: '1'},
+ );
+ expect(
+ buffer.toString(),
+ allOf(
+ contains('Resolving dependencies'),
+ contains('+ foo 1.0.0'),
+ contains('42'),
+ ),
+ );
+ });
}
String _filter(String input) {
diff --git a/test/testdata/goldens/embedding/embedding_test/--color forces colors.txt b/test/testdata/goldens/embedding/embedding_test/--color forces colors.txt
index e4ad1d1..ed56eb3 100644
--- a/test/testdata/goldens/embedding/embedding_test/--color forces colors.txt
+++ b/test/testdata/goldens/embedding/embedding_test/--color forces colors.txt
@@ -1,14 +1,14 @@
# GENERATED BY: test/embedding/embedding_test.dart
$ tool/test-bin/pub_command_runner.dart pub --no-color get
-Resolving dependencies...
+Resolving dependencies...
+ foo 1.0.0 (2.0.0 available)
Changed 1 dependency!
-------------------------------- END OF OUTPUT ---------------------------------
$ tool/test-bin/pub_command_runner.dart pub --color get
-Resolving dependencies...
+Resolving dependencies...
[1mfoo[0m 1.0.0 [36m(2.0.0 available)[39m
Got dependencies!
diff --git a/test/testdata/goldens/embedding/embedding_test/run works, though hidden.txt b/test/testdata/goldens/embedding/embedding_test/run works, though hidden.txt
index 214712a..8eea7f3 100644
--- a/test/testdata/goldens/embedding/embedding_test/run works, though hidden.txt
+++ b/test/testdata/goldens/embedding/embedding_test/run works, though hidden.txt
@@ -1,7 +1,7 @@
# GENERATED BY: test/embedding/embedding_test.dart
$ tool/test-bin/pub_command_runner.dart pub get
-Resolving dependencies...
+Resolving dependencies...
Got dependencies!
-------------------------------- END OF OUTPUT ---------------------------------