diff --git a/src/cli/common.rs b/src/cli/common.rs index 7d9908cb0e..51ff39de1c 100644 --- a/src/cli/common.rs +++ b/src/cli/common.rs @@ -244,7 +244,7 @@ fn show_channel_updates( Ok((pkg, banner, width, color, version, previous_version)) }); - let mut t = cfg.process.stdout().terminal(cfg.process); + let mut t = cfg.process.stdout(); let data: Vec<_> = data.collect::>()?; let max_width = data @@ -307,7 +307,7 @@ pub(super) fn list_items( quiet: bool, process: &Process, ) -> Result { - let mut t = process.stdout().terminal(process); + let mut t = process.stdout(); for (name, installed) in items { if installed && !installed_only && !quiet { t.attr(terminalsource::Attr::Bold)?; diff --git a/src/cli/log.rs b/src/cli/log.rs index 8019443783..c50321efa7 100644 --- a/src/cli/log.rs +++ b/src/cli/log.rs @@ -54,7 +54,7 @@ where Ok(s) if s.eq_ignore_ascii_case("never") => false, // `RUSTUP_TERM_COLOR` is prioritized over `NO_COLOR`. _ if process.var("NO_COLOR").is_ok() => false, - _ => process.stderr().is_a_tty(process), + _ => process.stderr().is_a_tty(), }; let maybe_rustup_log_directives = process.var("RUSTUP_LOG"); let process = process.clone(); diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index 20cbacd571..67ec21b7cf 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -797,7 +797,7 @@ async fn default_( } async fn check_updates(cfg: &Cfg<'_>, opts: CheckOpts) -> Result { - let t = cfg.process.stdout().terminal(cfg.process); + let t = cfg.process.stdout(); let use_colors = matches!(t.color_choice(), ColorChoice::Auto | ColorChoice::Always); let mut update_available = false; let channels = cfg.list_channels()?; @@ -890,7 +890,7 @@ async fn check_updates(cfg: &Cfg<'_>, opts: CheckOpts) -> Result, verbose: bool) -> Result { // Print host triple { - let mut t = cfg.process.stdout().terminal(cfg.process); + let mut t = cfg.process.stdout(); t.attr(terminalsource::Attr::Bold)?; write!(t.lock(), "Default host: ")?; t.reset()?; @@ -1081,7 +1081,7 @@ async fn show(cfg: &Cfg<'_>, verbose: bool) -> Result { // Print rustup home directory { - let mut t = cfg.process.stdout().terminal(cfg.process); + let mut t = cfg.process.stdout(); t.attr(terminalsource::Attr::Bold)?; write!(t.lock(), "rustup home: ")?; t.reset()?; @@ -1121,7 +1121,7 @@ async fn show(cfg: &Cfg<'_>, verbose: bool) -> Result { // show installed toolchains { - let mut t = cfg.process.stdout().terminal(cfg.process); + let mut t = cfg.process.stdout(); print_header::(&mut t, "installed toolchains")?; @@ -1157,7 +1157,7 @@ async fn show(cfg: &Cfg<'_>, verbose: bool) -> Result { // show active toolchain { - let mut t = cfg.process.stdout().terminal(cfg.process); + let mut t = cfg.process.stdout(); writeln!(t.lock())?; diff --git a/src/cli/self_update.rs b/src/cli/self_update.rs index f3a5ab133b..7f39bbb7fa 100644 --- a/src/cli/self_update.rs +++ b/src/cli/self_update.rs @@ -537,7 +537,7 @@ pub(crate) async fn install( exit_code &= unix::do_anti_sudo_check(no_prompt, process)?; } - let mut term = process.stdout().terminal(process); + let mut term = process.stdout(); #[cfg(windows)] windows::maybe_install_msvc(&mut term, no_prompt, quiet, &opts, process).await?; @@ -991,7 +991,7 @@ pub(crate) fn uninstall(no_prompt: bool, process: &Process) -> Result anyhow::Result { - let mut t = process.stdout().terminal(process); + let mut t = process.stdout(); // Get current rustup version let current_version = env!("CARGO_PKG_VERSION"); diff --git a/src/cli/setup_mode.rs b/src/cli/setup_mode.rs index 085c3f3020..0cabf4aeef 100644 --- a/src/cli/setup_mode.rs +++ b/src/cli/setup_mode.rs @@ -97,6 +97,7 @@ pub async fn main( } = match RustupInit::try_parse() { Ok(args) => args, Err(e) if [ErrorKind::DisplayHelp, ErrorKind::DisplayVersion].contains(&e.kind()) => { + use std::io::Write as _; write!(process.stdout().lock(), "{e}")?; return Ok(utils::ExitCode(0)); } diff --git a/src/dist/component/package.rs b/src/dist/component/package.rs index ec79eae2c9..dcfefac91c 100644 --- a/src/dist/component/package.rs +++ b/src/dist/component/package.rs @@ -456,6 +456,7 @@ fn unpack_without_first_dir( None => { // Tar has item before containing directory // Complain about this so we can see if these exist. + use std::io::Write as _; writeln!( cx.process.stderr().lock(), "Unexpected: missing parent '{}' for '{}'", diff --git a/src/process.rs b/src/process.rs index 706d40cd63..a3e643ac75 100644 --- a/src/process.rs +++ b/src/process.rs @@ -148,19 +148,25 @@ impl Process { } } - pub(crate) fn stdout(&self) -> Box { + pub(crate) fn stdout(&self) -> terminalsource::ColorableTerminal { match self { - Process::OsProcess(_) => Box::new(io::stdout()), + Process::OsProcess(_) => terminalsource::ColorableTerminal::stdout(self), #[cfg(feature = "test")] - Process::TestProcess(p) => Box::new(filesource::TestWriter(p.stdout.clone())), + Process::TestProcess(p) => terminalsource::ColorableTerminal::test( + filesource::TestWriter(p.stdout.clone()), + self, + ), } } - pub(crate) fn stderr(&self) -> Box { + pub(crate) fn stderr(&self) -> terminalsource::ColorableTerminal { match self { - Process::OsProcess(_) => Box::new(io::stderr()), + Process::OsProcess(_) => terminalsource::ColorableTerminal::stderr(self), #[cfg(feature = "test")] - Process::TestProcess(p) => Box::new(filesource::TestWriter(p.stderr.clone())), + Process::TestProcess(p) => terminalsource::ColorableTerminal::test( + filesource::TestWriter(p.stderr.clone()), + self, + ), } } @@ -178,7 +184,7 @@ impl Process { #[cfg(feature = "test")] Process::TestProcess(_) => return ProgressDrawTarget::hidden(), } - let t = self.stdout().terminal(self); + let t = self.stdout(); match self.var("RUSTUP_TERM_PROGRESS_WHEN") { Ok(s) if s.eq_ignore_ascii_case("always") => ProgressDrawTarget::term_like(Box::new(t)), Ok(s) if s.eq_ignore_ascii_case("never") => ProgressDrawTarget::hidden(), diff --git a/src/process/filesource.rs b/src/process/filesource.rs index 33c17116be..98c7bff256 100644 --- a/src/process/filesource.rs +++ b/src/process/filesource.rs @@ -1,7 +1,4 @@ -use std::io::{self, BufRead, Read, Write}; - -use super::terminalsource::{ColorableTerminal, StreamSelector}; -use crate::process::Process; +use std::io::{self, BufRead, Read}; /// Stand-in for std::io::Stdin pub trait Stdin { @@ -21,75 +18,13 @@ impl Stdin for io::Stdin { } } -// -------------- stdout ------------------------------- - -/// This is a stand-in for [`std::io::StdoutLock`] and [`std::io::StderrLock`]. -pub trait WriterLock: Write {} - -/// This is a stand-in for [`std::io::Stdout`] or [`std::io::Stderr`]. -/// TODO: remove Sync. -pub trait Writer: Write + Send + Sync { - /// This is a stand-in for [`std::io::Stdout::lock`] or [`std::io::Stderr::lock`]. - fn lock(&self) -> Box; - - /// Query whether a TTY is present. Used in download_tracker - we may want - /// to remove this entirely with a better progress bar system (in favour of - /// filtering in the Terminal layer?) - fn is_a_tty(&self, process: &Process) -> bool; - - /// Construct a terminal on this writer. - fn terminal(&self, process: &Process) -> ColorableTerminal; -} - -// ----------------- OS support for writers ----------------- - -impl WriterLock for io::StdoutLock<'_> {} - -impl Writer for io::Stdout { - fn is_a_tty(&self, process: &Process) -> bool { - match process { - crate::process::Process::OsProcess(p) => p.stdout_is_a_tty, - #[cfg(feature = "test")] - crate::process::Process::TestProcess(_) => unreachable!(), - } - } - - fn lock(&self) -> Box { - Box::new(io::Stdout::lock(self)) - } - - fn terminal(&self, process: &Process) -> ColorableTerminal { - ColorableTerminal::new(StreamSelector::Stdout, process) - } -} - -impl WriterLock for io::StderrLock<'_> {} - -impl Writer for io::Stderr { - fn is_a_tty(&self, process: &Process) -> bool { - match process { - crate::process::Process::OsProcess(p) => p.stderr_is_a_tty, - #[cfg(feature = "test")] - crate::process::Process::TestProcess(_) => unreachable!(), - } - } - - fn lock(&self) -> Box { - Box::new(io::Stderr::lock(self)) - } - - fn terminal(&self, process: &Process) -> ColorableTerminal { - ColorableTerminal::new(StreamSelector::Stderr, process) - } -} - #[cfg(feature = "test")] pub(crate) use self::test_support::*; #[cfg(feature = "test")] mod test_support { use std::{ - io::Cursor, + io::{Cursor, Write}, sync::{Arc, Mutex, MutexGuard}, }; @@ -136,8 +71,6 @@ mod test_support { inner: MutexGuard<'a, Vec>, } - impl WriterLock for TestWriterLock<'_> {} - impl Write for TestWriterLock<'_> { fn write(&mut self, buf: &[u8]) -> io::Result { self.inner.write(buf) @@ -164,20 +97,6 @@ mod test_support { } } - impl Writer for TestWriter { - fn is_a_tty(&self, _: &Process) -> bool { - false - } - - fn lock(&self) -> Box { - Box::new(self.lock()) - } - - fn terminal(&self, process: &Process) -> ColorableTerminal { - ColorableTerminal::new(StreamSelector::TestWriter(self.clone()), process) - } - } - impl Write for TestWriter { fn write(&mut self, buf: &[u8]) -> io::Result { self.lock().write(buf) diff --git a/src/process/terminalsource.rs b/src/process/terminalsource.rs index b0331b4b01..ef3fa543d6 100644 --- a/src/process/terminalsource.rs +++ b/src/process/terminalsource.rs @@ -104,12 +104,25 @@ impl TerminalInnerLocked { } impl ColorableTerminal { + pub(super) fn stdout(process: &Process) -> Self { + Self::new(StreamSelector::Stdout, process) + } + + pub(super) fn stderr(process: &Process) -> Self { + Self::new(StreamSelector::Stderr, process) + } + + #[cfg(feature = "test")] + pub(super) fn test(writer: TestWriter, process: &Process) -> Self { + Self::new(StreamSelector::TestWriter(writer), process) + } + /// A terminal that supports colorisation of a stream. /// If `RUSTUP_TERM_COLOR` is set to `always`, or if the stream is a tty and /// `RUSTUP_TERM_COLOR` either unset or set to `auto`, /// then color commands will be sent to the stream. /// Otherwise color commands are discarded. - pub(super) fn new(stream: StreamSelector, process: &Process) -> Self { + fn new(stream: StreamSelector, process: &Process) -> Self { let is_a_tty = stream.is_a_tty(process); let choice = match process.var("RUSTUP_TERM_COLOR") { Ok(s) if s.eq_ignore_ascii_case("always") => ColorChoice::Always,