Skip to content

Commit

Permalink
feat: create solar-cli and meta crate solar (#71)
Browse files Browse the repository at this point in the history
  • Loading branch information
DaniPopes authored Oct 19, 2024
1 parent 1a4fede commit d131482
Show file tree
Hide file tree
Showing 15 changed files with 400 additions and 275 deletions.
34 changes: 25 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ inherits = "profiling"
[workspace.dependencies]
# compiler crates
solar-ast = { version = "0.0.0", path = "crates/ast" }
solar-cli = { version = "0.0.0", path = "crates/cli" }
solar-config = { version = "0.0.0", path = "crates/config" }
solar-data-structures = { version = "0.0.0", path = "crates/data-structures" }
solar-interface = { version = "0.0.0", path = "crates/interface" }
Expand Down
58 changes: 58 additions & 0 deletions crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
[package]
name = "solar-cli"
description = "Solidity compiler CLI implementation"
homepage = "https://github.com/ithacaxyz/solar/"

version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
keywords.workspace = true
categories.workspace = true

[lints]
workspace = true

[build-dependencies]
vergen = { workspace = true, features = ["build", "git", "gitcl"] }

[dependencies]
solar-config = { workspace = true, features = ["clap"] }
solar-interface = { workspace = true, features = ["json"] }
solar-sema.workspace = true

alloy-primitives.workspace = true
cfg-if.workspace = true
clap = { workspace = true, features = ["derive"] }
rayon.workspace = true
tracing.workspace = true
tracing-subscriber = { workspace = true, features = ["registry", "env-filter"] }

tracing-chrome = { version = "0.7", optional = true }
tracing-tracy = { version = "0.11", optional = true, features = ["demangle"] }

[target.'cfg(unix)'.dependencies]
libc.workspace = true

tikv-jemallocator = { workspace = true, optional = true }

[features]
default = ["jemalloc", "tracing-off"]
# Nightly-only features for faster/smaller builds.
nightly = [
"solar-config/nightly",
"solar-interface/nightly",
"solar-sema/nightly",
]
# Faster but less portable algorithm implementations, such as Keccak-256.
asm = ["alloy-primitives/asm-keccak"]
# Faster but less portable allocator.
jemalloc = ["dep:tikv-jemallocator"]

# Debugging and profiling.
tracing-off = ["tracing/release_max_level_off"]
tracing-chrome = ["dep:tracing-chrome"]
tracy = ["dep:tracing-tracy"]
tracy-allocator = ["tracy"]
File renamed without changes.
6 changes: 5 additions & 1 deletion crates/solar/src/cli.rs → crates/cli/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Solar CLI arguments.

use clap::{ColorChoice, Parser, ValueHint};
use solar_config::{CompilerOutput, CompilerStage, Dump, EvmVersion, Language};
use std::path::PathBuf;
Expand Down Expand Up @@ -79,7 +81,8 @@ pub struct Args {
}

impl Args {
pub(crate) fn populate_unstable(&mut self) -> Result<(), clap::Error> {
/// Parses the `-Z` arguments into the `unstable` field.
pub fn populate_unstable(&mut self) -> Result<(), clap::Error> {
if !self._unstable.is_empty() {
let hack = self._unstable.iter().map(|s| format!("--{s}"));
self.unstable =
Expand All @@ -99,6 +102,7 @@ pub enum ErrorFormat {
RichJson,
}

/// A single import map, AKA remapping: `map=path`.
#[derive(Clone, Debug)]
pub struct ImportMap {
pub map: PathBuf,
Expand Down
167 changes: 167 additions & 0 deletions crates/cli/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
//! The main entry point for the Solar compiler.

#![doc(
html_logo_url = "https://raw.githubusercontent.com/ithacaxyz/solar/main/assets/logo.jpg",
html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256"
)]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]

use clap::Parser as _;
use cli::Args;
use solar_interface::{
diagnostics::{DiagCtxt, DynEmitter, HumanEmitter, JsonEmitter},
Result, Session, SessionGlobals, SourceMap,
};
use std::{collections::BTreeSet, num::NonZeroUsize, path::Path, sync::Arc};

pub mod cli;
pub mod utils;

#[cfg(all(unix, any(target_env = "gnu", target_os = "macos")))]
pub mod sigsegv_handler;

/// Signal handler to extract a backtrace from stack overflow.
///
/// This is a no-op because this platform doesn't support our signal handler's requirements.
#[cfg(not(all(unix, any(target_env = "gnu", target_os = "macos"))))]
pub mod sigsegv_handler {
/// No-op function.
pub fn install() {}
}

// `asm` feature.
use alloy_primitives as _;

use tracing as _;

pub fn parse_args<I, T>(itr: I) -> Result<Args, clap::Error>
where
I: IntoIterator<Item = T>,
T: Into<std::ffi::OsString> + Clone,
{
let mut args = Args::try_parse_from(itr)?;
args.populate_unstable()?;
Ok(args)
}

pub fn run_compiler_args(args: Args) -> Result<()> {
run_compiler_with(args, Compiler::run_default)
}

pub struct Compiler {
pub sess: Session,
pub args: Args,
}

impl Compiler {
pub fn run_default(&self) -> Result<()> {
let Self { sess, args } = self;

if sess.language.is_yul() && !args.unstable.parse_yul {
return Err(sess.dcx.err("Yul is not supported yet").emit());
}

// Partition arguments into three categories:
// - `stdin`: `-`, occurrences after the first are ignored
// - remappings: `path=mapped`
// - paths: everything else
let stdin = args.input.iter().any(|arg| *arg == Path::new("-"));
let non_stdin_args = args.input.iter().filter(|arg| *arg != Path::new("-"));
let arg_remappings = non_stdin_args
.clone()
.filter_map(|arg| arg.to_str().unwrap_or("").parse::<cli::ImportMap>().ok());
let paths =
non_stdin_args.filter(|arg| !arg.as_os_str().as_encoded_bytes().contains(&b'='));

let mut pcx = solar_sema::ParsingContext::new(sess);
let remappings = arg_remappings.chain(args.import_map.iter().cloned());
for map in remappings {
pcx.file_resolver.add_import_map(map.map, map.path);
}
for path in &args.import_path {
let new = pcx.file_resolver.add_import_path(path.clone());
if !new {
let msg = format!("import path {} already specified", path.display());
return Err(sess.dcx.err(msg).emit());
}
}

if stdin {
pcx.load_stdin()?;
}
pcx.load_files(paths)?;

pcx.parse_and_resolve()?;

Ok(())
}

fn finish_diagnostics(&self) -> Result {
self.sess.dcx.print_error_count()
}
}

fn run_compiler_with(args: Args, f: impl FnOnce(&Compiler) -> Result + Send) -> Result {
utils::run_in_thread_pool_with_globals(args.threads, |jobs| {
let ui_testing = args.unstable.ui_testing;
let source_map = Arc::new(SourceMap::empty());
let emitter: Box<DynEmitter> = match args.error_format {
cli::ErrorFormat::Human => {
let color = match args.color {
clap::ColorChoice::Always => solar_interface::ColorChoice::Always,
clap::ColorChoice::Auto => solar_interface::ColorChoice::Auto,
clap::ColorChoice::Never => solar_interface::ColorChoice::Never,
};
let human = HumanEmitter::stderr(color)
.source_map(Some(source_map.clone()))
.ui_testing(ui_testing);
Box::new(human)
}
cli::ErrorFormat::Json | cli::ErrorFormat::RichJson => {
let writer = Box::new(std::io::BufWriter::new(std::io::stderr()));
let json = JsonEmitter::new(writer, source_map.clone())
.pretty(args.pretty_json_err)
.rustc_like(matches!(args.error_format, cli::ErrorFormat::RichJson))
.ui_testing(ui_testing);
Box::new(json)
}
};
let dcx = DiagCtxt::new(emitter).set_flags(|flags| {
flags.deduplicate_diagnostics &= !ui_testing;
flags.track_diagnostics &= !ui_testing;
flags.track_diagnostics |= args.unstable.track_diagnostics;
});

let mut sess = Session::new(dcx, source_map);
sess.evm_version = args.evm_version;
sess.language = args.language;
sess.stop_after = args.stop_after;
sess.dump = args.unstable.dump.clone();
sess.jobs = NonZeroUsize::new(jobs).unwrap();
if !args.input.is_empty()
&& args.input.iter().all(|arg| arg.extension() == Some("yul".as_ref()))
{
sess.language = solar_config::Language::Yul;
}
sess.emit = {
let mut set = BTreeSet::default();
for &emit in &args.emit {
if !set.insert(emit) {
let msg = format!("cannot specify `--emit {emit}` twice");
return Err(sess.dcx.err(msg).emit());
}
}
set
};
sess.out_dir = args.out_dir.clone();
sess.pretty_json = args.pretty_json;

let compiler = Compiler { sess, args };

SessionGlobals::with_source_map(compiler.sess.clone_source_map(), move || {
let mut r = f(&compiler);
r = compiler.finish_diagnostics().and(r);
r
})
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ impl fmt::Write for RawStderr {
/// We don't really care how many bytes we actually get out. SIGSEGV comes for our head.
/// Splash stderr with letters of our own blood to warn our friends about the monster.
macro_rules! raw_errln {
($tokens:tt) => {
let _ = ::core::fmt::Write::write_fmt(&mut RawStderr(()), format_args!($tokens));
($($tokens:tt)*) => {
let _ = ::core::fmt::Write::write_fmt(&mut RawStderr(()), format_args!($($tokens)*));
let _ = ::core::fmt::Write::write_char(&mut RawStderr(()), '\n');
};
}
Expand Down
Loading

0 comments on commit d131482

Please sign in to comment.