Ignore broken console output in some situations.

This commit is contained in:
Eric Huss 2020-05-12 08:03:35 -07:00
parent 55869de80a
commit 7274307af4
25 changed files with 294 additions and 141 deletions

View File

@ -1,5 +1,5 @@
use cargo::core::features; use cargo::core::features;
use cargo::{self, CliResult, Config}; use cargo::{self, drop_print, drop_println, CliResult, Config};
use clap::{AppSettings, Arg, ArgMatches}; use clap::{AppSettings, Arg, ArgMatches};
use super::commands; use super::commands;
@ -25,7 +25,8 @@ pub fn main(config: &mut Config) -> CliResult {
}; };
if args.value_of("unstable-features") == Some("help") { if args.value_of("unstable-features") == Some("help") {
println!( drop_println!(
config,
" "
Available unstable (nightly-only) flags: Available unstable (nightly-only) flags:
@ -40,7 +41,8 @@ Available unstable (nightly-only) flags:
Run with 'cargo -Z [FLAG] [SUBCOMMAND]'" Run with 'cargo -Z [FLAG] [SUBCOMMAND]'"
); );
if !features::nightly_features_allowed() { if !features::nightly_features_allowed() {
println!( drop_println!(
config,
"\nUnstable flags are only available on the nightly channel \ "\nUnstable flags are only available on the nightly channel \
of Cargo, but this is the `{}` channel.\n\ of Cargo, but this is the `{}` channel.\n\
{}", {}",
@ -48,7 +50,8 @@ Run with 'cargo -Z [FLAG] [SUBCOMMAND]'"
features::SEE_CHANNELS features::SEE_CHANNELS
); );
} }
println!( drop_println!(
config,
"\nSee https://doc.rust-lang.org/nightly/cargo/reference/unstable.html \ "\nSee https://doc.rust-lang.org/nightly/cargo/reference/unstable.html \
for more information about these flags." for more information about these flags."
); );
@ -58,7 +61,7 @@ Run with 'cargo -Z [FLAG] [SUBCOMMAND]'"
let is_verbose = args.occurrences_of("verbose") > 0; let is_verbose = args.occurrences_of("verbose") > 0;
if args.is_present("version") { if args.is_present("version") {
let version = get_version_string(is_verbose); let version = get_version_string(is_verbose);
print!("{}", version); drop_print!(config, "{}", version);
return Ok(()); return Ok(());
} }
@ -69,19 +72,19 @@ Run with 'cargo -Z [FLAG] [SUBCOMMAND]'"
} }
if args.is_present("list") { if args.is_present("list") {
println!("Installed Commands:"); drop_println!(config, "Installed Commands:");
for command in list_commands(config) { for command in list_commands(config) {
match command { match command {
CommandInfo::BuiltIn { name, about } => { CommandInfo::BuiltIn { name, about } => {
let summary = about.unwrap_or_default(); let summary = about.unwrap_or_default();
let summary = summary.lines().next().unwrap_or(&summary); // display only the first line let summary = summary.lines().next().unwrap_or(&summary); // display only the first line
println!(" {:<20} {}", name, summary) drop_println!(config, " {:<20} {}", name, summary);
} }
CommandInfo::External { name, path } => { CommandInfo::External { name, path } => {
if is_verbose { if is_verbose {
println!(" {:<20} {}", name, path.display()) drop_println!(config, " {:<20} {}", name, path.display());
} else { } else {
println!(" {}", name) drop_println!(config, " {}", name);
} }
} }
} }

View File

@ -1,6 +1,4 @@
use crate::command_prelude::*; use crate::command_prelude::*;
use cargo::print_json;
use serde::Serialize; use serde::Serialize;
pub fn cli() -> App { pub fn cli() -> App {
@ -30,6 +28,6 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
let location = ProjectLocation { root }; let location = ProjectLocation { root };
print_json(&location); config.shell().print_json(&location);
Ok(()) Ok(())
} }

View File

@ -1,7 +1,5 @@
use crate::command_prelude::*; use crate::command_prelude::*;
use cargo::ops::{self, OutputMetadataOptions}; use cargo::ops::{self, OutputMetadataOptions};
use cargo::print_json;
pub fn cli() -> App { pub fn cli() -> App {
subcommand("metadata") subcommand("metadata")
@ -54,6 +52,6 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
}; };
let result = ops::output_metadata(&ws, &options)?; let result = ops::output_metadata(&ws, &options)?;
print_json(&result); config.shell().print_json(&result);
Ok(()) Ok(())
} }

View File

@ -37,6 +37,6 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
let ws = args.workspace(config)?; let ws = args.workspace(config)?;
let spec = args.value_of("spec").or_else(|| args.value_of("package")); let spec = args.value_of("spec").or_else(|| args.value_of("package"));
let spec = ops::pkgid(&ws, spec)?; let spec = ops::pkgid(&ws, spec)?;
println!("{}", spec); cargo::drop_println!(config, "{}", spec);
Ok(()) Ok(())
} }

View File

