solar_cli/
lib.rs

1#![doc = include_str!("../README.md")]
2#![doc(
3    html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/solar/main/assets/logo.png",
4    html_favicon_url = "https://raw.githubusercontent.com/paradigmxyz/solar/main/assets/favicon.ico"
5)]
6#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
7
8use clap::Parser as _;
9use solar_config::{CompilerStage, ErrorFormat, ImportRemapping};
10use solar_interface::{
11    Result, Session, SourceMap,
12    diagnostics::{DiagCtxt, DynEmitter, HumanEmitter, JsonEmitter},
13};
14use std::sync::Arc;
15
16pub use solar_config::{self as config, Opts, UnstableOpts, version};
17
18pub mod utils;
19
20#[cfg(all(unix, any(target_env = "gnu", target_os = "macos")))]
21pub mod sigsegv_handler;
22
23/// Signal handler to extract a backtrace from stack overflow.
24///
25/// This is a no-op because this platform doesn't support our signal handler's requirements.
26#[cfg(not(all(unix, any(target_env = "gnu", target_os = "macos"))))]
27pub mod sigsegv_handler {
28    #[cfg(unix)]
29    use libc as _;
30
31    /// No-op function.
32    pub fn install() {}
33}
34
35// `asm` feature.
36use alloy_primitives as _;
37
38use tracing as _;
39
40pub fn parse_args<I, T>(itr: I) -> Result<Opts, clap::Error>
41where
42    I: IntoIterator<Item = T>,
43    T: Into<std::ffi::OsString> + Clone,
44{
45    let mut opts = Opts::try_parse_from(itr)?;
46    opts.finish()?;
47    Ok(opts)
48}
49
50pub fn run_compiler_args(opts: Opts) -> Result<()> {
51    run_compiler_with(opts, Compiler::run_default)
52}
53
54pub struct Compiler {
55    pub sess: Session,
56}
57
58impl Compiler {
59    pub fn run_default(&self) -> Result<()> {
60        let Self { sess } = self;
61
62        if sess.opts.language.is_yul() && !sess.opts.unstable.parse_yul {
63            return Err(sess.dcx.err("Yul is not supported yet").emit());
64        }
65
66        let mut pcx = solar_sema::ParsingContext::new(sess);
67        pcx.set_resolve_imports(sess.opts.stop_after != Some(CompilerStage::Parsed));
68        pcx.file_resolver.add_include_paths(sess.opts.include_path.iter().cloned());
69
70        // Partition arguments into three categories:
71        // - `stdin`: `-`, occurrences after the first are ignored
72        // - remappings: `[context:]prefix=path`
73        // - paths: everything else
74        let mut seen_stdin = false;
75        for arg in sess.opts.input.iter().map(String::as_str) {
76            if arg == "-" {
77                if !seen_stdin {
78                    pcx.load_stdin()?;
79                }
80                seen_stdin = true;
81                continue;
82            }
83
84            if arg.contains('=') {
85                let remapping = arg.parse::<ImportRemapping>().map_err(|e| {
86                    self.sess.dcx.err(format!("invalid remapping {arg:?}: {e}")).emit()
87                })?;
88                pcx.file_resolver.add_import_remapping(remapping);
89                continue;
90            }
91
92            pcx.load_file(arg.as_ref())?;
93        }
94
95        pcx.parse_and_resolve()?;
96
97        Ok(())
98    }
99
100    fn finish_diagnostics(&self) -> Result {
101        self.sess.dcx.print_error_count()
102    }
103}
104
105fn run_compiler_with(opts: Opts, f: impl FnOnce(&Compiler) -> Result + Send) -> Result {
106    let ui_testing = opts.unstable.ui_testing;
107    let source_map = Arc::new(SourceMap::empty());
108    let emitter: Box<DynEmitter> = match opts.error_format {
109        ErrorFormat::Human => {
110            let color = match opts.color {
111                clap::ColorChoice::Always => solar_interface::ColorChoice::Always,
112                clap::ColorChoice::Auto => solar_interface::ColorChoice::Auto,
113                clap::ColorChoice::Never => solar_interface::ColorChoice::Never,
114            };
115            let human = HumanEmitter::stderr(color)
116                .source_map(Some(source_map.clone()))
117                .ui_testing(ui_testing);
118            Box::new(human)
119        }
120        ErrorFormat::Json | ErrorFormat::RustcJson => {
121            // `io::Stderr` is not buffered.
122            let writer = Box::new(std::io::BufWriter::new(std::io::stderr()));
123            let json = JsonEmitter::new(writer, source_map.clone())
124                .pretty(opts.pretty_json_err)
125                .rustc_like(matches!(opts.error_format, ErrorFormat::RustcJson))
126                .ui_testing(ui_testing);
127            Box::new(json)
128        }
129        format => todo!("{format:?}"),
130    };
131    let dcx = DiagCtxt::new(emitter).set_flags(|flags| {
132        flags.deduplicate_diagnostics &= !ui_testing;
133        flags.track_diagnostics &= !ui_testing;
134        flags.track_diagnostics |= opts.unstable.track_diagnostics;
135        flags.can_emit_warnings |= !opts.no_warnings;
136    });
137
138    let mut sess = Session::builder().dcx(dcx).source_map(source_map).opts(opts).build();
139    sess.infer_language();
140    sess.validate()?;
141
142    let compiler = Compiler { sess };
143    compiler.sess.enter_parallel(|| {
144        let mut r = f(&compiler);
145        r = compiler.finish_diagnostics().and(r);
146        r
147    })
148}