From 5e212b4274105c61ddc58bf003436320f325e86c Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sun, 22 Dec 2024 18:27:12 +0100 Subject: [PATCH 01/13] chore: move opts to config crate --- Cargo.lock | 42 +------------ crates/cli/Cargo.toml | 6 +- crates/cli/build.rs | 24 -------- crates/cli/src/lib.rs | 24 ++++---- crates/cli/src/version.rs | 47 --------------- crates/config/Cargo.toml | 15 ++--- crates/config/build.rs | 44 ++++++++++++++ crates/config/src/lib.rs | 56 +++++++++++++---- crates/config/src/macros.rs | 13 +--- crates/{cli/src/cli.rs => config/src/opts.rs} | 60 +++++-------------- crates/config/src/utils.rs | 4 +- crates/config/src/version.rs | 5 ++ 12 files changed, 134 insertions(+), 206 deletions(-) delete mode 100644 crates/cli/build.rs delete mode 100644 crates/cli/src/version.rs create mode 100644 crates/config/build.rs rename crates/{cli/src/cli.rs => config/src/opts.rs} (81%) create mode 100644 crates/config/src/version.rs diff --git a/Cargo.lock b/Cargo.lock index 7d1655e2..0f4ff325 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -671,27 +671,6 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "const_format" -version = "0.2.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" -dependencies = [ - "const_format_proc_macros", - "konst", -] - -[[package]] -name = "const_format_proc_macros" -version = "0.2.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - [[package]] name = "convert_case" version = "0.6.0" @@ -1464,21 +1443,6 @@ dependencies = [ "sha3-asm", ] -[[package]] -name = "konst" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330f0e13e6483b8c34885f7e6c9f19b1a7bd449c673fbb948a51c99d66ef74f4" -dependencies = [ - "konst_macro_rules", -] - -[[package]] -name = "konst_macro_rules" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" - [[package]] name = "lalrpop" version = "0.20.2" @@ -2583,7 +2547,6 @@ dependencies = [ "alloy-primitives", "cfg-if", "clap", - "const_format", "libc", "solar-config", "solar-interface", @@ -2593,7 +2556,6 @@ dependencies = [ "tracing-chrome", "tracing-subscriber", "tracing-tracy", - "vergen", ] [[package]] @@ -2617,10 +2579,12 @@ dependencies = [ name = "solar-config" version = "0.1.0" dependencies = [ - "clap_builder", + "clap", + "derive_builder", "serde", "serde_json", "strum", + "vergen", ] [[package]] diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 7be15b18..89f12d1a 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -15,18 +15,14 @@ categories.workspace = true [lints] workspace = true -[build-dependencies] -vergen = { workspace = true, features = ["build", "git", "gitcl", "cargo"] } - [dependencies] -solar-config = { workspace = true, features = ["clap"] } +solar-config.workspace = true solar-interface = { workspace = true, features = ["json"] } solar-sema.workspace = true alloy-primitives.workspace = true cfg-if.workspace = true clap = { workspace = true, features = ["derive"] } -const_format = { workspace = true, features = ["rust_1_64"] } tracing.workspace = true tracing-subscriber = { workspace = true, features = ["registry", "env-filter"] } diff --git a/crates/cli/build.rs b/crates/cli/build.rs deleted file mode 100644 index 19152b47..00000000 --- a/crates/cli/build.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::env; - -fn main() { - vergen::EmitBuilder::builder() - .git_describe(false, true, None) - .git_dirty(true) - .git_sha(false) - .build_timestamp() - .cargo_features() - .cargo_target_triple() - .emit_and_set() - .unwrap(); - - let sha = env::var("VERGEN_GIT_SHA").unwrap(); - let sha_short = &sha[0..7]; - - let is_dirty = env::var("VERGEN_GIT_DIRTY").unwrap() == "true"; - // > git describe --always --tags - // if not on a tag: v0.2.0-beta.3-82-g1939939b - // if on a tag: v0.2.0-beta.3 - let not_on_tag = env::var("VERGEN_GIT_DESCRIBE").unwrap().ends_with(&format!("-g{sha_short}")); - let is_dev = is_dirty || not_on_tag; - println!("cargo:rustc-env=VERSION_SUFFIX={}", if is_dev { "-dev" } else { "" }); -} diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index f9ad5a96..558c6451 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -6,16 +6,16 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] use clap::Parser as _; -use cli::Args; +use solar_config::{ErrorFormat, ImportMap}; use solar_interface::{ diagnostics::{DiagCtxt, DynEmitter, HumanEmitter, JsonEmitter}, Result, Session, SourceMap, }; use std::{collections::BTreeSet, num::NonZeroUsize, path::Path, sync::Arc}; -pub mod cli; +pub use solar_config::{self as config, version, Opts, UnstableOpts}; + pub mod utils; -pub mod version; #[cfg(all(unix, any(target_env = "gnu", target_os = "macos")))] pub mod sigsegv_handler; @@ -34,23 +34,23 @@ use alloy_primitives as _; use tracing as _; -pub fn parse_args(itr: I) -> Result +pub fn parse_args(itr: I) -> Result where I: IntoIterator, T: Into + Clone, { - let mut args = Args::try_parse_from(itr)?; + let mut args = Opts::try_parse_from(itr)?; args.finish()?; Ok(args) } -pub fn run_compiler_args(args: Args) -> Result<()> { +pub fn run_compiler_args(args: Opts) -> Result<()> { run_compiler_with(args, Compiler::run_default) } pub struct Compiler { pub sess: Session, - pub args: Args, + pub args: Opts, } impl Compiler { @@ -69,7 +69,7 @@ impl Compiler { 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::().ok()); + .filter_map(|arg| arg.to_str().unwrap_or("").parse::().ok()); let paths = non_stdin_args.filter(|arg| !arg.as_os_str().as_encoded_bytes().contains(&b'=')); @@ -101,11 +101,11 @@ impl Compiler { } } -fn run_compiler_with(args: Args, f: impl FnOnce(&Compiler) -> Result + Send) -> Result { +fn run_compiler_with(args: Opts, f: impl FnOnce(&Compiler) -> Result + Send) -> Result { let ui_testing = args.unstable.ui_testing; let source_map = Arc::new(SourceMap::empty()); let emitter: Box = match args.error_format { - cli::ErrorFormat::Human => { + ErrorFormat::Human => { let color = match args.color { clap::ColorChoice::Always => solar_interface::ColorChoice::Always, clap::ColorChoice::Auto => solar_interface::ColorChoice::Auto, @@ -116,12 +116,12 @@ fn run_compiler_with(args: Args, f: impl FnOnce(&Compiler) -> Result + Send) -> .ui_testing(ui_testing); Box::new(human) } - cli::ErrorFormat::Json | cli::ErrorFormat::RustcJson => { + ErrorFormat::Json | ErrorFormat::RustcJson => { // `io::Stderr` is not buffered. 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::RustcJson)) + .rustc_like(matches!(args.error_format, ErrorFormat::RustcJson)) .ui_testing(ui_testing); Box::new(json) } diff --git a/crates/cli/src/version.rs b/crates/cli/src/version.rs deleted file mode 100644 index 26ed8f30..00000000 --- a/crates/cli/src/version.rs +++ /dev/null @@ -1,47 +0,0 @@ -/// The short version information. -pub const SHORT_VERSION: &str = const_format::concatcp!( - env!("CARGO_PKG_VERSION"), - env!("VERSION_SUFFIX"), - " (", - VERGEN_GIT_SHA, - " ", - env!("VERGEN_BUILD_TIMESTAMP"), - ")", -); - -/// The long version information. -pub const LONG_VERSION: &str = const_format::concatcp!( - "Version: ", - env!("CARGO_PKG_VERSION"), - "\n", - "Commit SHA: ", - VERGEN_GIT_SHA_LONG, - "\n", - "Build Timestamp: ", - env!("VERGEN_BUILD_TIMESTAMP"), - "\n", - "Build Features: ", - env!("VERGEN_CARGO_FEATURES"), - "\n", - "Build Profile: ", - BUILD_PROFILE_NAME, -); - -/// The build profile name. -const BUILD_PROFILE_NAME: &str = { - // https://stackoverflow.com/questions/73595435/how-to-get-profile-from-cargo-toml-in-build-rs-or-at-runtime - const OUT_DIR: &str = env!("OUT_DIR"); - let unix_parts = const_format::str_split!(OUT_DIR, '/'); - if unix_parts.len() >= 4 { - unix_parts[unix_parts.len() - 4] - } else { - let win_parts = const_format::str_split!(OUT_DIR, '\\'); - win_parts[win_parts.len() - 4] - } -}; - -/// The full SHA of the latest commit. -const VERGEN_GIT_SHA_LONG: &str = env!("VERGEN_GIT_SHA"); - -/// The 8 character short SHA of the latest commit. -const VERGEN_GIT_SHA: &str = const_format::str_index!(VERGEN_GIT_SHA_LONG, ..8); diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index 28f19dd1..1702cb5d 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -15,20 +15,17 @@ categories.workspace = true [lints] workspace = true +[build-dependencies] +vergen = { workspace = true, features = ["build", "git", "gitcl", "cargo"] } + [dependencies] strum = { workspace = true, features = ["derive"] } - -# clap -clap_builder = { workspace = true, optional = true } - -# serde -serde = { workspace = true, optional = true } +derive_builder.workspace = true +clap = { workspace = true, features = ["derive"] } +serde.workspace = true [dev-dependencies] serde_json.workspace = true [features] nightly = [] - -clap = ["dep:clap_builder"] -serde = ["dep:serde"] diff --git a/crates/config/build.rs b/crates/config/build.rs new file mode 100644 index 00000000..09b20eb0 --- /dev/null +++ b/crates/config/build.rs @@ -0,0 +1,44 @@ +use std::env; + +fn main() { + vergen::EmitBuilder::builder() + .git_describe(false, true, None) + .git_dirty(true) + .git_sha(false) + .build_timestamp() + .cargo_features() + .cargo_target_triple() + .emit_and_set() + .unwrap(); + + let sha = env::var("VERGEN_GIT_SHA").unwrap(); + let sha_short = &sha[..7]; + + let is_dev = { + let is_dirty = env::var("VERGEN_GIT_DIRTY").unwrap() == "true"; + + // > git describe --always --tags + // if not on a tag: v0.2.0-beta.3-82-g1939939b + // if on a tag: v0.2.0-beta.3 + let not_on_tag = + env::var("VERGEN_GIT_DESCRIBE").unwrap().ends_with(&format!("-g{sha_short}")); + + is_dirty || not_on_tag + }; + let version_suffix = if is_dev { "-dev" } else { "" }; + + let version = env::var("CARGO_PKG_VERSION").unwrap(); + let version_suffixed = format!("{version}{version_suffix}"); + + let timestamp = env::var("VERGEN_BUILD_TIMESTAMP").unwrap(); + + let short_version = format!("{version_suffixed} ({sha_short} {timestamp})"); + println!("cargo:rustc-env=SHORT_VERSION={short_version}"); + + let long_version = format!( + "Version: {version}\nCommit SHA: {sha}\nBuild Timestamp: {timestamp}\nBuild Features: {}\nBuild Profile: {}", + env::var("VERGEN_CARGO_FEATURES").unwrap(), + env::var("PROFILE").unwrap(), + ); + println!("cargo:rustc-env=LONG_VERSION={long_version}"); +} diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 20ff3d50..90f16dd8 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -5,13 +5,19 @@ )] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +use std::path::PathBuf; use strum::EnumIs; #[macro_use] mod macros; +mod opts; +pub use opts::{Opts, UnstableOpts}; + mod utils; +pub mod version; + str_enum! { /// Compiler stage. #[derive(strum::EnumIs)] @@ -119,7 +125,6 @@ pub struct Dump { pub paths: Option>, } -#[cfg(feature = "clap")] impl std::str::FromStr for Dump { type Err = String; @@ -130,7 +135,7 @@ impl std::str::FromStr for Dump { } else { (s, None) }; - let kind = ::from_str(kind, false)?; + let kind = ::from_str(kind, false)?; Ok(Self { kind, paths }) } } @@ -147,26 +152,55 @@ str_enum! { } } +str_enum! { + /// How errors and other messages are produced. + #[derive(Default)] + #[strum(serialize_all = "kebab-case")] + pub enum ErrorFormat { + /// Human-readable output. + #[default] + Human, + /// Solc-like JSON output. + Json, + /// Rustc-like JSON output. + RustcJson, + } +} + +/// A single import map, AKA remapping: `map=path`. +#[derive(Clone, Debug)] +pub struct ImportMap { + pub map: PathBuf, + pub path: PathBuf, +} + +impl std::str::FromStr for ImportMap { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + if let Some((a, b)) = s.split_once('=') { + Ok(Self { map: a.into(), path: b.into() }) + } else { + Err("missing '='") + } + } +} + #[cfg(test)] mod tests { use super::*; use strum::IntoEnumIterator; - #[cfg(not(feature = "serde"))] - use serde_json as _; - #[test] fn string_enum() { for value in EvmVersion::iter() { let s = value.to_str(); assert_eq!(value.to_string(), s); assert_eq!(value, s.parse().unwrap()); - #[cfg(feature = "serde")] - { - let json_s = format!("\"{value}\""); - assert_eq!(serde_json::to_string(&value).unwrap(), json_s); - assert_eq!(serde_json::from_str::(&json_s).unwrap(), value); - } + + let json_s = format!("\"{value}\""); + assert_eq!(serde_json::to_string(&value).unwrap(), json_s); + assert_eq!(serde_json::from_str::(&json_s).unwrap(), value); } } } diff --git a/crates/config/src/macros.rs b/crates/config/src/macros.rs index b3d4a48f..89d5b32a 100644 --- a/crates/config/src/macros.rs +++ b/crates/config/src/macros.rs @@ -11,34 +11,28 @@ macro_rules! str_enum { } impl std::fmt::Display for $name { - #[inline] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(self.to_str()) } } - #[cfg(feature = "clap")] - impl clap_builder::ValueEnum for $name { + impl clap::ValueEnum for $name { fn value_variants<'a>() -> &'a [Self] { &[$(Self::$var),*] } - fn to_possible_value(&self) -> Option { - Some(clap_builder::builder::PossibleValue::new(self.to_str())) + fn to_possible_value(&self) -> Option { + Some(clap::builder::PossibleValue::new(self.to_str())) } } - #[cfg(feature = "serde")] impl serde::Serialize for $name { - #[inline] fn serialize(&self, serializer: S) -> Result { serializer.serialize_str(self.to_str()) } } - #[cfg(feature = "serde")] impl<'de> serde::Deserialize<'de> for $name { - #[inline] fn deserialize>(deserializer: D) -> Result { deserializer.deserialize_any(crate::utils::StrumVisitor::::new()) } @@ -46,7 +40,6 @@ macro_rules! str_enum { impl $name { /// Returns the string representation of `self`. - #[inline] pub fn to_str(self) -> &'static str { self.into() } diff --git a/crates/cli/src/cli.rs b/crates/config/src/opts.rs similarity index 81% rename from crates/cli/src/cli.rs rename to crates/config/src/opts.rs index f5534871..686efce2 100644 --- a/crates/cli/src/cli.rs +++ b/crates/config/src/opts.rs @@ -1,11 +1,11 @@ //! Solar CLI arguments. +use crate::{CompilerOutput, CompilerStage, Dump, ErrorFormat, EvmVersion, ImportMap, Language}; use clap::{ColorChoice, Parser, ValueHint}; -use solar_config::{CompilerOutput, CompilerStage, Dump, EvmVersion, Language}; use std::path::PathBuf; /// Blazingly fast Solidity compiler. -#[derive(Parser)] +#[derive(Clone, Debug, Default, derive_builder::Builder, clap::Parser)] #[command( name = "solar", version = crate::version::SHORT_VERSION, @@ -13,7 +13,7 @@ use std::path::PathBuf; arg_required_else_help = true, )] #[non_exhaustive] -pub struct Args { +pub struct Opts { /// Files to compile or import remappings. #[arg(value_hint = ValueHint::FilePath)] pub input: Vec, @@ -71,10 +71,10 @@ pub struct Args { /// Parsed unstable flags. #[arg(skip)] - pub unstable: UnstableFeatures, + pub unstable: UnstableOpts, } -impl Args { +impl Opts { /// Finishes argument parsing. /// /// This currently only parses the `-Z` arguments into the `unstable` field, but may be extended @@ -83,14 +83,14 @@ impl Args { if !self._unstable.is_empty() { let hack = self._unstable.iter().map(|s| format!("--{s}")); self.unstable = - UnstableFeatures::try_parse_from(std::iter::once(String::new()).chain(hack))?; + UnstableOpts::try_parse_from(std::iter::once(String::new()).chain(hack))?; } Ok(()) } } /// Internal options. -#[derive(Clone, Debug, Default, Parser)] +#[derive(Clone, Debug, Default, derive_builder::Builder, clap::Parser)] #[clap( disable_help_flag = true, before_help = concat!( @@ -103,7 +103,7 @@ impl Args { )] #[non_exhaustive] #[allow(clippy::manual_non_exhaustive)] -pub struct UnstableFeatures { +pub struct UnstableOpts { /// Enables UI testing mode. #[arg(long)] pub ui_testing: bool, @@ -140,38 +140,6 @@ pub struct UnstableFeatures { test_value: Option, } -/// How errors and other messages are produced. -#[derive(Clone, Debug, Default, clap::ValueEnum)] -#[value(rename_all = "kebab-case")] -pub enum ErrorFormat { - /// Human-readable output. - #[default] - Human, - /// Solc-like JSON output. - Json, - /// Rustc-like JSON output. - RustcJson, -} - -/// A single import map, AKA remapping: `map=path`. -#[derive(Clone, Debug)] -pub struct ImportMap { - pub map: PathBuf, - pub path: PathBuf, -} - -impl std::str::FromStr for ImportMap { - type Err = &'static str; - - fn from_str(s: &str) -> Result { - if let Some((a, b)) = s.split_once('=') { - Ok(Self { map: a.into(), path: b.into() }) - } else { - Err("missing '='") - } - } -} - #[cfg(test)] mod tests { use super::*; @@ -179,13 +147,13 @@ mod tests { #[test] fn verify_cli() { - Args::command().debug_assert(); - UnstableFeatures::command().debug_assert(); + Opts::command().debug_assert(); + UnstableOpts::command().debug_assert(); } #[test] fn unstable_features() { - fn parse(args: &[&str]) -> Result { + fn parse(args: &[&str]) -> Result { struct UnwrapDisplay(T); impl std::fmt::Debug for UnwrapDisplay { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -193,9 +161,9 @@ mod tests { } } (|| { - let mut args = Args::try_parse_from(args)?; - args.finish()?; - Ok::<_, clap::Error>(args.unstable) + let mut opts = Opts::try_parse_from(args)?; + opts.finish()?; + Ok::<_, clap::Error>(opts.unstable) })() .map_err(|e| UnwrapDisplay(e.render().ansi().to_string())) } diff --git a/crates/config/src/utils.rs b/crates/config/src/utils.rs index 32b4211e..2f49512b 100644 --- a/crates/config/src/utils.rs +++ b/crates/config/src/utils.rs @@ -1,14 +1,12 @@ -#[cfg(feature = "serde")] +/// [`strum`] -> [`serde`] adapter. pub(crate) struct StrumVisitor(std::marker::PhantomData); -#[cfg(feature = "serde")] impl StrumVisitor { pub(crate) fn new() -> Self { Self(std::marker::PhantomData) } } -#[cfg(feature = "serde")] impl serde::de::Visitor<'_> for StrumVisitor { type Value = T; diff --git a/crates/config/src/version.rs b/crates/config/src/version.rs new file mode 100644 index 00000000..db5c5386 --- /dev/null +++ b/crates/config/src/version.rs @@ -0,0 +1,5 @@ +/// The short version information. +pub const SHORT_VERSION: &str = env!("SHORT_VERSION"); + +/// The long version information. +pub const LONG_VERSION: &str = env!("LONG_VERSION"); From a3cf22b44ba8dcbeddc5dc20654952777a6ca987 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sun, 22 Dec 2024 19:17:38 +0100 Subject: [PATCH 02/13] feat: unify session and CLI options --- Cargo.lock | 1 - crates/cli/src/lib.rs | 69 +++++++++----------------- crates/config/Cargo.toml | 3 +- crates/config/src/lib.rs | 50 ++++++++++++++++++- crates/config/src/opts.rs | 22 ++++++--- crates/interface/src/session.rs | 88 +++++++++++++++++++-------------- crates/sema/src/emit.rs | 6 +-- crates/sema/src/lib.rs | 10 ++-- crates/sema/src/parse.rs | 2 +- 9 files changed, 150 insertions(+), 101 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f4ff325..eec38d90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2580,7 +2580,6 @@ name = "solar-config" version = "0.1.0" dependencies = [ "clap", - "derive_builder", "serde", "serde_json", "strum", diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 558c6451..a868e664 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -11,7 +11,7 @@ use solar_interface::{ diagnostics::{DiagCtxt, DynEmitter, HumanEmitter, JsonEmitter}, Result, Session, SourceMap, }; -use std::{collections::BTreeSet, num::NonZeroUsize, path::Path, sync::Arc}; +use std::{path::Path, sync::Arc}; pub use solar_config::{self as config, version, Opts, UnstableOpts}; @@ -39,25 +39,24 @@ where I: IntoIterator, T: Into + Clone, { - let mut args = Opts::try_parse_from(itr)?; - args.finish()?; - Ok(args) + let mut opts = Opts::try_parse_from(itr)?; + opts.finish()?; + Ok(opts) } -pub fn run_compiler_args(args: Opts) -> Result<()> { - run_compiler_with(args, Compiler::run_default) +pub fn run_compiler_args(opts: Opts) -> Result<()> { + run_compiler_with(opts, Compiler::run_default) } pub struct Compiler { pub sess: Session, - pub args: Opts, } impl Compiler { pub fn run_default(&self) -> Result<()> { - let Self { sess, args } = self; + let Self { sess } = self; - if sess.language.is_yul() && !args.unstable.parse_yul { + if sess.opts.language.is_yul() && !sess.opts.unstable.parse_yul { return Err(sess.dcx.err("Yul is not supported yet").emit()); } @@ -65,8 +64,8 @@ impl Compiler { // - `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 stdin = sess.opts.input.iter().any(|arg| *arg == Path::new("-")); + let non_stdin_args = sess.opts.input.iter().filter(|arg| *arg != Path::new("-")); let arg_remappings = non_stdin_args .clone() .filter_map(|arg| arg.to_str().unwrap_or("").parse::().ok()); @@ -74,11 +73,11 @@ impl Compiler { 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()); + let remappings = arg_remappings.chain(sess.opts.import_map.iter().cloned()); for map in remappings { pcx.file_resolver.add_import_map(map.map, map.path); } - for path in &args.import_path { + for path in &sess.opts.import_path { let new = pcx.file_resolver.add_import_path(path.clone()); if !new { let msg = format!("import path {} already specified", path.display()); @@ -101,12 +100,12 @@ impl Compiler { } } -fn run_compiler_with(args: Opts, f: impl FnOnce(&Compiler) -> Result + Send) -> Result { - let ui_testing = args.unstable.ui_testing; +fn run_compiler_with(opts: Opts, f: impl FnOnce(&Compiler) -> Result + Send) -> Result { + let ui_testing = opts.unstable.ui_testing; let source_map = Arc::new(SourceMap::empty()); - let emitter: Box = match args.error_format { + let emitter: Box = match opts.error_format { ErrorFormat::Human => { - let color = match args.color { + let color = match opts.color { clap::ColorChoice::Always => solar_interface::ColorChoice::Always, clap::ColorChoice::Auto => solar_interface::ColorChoice::Auto, clap::ColorChoice::Never => solar_interface::ColorChoice::Never, @@ -120,8 +119,8 @@ fn run_compiler_with(args: Opts, f: impl FnOnce(&Compiler) -> Result + Send) -> // `io::Stderr` is not buffered. 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, ErrorFormat::RustcJson)) + .pretty(opts.pretty_json_err) + .rustc_like(matches!(opts.error_format, ErrorFormat::RustcJson)) .ui_testing(ui_testing); Box::new(json) } @@ -129,36 +128,14 @@ fn run_compiler_with(args: Opts, f: impl FnOnce(&Compiler) -> Result + Send) -> 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; + flags.track_diagnostics |= opts.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.ast_stats = args.unstable.ast_stats; - sess.jobs = NonZeroUsize::new(args.threads) - .unwrap_or_else(|| std::thread::available_parallelism().unwrap_or(NonZeroUsize::MIN)); - 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 mut sess = Session::builder().dcx(dcx).source_map(source_map).opts(opts).build(); + sess.infer_language(); + sess.validate()?; - let compiler = Compiler { sess, args }; + let compiler = Compiler { sess }; compiler.sess.enter(|| { let mut r = f(&compiler); r = compiler.finish_diagnostics().and(r); diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index 1702cb5d..fe81adc6 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -19,10 +19,9 @@ workspace = true vergen = { workspace = true, features = ["build", "git", "gitcl", "cargo"] } [dependencies] -strum = { workspace = true, features = ["derive"] } -derive_builder.workspace = true clap = { workspace = true, features = ["derive"] } serde.workspace = true +strum = { workspace = true, features = ["derive"] } [dev-dependencies] serde_json.workspace = true diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 90f16dd8..3d758201 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -5,7 +5,7 @@ )] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -use std::path::PathBuf; +use std::{num::NonZeroUsize, path::PathBuf}; use strum::EnumIs; #[macro_use] @@ -186,6 +186,54 @@ impl std::str::FromStr for ImportMap { } } +/// Wrapper to implement a custom `Default` value for the number of threads. +#[derive(Clone, Copy)] +pub struct Threads(pub NonZeroUsize); + +impl From for NonZeroUsize { + fn from(threads: Threads) -> Self { + threads.0 + } +} + +impl From for Threads { + fn from(n: NonZeroUsize) -> Self { + Self(n) + } +} + +impl Default for Threads { + fn default() -> Self { + Self(NonZeroUsize::new(8).unwrap()) + } +} + +impl std::str::FromStr for Threads { + type Err = ::Err; + + fn from_str(s: &str) -> Result { + s.parse::().map(|n| { + Self( + NonZeroUsize::new(n) + .or_else(|| std::thread::available_parallelism().ok()) + .unwrap_or(NonZeroUsize::MIN), + ) + }) + } +} + +impl std::fmt::Display for Threads { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl std::fmt::Debug for Threads { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/config/src/opts.rs b/crates/config/src/opts.rs index 686efce2..7a25870c 100644 --- a/crates/config/src/opts.rs +++ b/crates/config/src/opts.rs @@ -1,11 +1,13 @@ //! Solar CLI arguments. -use crate::{CompilerOutput, CompilerStage, Dump, ErrorFormat, EvmVersion, ImportMap, Language}; +use crate::{ + CompilerOutput, CompilerStage, Dump, ErrorFormat, EvmVersion, ImportMap, Language, Threads, +}; use clap::{ColorChoice, Parser, ValueHint}; -use std::path::PathBuf; +use std::{num::NonZeroUsize, path::PathBuf}; /// Blazingly fast Solidity compiler. -#[derive(Clone, Debug, Default, derive_builder::Builder, clap::Parser)] +#[derive(Clone, Debug, Default, clap::Parser)] #[command( name = "solar", version = crate::version::SHORT_VERSION, @@ -28,8 +30,8 @@ pub struct Opts { pub language: Language, /// Number of threads to use. Zero specifies the number of logical cores. - #[arg(long, short = 'j', visible_alias = "jobs", default_value = "8")] - pub threads: usize, + #[arg(long, short = 'j', visible_alias = "jobs", default_value_t)] + pub threads: Threads, /// EVM version. #[arg(long, value_enum, default_value_t)] pub evm_version: EvmVersion, @@ -75,6 +77,12 @@ pub struct Opts { } impl Opts { + /// Returns the number of threads to use. + #[inline] + pub fn threads(&self) -> NonZeroUsize { + self.threads.0 + } + /// Finishes argument parsing. /// /// This currently only parses the `-Z` arguments into the `unstable` field, but may be extended @@ -90,7 +98,7 @@ impl Opts { } /// Internal options. -#[derive(Clone, Debug, Default, derive_builder::Builder, clap::Parser)] +#[derive(Clone, Debug, Default, clap::Parser)] #[clap( disable_help_flag = true, before_help = concat!( @@ -148,7 +156,9 @@ mod tests { #[test] fn verify_cli() { Opts::command().debug_assert(); + let _ = Opts::default(); UnstableOpts::command().debug_assert(); + let _ = UnstableOpts::default(); } #[test] diff --git a/crates/interface/src/session.rs b/crates/interface/src/session.rs index faa2576c..d4da85cc 100644 --- a/crates/interface/src/session.rs +++ b/crates/interface/src/session.rs @@ -2,8 +2,8 @@ use crate::{ diagnostics::{DiagCtxt, EmittedDiagnostics}, ColorChoice, SessionGlobals, SourceMap, }; -use solar_config::{CompilerOutput, CompilerStage, Dump, EvmVersion, Language}; -use std::{collections::BTreeSet, num::NonZeroUsize, path::PathBuf, sync::Arc}; +use solar_config::{CompilerOutput, CompilerStage, Opts, UnstableOpts}; +use std::sync::Arc; /// Information about the current compiler session. #[derive(derive_builder::Builder)] @@ -15,36 +15,8 @@ pub struct Session { #[builder(default)] source_map: Arc, - /// EVM version. - #[builder(default)] - pub evm_version: EvmVersion, - /// Source code language. - #[builder(default)] - pub language: Language, - /// Stop execution after the given compiler stage. - #[builder(default)] - pub stop_after: Option, - /// Types of output to emit. - #[builder(default)] - pub emit: BTreeSet, - /// Output directory. - #[builder(default)] - pub out_dir: Option, - /// Internal state to dump to stdout. - #[builder(default)] - pub dump: Option, - /// Pretty-print any JSON output. - #[builder(default)] - pub pretty_json: bool, - /// Number of threads to use. Already resolved to a non-zero value. - /// - /// Note that this defaults to 1. If you wish to use parallelism, you must manually set this to - /// a value greater than 1. - #[builder(default = "NonZeroUsize::MIN")] - pub jobs: NonZeroUsize, - /// Whether to emit AST stats. - #[builder(default)] - pub ast_stats: bool, + /// The compiler options. + pub opts: Opts, } impl SessionBuilder { @@ -133,6 +105,44 @@ impl Session { SessionBuilder::default() } + /// Infers the language from the input files. + pub fn infer_language(&mut self) { + if !self.opts.input.is_empty() + && self.opts.input.iter().all(|arg| arg.extension() == Some("yul".as_ref())) + { + self.opts.language = solar_config::Language::Yul; + } + } + + /// Validates the session options. + pub fn validate(&self) -> crate::Result<()> { + let mut result = Ok(()); + result = result.and(self.check_unique("emit", &self.opts.emit)); + result + } + + fn check_unique( + &self, + name: &str, + list: &[T], + ) -> crate::Result<()> { + let mut result = Ok(()); + let mut seen = std::collections::HashSet::new(); + for item in list { + if !seen.insert(item) { + let msg = format!("cannot specify `--{name} {item}` twice"); + result = Err(self.dcx.err(msg).emit()); + } + } + result + } + + /// Returns the unstable options. + #[inline] + pub fn unstable(&self) -> &UnstableOpts { + &self.opts.unstable + } + /// Returns the emitted diagnostics. Can be empty. /// /// Returns `None` if the underlying emitter is not a human buffer emitter created with @@ -166,13 +176,19 @@ impl Session { /// Returns `true` if compilation should stop after the given stage. #[inline] pub fn stop_after(&self, stage: CompilerStage) -> bool { - self.stop_after >= Some(stage) + self.opts.stop_after >= Some(stage) + } + + /// Returns the number of threads to use for parallelism. + #[inline] + pub fn threads(&self) -> usize { + self.opts.threads().get() } /// Returns `true` if parallelism is not enabled. #[inline] pub fn is_sequential(&self) -> bool { - self.jobs == NonZeroUsize::MIN + self.threads() == 1 } /// Returns `true` if parallelism is enabled. @@ -184,7 +200,7 @@ impl Session { /// Returns `true` if the given output should be emitted. #[inline] pub fn do_emit(&self, output: CompilerOutput) -> bool { - self.emit.contains(&output) + self.opts.emit.contains(&output) } /// Spawns the given closure on the thread pool or executes it immediately if parallelism is not @@ -237,7 +253,7 @@ impl Session { pub fn enter(&self, f: impl FnOnce() -> R + Send) -> R { SessionGlobals::with_or_default(|session_globals| { SessionGlobals::with_source_map(self.clone_source_map(), || { - run_in_thread_pool_with_globals(self.jobs.get(), session_globals, f) + run_in_thread_pool_with_globals(self.threads(), session_globals, f) }) }) } diff --git a/crates/sema/src/emit.rs b/crates/sema/src/emit.rs index a3535b8a..8e9ff69f 100644 --- a/crates/sema/src/emit.rs +++ b/crates/sema/src/emit.rs @@ -31,7 +31,7 @@ pub(crate) fn emit(gcx: Gcx<'_>) { for id in gcx.hir.contract_ids() { let name = gcx.contract_fully_qualified_name(id).to_string(); let contract_output = output.contracts.entry(name).or_default(); - for &emit in &gcx.sess.emit { + for &emit in &gcx.sess.opts.emit { match emit { CompilerOutput::Abi => contract_output.abi = Some(gcx.contract_abi(id)), CompilerOutput::Hashes => { @@ -48,9 +48,9 @@ pub(crate) fn emit(gcx: Gcx<'_>) { } } let _ = (|| { - let out_path = gcx.sess.out_dir.as_deref().map(|dir| dir.join("combined.json")); + let out_path = gcx.sess.opts.out_dir.as_deref().map(|dir| dir.join("combined.json")); let mut writer = out_writer(out_path.as_deref())?; - to_json(&mut writer, &output, gcx.sess.pretty_json)?; + to_json(&mut writer, &output, gcx.sess.opts.pretty_json)?; writer.flush()?; Ok::<_, io::Error>(()) })() diff --git a/crates/sema/src/lib.rs b/crates/sema/src/lib.rs index c0417f80..b312ea40 100644 --- a/crates/sema/src/lib.rs +++ b/crates/sema/src/lib.rs @@ -54,19 +54,19 @@ pub fn parse_and_resolve(pcx: ParsingContext<'_>) -> Result<()> { }); let mut sources = pcx.parse(&ast_arenas); - if let Some(dump) = &sess.dump { + if let Some(dump) = &sess.opts.unstable.dump { if dump.kind.is_ast() { dump_ast(sess, &sources, dump.paths.as_deref())?; } } - if sess.ast_stats { + if sess.opts.unstable.ast_stats { for source in sources.asts() { stats::print_ast_stats(source, "AST STATS", "ast-stats"); } } - if sess.language.is_yul() || sess.stop_after(CompilerStage::Parsed) { + if sess.opts.language.is_yul() || sess.stop_after(CompilerStage::Parsed) { return Ok(()); } @@ -119,7 +119,7 @@ fn lower<'sess, 'hir>( #[instrument(level = "debug", skip_all)] fn analysis(gcx: Gcx<'_>) -> Result<()> { - if let Some(dump) = &gcx.sess.dump { + if let Some(dump) = &gcx.sess.opts.unstable.dump { if dump.kind.is_hir() { dump_hir(gcx, dump.paths.as_deref())?; } @@ -139,7 +139,7 @@ fn analysis(gcx: Gcx<'_>) -> Result<()> { typeck::check(gcx); gcx.sess.dcx.has_errors()?; - if !gcx.sess.emit.is_empty() { + if !gcx.sess.opts.emit.is_empty() { emit::emit(gcx); gcx.sess.dcx.has_errors()?; } diff --git a/crates/sema/src/parse.rs b/crates/sema/src/parse.rs index 2d1a6dc8..ccb596c7 100644 --- a/crates/sema/src/parse.rs +++ b/crates/sema/src/parse.rs @@ -180,7 +180,7 @@ impl<'sess> ParsingContext<'sess> { ) -> Option> { let lexer = Lexer::from_source_file(self.sess, file); let mut parser = Parser::from_lexer(arena, lexer); - let r = if self.sess.language.is_yul() { + let r = if self.sess.opts.language.is_yul() { let _file = parser.parse_yul_file_object().map_err(|e| e.emit()); None } else { From d44d858f9383c0dcaf199adf59a8b02932475dd6 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 23 Dec 2024 12:35:02 +0100 Subject: [PATCH 03/13] chore: unused build-dependency --- Cargo.lock | 1 - crates/solar/Cargo.toml | 3 --- 2 files changed, 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eec38d90..badd3f8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2572,7 +2572,6 @@ dependencies = [ "solar-parse", "solar-sema", "solar-tester", - "vergen", ] [[package]] diff --git a/crates/solar/Cargo.toml b/crates/solar/Cargo.toml index fff3ca4e..d1c7205d 100644 --- a/crates/solar/Cargo.toml +++ b/crates/solar/Cargo.toml @@ -29,9 +29,6 @@ name = "tests" path = "./tests.rs" harness = false -[build-dependencies] -vergen = { workspace = true, features = ["build", "git", "gitcl"] } - [dependencies] solar-ast.workspace = true solar-config.workspace = true From fbe5e290311f6eccaca194d94b104ecb89749d7c Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 23 Dec 2024 12:45:43 +0100 Subject: [PATCH 04/13] fix: long version --- crates/config/build.rs | 5 ++++- crates/config/src/version.rs | 12 +++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/crates/config/build.rs b/crates/config/build.rs index 09b20eb0..77da48d5 100644 --- a/crates/config/build.rs +++ b/crates/config/build.rs @@ -40,5 +40,8 @@ fn main() { env::var("VERGEN_CARGO_FEATURES").unwrap(), env::var("PROFILE").unwrap(), ); - println!("cargo:rustc-env=LONG_VERSION={long_version}"); + assert_eq!(long_version.lines().count(), 5); + for (i, line) in long_version.lines().enumerate() { + println!("cargo:rustc-env=LONG_VERSION{i}={line}"); + } } diff --git a/crates/config/src/version.rs b/crates/config/src/version.rs index db5c5386..2b956295 100644 --- a/crates/config/src/version.rs +++ b/crates/config/src/version.rs @@ -2,4 +2,14 @@ pub const SHORT_VERSION: &str = env!("SHORT_VERSION"); /// The long version information. -pub const LONG_VERSION: &str = env!("LONG_VERSION"); +pub const LONG_VERSION: &str = concat!( + env!("LONG_VERSION0"), + "\n", + env!("LONG_VERSION1"), + "\n", + env!("LONG_VERSION2"), + "\n", + env!("LONG_VERSION3"), + "\n", + env!("LONG_VERSION4"), +); From 43ff060f55a4c5d7485290e2c6fdbea4b4fc66a4 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 23 Dec 2024 12:51:35 +0100 Subject: [PATCH 05/13] fix: implement non_exhaustive manually --- crates/config/src/opts.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/crates/config/src/opts.rs b/crates/config/src/opts.rs index 7a25870c..fc6334f5 100644 --- a/crates/config/src/opts.rs +++ b/crates/config/src/opts.rs @@ -14,7 +14,7 @@ use std::{num::NonZeroUsize, path::PathBuf}; long_version = crate::version::LONG_VERSION, arg_required_else_help = true, )] -#[non_exhaustive] +#[allow(clippy::manual_non_exhaustive)] pub struct Opts { /// Files to compile or import remappings. #[arg(value_hint = ValueHint::FilePath)] @@ -74,6 +74,11 @@ pub struct Opts { /// Parsed unstable flags. #[arg(skip)] pub unstable: UnstableOpts, + + // Allows `Opts { x: y, ..Default::default() }`. + #[doc(hidden)] + #[arg(skip)] + pub _non_exhaustive: (), } impl Opts { @@ -109,7 +114,6 @@ impl Opts { ), help_template = "{before-help}{all-args}" )] -#[non_exhaustive] #[allow(clippy::manual_non_exhaustive)] pub struct UnstableOpts { /// Enables UI testing mode. @@ -138,14 +142,19 @@ pub struct UnstableOpts { /// Print help. #[arg(long, action = clap::ArgAction::Help)] - help: (), + pub help: (), + + // Allows `UnstableOpts { x: y, ..Default::default() }`. + #[doc(hidden)] + #[arg(skip)] + pub _non_exhaustive: (), #[cfg(test)] #[arg(long)] - test_bool: bool, + pub test_bool: bool, #[cfg(test)] #[arg(long)] - test_value: Option, + pub test_value: Option, } #[cfg(test)] From 79d457e25705904d138d89bdd3a9f3bf336aa5b4 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 23 Dec 2024 13:11:11 +0100 Subject: [PATCH 06/13] chore: re-add feature gates --- crates/cli/Cargo.toml | 2 +- crates/config/Cargo.toml | 16 +++++-- crates/config/build.rs | 15 ++++++- crates/config/src/lib.rs | 16 ++++--- crates/config/src/macros.rs | 16 ++++++- crates/config/src/opts.rs | 86 ++++++++++++++++++++++-------------- crates/config/src/utils.rs | 3 ++ crates/config/src/version.rs | 2 + 8 files changed, 110 insertions(+), 46 deletions(-) diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 89f12d1a..6e3d0fc7 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -16,7 +16,7 @@ categories.workspace = true workspace = true [dependencies] -solar-config.workspace = true +solar-config = { workspace = true, features = ["clap"] } solar-interface = { workspace = true, features = ["json"] } solar-sema.workspace = true diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index fe81adc6..de0cab83 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -16,15 +16,25 @@ categories.workspace = true workspace = true [build-dependencies] -vergen = { workspace = true, features = ["build", "git", "gitcl", "cargo"] } +vergen = { workspace = true, optional = true, features = [ + "build", + "git", + "gitcl", + "cargo", +] } [dependencies] -clap = { workspace = true, features = ["derive"] } -serde.workspace = true strum = { workspace = true, features = ["derive"] } +clap = { workspace = true, optional = true, features = ["derive"] } +serde = { workspace = true, optional = true } + [dev-dependencies] serde_json.workspace = true [features] nightly = [] + +clap = ["version", "dep:clap"] +version = ["dep:vergen"] +serde = ["dep:serde"] diff --git a/crates/config/build.rs b/crates/config/build.rs index 77da48d5..48300d5e 100644 --- a/crates/config/build.rs +++ b/crates/config/build.rs @@ -1,6 +1,9 @@ -use std::env; +const LONG_VERSION_LINES: usize = 5; +#[cfg(feature = "version")] fn main() { + use std::env; + vergen::EmitBuilder::builder() .git_describe(false, true, None) .git_dirty(true) @@ -40,8 +43,16 @@ fn main() { env::var("VERGEN_CARGO_FEATURES").unwrap(), env::var("PROFILE").unwrap(), ); - assert_eq!(long_version.lines().count(), 5); + assert_eq!(long_version.lines().count(), LONG_VERSION_LINES); for (i, line) in long_version.lines().enumerate() { println!("cargo:rustc-env=LONG_VERSION{i}={line}"); } } + +#[cfg(not(feature = "version"))] +fn main() { + println!("cargo:rustc-env=SHORT_VERSION="); + for i in 0..LONG_VERSION_LINES { + println!("cargo:rustc-env=LONG_VERSION{i}="); + } +} diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 3d758201..4d2f35d5 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -16,6 +16,7 @@ pub use opts::{Opts, UnstableOpts}; mod utils; +#[cfg(feature = "version")] pub mod version; str_enum! { @@ -135,8 +136,7 @@ impl std::str::FromStr for Dump { } else { (s, None) }; - let kind = ::from_str(kind, false)?; - Ok(Self { kind, paths }) + Ok(Self { kind: kind.parse()?, paths }) } } @@ -239,6 +239,9 @@ mod tests { use super::*; use strum::IntoEnumIterator; + #[cfg(not(feature = "serde"))] + use serde_json as _; + #[test] fn string_enum() { for value in EvmVersion::iter() { @@ -246,9 +249,12 @@ mod tests { assert_eq!(value.to_string(), s); assert_eq!(value, s.parse().unwrap()); - let json_s = format!("\"{value}\""); - assert_eq!(serde_json::to_string(&value).unwrap(), json_s); - assert_eq!(serde_json::from_str::(&json_s).unwrap(), value); + #[cfg(feature = "serde")] + { + let json_s = format!("\"{value}\""); + assert_eq!(serde_json::to_string(&value).unwrap(), json_s); + assert_eq!(serde_json::from_str::(&json_s).unwrap(), value); + } } } } diff --git a/crates/config/src/macros.rs b/crates/config/src/macros.rs index 89d5b32a..025b26bd 100644 --- a/crates/config/src/macros.rs +++ b/crates/config/src/macros.rs @@ -1,7 +1,9 @@ macro_rules! str_enum { ($(#[$attr:meta])* $vis:vis enum $name:ident { $( $(#[$var_attr:meta])* $var:ident),* $(,)? }) => { #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] - #[derive(strum::IntoStaticStr, strum::EnumIter, strum::EnumCount, strum::EnumString, strum::VariantNames)] + #[derive(strum::IntoStaticStr, strum::EnumIter, strum::EnumCount, strum::VariantNames)] + // Use `clap` in `FromStr` for a better error message when available. + #[cfg_attr(not(feature = "clap"), derive(strum::EnumString))] $(#[$attr])* $vis enum $name { $( @@ -16,6 +18,7 @@ macro_rules! str_enum { } } + #[cfg(feature = "clap")] impl clap::ValueEnum for $name { fn value_variants<'a>() -> &'a [Self] { &[$(Self::$var),*] @@ -26,12 +29,23 @@ macro_rules! str_enum { } } + #[cfg(feature = "clap")] + impl std::str::FromStr for $name { + type Err = String; + + fn from_str(s: &str) -> Result { + ::from_str(s, false).map_err(|e| e.to_string()) + } + } + + #[cfg(feature = "serde")] impl serde::Serialize for $name { fn serialize(&self, serializer: S) -> Result { serializer.serialize_str(self.to_str()) } } + #[cfg(feature = "serde")] impl<'de> serde::Deserialize<'de> for $name { fn deserialize>(deserializer: D) -> Result { deserializer.deserialize_any(crate::utils::StrumVisitor::::new()) diff --git a/crates/config/src/opts.rs b/crates/config/src/opts.rs index fc6334f5..8d579b86 100644 --- a/crates/config/src/opts.rs +++ b/crates/config/src/opts.rs @@ -3,81 +3,97 @@ use crate::{ CompilerOutput, CompilerStage, Dump, ErrorFormat, EvmVersion, ImportMap, Language, Threads, }; -use clap::{ColorChoice, Parser, ValueHint}; use std::{num::NonZeroUsize, path::PathBuf}; +#[cfg(feature = "clap")] +use clap::{ColorChoice, Parser, ValueHint}; + /// Blazingly fast Solidity compiler. -#[derive(Clone, Debug, Default, clap::Parser)] -#[command( +#[derive(Clone, Debug, Default)] +#[cfg_attr(feature = "clap", derive(Parser))] +#[cfg_attr(feature = "clap", command( name = "solar", version = crate::version::SHORT_VERSION, long_version = crate::version::LONG_VERSION, arg_required_else_help = true, -)] +))] #[allow(clippy::manual_non_exhaustive)] pub struct Opts { /// Files to compile or import remappings. - #[arg(value_hint = ValueHint::FilePath)] + #[cfg_attr(feature = "clap", arg(value_hint = ValueHint::FilePath))] pub input: Vec, /// Directory to search for files. - #[arg(help_heading = "Input options", long, short = 'I', visible_alias = "base-path", value_hint = ValueHint::FilePath)] + #[cfg_attr(feature = "clap", arg(help_heading = "Input options", long, short = 'I', visible_alias = "base-path", value_hint = ValueHint::FilePath))] pub import_path: Vec, /// Map to search for files. Can also be provided as a positional argument. - #[arg(help_heading = "Input options", long, short = 'm', value_name = "MAP=PATH")] + #[cfg_attr( + feature = "clap", + arg(help_heading = "Input options", long, short = 'm', value_name = "MAP=PATH") + )] pub import_map: Vec, /// Source code language. Only Solidity is currently implemented. - #[arg(help_heading = "Input options", long, value_enum, default_value_t, hide = true)] + #[cfg_attr( + feature = "clap", + arg(help_heading = "Input options", long, value_enum, default_value_t, hide = true) + )] pub language: Language, /// Number of threads to use. Zero specifies the number of logical cores. - #[arg(long, short = 'j', visible_alias = "jobs", default_value_t)] + #[cfg_attr(feature = "clap", arg(long, short = 'j', visible_alias = "jobs", default_value_t))] pub threads: Threads, /// EVM version. - #[arg(long, value_enum, default_value_t)] + #[cfg_attr(feature = "clap", arg(long, value_enum, default_value_t))] pub evm_version: EvmVersion, /// Stop execution after the given compiler stage. - #[arg(long, value_enum)] + #[cfg_attr(feature = "clap", arg(long, value_enum))] pub stop_after: Option, /// Directory to write output files. - #[arg(long, value_hint = ValueHint::DirPath)] + #[cfg_attr(feature = "clap", arg(long, value_hint = ValueHint::DirPath))] pub out_dir: Option, /// Comma separated list of types of output for the compiler to emit. - #[arg(long, value_delimiter = ',')] + #[cfg_attr(feature = "clap", arg(long, value_delimiter = ','))] pub emit: Vec, /// Coloring. - #[arg(help_heading = "Display options", long, value_enum, default_value = "auto")] + #[cfg(feature = "clap")] // TODO + #[cfg_attr( + feature = "clap", + arg(help_heading = "Display options", long, value_enum, default_value = "auto") + )] pub color: ColorChoice, /// Use verbose output. - #[arg(help_heading = "Display options", long, short)] + #[cfg_attr(feature = "clap", arg(help_heading = "Display options", long, short))] pub verbose: bool, /// Pretty-print JSON output. /// /// Does not include errors. See `--pretty-json-err`. - #[arg(help_heading = "Display options", long)] + #[cfg_attr(feature = "clap", arg(help_heading = "Display options", long))] pub pretty_json: bool, /// Pretty-print error JSON output. - #[arg(help_heading = "Display options", long)] + #[cfg_attr(feature = "clap", arg(help_heading = "Display options", long))] pub pretty_json_err: bool, /// How errors and other messages are produced. - #[arg(help_heading = "Display options", long, value_enum, default_value_t)] + #[cfg_attr( + feature = "clap", + arg(help_heading = "Display options", long, value_enum, default_value_t) + )] pub error_format: ErrorFormat, /// Unstable flags. WARNING: these are completely unstable, and may change at any time. /// /// See `-Zhelp` for more details. #[doc(hidden)] - #[arg(id = "unstable-features", value_name = "FLAG", short = 'Z')] + #[cfg_attr(feature = "clap", arg(id = "unstable-features", value_name = "FLAG", short = 'Z'))] pub _unstable: Vec, /// Parsed unstable flags. - #[arg(skip)] + #[cfg_attr(feature = "clap", arg(skip))] pub unstable: UnstableOpts, // Allows `Opts { x: y, ..Default::default() }`. #[doc(hidden)] - #[arg(skip)] + #[cfg_attr(feature = "clap", arg(skip))] pub _non_exhaustive: (), } @@ -92,6 +108,7 @@ impl Opts { /// /// This currently only parses the `-Z` arguments into the `unstable` field, but may be extended /// in the future. + #[cfg(feature = "clap")] pub fn finish(&mut self) -> Result<(), clap::Error> { if !self._unstable.is_empty() { let hack = self._unstable.iter().map(|s| format!("--{s}")); @@ -103,8 +120,9 @@ impl Opts { } /// Internal options. -#[derive(Clone, Debug, Default, clap::Parser)] -#[clap( +#[derive(Clone, Debug, Default)] +#[cfg_attr(feature = "clap", derive(Parser))] +#[cfg_attr(feature = "clap", clap( disable_help_flag = true, before_help = concat!( "List of all unstable flags.\n", @@ -113,51 +131,51 @@ impl Opts { " NOTE: the following flags should be passed on the command-line using `-Z`, not `--`", ), help_template = "{before-help}{all-args}" -)] +))] #[allow(clippy::manual_non_exhaustive)] pub struct UnstableOpts { /// Enables UI testing mode. - #[arg(long)] + #[cfg_attr(feature = "clap", arg(long))] pub ui_testing: bool, /// Prints a note for every diagnostic that is emitted with the creation and emission location. /// /// This is enabled by default on debug builds. - #[arg(long)] + #[cfg_attr(feature = "clap", arg(long))] pub track_diagnostics: bool, /// Enables parsing Yul files for testing. - #[arg(long)] + #[cfg_attr(feature = "clap", arg(long))] pub parse_yul: bool, /// Print additional information about the compiler's internal state. /// /// Valid kinds are `ast` and `hir`. - #[arg(long, value_name = "KIND[=PATHS...]")] + #[cfg_attr(feature = "clap", arg(long, value_name = "KIND[=PATHS...]"))] pub dump: Option, /// Print AST stats. - #[arg(long)] + #[cfg_attr(feature = "clap", arg(long))] pub ast_stats: bool, /// Print help. - #[arg(long, action = clap::ArgAction::Help)] + #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Help))] pub help: (), // Allows `UnstableOpts { x: y, ..Default::default() }`. #[doc(hidden)] - #[arg(skip)] + #[cfg_attr(feature = "clap", arg(skip))] pub _non_exhaustive: (), #[cfg(test)] - #[arg(long)] + #[cfg_attr(feature = "clap", arg(long))] pub test_bool: bool, #[cfg(test)] - #[arg(long)] + #[cfg_attr(feature = "clap", arg(long))] pub test_value: Option, } -#[cfg(test)] +#[cfg(all(test, feature = "clap"))] mod tests { use super::*; use clap::CommandFactory; diff --git a/crates/config/src/utils.rs b/crates/config/src/utils.rs index 2f49512b..6f6a89a3 100644 --- a/crates/config/src/utils.rs +++ b/crates/config/src/utils.rs @@ -1,12 +1,15 @@ /// [`strum`] -> [`serde`] adapter. +#[cfg(feature = "serde")] pub(crate) struct StrumVisitor(std::marker::PhantomData); +#[cfg(feature = "serde")] impl StrumVisitor { pub(crate) fn new() -> Self { Self(std::marker::PhantomData) } } +#[cfg(feature = "serde")] impl serde::de::Visitor<'_> for StrumVisitor { type Value = T; diff --git a/crates/config/src/version.rs b/crates/config/src/version.rs index 2b956295..eb8052e2 100644 --- a/crates/config/src/version.rs +++ b/crates/config/src/version.rs @@ -1,3 +1,5 @@ +//! Solar version information. + /// The short version information. pub const SHORT_VERSION: &str = env!("SHORT_VERSION"); From 60b4d7a0f92495032187b6fd815a0629c4f3224a Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 23 Dec 2024 13:14:23 +0100 Subject: [PATCH 07/13] chore: simplify build script --- crates/config/build.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/crates/config/build.rs b/crates/config/build.rs index 48300d5e..b2299a7c 100644 --- a/crates/config/build.rs +++ b/crates/config/build.rs @@ -1,5 +1,3 @@ -const LONG_VERSION_LINES: usize = 5; - #[cfg(feature = "version")] fn main() { use std::env; @@ -43,16 +41,11 @@ fn main() { env::var("VERGEN_CARGO_FEATURES").unwrap(), env::var("PROFILE").unwrap(), ); - assert_eq!(long_version.lines().count(), LONG_VERSION_LINES); + assert_eq!(long_version.lines().count(), 5); // `version.rs` must be updated as well. for (i, line) in long_version.lines().enumerate() { println!("cargo:rustc-env=LONG_VERSION{i}={line}"); } } #[cfg(not(feature = "version"))] -fn main() { - println!("cargo:rustc-env=SHORT_VERSION="); - for i in 0..LONG_VERSION_LINES { - println!("cargo:rustc-env=LONG_VERSION{i}="); - } -} +fn main() {} From 8ab3632333d3e09a6a03fa5eb10bdca08401bfb1 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 23 Dec 2024 13:15:40 +0100 Subject: [PATCH 08/13] fix --- crates/interface/src/session.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/interface/src/session.rs b/crates/interface/src/session.rs index d4da85cc..d20a3e9f 100644 --- a/crates/interface/src/session.rs +++ b/crates/interface/src/session.rs @@ -16,6 +16,7 @@ pub struct Session { source_map: Arc, /// The compiler options. + #[builder(default)] pub opts: Opts, } From 93546b25f1c874111a4223a911dfafbacb360c55 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 23 Dec 2024 13:16:47 +0100 Subject: [PATCH 09/13] chore: use get_or_insert_default --- crates/interface/src/session.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/interface/src/session.rs b/crates/interface/src/session.rs index d20a3e9f..a8572144 100644 --- a/crates/interface/src/session.rs +++ b/crates/interface/src/session.rs @@ -56,7 +56,7 @@ impl SessionBuilder { /// Gets the source map from the diagnostics context. fn get_source_map(&mut self) -> Arc { - self.source_map.get_or_insert_with(Default::default).clone() + self.source_map.get_or_insert_default().clone() } /// Consumes the builder to create a new session. From 9803ad9f81fa19cbcf5356360e58c0abeb903041 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 23 Dec 2024 13:21:36 +0100 Subject: [PATCH 10/13] fix --- crates/config/src/lib.rs | 2 +- crates/config/src/macros.rs | 13 +------------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 4d2f35d5..0742620f 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -136,7 +136,7 @@ impl std::str::FromStr for Dump { } else { (s, None) }; - Ok(Self { kind: kind.parse()?, paths }) + Ok(Self { kind: kind.parse::().map_err(|e| e.to_string())?, paths }) } } diff --git a/crates/config/src/macros.rs b/crates/config/src/macros.rs index 025b26bd..950df6aa 100644 --- a/crates/config/src/macros.rs +++ b/crates/config/src/macros.rs @@ -1,9 +1,7 @@ macro_rules! str_enum { ($(#[$attr:meta])* $vis:vis enum $name:ident { $( $(#[$var_attr:meta])* $var:ident),* $(,)? }) => { #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] - #[derive(strum::IntoStaticStr, strum::EnumIter, strum::EnumCount, strum::VariantNames)] - // Use `clap` in `FromStr` for a better error message when available. - #[cfg_attr(not(feature = "clap"), derive(strum::EnumString))] + #[derive(strum::IntoStaticStr, strum::EnumIter, strum::EnumCount, strum::EnumString, strum::VariantNames)] $(#[$attr])* $vis enum $name { $( @@ -29,15 +27,6 @@ macro_rules! str_enum { } } - #[cfg(feature = "clap")] - impl std::str::FromStr for $name { - type Err = String; - - fn from_str(s: &str) -> Result { - ::from_str(s, false).map_err(|e| e.to_string()) - } - } - #[cfg(feature = "serde")] impl serde::Serialize for $name { fn serialize(&self, serializer: S) -> Result { From 35c64fe8908a7ee9248d265a94853996d9121e2b Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 23 Dec 2024 13:30:57 +0100 Subject: [PATCH 11/13] fix: benches --- benches/src/lib.rs | 8 ++++++-- crates/config/src/lib.rs | 25 ++++++++++++++++++------- crates/interface/src/session.rs | 18 ++++++++++++++++++ 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/benches/src/lib.rs b/benches/src/lib.rs index b00d18fe..d4454f67 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -97,7 +97,7 @@ impl Parser for Solar { } fn lex(&self, src: &str) { - let sess = Session::builder().with_stderr_emitter().build(); + let sess = session(); for token in solar_parse::Lexer::new(&sess, src) { black_box(token); } @@ -105,7 +105,7 @@ impl Parser for Solar { } fn parse(&self, src: &str) { - let sess = Session::builder().with_stderr_emitter().build(); + let sess = session(); sess.enter(|| -> solar_parse::interface::Result { let arena = solar_parse::ast::Arena::new(); let filename = PathBuf::from("test.sol"); @@ -120,6 +120,10 @@ impl Parser for Solar { } } +fn session() -> Session { + Session::builder().with_stderr_emitter().single_threaded().build() +} + pub struct Solang; impl Parser for Solang { fn name(&self) -> &'static str { diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 0742620f..eb9047fa 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -202,6 +202,12 @@ impl From for Threads { } } +impl From for Threads { + fn from(n: usize) -> Self { + Self::resolve(n) + } +} + impl Default for Threads { fn default() -> Self { Self(NonZeroUsize::new(8).unwrap()) @@ -212,13 +218,7 @@ impl std::str::FromStr for Threads { type Err = ::Err; fn from_str(s: &str) -> Result { - s.parse::().map(|n| { - Self( - NonZeroUsize::new(n) - .or_else(|| std::thread::available_parallelism().ok()) - .unwrap_or(NonZeroUsize::MIN), - ) - }) + s.parse::().map(Self::resolve) } } @@ -234,6 +234,17 @@ impl std::fmt::Debug for Threads { } } +impl Threads { + /// Resolves the number of threads to use. + pub fn resolve(n: usize) -> Self { + Self( + NonZeroUsize::new(n) + .or_else(|| std::thread::available_parallelism().ok()) + .unwrap_or(NonZeroUsize::MIN), + ) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/interface/src/session.rs b/crates/interface/src/session.rs index a8572144..4e2c21c3 100644 --- a/crates/interface/src/session.rs +++ b/crates/interface/src/session.rs @@ -54,11 +54,29 @@ impl SessionBuilder { self.dcx(DiagCtxt::with_silent_emitter(fatal_note)) } + /// Sets the number of threads to use for parallelism to 1. + #[inline] + pub fn single_threaded(self) -> Self { + self.threads(1) + } + + /// Sets the number of threads to use for parallelism. + #[inline] + pub fn threads(mut self, threads: usize) -> Self { + self.opts_mut().threads = threads.into(); + self + } + /// Gets the source map from the diagnostics context. fn get_source_map(&mut self) -> Arc { self.source_map.get_or_insert_default().clone() } + /// Returns a mutable reference to the options. + fn opts_mut(&mut self) -> &mut Opts { + self.opts.get_or_insert_default() + } + /// Consumes the builder to create a new session. /// /// The diagnostics context must be set before calling this method, either by calling From c61df36561a63db1883b5b8359826aea28a10cfa Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 23 Dec 2024 13:33:32 +0100 Subject: [PATCH 12/13] docs --- crates/interface/src/session.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/interface/src/session.rs b/crates/interface/src/session.rs index 4e2c21c3..2e6bf3e0 100644 --- a/crates/interface/src/session.rs +++ b/crates/interface/src/session.rs @@ -60,7 +60,8 @@ impl SessionBuilder { self.threads(1) } - /// Sets the number of threads to use for parallelism. + /// Sets the number of threads to use for parallelism. Zero specifies the number of logical + /// cores. #[inline] pub fn threads(mut self, threads: usize) -> Self { self.opts_mut().threads = threads.into(); From f3ad495e7b43429df178b6eabe39549677839fb8 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 23 Dec 2024 13:36:08 +0100 Subject: [PATCH 13/13] less noisy session --- benches/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/benches/src/lib.rs b/benches/src/lib.rs index d4454f67..9e3993ed 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -121,7 +121,10 @@ impl Parser for Solar { } fn session() -> Session { - Session::builder().with_stderr_emitter().single_threaded().build() + Session::builder() + .with_stderr_emitter_and_color(solar_parse::interface::ColorChoice::Always) + .single_threaded() + .build() } pub struct Solang;