foundry_compilers/report/
mod.rs1#![allow(static_mut_refs)] use foundry_compilers_artifacts::remappings::Remapping;
20use semver::Version;
21use std::{
22 any::{Any, TypeId},
23 cell::RefCell,
24 error::Error,
25 fmt,
26 path::{Path, PathBuf},
27 ptr::NonNull,
28 sync::{
29 atomic::{AtomicBool, AtomicUsize, Ordering},
30 Arc,
31 },
32 time::Duration,
33};
34
35mod compiler;
36pub use compiler::SolcCompilerIoReporter;
37
38thread_local! {
39 static CURRENT_STATE: State = State {
40 scoped: RefCell::new(Report::none()),
41 };
42}
43
44static EXISTS: AtomicBool = AtomicBool::new(false);
45static SCOPED_COUNT: AtomicUsize = AtomicUsize::new(0);
46
47static GLOBAL_REPORTER_STATE: AtomicUsize = AtomicUsize::new(UN_SET);
49
50const UN_SET: usize = 0;
51const SETTING: usize = 1;
52const SET: usize = 2;
53
54static mut GLOBAL_REPORTER: Option<Report> = None;
55
56pub fn try_init<T>(reporter: T) -> Result<(), Box<dyn Error + Send + Sync + 'static>>
64where
65 T: Reporter + Send + Sync + 'static,
66{
67 set_global_reporter(Report::new(reporter))?;
68 Ok(())
69}
70
71pub fn init<T>(reporter: T)
82where
83 T: Reporter + Send + Sync + 'static,
84{
85 try_init(reporter).expect("Failed to install global reporter")
86}
87
88pub trait Reporter: 'static + std::fmt::Debug {
99 fn on_compiler_spawn(
107 &self,
108 _compiler_name: &str,
109 _version: &Version,
110 _dirty_files: &[PathBuf],
111 ) {
112 }
113
114 fn on_compiler_success(&self, _compiler_name: &str, _version: &Version, _duration: &Duration) {}
118
119 fn on_solc_installation_start(&self, _version: &Version) {}
121
122 fn on_solc_installation_success(&self, _version: &Version) {}
124
125 fn on_solc_installation_error(&self, _version: &Version, _error: &str) {}
127
128 fn on_unresolved_imports(&self, _imports: &[(&Path, &Path)], _remappings: &[Remapping]) {}
131
132 unsafe fn downcast_raw(&self, id: TypeId) -> Option<NonNull<()>> {
151 if id == TypeId::of::<Self>() {
152 Some(NonNull::from(self).cast())
153 } else {
154 None
155 }
156 }
157}
158
159impl dyn Reporter {
160 pub fn is<T: Any>(&self) -> bool {
162 self.downcast_ref::<T>().is_some()
163 }
164
165 pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
168 unsafe {
169 let raw = self.downcast_raw(TypeId::of::<T>())?;
170 Some(&*(raw.cast().as_ptr()))
171 }
172 }
173}
174
175pub(crate) fn compiler_spawn(compiler_name: &str, version: &Version, dirty_files: &[PathBuf]) {
176 get_default(|r| r.reporter.on_compiler_spawn(compiler_name, version, dirty_files));
177}
178
179pub(crate) fn compiler_success(compiler_name: &str, version: &Version, duration: &Duration) {
180 get_default(|r| r.reporter.on_compiler_success(compiler_name, version, duration));
181}
182
183#[allow(dead_code)]
184pub(crate) fn solc_installation_start(version: &Version) {
185 get_default(|r| r.reporter.on_solc_installation_start(version));
186}
187
188#[allow(dead_code)]
189pub(crate) fn solc_installation_success(version: &Version) {
190 get_default(|r| r.reporter.on_solc_installation_success(version));
191}
192
193#[allow(dead_code)]
194pub(crate) fn solc_installation_error(version: &Version, error: &str) {
195 get_default(|r| r.reporter.on_solc_installation_error(version, error));
196}
197
198pub(crate) fn unresolved_imports(imports: &[(&Path, &Path)], remappings: &[Remapping]) {
199 get_default(|r| r.reporter.on_unresolved_imports(imports, remappings));
200}
201
202fn get_global() -> Option<&'static Report> {
203 if GLOBAL_REPORTER_STATE.load(Ordering::SeqCst) != SET {
204 return None;
205 }
206 unsafe {
207 Some(GLOBAL_REPORTER.as_ref().expect(
210 "Reporter invariant violated: GLOBAL_REPORTER must be initialized before GLOBAL_REPORTER_STATE is set",
211 ))
212 }
213}
214
215#[inline(always)]
217pub fn get_default<T, F>(mut f: F) -> T
218where
219 F: FnMut(&Report) -> T,
220{
221 if SCOPED_COUNT.load(Ordering::Acquire) == 0 {
222 return if let Some(glob) = get_global() { f(glob) } else { f(&Report::none()) };
225 }
226
227 get_default_scoped(f)
228}
229
230#[inline(never)]
231fn get_default_scoped<T, F>(mut f: F) -> T
232where
233 F: FnMut(&Report) -> T,
234{
235 CURRENT_STATE
236 .try_with(|state| {
237 let scoped = state.scoped.borrow_mut();
238 f(&scoped)
239 })
240 .unwrap_or_else(|_| f(&Report::none()))
241}
242
243pub fn with_global<T>(f: impl FnOnce(&Report) -> T) -> Option<T> {
245 let report = get_global()?;
246 Some(f(report))
247}
248
249pub fn with_scoped<T>(report: &Report, f: impl FnOnce() -> T) -> T {
251 let _guard = set_scoped(report);
256 f()
257}
258
259struct State {
261 scoped: RefCell<Report>,
263}
264
265impl State {
266 #[inline]
272 fn set_scoped(new_report: Report) -> ScopeGuard {
273 let prior = CURRENT_STATE.try_with(|state| state.scoped.replace(new_report)).ok();
274 EXISTS.store(true, Ordering::Release);
275 SCOPED_COUNT.fetch_add(1, Ordering::Release);
276 ScopeGuard(prior)
277 }
278}
279
280#[derive(Debug)]
283pub struct ScopeGuard(Option<Report>);
284
285impl Drop for ScopeGuard {
286 #[inline]
287 fn drop(&mut self) {
288 SCOPED_COUNT.fetch_sub(1, Ordering::Release);
289 if let Some(report) = self.0.take() {
290 let prev = CURRENT_STATE.try_with(|state| state.scoped.replace(report));
293 drop(prev)
294 }
295 }
296}
297
298#[must_use = "Dropping the guard unregisters the reporter."]
301pub fn set_scoped(reporter: &Report) -> ScopeGuard {
302 State::set_scoped(reporter.clone())
306}
307
308#[derive(Clone, Copy, Debug, Default)]
310pub struct NoReporter(());
311
312impl Reporter for NoReporter {}
313
314#[derive(Clone, Debug, Default)]
316pub struct BasicStdoutReporter {
317 _priv: (),
318}
319
320impl Reporter for BasicStdoutReporter {
321 fn on_compiler_spawn(&self, compiler_name: &str, version: &Version, dirty_files: &[PathBuf]) {
325 println!(
326 "Compiling {} files with {} {}.{}.{}",
327 dirty_files.len(),
328 compiler_name,
329 version.major,
330 version.minor,
331 version.patch
332 );
333 }
334
335 fn on_compiler_success(&self, compiler_name: &str, version: &Version, duration: &Duration) {
336 println!(
337 "{} {}.{}.{} finished in {duration:.2?}",
338 compiler_name, version.major, version.minor, version.patch
339 );
340 }
341
342 fn on_solc_installation_start(&self, version: &Version) {
344 println!("installing solc version \"{version}\"");
345 }
346
347 fn on_solc_installation_success(&self, version: &Version) {
349 println!("Successfully installed solc {version}");
350 }
351
352 fn on_solc_installation_error(&self, version: &Version, error: &str) {
353 eprintln!("Failed to install solc {version}: {error}");
354 }
355
356 fn on_unresolved_imports(&self, imports: &[(&Path, &Path)], remappings: &[Remapping]) {
357 if imports.is_empty() {
358 return;
359 }
360 println!("{}", format_unresolved_imports(imports, remappings))
361 }
362}
363
364pub fn format_unresolved_imports(imports: &[(&Path, &Path)], remappings: &[Remapping]) -> String {
366 let info = imports
367 .iter()
368 .map(|(import, file)| format!("\"{}\" in \"{}\"", import.display(), file.display()))
369 .collect::<Vec<_>>()
370 .join("\n ");
371 format!(
372 "Unable to resolve imports:\n {}\nwith remappings:\n {}",
373 info,
374 remappings.iter().map(|r| r.to_string()).collect::<Vec<_>>().join("\n ")
375 )
376}
377
378#[derive(Debug)]
380pub struct SetGlobalReporterError {
381 _priv: (),
383}
384
385impl fmt::Display for SetGlobalReporterError {
386 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
387 f.pad("a global reporter has already been set")
388 }
389}
390
391impl Error for SetGlobalReporterError {}
392
393#[derive(Clone)]
395pub struct Report {
396 reporter: Arc<dyn Reporter + Send + Sync>,
397}
398
399impl Report {
400 pub fn none() -> Self {
402 Self { reporter: Arc::new(NoReporter::default()) }
403 }
404
405 pub fn new<S>(reporter: S) -> Self
409 where
410 S: Reporter + Send + Sync + 'static,
411 {
412 Self { reporter: Arc::new(reporter) }
413 }
414
415 #[inline]
418 pub fn is<T: Any>(&self) -> bool {
419 <dyn Reporter>::is::<T>(&*self.reporter)
420 }
421}
422
423impl fmt::Debug for Report {
424 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
425 f.pad("Report(...)")
426 }
427}
428
429fn set_global_reporter(report: Report) -> Result<(), SetGlobalReporterError> {
434 if GLOBAL_REPORTER_STATE
438 .compare_exchange(UN_SET, SETTING, Ordering::SeqCst, Ordering::SeqCst)
439 .is_ok()
440 {
441 unsafe {
442 GLOBAL_REPORTER = Some(report);
443 }
444 GLOBAL_REPORTER_STATE.store(SET, Ordering::SeqCst);
445 Ok(())
446 } else {
447 Err(SetGlobalReporterError { _priv: () })
448 }
449}
450
451#[cfg(test)]
452mod tests {
453 use super::*;
454 use std::str::FromStr;
455
456 #[test]
457 fn scoped_reporter_works() {
458 #[derive(Debug)]
459 struct TestReporter;
460 impl Reporter for TestReporter {}
461
462 with_scoped(&Report::new(TestReporter), || {
463 get_default(|reporter| assert!(reporter.is::<TestReporter>()))
464 });
465 }
466
467 #[test]
468 fn global_and_scoped_reporter_works() {
469 get_default(|reporter| {
470 assert!(reporter.is::<NoReporter>());
471 });
472
473 set_global_reporter(Report::new(BasicStdoutReporter::default())).unwrap();
474 #[derive(Debug)]
475 struct TestReporter;
476 impl Reporter for TestReporter {}
477
478 with_scoped(&Report::new(TestReporter), || {
479 get_default(|reporter| assert!(reporter.is::<TestReporter>()))
480 });
481
482 get_default(|reporter| assert!(reporter.is::<BasicStdoutReporter>()))
483 }
484
485 #[test]
486 fn test_unresolved_message() {
487 let unresolved = vec![(Path::new("./src/Import.sol"), Path::new("src/File.col"))];
488
489 let remappings = vec![Remapping::from_str("oz=a/b/c/d").unwrap()];
490
491 assert_eq!(
492 format_unresolved_imports(&unresolved, &remappings).trim(),
493 r#"
494Unable to resolve imports:
495 "./src/Import.sol" in "src/File.col"
496with remappings:
497 oz/=a/b/c/d/"#
498 .trim()
499 )
500 }
501}