rustc: Migrate to `termcolor` crate from `term`

This crate moves the compiler's error reporting to using the `termcolor` crate
from crates.io. Previously rustc used a super-old version of the `term` crate
in-tree which is basically unmaintained at this point, but Cargo has been using
`termcolor` for some time now and tools like `rg` are using `termcolor` as well,
so it seems like a good strategy to take!

Note that the `term` crate remains in-tree for libtest. Changing libtest will be
a bit tricky due to how the build works, but we can always tackle that later.

cc #45728
This commit is contained in:
Alex Crichton 2018-02-27 10:33:02 -08:00
parent 4cdbac639a
commit a919efad2e
4 changed files with 137 additions and 167 deletions

12
src/Cargo.lock generated
View File

@ -78,7 +78,7 @@ dependencies = [
[[package]]
name = "atty"
version = "0.2.6"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
@ -179,7 +179,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
name = "cargo"
version = "0.26.0"
dependencies = [
"atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"bufstream 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"cargotest 0.1.0",
"core-foundation 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -289,7 +289,7 @@ version = "2.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -643,7 +643,7 @@ name = "env_logger"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1937,9 +1937,11 @@ dependencies = [
name = "rustc_errors"
version = "0.0.0"
dependencies = [
"atty 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc_data_structures 0.0.0",
"serialize 0.0.0",
"syntax_pos 0.0.0",
"termcolor 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -2876,7 +2878,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b3568b48b7cefa6b8ce125f9bb4989e52fbcc29ebea88df04cc7c5f12f70455"
"checksum ar 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "35c7a5669cb64f085739387e1308b74e6d44022464b7f1b63bbd4ceb6379ec31"
"checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef"
"checksum atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8352656fd42c30a0c3c89d26dea01e3b77c0ab2af18230835c15e2e13cd51859"
"checksum atty 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "af80143d6f7608d746df1520709e5d141c96f240b0e62b0aa41bdfb53374d9d4"
"checksum backtrace 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ebbbf59b1c43eefa8c3ede390fcc36820b4999f7914104015be25025e0d62af2"
"checksum backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "44585761d6161b0f57afc49482ab6bd067e4edef48c12a152c237eb0203f7661"
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"

View File

@ -13,3 +13,5 @@ serialize = { path = "../libserialize" }
syntax_pos = { path = "../libsyntax_pos" }
rustc_data_structures = { path = "../librustc_data_structures" }
unicode-width = "0.1.4"
atty = "0.2"
termcolor = "0.3"

View File

@ -17,12 +17,14 @@ use snippet::{Annotation, AnnotationType, Line, MultilineAnnotation, StyledStrin
use styled_buffer::StyledBuffer;
use rustc_data_structures::sync::Lrc;
use atty;
use std::borrow::Cow;
use std::io::prelude::*;
use std::io;
use term;
use std::collections::{HashMap, HashSet};
use std::cmp::min;
use termcolor::{StandardStream, ColorChoice, ColorSpec, BufferWriter};
use termcolor::{WriteColor, Color, Buffer};
use unicode_width;
const ANONYMIZED_LINE_NUM: &str = "LL";
@ -95,11 +97,14 @@ pub enum ColorConfig {
}
impl ColorConfig {
fn use_color(&self) -> bool {
fn to_color_choice(&self) -> ColorChoice {
match *self {
ColorConfig::Always => true,
ColorConfig::Never => false,
ColorConfig::Auto => stderr_isatty(),
ColorConfig::Always => ColorChoice::Always,
ColorConfig::Never => ColorChoice::Never,
ColorConfig::Auto if atty::is(atty::Stream::Stderr) => {
ColorChoice::Auto
}
ColorConfig::Auto => ColorChoice::Never,
}
}
}
@ -123,25 +128,26 @@ impl Drop for EmitterWriter {
fn drop(&mut self) {
if !self.short_message && !self.error_codes.is_empty() {
let mut error_codes = self.error_codes.clone().into_iter().collect::<Vec<_>>();
let mut dst = self.dst.writable();
error_codes.sort();
if error_codes.len() > 1 {
let limit = if error_codes.len() > 9 { 9 } else { error_codes.len() };
writeln!(self.dst,
writeln!(dst,
"You've got a few errors: {}{}",
error_codes[..limit].join(", "),
if error_codes.len() > 9 { "..." } else { "" }
).expect("failed to give tips...");
writeln!(self.dst,
writeln!(dst,
"If you want more information on an error, try using \
\"rustc --explain {}\"",
&error_codes[0]).expect("failed to give tips...");
} else {
writeln!(self.dst,
writeln!(dst,
"If you want more information on this error, try using \
\"rustc --explain {}\"",
&error_codes[0]).expect("failed to give tips...");
}
self.dst.flush().expect("failed to emit errors");
dst.flush().expect("failed to emit errors");
}
}
}
@ -152,25 +158,14 @@ impl EmitterWriter {
short_message: bool,
teach: bool)
-> EmitterWriter {
if color_config.use_color() {
let dst = Destination::from_stderr();
EmitterWriter {
dst,
cm: code_map,
short_message,
teach,
error_codes: HashSet::new(),
ui_testing: false,
}
} else {
EmitterWriter {
dst: Raw(Box::new(io::stderr())),
cm: code_map,
short_message,
teach,
error_codes: HashSet::new(),
ui_testing: false,
}
let dst = Destination::from_stderr(color_config);
EmitterWriter {
dst,
cm: code_map,
short_message,
teach,
error_codes: HashSet::new(),
ui_testing: false,
}
}
@ -1356,10 +1351,12 @@ impl EmitterWriter {
}
Err(e) => panic!("failed to emit error: {}", e),
}
match write!(&mut self.dst, "\n") {
let mut dst = self.dst.writable();
match write!(dst, "\n") {
Err(e) => panic!("failed to emit error: {}", e),
_ => {
match self.dst.flush() {
match dst.flush() {
Err(e) => panic!("failed to emit error: {}", e),
_ => (),
}
@ -1424,6 +1421,8 @@ fn emit_to_destination(rendered_buffer: &Vec<Vec<StyledString>>,
-> io::Result<()> {
use lock;
let mut dst = dst.writable();
// In order to prevent error message interleaving, where multiple error lines get intermixed
// when multiple compiler processes error simultaneously, we emit errors with additional
// steps.
@ -1444,7 +1443,7 @@ fn emit_to_destination(rendered_buffer: &Vec<Vec<StyledString>>,
if !short_message && part.text.len() == 12 && part.text.starts_with("error[E") {
error_codes.insert(part.text[6..11].to_owned());
}
dst.reset_attrs()?;
dst.reset()?;
}
if !short_message {
write!(dst, "\n")?;
@ -1454,180 +1453,136 @@ fn emit_to_destination(rendered_buffer: &Vec<Vec<StyledString>>,
Ok(())
}
#[cfg(unix)]
fn stderr_isatty() -> bool {
use libc;
unsafe { libc::isatty(libc::STDERR_FILENO) != 0 }
}
#[cfg(windows)]
fn stderr_isatty() -> bool {
type DWORD = u32;
type BOOL = i32;
type HANDLE = *mut u8;
const STD_ERROR_HANDLE: DWORD = -12i32 as DWORD;
extern "system" {
fn GetStdHandle(which: DWORD) -> HANDLE;
fn GetConsoleMode(hConsoleHandle: HANDLE, lpMode: *mut DWORD) -> BOOL;
}
unsafe {
let handle = GetStdHandle(STD_ERROR_HANDLE);
let mut out = 0;
GetConsoleMode(handle, &mut out) != 0
}
}
pub type BufferedStderr = term::Terminal<Output = BufferedWriter> + Send;
pub enum Destination {
Terminal(Box<term::StderrTerminal>),
BufferedTerminal(Box<BufferedStderr>),
Terminal(StandardStream),
Buffered(BufferWriter),
Raw(Box<Write + Send>),
}
/// Buffered writer gives us a way on Unix to buffer up an entire error message before we output
/// it. This helps to prevent interleaving of multiple error messages when multiple compiler
/// processes error simultaneously
pub struct BufferedWriter {
buffer: Vec<u8>,
}
impl BufferedWriter {
// note: we use _new because the conditional compilation at its use site may make this
// this function unused on some platforms
fn _new() -> BufferedWriter {
BufferedWriter { buffer: vec![] }
}
}
impl Write for BufferedWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
for b in buf {
self.buffer.push(*b);
}
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
let mut stderr = io::stderr();
let result = stderr.write_all(&self.buffer)
.and_then(|_| stderr.flush());
self.buffer.clear();
result
}
pub enum WritableDst<'a> {
Terminal(&'a mut StandardStream),
Buffered(&'a mut BufferWriter, Buffer),
Raw(&'a mut Box<Write + Send>),
}
impl Destination {
#[cfg(not(windows))]
/// When not on Windows, prefer the buffered terminal so that we can buffer an entire error
/// to be emitted at one time.
fn from_stderr() -> Destination {
let stderr: Option<Box<BufferedStderr>> =
term::TerminfoTerminal::new(BufferedWriter::_new())
.map(|t| Box::new(t) as Box<BufferedStderr>);
match stderr {
Some(t) => BufferedTerminal(t),
None => Raw(Box::new(io::stderr())),
fn from_stderr(color: ColorConfig) -> Destination {
let choice = color.to_color_choice();
// On Windows we'll be performing global synchronization on the entire
// system for emitting rustc errors, so there's no need to buffer
// anything.
//
// On non-Windows we rely on the atomicity of `write` to ensure errors
// don't get all jumbled up.
if cfg!(windows) {
Terminal(StandardStream::stderr(choice))
} else {
Buffered(BufferWriter::stderr(choice))
}
}
#[cfg(windows)]
/// Return a normal, unbuffered terminal when on Windows.
fn from_stderr() -> Destination {
let stderr: Option<Box<term::StderrTerminal>> = term::TerminfoTerminal::new(io::stderr())
.map(|t| Box::new(t) as Box<term::StderrTerminal>)
.or_else(|| {
term::WinConsole::new(io::stderr())
.ok()
.map(|t| Box::new(t) as Box<term::StderrTerminal>)
});
match stderr {
Some(t) => Terminal(t),
None => Raw(Box::new(io::stderr())),
fn writable<'a>(&'a mut self) -> WritableDst<'a> {
match *self {
Destination::Terminal(ref mut t) => WritableDst::Terminal(t),
Destination::Buffered(ref mut t) => {
let buf = t.buffer();
WritableDst::Buffered(t, buf)
}
Destination::Raw(ref mut t) => WritableDst::Raw(t),
}
}
}
impl<'a> WritableDst<'a> {
fn apply_style(&mut self, lvl: Level, style: Style) -> io::Result<()> {
let mut spec = ColorSpec::new();
match style {
Style::LineAndColumn => {}
Style::LineNumber => {
self.start_attr(term::Attr::Bold)?;
spec.set_bold(true);
spec.set_intense(true);
if cfg!(windows) {
self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_CYAN))?;
spec.set_fg(Some(Color::Cyan));
} else {
self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_BLUE))?;
spec.set_fg(Some(Color::Blue));
}
}
Style::Quotation => {}
Style::OldSchoolNoteText | Style::HeaderMsg => {
self.start_attr(term::Attr::Bold)?;
spec.set_bold(true);
if cfg!(windows) {
self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_WHITE))?;
spec.set_intense(true)
.set_fg(Some(Color::White));
}
}
Style::UnderlinePrimary | Style::LabelPrimary => {
self.start_attr(term::Attr::Bold)?;
self.start_attr(term::Attr::ForegroundColor(lvl.color()))?;
spec = lvl.color();
spec.set_bold(true);
}
Style::UnderlineSecondary |
Style::LabelSecondary => {
self.start_attr(term::Attr::Bold)?;
spec.set_bold(true)
.set_intense(true);
if cfg!(windows) {
self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_CYAN))?;
spec.set_fg(Some(Color::Cyan));
} else {
self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_BLUE))?;
spec.set_fg(Some(Color::Blue));
}
}
Style::NoStyle => {}
Style::Level(l) => {
self.start_attr(term::Attr::Bold)?;
self.start_attr(term::Attr::ForegroundColor(l.color()))?;
Style::Level(lvl) => {
spec = lvl.color();
spec.set_bold(true);
}
Style::Highlight => {
spec.set_bold(true);
}
Style::Highlight => self.start_attr(term::Attr::Bold)?,
}
Ok(())
self.set_color(&spec)
}
fn start_attr(&mut self, attr: term::Attr) -> io::Result<()> {
fn set_color(&mut self, color: &ColorSpec) -> io::Result<()> {
match *self {
Terminal(ref mut t) => {
t.attr(attr)?;
}
BufferedTerminal(ref mut t) => {
t.attr(attr)?;
}
Raw(_) => {}
WritableDst::Terminal(ref mut t) => t.set_color(color),
WritableDst::Buffered(_, ref mut t) => t.set_color(color),
WritableDst::Raw(_) => Ok(())
}
Ok(())
}
fn reset_attrs(&mut self) -> io::Result<()> {
fn reset(&mut self) -> io::Result<()> {
match *self {
Terminal(ref mut t) => {
t.reset()?;
}
BufferedTerminal(ref mut t) => {
t.reset()?;
}
Raw(_) => {}
WritableDst::Terminal(ref mut t) => t.reset(),
WritableDst::Buffered(_, ref mut t) => t.reset(),
WritableDst::Raw(_) => Ok(()),
}
Ok(())
}
}
impl Write for Destination {
impl<'a> Write for WritableDst<'a> {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
match *self {
Terminal(ref mut t) => t.write(bytes),
BufferedTerminal(ref mut t) => t.write(bytes),
Raw(ref mut w) => w.write(bytes),
WritableDst::Terminal(ref mut t) => t.write(bytes),
WritableDst::Buffered(_, ref mut buf) => buf.write(bytes),
WritableDst::Raw(ref mut w) => w.write(bytes),
}
}
fn flush(&mut self) -> io::Result<()> {
match *self {
Terminal(ref mut t) => t.flush(),
BufferedTerminal(ref mut t) => t.flush(),
Raw(ref mut w) => w.flush(),
WritableDst::Terminal(ref mut t) => t.flush(),
WritableDst::Buffered(_, ref mut buf) => buf.flush(),
WritableDst::Raw(ref mut w) => w.flush(),
}
}
}
impl<'a> Drop for WritableDst<'a> {
fn drop(&mut self) {
match *self {
WritableDst::Buffered(ref mut dst, ref mut buf) => {
drop(dst.print(buf));
}
_ => {}
}
}
}

View File

@ -21,7 +21,8 @@
#![feature(i128_type)]
#![feature(optin_builtin_traits)]
extern crate term;
extern crate atty;
extern crate termcolor;
#[cfg(unix)]
extern crate libc;
extern crate rustc_data_structures;
@ -47,6 +48,8 @@ use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering::SeqCst;
use std::panic;
use termcolor::{ColorSpec, Color};
mod diagnostic;
mod diagnostic_builder;
pub mod emitter;
@ -660,20 +663,28 @@ impl fmt::Display for Level {
}
impl Level {
fn color(self) -> term::color::Color {
fn color(self) -> ColorSpec {
let mut spec = ColorSpec::new();
match self {
Bug | Fatal | PhaseFatal | Error => term::color::BRIGHT_RED,
Warning => {
if cfg!(windows) {
term::color::BRIGHT_YELLOW
} else {
term::color::YELLOW
}
Bug | Fatal | PhaseFatal | Error => {
spec.set_fg(Some(Color::Red))
.set_intense(true);
}
Warning => {
spec.set_fg(Some(Color::Yellow))
.set_intense(cfg!(windows));
}
Note => {
spec.set_fg(Some(Color::Green))
.set_intense(true);
}
Help => {
spec.set_fg(Some(Color::Cyan))
.set_intense(true);
}
Note => term::color::BRIGHT_GREEN,
Help => term::color::BRIGHT_CYAN,
Cancelled => unreachable!(),
}
return spec
}
pub fn to_str(self) -> &'static str {