189 lines
5.2 KiB
Rust
189 lines
5.2 KiB
Rust
//! Types and function used to emit pretty diagnostics for `bindgen`.
|
|
//!
|
|
//! The entry point of this module is the [`Diagnostic`] type.
|
|
|
|
use std::fmt::Write;
|
|
use std::io::{self, BufRead, BufReader};
|
|
use std::{borrow::Cow, fs::File};
|
|
|
|
use annotate_snippets::{
|
|
display_list::{DisplayList, FormatOptions},
|
|
snippet::{Annotation, Slice as ExtSlice, Snippet},
|
|
};
|
|
|
|
use annotate_snippets::snippet::AnnotationType;
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub(crate) enum Level {
|
|
Error,
|
|
Warn,
|
|
Info,
|
|
Note,
|
|
Help,
|
|
}
|
|
|
|
impl From<Level> for AnnotationType {
|
|
fn from(level: Level) -> Self {
|
|
match level {
|
|
Level::Error => Self::Error,
|
|
Level::Warn => Self::Warning,
|
|
Level::Info => Self::Info,
|
|
Level::Note => Self::Note,
|
|
Level::Help => Self::Help,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A `bindgen` diagnostic.
|
|
#[derive(Default)]
|
|
pub(crate) struct Diagnostic<'a> {
|
|
title: Option<(Cow<'a, str>, Level)>,
|
|
slices: Vec<Slice<'a>>,
|
|
footer: Vec<(Cow<'a, str>, Level)>,
|
|
}
|
|
|
|
impl<'a> Diagnostic<'a> {
|
|
/// Add a title to the diagnostic and set its type.
|
|
pub(crate) fn with_title(
|
|
&mut self,
|
|
title: impl Into<Cow<'a, str>>,
|
|
level: Level,
|
|
) -> &mut Self {
|
|
self.title = Some((title.into(), level));
|
|
self
|
|
}
|
|
|
|
/// Add a slice of source code to the diagnostic.
|
|
pub(crate) fn add_slice(&mut self, slice: Slice<'a>) -> &mut Self {
|
|
self.slices.push(slice);
|
|
self
|
|
}
|
|
|
|
/// Add a footer annotation to the diagnostic. This annotation will have its own type.
|
|
pub(crate) fn add_annotation(
|
|
&mut self,
|
|
msg: impl Into<Cow<'a, str>>,
|
|
level: Level,
|
|
) -> &mut Self {
|
|
self.footer.push((msg.into(), level));
|
|
self
|
|
}
|
|
|
|
/// Print this diagnostic.
|
|
///
|
|
/// The diagnostic is printed using `cargo:warning` if `bindgen` is being invoked by a build
|
|
/// script or using `eprintln` otherwise.
|
|
pub(crate) fn display(&self) {
|
|
std::thread_local! {
|
|
static INVOKED_BY_BUILD_SCRIPT: bool = std::env::var_os("CARGO_CFG_TARGET_ARCH").is_some();
|
|
}
|
|
|
|
let mut title = None;
|
|
let mut footer = vec![];
|
|
let mut slices = vec![];
|
|
if let Some((msg, level)) = &self.title {
|
|
title = Some(Annotation {
|
|
id: Some("bindgen"),
|
|
label: Some(msg.as_ref()),
|
|
annotation_type: (*level).into(),
|
|
})
|
|
}
|
|
|
|
for (msg, level) in &self.footer {
|
|
footer.push(Annotation {
|
|
id: None,
|
|
label: Some(msg.as_ref()),
|
|
annotation_type: (*level).into(),
|
|
});
|
|
}
|
|
|
|
// add additional info that this is generated by bindgen
|
|
// so as to not confuse with rustc warnings
|
|
footer.push(Annotation {
|
|
id: None,
|
|
label: Some("This diagnostic was generated by bindgen."),
|
|
annotation_type: AnnotationType::Info,
|
|
});
|
|
|
|
for slice in &self.slices {
|
|
if let Some(source) = &slice.source {
|
|
slices.push(ExtSlice {
|
|
source: source.as_ref(),
|
|
line_start: slice.line.unwrap_or_default(),
|
|
origin: slice.filename.as_deref(),
|
|
annotations: vec![],
|
|
fold: false,
|
|
})
|
|
}
|
|
}
|
|
|
|
let snippet = Snippet {
|
|
title,
|
|
footer,
|
|
slices,
|
|
opt: FormatOptions {
|
|
color: true,
|
|
..Default::default()
|
|
},
|
|
};
|
|
let dl = DisplayList::from(snippet);
|
|
|
|
if INVOKED_BY_BUILD_SCRIPT.with(Clone::clone) {
|
|
// This is just a hack which hides the `warning:` added by cargo at the beginning of
|
|
// every line. This should be fine as our diagnostics already have a colorful title.
|
|
// FIXME (pvdrz): Could it be that this doesn't work in other languages?
|
|
let hide_warning = "\r \r";
|
|
let string = dl.to_string();
|
|
for line in string.lines() {
|
|
println!("cargo:warning={}{}", hide_warning, line);
|
|
}
|
|
} else {
|
|
eprintln!("{}\n", dl);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A slice of source code.
|
|
#[derive(Default)]
|
|
pub(crate) struct Slice<'a> {
|
|
source: Option<Cow<'a, str>>,
|
|
filename: Option<String>,
|
|
line: Option<usize>,
|
|
}
|
|
|
|
impl<'a> Slice<'a> {
|
|
/// Set the source code.
|
|
pub(crate) fn with_source(
|
|
&mut self,
|
|
source: impl Into<Cow<'a, str>>,
|
|
) -> &mut Self {
|
|
self.source = Some(source.into());
|
|
self
|
|
}
|
|
|
|
/// Set the file, line and column.
|
|
pub(crate) fn with_location(
|
|
&mut self,
|
|
mut name: String,
|
|
line: usize,
|
|
col: usize,
|
|
) -> &mut Self {
|
|
write!(name, ":{}:{}", line, col)
|
|
.expect("Writing to a string cannot fail");
|
|
self.filename = Some(name);
|
|
self.line = Some(line);
|
|
self
|
|
}
|
|
}
|
|
|
|
pub(crate) fn get_line(
|
|
filename: &str,
|
|
line: usize,
|
|
) -> io::Result<Option<String>> {
|
|
let file = BufReader::new(File::open(filename)?);
|
|
if let Some(line) = file.lines().nth(line.wrapping_sub(1)) {
|
|
return line.map(Some);
|
|
}
|
|
|
|
Ok(None)
|
|
}
|