@ -1,7 +1,5 @@
use crate::command_prelude::*; use crate::command_prelude::*;
use cargo::print_json;
pub fn cli() -> App { pub fn cli() -> App {
subcommand("read-manifest") subcommand("read-manifest")
.about( .about(
@ -17,6 +15,6 @@ Deprecated, use `cargo metadata --no-deps` instead.\
pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
let ws = args.workspace(config)?; let ws = args.workspace(config)?;
print_json(&ws.current()?); config.shell().print_json(&ws.current()?);
Ok(()) Ok(())
} }

View File

@ -102,7 +102,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
if args.is_present("version") { if args.is_present("version") {
let verbose = args.occurrences_of("verbose") > 0; let verbose = args.occurrences_of("verbose") > 0;
let version = cli::get_version_string(verbose); let version = cli::get_version_string(verbose);
print!("{}", version); cargo::drop_print!(config, "{}", version);
return Ok(()); return Ok(());
} }
let prefix = if args.is_present("no-indent") { let prefix = if args.is_present("no-indent") {

View File

@ -3,8 +3,6 @@ use crate::command_prelude::*;
use std::collections::HashMap; use std::collections::HashMap;
use std::process; use std::process;
use cargo::print_json;
pub fn cli() -> App { pub fn cli() -> App {
subcommand("verify-project") subcommand("verify-project")
.about("Check correctness of crate manifest") .about("Check correctness of crate manifest")
@ -13,19 +11,15 @@ pub fn cli() -> App {
} }
pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
fn fail(reason: &str, value: &str) -> ! {
let mut h = HashMap::new();
h.insert(reason.to_string(), value.to_string());
print_json(&h);
process::exit(1)
}
if let Err(e) = args.workspace(config) { if let Err(e) = args.workspace(config) {
fail("invalid", &e.to_string()) let mut h = HashMap::new();
h.insert("invalid".to_string(), e.to_string());
config.shell().print_json(&h);
process::exit(1)
} }
let mut h = HashMap::new(); let mut h = HashMap::new();
h.insert("success".to_string(), "true".to_string()); h.insert("success".to_string(), "true".to_string());
print_json(&h); config.shell().print_json(&h);
Ok(()) Ok(())
} }

View File

@ -1,6 +1,5 @@
use crate::command_prelude::*;
use crate::cli; use crate::cli;
use crate::command_prelude::*;
pub fn cli() -> App { pub fn cli() -> App {
subcommand("version") subcommand("version")
@ -8,9 +7,9 @@ pub fn cli() -> App {
.arg(opt("quiet", "No output printed to stdout").short("q")) .arg(opt("quiet", "No output printed to stdout").short("q"))
} }
pub fn exec(_config: &mut Config, args: &ArgMatches<'_>) -> CliResult { pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
let verbose = args.occurrences_of("verbose") > 0; let verbose = args.occurrences_of("verbose") > 0;
let version = cli::get_version_string(verbose); let version = cli::get_version_string(verbose);
print!("{}", version); cargo::drop_print!(config, "{}", version);
Ok(()) Ok(())
} }

View File

@ -500,7 +500,7 @@ impl<'cfg> DrainState<'cfg> {
plan.update(&module_name, &cmd, &filenames)?; plan.update(&module_name, &cmd, &filenames)?;
} }
Message::Stdout(out) => { Message::Stdout(out) => {
cx.bcx.config.shell().stdout_println(out); writeln!(cx.bcx.config.shell().out(), "{}", out)?;
} }
Message::Stderr(err) => { Message::Stderr(err) => {
let mut shell = cx.bcx.config.shell(); let mut shell = cx.bcx.config.shell();
@ -700,7 +700,7 @@ impl<'cfg> DrainState<'cfg> {
success: error.is_none(), success: error.is_none(),
} }
.to_json_string(); .to_json_string();
cx.bcx.config.shell().stdout_println(msg); writeln!(cx.bcx.config.shell().out(), "{}", msg)?;
} }
if let Some(e) = error { if let Some(e) = error {

View File

@ -146,7 +146,7 @@ fn compile<'cfg>(
&unit.target, &unit.target,
cx.files().message_cache_path(unit), cx.files().message_cache_path(unit),
cx.bcx.build_config.message_format, cx.bcx.build_config.message_format,
cx.bcx.config.shell().supports_color(), cx.bcx.config.shell().err_supports_color(),
) )
} else { } else {
Work::noop() Work::noop()
@ -1109,7 +1109,7 @@ struct OutputOptions {
impl OutputOptions { impl OutputOptions {
fn new(cx: &Context<'_, '_>, unit: &Unit) -> OutputOptions { fn new(cx: &Context<'_, '_>, unit: &Unit) -> OutputOptions {
let look_for_metadata_directive = cx.rmeta_required(unit); let look_for_metadata_directive = cx.rmeta_required(unit);
let color = cx.bcx.config.shell().supports_color(); let color = cx.bcx.config.shell().err_supports_color();
let path = cx.files().message_cache_path(unit); let path = cx.files().message_cache_path(unit);
// Remove old cache, ignore ENOENT, which is the common case. // Remove old cache, ignore ENOENT, which is the common case.
drop(fs::remove_file(&path)); drop(fs::remove_file(&path));

View File

@ -245,7 +245,7 @@ impl<'cfg> Timings<'cfg> {
rmeta_time: unit_time.rmeta_time, rmeta_time: unit_time.rmeta_time,
} }
.to_json_string(); .to_json_string();
self.config.shell().stdout_println(msg); crate::drop_println!(self.config, "{}", msg);
} }
self.unit_times.push(unit_time); self.unit_times.push(unit_time);
} }

View File

@ -113,6 +113,6 @@ pub fn emit_serialized_unit_graph(root_units: &[Unit], unit_graph: &UnitGraph) -
let stdout = std::io::stdout(); let stdout = std::io::stdout();
let mut lock = stdout.lock(); let mut lock = stdout.lock();
serde_json::to_writer(&mut lock, &s)?; serde_json::to_writer(&mut lock, &s)?;
writeln!(lock)?; drop(writeln!(lock));
Ok(()) Ok(())
} }

View File

