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::{ErrorFormat, ImportMap};
10use solar_interface::{
11    diagnostics::{DiagCtxt, DynEmitter, HumanEmitter, JsonEmitter},
12    Result, Session, SourceMap,
13};
14use std::{path::Path, sync::Arc};
15
16pub use solar_config::{self as config, version, Opts, UnstableOpts};
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    /// No-op function.
29    pub fn install() {}
30}
31
32// `asm` feature.
33use alloy_primitives as _;
34
35use tracing as _;
36
37pub fn parse_args<I, T>(itr: I) -> Result<Opts, clap::Error>
38where
39    I: IntoIterator<Item = T>,
40    T: Into<std::ffi::OsString> + Clone,
41{
42    let mut opts = Opts::try_parse_from(itr)?;
43    opts.finish()?;
44    Ok(opts)
45}
46
47pub fn run_compiler_args(opts: Opts) -> Result<()> {
48    run_compiler_with(opts, Compiler::run_default)
49}
50
51pub struct Compiler {
52    pub sess: Session,
53}
54
55impl Compiler {
56    pub fn run_default(&self) -> Result<()> {
57        let Self { sess } = self;
58
59        if sess.opts.language.is_yul() && !sess.opts.unstable.parse_yul {
60            return Err(sess.dcx.err("Yul is not supported yet").emit());
61        }
62
63        // Partition arguments into three categories:
64        // - `stdin`: `-`, occurrences after the first are ignored
65        // - remappings: `path=mapped`
66        // - paths: everything else
67        let stdin = sess.opts.input.iter().any(|arg| *arg == Path::new("-"));
68        let non_stdin_args = sess.opts.input.iter().filter(|arg| *arg != Path::new("-"));
69        let arg_remappings = non_stdin_args
70            .clone()
71            .filter_map(|arg| arg.to_str().unwrap_or("").parse::<ImportMap>().ok());
72        let paths =
73            non_stdin_args.filter(|arg| !arg.as_os_str().as_encoded_bytes().contains(&b'='));
74
75        let mut pcx = solar_sema::ParsingContext::new(sess);
76        let remappings = arg_remappings.chain(sess.opts.import_map.iter().cloned());
77        for map in remappings {
78            pcx.file_resolver.add_import_map(map.map, map.path);
79        }
80        for path in &sess.opts.import_path {
81            let new = pcx.file_resolver.add_import_path(path.clone());
82            if !new {
83                let msg = format!("import path {} already specified", path.display());
84                return Err(sess.dcx.err(msg).emit());
85            }
86        }
87
88        if stdin {
89            pcx.load_stdin()?;
90        }
91        pcx.load_files(paths)?;
92
93        pcx.parse_and_resolve()?;
94
95        Ok(())
96    }
97
98    fn finish_diagnostics(&self) -> Result {
99        self.sess.dcx.print_error_count()
100    }
101}
102
103fn run_compiler_with(opts: Opts, f: impl FnOnce(&Compiler) -> Result + Send) -> Result {
104    let ui_testing = opts.unstable.ui_testing;
105    let source_map = Arc::new(SourceMap::empty());
106    let emitter: Box<DynEmitter> = match opts.error_format {
107        ErrorFormat::Human => {
108            let color = match opts.color {
109                clap::ColorChoice::Always => solar_interface::ColorChoice::Always,
110                clap::ColorChoice::Auto => solar_interface::ColorChoice::Auto,
111                clap::ColorChoice::Never => solar_interface::ColorChoice::Never,
112            };
113            let human = HumanEmitter::stderr(color)
114                .source_map(Some(source_map.clone()))
115                .ui_testing(ui_testing);
116            Box::new(human)
117        }
118        ErrorFormat::Json | ErrorFormat::RustcJson => {
119            // `io::Stderr` is not buffered.
120            let writer = Box::new(std::io::BufWriter::new(std::io::stderr()));
121            let json = JsonEmitter::new(writer, source_map.clone())
122                .pretty(opts.pretty_json_err)
123                .rustc_like(matches!(opts.error_format, ErrorFormat::RustcJson))
124                .ui_testing(ui_testing);
125            Box::new(json)
126        }
127    };
128    let dcx = DiagCtxt::new(emitter).set_flags(|flags| {
129        flags.deduplicate_diagnostics &= !ui_testing;
130        flags.track_diagnostics &= !ui_testing;
131        flags.track_diagnostics |= opts.unstable.track_diagnostics;
132    });
133
134    let mut sess = Session::builder().dcx(dcx).source_map(source_map).opts(opts).build();
135    sess.infer_language();
136    sess.validate()?;
137
138    let compiler = Compiler { sess };
139    compiler.sess.enter_parallel(|| {
140        let mut r = f(&compiler);
141        r = compiler.finish_diagnostics().and(r);
142        r
143    })
144}