1 //! Types and function used to emit pretty diagnostics for `bindgen`.
2 //!
3 //! The entry point of this module is the [`Diagnostic`] type.
4
5 use std::fmt::Write;
6 use std::io::{self, BufRead, BufReader};
7 use std::{borrow::Cow, fs::File};
8
9 use annotate_snippets::{
10 display_list::{DisplayList, FormatOptions},
11 snippet::{Annotation, Slice as ExtSlice, Snippet},
12 };
13
14 use annotate_snippets::snippet::AnnotationType;
15
16 #[derive(Clone, Copy, Debug)]
17 pub(crate) enum Level {
18 Error,
19 Warn,
20 Info,
21 Note,
22 Help,
23 }
24
25 impl From<Level> for AnnotationType {
from(level: Level) -> Self26 fn from(level: Level) -> Self {
27 match level {
28 Level::Error => Self::Error,
29 Level::Warn => Self::Warning,
30 Level::Info => Self::Info,
31 Level::Note => Self::Note,
32 Level::Help => Self::Help,
33 }
34 }
35 }
36
37 /// A `bindgen` diagnostic.
38 #[derive(Default)]
39 pub(crate) struct Diagnostic<'a> {
40 title: Option<(Cow<'a, str>, Level)>,
41 slices: Vec<Slice<'a>>,
42 footer: Vec<(Cow<'a, str>, Level)>,
43 }
44
45 impl<'a> Diagnostic<'a> {
46 /// Add a title to the diagnostic and set its type.
with_title( &mut self, title: impl Into<Cow<'a, str>>, level: Level, ) -> &mut Self47 pub(crate) fn with_title(
48 &mut self,
49 title: impl Into<Cow<'a, str>>,
50 level: Level,
51 ) -> &mut Self {
52 self.title = Some((title.into(), level));
53 self
54 }
55
56 /// Add a slice of source code to the diagnostic.
add_slice(&mut self, slice: Slice<'a>) -> &mut Self57 pub(crate) fn add_slice(&mut self, slice: Slice<'a>) -> &mut Self {
58 self.slices.push(slice);
59 self
60 }
61
62 /// Add a footer annotation to the diagnostic. This annotation will have its own type.
add_annotation( &mut self, msg: impl Into<Cow<'a, str>>, level: Level, ) -> &mut Self63 pub(crate) fn add_annotation(
64 &mut self,
65 msg: impl Into<Cow<'a, str>>,
66 level: Level,
67 ) -> &mut Self {
68 self.footer.push((msg.into(), level));
69 self
70 }
71
72 /// Print this diagnostic.
73 ///
74 /// The diagnostic is printed using `cargo:warning` if `bindgen` is being invoked by a build
75 /// script or using `eprintln` otherwise.
display(&self)76 pub(crate) fn display(&self) {
77 std::thread_local! {
78 static INVOKED_BY_BUILD_SCRIPT: bool = std::env::var_os("CARGO_CFG_TARGET_ARCH").is_some();
79 }
80
81 let mut title = None;
82 let mut footer = vec![];
83 let mut slices = vec![];
84 if let Some((msg, level)) = &self.title {
85 title = Some(Annotation {
86 id: Some("bindgen"),
87 label: Some(msg.as_ref()),
88 annotation_type: (*level).into(),
89 })
90 }
91
92 for (msg, level) in &self.footer {
93 footer.push(Annotation {
94 id: None,
95 label: Some(msg.as_ref()),
96 annotation_type: (*level).into(),
97 });
98 }
99
100 // add additional info that this is generated by bindgen
101 // so as to not confuse with rustc warnings
102 footer.push(Annotation {
103 id: None,
104 label: Some("This diagnostic was generated by bindgen."),
105 annotation_type: AnnotationType::Info,
106 });
107
108 for slice in &self.slices {
109 if let Some(source) = &slice.source {
110 slices.push(ExtSlice {
111 source: source.as_ref(),
112 line_start: slice.line.unwrap_or_default(),
113 origin: slice.filename.as_deref(),
114 annotations: vec![],
115 fold: false,
116 })
117 }
118 }
119
120 let snippet = Snippet {
121 title,
122 footer,
123 slices,
124 opt: FormatOptions {
125 color: true,
126 ..Default::default()
127 },
128 };
129 let dl = DisplayList::from(snippet);
130
131 if INVOKED_BY_BUILD_SCRIPT.with(Clone::clone) {
132 // This is just a hack which hides the `warning:` added by cargo at the beginning of
133 // every line. This should be fine as our diagnostics already have a colorful title.
134 // FIXME (pvdrz): Could it be that this doesn't work in other languages?
135 let hide_warning = "\r \r";
136 let string = dl.to_string();
137 for line in string.lines() {
138 println!("cargo:warning={}{}", hide_warning, line);
139 }
140 } else {
141 eprintln!("{}\n", dl);
142 }
143 }
144 }
145
146 /// A slice of source code.
147 #[derive(Default)]
148 pub(crate) struct Slice<'a> {
149 source: Option<Cow<'a, str>>,
150 filename: Option<String>,
151 line: Option<usize>,
152 }
153
154 impl<'a> Slice<'a> {
155 /// Set the source code.
with_source( &mut self, source: impl Into<Cow<'a, str>>, ) -> &mut Self156 pub(crate) fn with_source(
157 &mut self,
158 source: impl Into<Cow<'a, str>>,
159 ) -> &mut Self {
160 self.source = Some(source.into());
161 self
162 }
163
164 /// Set the file, line and column.
with_location( &mut self, mut name: String, line: usize, col: usize, ) -> &mut Self165 pub(crate) fn with_location(
166 &mut self,
167 mut name: String,
168 line: usize,
169 col: usize,
170 ) -> &mut Self {
171 write!(name, ":{}:{}", line, col)
172 .expect("Writing to a string cannot fail");
173 self.filename = Some(name);
174 self.line = Some(line);
175 self
176 }
177 }
178
get_line( filename: &str, line: usize, ) -> io::Result<Option<String>>179 pub(crate) fn get_line(
180 filename: &str,
181 line: usize,
182 ) -> io::Result<Option<String>> {
183 let file = BufReader::new(File::open(filename)?);
184 if let Some(line) = file.lines().nth(line.wrapping_sub(1)) {
185 return line.map(Some);
186 }
187
188 Ok(None)
189 }
190