@ -500,7 +500,7 @@ impl Manifest {
pub fn print_teapot(&self, config: &Config) { pub fn print_teapot(&self, config: &Config) {
if let Some(teapot) = self.im_a_teapot { if let Some(teapot) = self.im_a_teapot {
if config.cli_unstable().print_im_a_teapot { if config.cli_unstable().print_im_a_teapot {
println!("im-a-teapot = {}", teapot); crate::drop_println!(config, "im-a-teapot = {}", teapot);
} }
} }
} }

View File

@ -573,9 +573,13 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
for ((pkg_id, dep_kind), features) in &self.activated_features { for ((pkg_id, dep_kind), features) in &self.activated_features {
let r_features = self.resolve.features(*pkg_id); let r_features = self.resolve.features(*pkg_id);
if !r_features.iter().eq(features.iter()) { if !r_features.iter().eq(features.iter()) {
eprintln!( crate::drop_eprintln!(
self.ws.config(),
"{}/{:?} features mismatch\nresolve: {:?}\nnew: {:?}\n", "{}/{:?} features mismatch\nresolve: {:?}\nnew: {:?}\n",
pkg_id, dep_kind, r_features, features pkg_id,
dep_kind,
r_features,
features
); );
found = true; found = true;
} }

View File

@ -14,13 +14,13 @@ pub enum Verbosity {
Quiet, Quiet,
} }
/// An abstraction around a `Write`able object that remembers preferences for output verbosity and /// An abstraction around console output that remembers preferences for output
/// color. /// verbosity and color.
pub struct Shell { pub struct Shell {
/// the `Write`able object, either with or without color support (represented by different enum /// Wrapper around stdout/stderr. This helps with supporting sending
/// variants) /// output to a memory buffer which is useful for tests.
err: ShellOut, output: ShellOut,
/// How verbose messages should be /// How verbose messages should be.
verbosity: Verbosity, verbosity: Verbosity,
/// Flag that indicates the current line needs to be cleared before /// Flag that indicates the current line needs to be cleared before
/// printing. Used when a progress bar is currently displayed. /// printing. Used when a progress bar is currently displayed.
@ -29,7 +29,7 @@ pub struct Shell {
impl fmt::Debug for Shell { impl fmt::Debug for Shell {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.err { match self.output {
ShellOut::Write(_) => f ShellOut::Write(_) => f
.debug_struct("Shell") .debug_struct("Shell")
.field("verbosity", &self.verbosity) .field("verbosity", &self.verbosity)
@ -49,8 +49,9 @@ enum ShellOut {
Write(Box<dyn Write>), Write(Box<dyn Write>),
/// Color-enabled stdio, with information on whether color should be used /// Color-enabled stdio, with information on whether color should be used
Stream { Stream {
stream: StandardStream, stdout: StandardStream,
tty: bool, stderr: StandardStream,
stderr_tty: bool,
color_choice: ColorChoice, color_choice: ColorChoice,
}, },
} }
@ -70,11 +71,13 @@ impl Shell {
/// Creates a new shell (color choice and verbosity), defaulting to 'auto' color and verbose /// Creates a new shell (color choice and verbosity), defaulting to 'auto' color and verbose
/// output. /// output.
pub fn new() -> Shell { pub fn new() -> Shell {
let auto = ColorChoice::CargoAuto.to_termcolor_color_choice();
Shell { Shell {
err: ShellOut::Stream { output: ShellOut::Stream {
stream: StandardStream::stderr(ColorChoice::CargoAuto.to_termcolor_color_choice()), stdout: StandardStream::stdout(auto),
stderr: StandardStream::stderr(auto),
color_choice: ColorChoice::CargoAuto, color_choice: ColorChoice::CargoAuto,
tty: atty::is(atty::Stream::Stderr), stderr_tty: atty::is(atty::Stream::Stderr),
}, },
verbosity: Verbosity::Verbose, verbosity: Verbosity::Verbose,
needs_clear: false, needs_clear: false,
@ -84,7 +87,7 @@ impl Shell {
/// Creates a shell from a plain writable object, with no color, and max verbosity. /// Creates a shell from a plain writable object, with no color, and max verbosity.
pub fn from_write(out: Box<dyn Write>) -> Shell { pub fn from_write(out: Box<dyn Write>) -> Shell {
Shell { Shell {
err: ShellOut::Write(out), output: ShellOut::Write(out),
verbosity: Verbosity::Verbose, verbosity: Verbosity::Verbose,
needs_clear: false, needs_clear: false,
} }
@ -105,18 +108,12 @@ impl Shell {
if self.needs_clear { if self.needs_clear {
self.err_erase_line(); self.err_erase_line();
} }
self.err.print(status, message, color, justified) self.output
.message_stderr(status, message, color, justified)
} }
} }
} }
pub fn stdout_println(&mut self, message: impl fmt::Display) {
if self.needs_clear {
self.err_erase_line();
}
println!("{}", message);
}
/// Sets whether the next print should clear the current line. /// Sets whether the next print should clear the current line.
pub fn set_needs_clear(&mut self, needs_clear: bool) { pub fn set_needs_clear(&mut self, needs_clear: bool) {
self.needs_clear = needs_clear; self.needs_clear = needs_clear;
@ -129,31 +126,44 @@ impl Shell {
/// Returns the width of the terminal in spaces, if any. /// Returns the width of the terminal in spaces, if any.
pub fn err_width(&self) -> Option<usize> { pub fn err_width(&self) -> Option<usize> {
match self.err { match self.output {
ShellOut::Stream { tty: true, .. } => imp::stderr_width(), ShellOut::Stream {
stderr_tty: true, ..
} => imp::stderr_width(),
_ => None, _ => None,
} }
} }
/// Returns `true` if stderr is a tty. /// Returns `true` if stderr is a tty.
pub fn is_err_tty(&self) -> bool { pub fn is_err_tty(&self) -> bool {
match self.err { match self.output {
ShellOut::Stream { tty, .. } => tty, ShellOut::Stream { stderr_tty, .. } => stderr_tty,
_ => false, _ => false,
} }
} }
/// Gets a reference to the underlying writer. /// Gets a reference to the underlying stdout writer.
pub fn out(&mut self) -> &mut dyn Write {
if self.needs_clear {
self.err_erase_line();
}
self.output.stdout()
}
/// Gets a reference to the underlying stderr writer.
pub fn err(&mut self) -> &mut dyn Write { pub fn err(&mut self) -> &mut dyn Write {
if self.needs_clear { if self.needs_clear {
self.err_erase_line(); self.err_erase_line();
} }
self.err.as_write() self.output.stderr()
} }
/// Erase from cursor to end of line. /// Erase from cursor to end of line.
pub fn err_erase_line(&mut self) { pub fn err_erase_line(&mut self) {
if let ShellOut::Stream { tty: true, .. } = self.err { if let ShellOut::Stream {
stderr_tty: true, ..
} = self.output
{
imp::err_erase_line(self); imp::err_erase_line(self);
self.needs_clear = false; self.needs_clear = false;
} }
@ -216,7 +226,8 @@ impl Shell {
if self.needs_clear { if self.needs_clear {
self.err_erase_line(); self.err_erase_line();
} }
self.err.print(&"error", Some(&message), Red, false) self.output
.message_stderr(&"error", Some(&message), Red, false)
} }
/// Prints an amber 'warning' message. /// Prints an amber 'warning' message.
@ -245,10 +256,11 @@ impl Shell {
/// Updates the color choice (always, never, or auto) from a string.. /// Updates the color choice (always, never, or auto) from a string..
pub fn set_color_choice(&mut self, color: Option<&str>) -> CargoResult<()> { pub fn set_color_choice(&mut self, color: Option<&str>) -> CargoResult<()> {
if let ShellOut::Stream { if let ShellOut::Stream {
ref mut stream, ref mut stdout,
ref mut stderr,
ref mut color_choice, ref mut color_choice,
.. ..
} = self.err } = self.output
{ {
let cfg = match color { let cfg = match color {
Some("always") => ColorChoice::Always, Some("always") => ColorChoice::Always,
@ -263,7 +275,9 @@ impl Shell {
), ),
}; };
*color_choice = cfg; *color_choice = cfg;
*stream = StandardStream::stderr(cfg.to_termcolor_color_choice()); let choice = cfg.to_termcolor_color_choice();
*stdout = StandardStream::stdout(choice);
*stderr = StandardStream::stderr(choice);
} }
Ok(()) Ok(())
} }
@ -273,17 +287,17 @@ impl Shell {
/// If we are not using a color stream, this will always return `Never`, even if the color /// If we are not using a color stream, this will always return `Never`, even if the color
/// choice has been set to something else. /// choice has been set to something else.
pub fn color_choice(&self) -> ColorChoice { pub fn color_choice(&self) -> ColorChoice {
match self.err { match self.output {
ShellOut::Stream { color_choice, .. } => color_choice, ShellOut::Stream { color_choice, .. } => color_choice,
ShellOut::Write(_) => ColorChoice::Never, ShellOut::Write(_) => ColorChoice::Never,
} }
} }
/// Whether the shell supports color. /// Whether the shell supports color.
pub fn supports_color(&self) -> bool { pub fn err_supports_color(&self) -> bool {
match &self.err { match &self.output {
ShellOut::Write(_) => false, ShellOut::Write(_) => false,
ShellOut::Stream { stream, .. } => stream.supports_color(), ShellOut::Stream { stderr, .. } => stderr.supports_color(),
} }
} }
@ -302,6 +316,11 @@ impl Shell {
self.err().write_all(message)?; self.err().write_all(message)?;
Ok(()) Ok(())
} }
pub fn print_json<T: serde::ser::Serialize>(&mut self, obj: &T) {
let encoded = serde_json::to_string(&obj).unwrap();
drop(writeln!(self.out(), "{}", encoded));
}
} }
impl Default for Shell { impl Default for Shell {
@ -314,7 +333,7 @@ impl ShellOut {
/// Prints out a message with a status. The status comes first, and is bold plus the given /// Prints out a message with a status. The status comes first, and is bold plus the given
/// color. The status can be justified, in which case the max width that will right align is /// color. The status can be justified, in which case the max width that will right align is
/// 12 chars. /// 12 chars.
fn print( fn message_stderr(
&mut self, &mut self,
status: &dyn fmt::Display, status: &dyn fmt::Display,
message: Option<&dyn fmt::Display>, message: Option<&dyn fmt::Display>,
@ -322,20 +341,20 @@ impl ShellOut {
justified: bool, justified: bool,
) -> CargoResult<()> { ) -> CargoResult<()> {
match *self { match *self {
ShellOut::Stream { ref mut stream, .. } => { ShellOut::Stream { ref mut stderr, .. } => {
stream.reset()?; stderr.reset()?;
stream.set_color(ColorSpec::new().set_bold(true).set_fg(Some(color)))?; stderr.set_color(ColorSpec::new().set_bold(true).set_fg(Some(color)))?;
if justified { if justified {
write!(stream, "{:>12}", status)?; write!(stderr, "{:>12}", status)?;
} else { } else {
write!(stream, "{}", status)?; write!(stderr, "{}", status)?;
stream.set_color(ColorSpec::new().set_bold(true))?; stderr.set_color(ColorSpec::new().set_bold(true))?;
write!(stream, ":")?; write!(stderr, ":")?;
} }
stream.reset()?; stderr.reset()?;
match message { match message {
Some(message) => writeln!(stream, " {}", message)?, Some(message) => writeln!(stderr, " {}", message)?,
None => write!(stream, " ")?, None => write!(stderr, " ")?,
} }
} }
ShellOut::Write(ref mut w) => { ShellOut::Write(ref mut w) => {
@ -353,10 +372,18 @@ impl ShellOut {
Ok(()) Ok(())
} }
/// Gets this object as a `io::Write`. /// Gets stdout as a `io::Write`.
fn as_write(&mut self) -> &mut dyn Write { fn stdout(&mut self) -> &mut dyn Write {
match *self { match *self {
ShellOut::Stream { ref mut stream, .. } => stream, ShellOut::Stream { ref mut stdout, .. } => stdout,
ShellOut::Write(ref mut w) => w,
}
}
/// Gets stderr as a `io::Write`.
fn stderr(&mut self) -> &mut dyn Write {
match *self {
ShellOut::Stream { ref mut stderr, .. } => stderr,
ShellOut::Write(ref mut w) => w, ShellOut::Write(ref mut w) => w,
} }
} }
@ -404,7 +431,7 @@ mod imp {
// This is the "EL - Erase in Line" sequence. It clears from the cursor // This is the "EL - Erase in Line" sequence. It clears from the cursor
// to the end of line. // to the end of line.
// https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences // https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences
let _ = shell.err.as_write().write_all(b"\x1B[K"); let _ = shell.output.stderr().write_all(b"\x1B[K");
} }
} }

View File

@ -34,7 +34,6 @@ use crate::core::shell::Verbosity::Verbose;
use crate::core::Shell; use crate::core::Shell;
use anyhow::Error; use anyhow::Error;
use log::debug; use log::debug;
use serde::ser;
use std::fmt; use std::fmt;
pub use crate::util::errors::{InternalError, VerboseError}; pub use crate::util::errors::{InternalError, VerboseError};
@ -93,11 +92,6 @@ impl fmt::Display for VersionInfo {
} }
} }
pub fn print_json<T: ser::Serialize>(obj: &T) {
let encoded = serde_json::to_string(&obj).unwrap();
println!("{}", encoded);
}
pub fn exit_with_error(err: CliError, shell: &mut Shell) -> ! { pub fn exit_with_error(err: CliError, shell: &mut Shell) -> ! {
debug!("exit_with_error; err={:?}", err); debug!("exit_with_error; err={:?}", err);
if let Some(ref err) = err.error { if let Some(ref err) = err.error {

View File

@ -9,11 +9,11 @@ use tempfile::Builder as TempFileBuilder;
use crate::core::compiler::Freshness; use crate::core::compiler::Freshness;
use crate::core::compiler::{CompileKind, DefaultExecutor, Executor}; use crate::core::compiler::{CompileKind, DefaultExecutor, Executor};
use crate::core::{Edition, Package, PackageId, Source, SourceId, Workspace}; use crate::core::{Edition, Package, PackageId, Source, SourceId, Workspace};
use crate::ops;
use crate::ops::common_for_install_and_uninstall::*; use crate::ops::common_for_install_and_uninstall::*;
use crate::sources::{GitSource, SourceConfigMap}; use crate::sources::{GitSource, SourceConfigMap};
use crate::util::errors::{CargoResult, CargoResultExt}; use crate::util::errors::{CargoResult, CargoResultExt};
use crate::util::{paths, Config, Filesystem}; use crate::util::{paths, Config, Filesystem};
use crate::{drop_println, ops};
struct Transaction { struct Transaction {
bins: Vec<PathBuf>, bins: Vec<PathBuf>,
@ -531,9 +531,9 @@ pub fn install_list(dst: Option<&str>, config: &Config) -> CargoResult<()> {
let root = resolve_root(dst, config)?; let root = resolve_root(dst, config)?;
let tracker = InstallTracker::load(config, &root)?; let tracker = InstallTracker::load(config, &root)?;
for (k, v) in tracker.all_installed_bins() { for (k, v) in tracker.all_installed_bins() {
println!("{}:", k); drop_println!(config, "{}:", k);
for bin in v { for bin in v {
println!(" {}", bin); drop_println!(config, " {}", bin);
} }
} }
Ok(()) Ok(())

View File

@ -15,12 +15,12 @@ use tar::{Archive, Builder, EntryType, Header};
use crate::core::compiler::{BuildConfig, CompileMode, DefaultExecutor, Executor}; use crate::core::compiler::{BuildConfig, CompileMode, DefaultExecutor, Executor};
use crate::core::{Feature, Shell, Verbosity, Workspace}; use crate::core::{Feature, Shell, Verbosity, Workspace};
use crate::core::{Package, PackageId, PackageSet, Resolve, Source, SourceId}; use crate::core::{Package, PackageId, PackageSet, Resolve, Source, SourceId};
use crate::ops;
use crate::sources::PathSource; use crate::sources::PathSource;
use crate::util::errors::{CargoResult, CargoResultExt}; use crate::util::errors::{CargoResult, CargoResultExt};
use crate::util::paths; use crate::util::paths;
use crate::util::toml::TomlManifest; use crate::util::toml::TomlManifest;
use crate::util::{self, restricted_names, Config, FileLock}; use crate::util::{self, restricted_names, Config, FileLock};
use crate::{drop_println, ops};
pub struct PackageOpts<'cfg> { pub struct PackageOpts<'cfg> {
pub config: &'cfg Config, pub config: &'cfg Config,
@ -102,7 +102,7 @@ pub fn package(ws: &Workspace<'_>, opts: &PackageOpts<'_>) -> CargoResult<Option
if opts.list { if opts.list {
for ar_file in ar_files { for ar_file in ar_files {
println!("{}", ar_file.rel_str); drop_println!(config, "{}", ar_file.rel_str);
} }
return Ok(None); return Ok(None);
} }

View File

@ -23,7 +23,7 @@ use crate::util::errors::{CargoResult, CargoResultExt};
use crate::util::important_paths::find_root_manifest_for_wd; use crate::util::important_paths::find_root_manifest_for_wd;
use crate::util::IntoUrl; use crate::util::IntoUrl;
use crate::util::{paths, validate_package_name}; use crate::util::{paths, validate_package_name};
use crate::version; use crate::{drop_print, drop_println, version};
/// Registry settings loaded from config files. /// Registry settings loaded from config files.
/// ///
@ -660,7 +660,8 @@ pub fn registry_login(
let token = match token { let token = match token {
Some(token) => token, Some(token) => token,
None => { None => {
println!( drop_println!(
config,
"please visit {}/me and paste the API Token below", "please visit {}/me and paste the API Token below",
registry.host() registry.host()
); );
@ -751,11 +752,11 @@ pub fn modify_owners(config: &Config, opts: &OwnersOptions) -> CargoResult<()> {
.list_owners(&name) .list_owners(&name)
.chain_err(|| format!("failed to list owners of crate {}", name))?; .chain_err(|| format!("failed to list owners of crate {}", name))?;
for owner in owners.iter() { for owner in owners.iter() {
print!("{}", owner.login); drop_print!(config, "{}", owner.login);
match (owner.name.as_ref(), owner.email.as_ref()) { match (owner.name.as_ref(), owner.email.as_ref()) {
(Some(name), Some(email)) => println!(" ({} <{}>)", name, email), (Some(name), Some(email)) => drop_println!(config, " ({} <{}>)", name, email),
(Some(s), None) | (None, Some(s)) => println!(" ({})", s), (Some(s), None) | (None, Some(s)) => drop_println!(config, " ({})", s),
(None, None) => println!(), (None, None) => drop_println!(config),
} }
} }
} }
@ -876,12 +877,13 @@ pub fn search(
} }
None => name, None => name,
}; };
println!("{}", line); drop_println!(config, "{}", line);
} }
let search_max_limit = 100; let search_max_limit = 100;
if total_crates > limit && limit < search_max_limit { if total_crates > limit && limit < search_max_limit {
println!( drop_println!(
config,
"... and {} crates more (use --limit N to see more)", "... and {} crates more (use --limit N to see more)",
total_crates - limit total_crates - limit
); );
@ -894,7 +896,12 @@ pub fn search(
} else { } else {
String::new() String::new()
}; };
println!("... and {} crates more{}", total_crates - limit, extra); drop_println!(
config,
"... and {} crates more{}",
total_crates - limit,
extra
);
} }
Ok(()) Ok(())

View File

@ -6,7 +6,8 @@ use crate::core::dependency::DepKind;
use crate::core::resolver::{HasDevUnits, ResolveOpts}; use crate::core::resolver::{HasDevUnits, ResolveOpts};
use crate::core::{Package, PackageId, PackageIdSpec, Workspace}; use crate::core::{Package, PackageId, PackageIdSpec, Workspace};
use crate::ops::{self, Packages}; use crate::ops::{self, Packages};
use crate::util::CargoResult; use crate::util::{CargoResult, Config};
use crate::{drop_print, drop_println};
use anyhow::{bail, Context}; use anyhow::{bail, Context};
use graph::Graph; use graph::Graph;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
@ -200,12 +201,17 @@ pub fn build_and_print(ws: &Workspace<'_>, opts: &TreeOptions) -> CargoResult<()
graph.invert(); graph.invert();
} }
print(opts, root_indexes, &graph)?; print(ws.config(), opts, root_indexes, &graph)?;
Ok(()) Ok(())
} }
/// Prints a tree for each given root. /// Prints a tree for each given root.
fn print(opts: &TreeOptions, roots: Vec<usize>, graph: &Graph<'_>) -> CargoResult<()> { fn print(
config: &Config,
opts: &TreeOptions,
roots: Vec<usize>,
graph: &Graph<'_>,
) -> CargoResult<()> {
let format = Pattern::new(&opts.format) let format = Pattern::new(&opts.format)
.with_context(|| format!("tree format `{}` not valid", opts.format))?; .with_context(|| format!("tree format `{}` not valid", opts.format))?;
@ -220,7 +226,7 @@ fn print(opts: &TreeOptions, roots: Vec<usize>, graph: &Graph<'_>) -> CargoResul
for (i, root_index) in roots.into_iter().enumerate() { for (i, root_index) in roots.into_iter().enumerate() {
if i != 0 { if i != 0 {
println!(); drop_println!(config);
} }
// A stack of bools used to determine where | symbols should appear // A stack of bools used to determine where | symbols should appear
@ -231,6 +237,7 @@ fn print(opts: &TreeOptions, roots: Vec<usize>, graph: &Graph<'_>) -> CargoResul
let mut print_stack = vec![]; let mut print_stack = vec![];
print_node( print_node(
config,
graph, graph,
root_index, root_index,
&format, &format,
@ -248,6 +255,7 @@ fn print(opts: &TreeOptions, roots: Vec<usize>, graph: &Graph<'_>) -> CargoResul
/// Prints a package and all of its dependencies. /// Prints a package and all of its dependencies.
fn print_node<'a>( fn print_node<'a>(
config: &Config,
graph: &'a Graph<'_>, graph: &'a Graph<'_>,
node_index: usize, node_index: usize,
format: &Pattern, format: &Pattern,
@ -261,12 +269,12 @@ fn print_node<'a>(
let new = no_dedupe || visited_deps.insert(node_index); let new = no_dedupe || visited_deps.insert(node_index);
match prefix { match prefix {
Prefix::Depth => print!("{}", levels_continue.len()), Prefix::Depth => drop_print!(config, "{}", levels_continue.len()),
Prefix::Indent => { Prefix::Indent => {
if let Some((last_continues, rest)) = levels_continue.split_last() { if let Some((last_continues, rest)) = levels_continue.split_last() {
for continues in rest { for continues in rest {
let c = if *continues { symbols.down } else { " " }; let c = if *continues { symbols.down } else { " " };
print!("{} ", c); drop_print!(config, "{} ", c);
} }
let c = if *last_continues { let c = if *last_continues {
@ -274,7 +282,7 @@ fn print_node<'a>(
} else { } else {
symbols.ell symbols.ell
}; };
print!("{0}{1}{1} ", c, symbols.right); drop_print!(config, "{0}{1}{1} ", c, symbols.right);
} }
} }
Prefix::None => {} Prefix::None => {}
@ -290,7 +298,7 @@ fn print_node<'a>(
} else { } else {
" (*)" " (*)"
}; };
println!("{}{}", format.display(graph, node_index), star); drop_println!(config, "{}{}", format.display(graph, node_index), star);
if !new || in_cycle { if !new || in_cycle {
return; return;
@ -304,6 +312,7 @@ fn print_node<'a>(
EdgeKind::Feature, EdgeKind::Feature,
] { ] {
print_dependencies( print_dependencies(
config,
graph, graph,
node_index, node_index,
format, format,
@ -321,6 +330,7 @@ fn print_node<'a>(
/// Prints all the dependencies of a package for the given dependency kind. /// Prints all the dependencies of a package for the given dependency kind.
fn print_dependencies<'a>( fn print_dependencies<'a>(
config: &Config,
graph: &'a Graph<'_>, graph: &'a Graph<'_>,
node_index: usize, node_index: usize,
format: &Pattern, format: &Pattern,
@ -348,10 +358,10 @@ fn print_dependencies<'a>(
if let Some(name) = name { if let Some(name) = name {
for continues in &**levels_continue { for continues in &**levels_continue {
let c = if *continues { symbols.down } else { " " }; let c = if *continues { symbols.down } else { " " };
print!("{} ", c); drop_print!(config, "{} ", c);
} }
println!("{}", name); drop_println!(config, "{}", name);
} }
} }
@ -359,6 +369,7 @@ fn print_dependencies<'a>(
while let Some(dependency) = it.next() { while let Some(dependency) = it.next() {
levels_continue.push(it.peek().is_some()); levels_continue.push(it.peek().is_some());
print_node( print_node(
config,
graph, graph,
*dependency, *dependency,
format, format,

View File

@ -19,20 +19,23 @@ pub struct VendorOptions<'a> {
} }
pub fn vendor(ws: &Workspace<'_>, opts: &VendorOptions<'_>) -> CargoResult<()> { pub fn vendor(ws: &Workspace<'_>, opts: &VendorOptions<'_>) -> CargoResult<()> {
let config = ws.config();
let mut extra_workspaces = Vec::new(); let mut extra_workspaces = Vec::new();
for extra in opts.extra.iter() { for extra in opts.extra.iter() {
let extra = ws.config().cwd().join(extra); let extra = config.cwd().join(extra);
let ws = Workspace::new(&extra, ws.config())?; let ws = Workspace::new(&extra, config)?;
extra_workspaces.push(ws); extra_workspaces.push(ws);
} }
let workspaces = extra_workspaces.iter().chain(Some(ws)).collect::<Vec<_>>(); let workspaces = extra_workspaces.iter().chain(Some(ws)).collect::<Vec<_>>();
let vendor_config = let vendor_config =
sync(ws.config(), &workspaces, opts).chain_err(|| "failed to sync".to_string())?; sync(config, &workspaces, opts).chain_err(|| "failed to sync".to_string())?;
let shell = ws.config().shell(); if config.shell().verbosity() != Verbosity::Quiet {
if shell.verbosity() != Verbosity::Quiet { crate::drop_eprint!(
eprint!("To use vendored sources, add this to your .cargo/config for this project:\n\n"); config,
print!("{}", &toml::to_string(&vendor_config).unwrap()); "To use vendored sources, add this to your .cargo/config for this project:\n\n"
);
crate::drop_print!(config, "{}", &toml::to_string(&vendor_config).unwrap());
} }
Ok(()) Ok(())

View File

@ -1744,3 +1744,45 @@ impl StringList {
&self.0 &self.0
} }
} }
#[macro_export]
macro_rules! __shell_print {
($config:expr, $which:ident, $newline:literal, $($arg:tt)*) => ({
let mut shell = $config.shell();
let out = shell.$which();
drop(out.write_fmt(format_args!($($arg)*)));
if $newline {
drop(out.write_all(b"\n"));
}
});
}
#[macro_export]
macro_rules! drop_println {
($config:expr) => ( $crate::drop_print!($config, "\n") );
($config:expr, $($arg:tt)*) => (
$crate::__shell_print!($config, out, true, $($arg)*)
);
}
#[macro_export]
macro_rules! drop_eprintln {
($config:expr) => ( $crate::drop_eprint!($config, "\n") );
($config:expr, $($arg:tt)*) => (
$crate::__shell_print!($config, err, true, $($arg)*)
);
}
#[macro_export]
macro_rules! drop_print {
($config:expr, $($arg:tt)*) => (
$crate::__shell_print!($config, out, false, $($arg)*)
);
}
#[macro_export]
macro_rules! drop_eprint {
($config:expr, $($arg:tt)*) => (
$crate::__shell_print!($config, err, false, $($arg)*)
);
}

View File

@ -2,7 +2,9 @@
use std::env; use std::env;
use std::fs::{self, File}; use std::fs::{self, File};
use std::io::Read;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Stdio;
use std::str; use std::str;
use cargo_test_support::cargo_process; use cargo_test_support::cargo_process;
@ -371,3 +373,24 @@ fn z_flags_help() {
.with_stdout_contains(" -Z unstable-options -- Allow the usage of unstable options") .with_stdout_contains(" -Z unstable-options -- Allow the usage of unstable options")
.run(); .run();
} }
#[cargo_test]
fn closed_output_ok() {
// Checks that closed output doesn't cause an error.
let mut p = cargo_process("--list").build_command();
p.stdout(Stdio::piped()).stderr(Stdio::piped());
let mut child = p.spawn().unwrap();
// Close stdout
drop(child.stdout.take());
// Read stderr
let mut s = String::new();
child
.stderr
.as_mut()
.unwrap()
.read_to_string(&mut s)
.unwrap();
let status = child.wait().unwrap();
assert!(status.success());
assert!(s.is_empty(), s);
}

View File

@ -23,6 +23,10 @@ fn simple_list() {
"id": 70, "id": 70,
"login": "github:rust-lang:core", "login": "github:rust-lang:core",
"name": "Core" "name": "Core"
},
{
"id": 123,
"login": "octocat"
} }
] ]
}"#; }"#;
@ -43,7 +47,14 @@ fn simple_list() {
.file("src/main.rs", "fn main() {}") .file("src/main.rs", "fn main() {}")
.build(); .build();
p.cargo("owner -l --token sekrit").run(); p.cargo("owner -l --token sekrit")
.with_stdout(
"\
github:rust-lang:core (Core)
octocat
",
)
.run();
} }
#[cargo_test] #[cargo_test]

View File

@ -35,9 +35,45 @@ fn write_crates(dest: &Path) {
"repository": "https://github.com/nick29581/libhoare", "repository": "https://github.com/nick29581/libhoare",
"updated_at": "2014-11-20T21:49:21Z", "updated_at": "2014-11-20T21:49:21Z",
"versions": null "versions": null
}], },
{
"id": "postgres",
"name": "postgres",
"updated_at": "2020-05-01T23:17:54.335921+00:00",
"versions": null,
"keywords": null,
"categories": null,
"badges": [
{
"badge_type": "circle-ci",
"attributes": {
"repository": "sfackler/rust-postgres",
"branch": null
}
}
],
"created_at": "2014-11-24T02:34:44.756689+00:00",
"downloads": 535491,
"recent_downloads": 88321,
"max_version": "0.17.3",
"newest_version": "0.17.3",
"description": "A native, synchronous PostgreSQL client",
"homepage": null,
"documentation": null,
"repository": "https://github.com/sfackler/rust-postgres",
"links": {
"version_downloads": "/api/v1/crates/postgres/downloads",
"versions": "/api/v1/crates/postgres/versions",
"owners": "/api/v1/crates/postgres/owners",
"owner_team": "/api/v1/crates/postgres/owner_team",
"owner_user": "/api/v1/crates/postgres/owner_user",
"reverse_dependencies": "/api/v1/crates/postgres/reverse_dependencies"
},
"exact_match": true
}
],
"meta": { "meta": {
"total": 1 "total": 2
} }
}"#; }"#;
@ -56,6 +92,11 @@ fn write_crates(dest: &Path) {
} }
} }
const SEARCH_RESULTS: &str = "\
hoare = \"0.1.1\" # Design by contract style assertions for Rust
postgres = \"0.17.3\" # A native, synchronous PostgreSQL client
";
fn setup() { fn setup() {
let cargo_home = paths::root().join(".cargo"); let cargo_home = paths::root().join(".cargo");
fs::create_dir_all(cargo_home).unwrap(); fs::create_dir_all(cargo_home).unwrap();
@ -114,7 +155,7 @@ fn not_update() {
drop(lock); drop(lock);
cargo_process("search postgres") cargo_process("search postgres")
.with_stdout_contains("hoare = \"0.1.1\" # Design by contract style assertions for Rust") .with_stdout_contains(SEARCH_RESULTS)
.with_stderr("") // without "Updating ... index" .with_stderr("") // without "Updating ... index"
.run(); .run();
} }
@ -125,7 +166,7 @@ fn replace_default() {
set_cargo_config(); set_cargo_config();
cargo_process("search postgres") cargo_process("search postgres")
.with_stdout_contains("hoare = \"0.1.1\" # Design by contract style assertions for Rust") .with_stdout_contains(SEARCH_RESULTS)
.with_stderr_contains("[..]Updating [..] index") .with_stderr_contains("[..]Updating [..] index")
.run(); .run();
} }
@ -136,7 +177,7 @@ fn simple() {
cargo_process("search postgres --index") cargo_process("search postgres --index")
.arg(registry_url().to_string()) .arg(registry_url().to_string())
.with_stdout_contains("hoare = \"0.1.1\" # Design by contract style assertions for Rust") .with_stdout_contains(SEARCH_RESULTS)
.run(); .run();
} }
@ -162,7 +203,7 @@ about this warning.
[UPDATING] `[CWD]/registry` index [UPDATING] `[CWD]/registry` index
", ",
) )
.with_stdout_contains("hoare = \"0.1.1\" # Design by contract style assertions for Rust") .with_stdout_contains(SEARCH_RESULTS)
.run(); .run();
} }
@ -190,7 +231,7 @@ about this warning.
[UPDATING] `[CWD]/registry` index [UPDATING] `[CWD]/registry` index
", ",
) )
.with_stdout_contains("hoare = \"0.1.1\" # Design by contract style assertions for Rust") .with_stdout_contains(SEARCH_RESULTS)
.run(); .run();
} }
@ -200,7 +241,7 @@ fn multiple_query_params() {
cargo_process("search postgres sql --index") cargo_process("search postgres sql --index")
.arg(registry_url().to_string()) .arg(registry_url().to_string())
.with_stdout_contains("hoare = \"0.1.1\" # Design by contract style assertions for Rust") .with_stdout_contains(SEARCH_RESULTS)
.run(); .run();
} }