From 8ed689e7ec0e98bcc4421b835d20fe043ec07ad2 Mon Sep 17 00:00:00 2001 From: xermicus Date: Mon, 3 Feb 2025 16:16:54 +0100 Subject: [PATCH] solidity: various small `resolc` fixes (#189) --- CHANGELOG.md | 4 ++ crates/solidity/src/build/contract.rs | 4 +- crates/solidity/src/lib.rs | 8 ++- crates/solidity/src/resolc/arguments.rs | 69 ++++++++----------- crates/solidity/src/resolc/main.rs | 38 ++++++---- crates/solidity/src/solc/combined_json/mod.rs | 3 +- crates/solidity/src/solc/solc_compiler.rs | 12 +++- .../src/solc/standard_json/input/mod.rs | 8 +-- .../src/tests/cli-tests/tests/common.test.ts | 14 ++-- 9 files changed, 84 insertions(+), 76 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 534884d..fff2dde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,14 @@ This is a development pre-release. - Support for the `coinbase` opcode. ### Changed +- Missing the `--overwrite` flag emits an error instead of a warning. +- The `resolc` executable prints the help by default. - Removed support for legacy EVM assembly (EVMLA) translation. ### Fixed - Solidity: Add the solc `--libraries` files to sources. +- A data race in tests. +- Fix `broken pipe` errors. ## v0.1.0-dev.9 diff --git a/crates/solidity/src/build/contract.rs b/crates/solidity/src/build/contract.rs index 8f92552..b2d75ed 100644 --- a/crates/solidity/src/build/contract.rs +++ b/crates/solidity/src/build/contract.rs @@ -64,7 +64,7 @@ impl Contract { file_path.push(file_name); if file_path.exists() && !overwrite { - eprintln!( + anyhow::bail!( "Refusing to overwrite an existing file {file_path:?} (use --overwrite to force)." ); } else { @@ -87,7 +87,7 @@ impl Contract { file_path.push(file_name); if file_path.exists() && !overwrite { - eprintln!( + anyhow::bail!( "Refusing to overwrite an existing file {file_path:?} (use --overwrite to force)." ); } else { diff --git a/crates/solidity/src/lib.rs b/crates/solidity/src/lib.rs index 93ae7b7..12eeca6 100644 --- a/crates/solidity/src/lib.rs +++ b/crates/solidity/src/lib.rs @@ -51,6 +51,7 @@ pub mod test_utils; pub mod tests; use std::collections::BTreeSet; +use std::io::Write; use std::path::PathBuf; /// Runs the Yul mode. @@ -161,7 +162,7 @@ pub fn standard_output( has_errors = true; } - eprintln!("{error}"); + writeln!(std::io::stderr(), "{error}")?; } if has_errors { @@ -278,10 +279,11 @@ pub fn combined_json( combined_json.write_to_directory(output_directory.as_path(), overwrite)?; } None => { - println!( + writeln!( + std::io::stdout(), "{}", serde_json::to_string(&combined_json).expect("Always valid") - ); + )?; } } std::process::exit(0); diff --git a/crates/solidity/src/resolc/arguments.rs b/crates/solidity/src/resolc/arguments.rs index b615fa6..3d12b64 100644 --- a/crates/solidity/src/resolc/arguments.rs +++ b/crates/solidity/src/resolc/arguments.rs @@ -13,14 +13,14 @@ use path_slash::PathExt; /// output directory. /// Example: resolc ERC20.sol -O3 --bin --output-dir './build/' #[derive(Debug, Parser)] -#[structopt(name = "The PolkaVM Solidity compiler")] +#[command(name = "The PolkaVM Solidity compiler", arg_required_else_help = true)] pub struct Arguments { /// Print the version and exit. - #[structopt(long = "version")] + #[arg(long = "version")] pub version: bool, /// Print the licence and exit. - #[structopt(long = "license")] + #[arg(long = "license")] pub license: bool, /// Specify the input paths and remappings. @@ -31,151 +31,140 @@ pub struct Arguments { /// Set the given path as the root of the source tree instead of the root of the filesystem. /// Passed to `solc` without changes. - #[structopt(long = "base-path")] + #[arg(long = "base-path")] pub base_path: Option, /// Make an additional source directory available to the default import callback. /// Can be used multiple times. Can only be used if the base path has a non-empty value. /// Passed to `solc` without changes. - #[structopt(long = "include-path")] + #[arg(long = "include-path")] pub include_paths: Vec, /// Allow a given path for imports. A list of paths can be supplied by separating them with a comma. /// Passed to `solc` without changes. - #[structopt(long = "allow-paths")] + #[arg(long = "allow-paths")] pub allow_paths: Option, /// Create one file per component and contract/file at the specified directory, if given. - #[structopt(short = 'o', long = "output-dir")] + #[arg(short = 'o', long = "output-dir")] pub output_directory: Option, /// Overwrite existing files (used together with -o). - #[structopt(long = "overwrite")] + #[arg(long = "overwrite")] pub overwrite: bool, /// Set the optimization parameter -O[0 | 1 | 2 | 3 | s | z]. /// Use `3` for best performance and `z` for minimal size. - #[structopt(short = 'O', long = "optimization")] + #[arg(short = 'O', long = "optimization")] pub optimization: Option, /// Try to recompile with -Oz if the bytecode is too large. - #[structopt(long = "fallback-Oz")] + #[arg(long = "fallback-Oz")] pub fallback_to_optimizing_for_size: bool, /// Disable the `solc` optimizer. /// Use it if your project uses the `MSIZE` instruction, or in other cases. /// Beware that it will prevent libraries from being inlined. - #[structopt(long = "disable-solc-optimizer")] + #[arg(long = "disable-solc-optimizer")] pub disable_solc_optimizer: bool, /// Specify the path to the `solc` executable. By default, the one in `${PATH}` is used. /// Yul mode: `solc` is used for source code validation, as `resolc` itself assumes that the input Yul is valid. /// LLVM IR mode: `solc` is unused. - #[structopt(long = "solc")] + #[arg(long = "solc")] pub solc: Option, /// The EVM target version to generate IR for. /// See https://github.com/paritytech/revive/blob/main/crates/common/src/evm_version.rs for reference. - #[structopt(long = "evm-version")] + #[arg(long = "evm-version")] pub evm_version: Option, /// Specify addresses of deployable libraries. Syntax: `=
[, or whitespace] ...`. /// Addresses are interpreted as hexadecimal strings prefixed with `0x`. - #[structopt(short = 'l', long = "libraries")] + #[arg(short = 'l', long = "libraries")] pub libraries: Vec, /// Output a single JSON document containing the specified information. /// Available arguments: `abi`, `hashes`, `metadata`, `devdoc`, `userdoc`, `storage-layout`, `ast`, `asm`, `bin`, `bin-runtime`. - #[structopt(long = "combined-json")] + #[arg(long = "combined-json")] pub combined_json: Option, /// Switch to standard JSON input/output mode. Read from stdin, write the result to stdout. /// This is the default used by the Hardhat plugin. - #[structopt(long = "standard-json")] + #[arg(long = "standard-json")] pub standard_json: bool, /// Switch to missing deployable libraries detection mode. /// Only available for standard JSON input/output mode. /// Contracts are not compiled in this mode, and all compilation artifacts are not included. - #[structopt(long = "detect-missing-libraries")] + #[arg(long = "detect-missing-libraries")] pub detect_missing_libraries: bool, /// Switch to Yul mode. /// Only one input Yul file is allowed. /// Cannot be used with combined and standard JSON modes. - #[structopt(long = "yul")] + #[arg(long = "yul")] pub yul: bool, /// Switch to LLVM IR mode. /// Only one input LLVM IR file is allowed. /// Cannot be used with combined and standard JSON modes. /// Use this mode at your own risk, as LLVM IR input validation is not implemented. - #[structopt(long = "llvm-ir")] + #[arg(long = "llvm-ir")] pub llvm_ir: bool, /// Set metadata hash mode. /// The only supported value is `none` that disables appending the metadata hash. /// Is enabled by default. - #[structopt(long = "metadata-hash")] + #[arg(long = "metadata-hash")] pub metadata_hash: Option, /// Output PolkaVM assembly of the contracts. - #[structopt(long = "asm")] + #[arg(long = "asm")] pub output_assembly: bool, /// Output PolkaVM bytecode of the contracts. - #[structopt(long = "bin")] + #[arg(long = "bin")] pub output_binary: bool, /// Suppress specified warnings. /// Available arguments: `ecrecover`, `sendtransfer`, `extcodesize`, `txorigin`, `blocktimestamp`, `blocknumber`, `blockhash`. - #[structopt(long = "suppress-warnings")] + #[arg(long = "suppress-warnings")] pub suppress_warnings: Option>, /// Generate source based debug information in the output code file. This only has an effect /// with the LLVM-IR code generator and is ignored otherwise. - #[structopt(short = 'g')] + #[arg(short = 'g')] pub emit_source_debug_info: bool, /// Dump all IRs to files in the specified directory. /// Only for testing and debugging. - #[structopt(long = "debug-output-dir")] + #[arg(long = "debug-output-dir")] pub debug_output_directory: Option, /// Set the verify-each option in LLVM. /// Only for testing and debugging. - #[structopt(long = "llvm-verify-each")] + #[arg(long = "llvm-verify-each")] pub llvm_verify_each: bool, /// Set the debug-logging option in LLVM. /// Only for testing and debugging. - #[structopt(long = "llvm-debug-logging")] + #[arg(long = "llvm-debug-logging")] pub llvm_debug_logging: bool, /// Run this process recursively and provide JSON input to compile a single contract. /// Only for usage from within the compiler. - #[structopt(long = "recursive-process")] + #[arg(long = "recursive-process")] pub recursive_process: bool, /// Specify the input file to use instead of stdin when --recursive-process is given. /// This is only intended for use when developing the compiler. #[cfg(debug_assertions)] - #[structopt(long = "recursive-process-input")] + #[arg(long = "recursive-process-input")] pub recursive_process_input: Option, } -impl Default for Arguments { - fn default() -> Self { - Self::new() - } -} - impl Arguments { - /// A shortcut constructor. - pub fn new() -> Self { - Self::parse() - } - /// Validates the arguments. #[allow(clippy::collapsible_if)] pub fn validate(&self) -> anyhow::Result<()> { diff --git a/crates/solidity/src/resolc/main.rs b/crates/solidity/src/resolc/main.rs index 5699510..11e7a31 100644 --- a/crates/solidity/src/resolc/main.rs +++ b/crates/solidity/src/resolc/main.rs @@ -2,6 +2,7 @@ pub mod arguments; +use std::io::Write; use std::str::FromStr; use revive_solidity::Process; @@ -16,28 +17,27 @@ const RAYON_WORKER_STACK_SIZE: usize = 16 * 1024 * 1024; #[global_allocator] static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; -/// The application entry point. -fn main() { +fn main() -> anyhow::Result<()> { std::process::exit(match main_inner() { Ok(()) => revive_common::EXIT_CODE_SUCCESS, Err(error) => { - eprintln!("{error}"); + writeln!(std::io::stderr(), "{error}")?; revive_common::EXIT_CODE_FAILURE } }) } -/// The auxiliary `main` function to facilitate the `?` error conversion operator. fn main_inner() -> anyhow::Result<()> { - let arguments = Arguments::new(); + let arguments = ::try_parse()?; arguments.validate()?; if arguments.version { - println!( + writeln!( + std::io::stdout(), "{} version {}", env!("CARGO_PKG_DESCRIPTION"), revive_solidity::ResolcVersion::default().long - ); + )?; return Ok(()); } @@ -45,7 +45,7 @@ fn main_inner() -> anyhow::Result<()> { let license_mit = include_str!("../../../../LICENSE-MIT"); let license_apache = include_str!("../../../../LICENSE-APACHE"); - println!("{}\n{}\n", license_mit, license_apache); + writeln!(std::io::stdout(), "{}\n{}\n", license_mit, license_apache)?; return Ok(()); } @@ -211,26 +211,36 @@ fn main_inner() -> anyhow::Result<()> { arguments.overwrite, )?; - eprintln!( + writeln!( + std::io::stderr(), "Compiler run successful. Artifact(s) can be found in directory {output_directory:?}." - ); + )?; } else if arguments.output_assembly || arguments.output_binary { for (path, contract) in build.contracts.into_iter() { if arguments.output_assembly { let assembly_text = contract.build.assembly_text; - println!("Contract `{}` assembly:\n\n{}", path, assembly_text); + writeln!( + std::io::stdout(), + "Contract `{}` assembly:\n\n{}", + path, + assembly_text + )?; } if arguments.output_binary { - println!( + writeln!( + std::io::stdout(), "Contract `{}` bytecode: 0x{}", path, hex::encode(contract.build.bytecode) - ); + )?; } } } else { - eprintln!("Compiler run successful. No output requested. Use --asm and --bin flags."); + writeln!( + std::io::stderr(), + "Compiler run successful. No output requested. Use --asm and --bin flags." + )?; } Ok(()) diff --git a/crates/solidity/src/solc/combined_json/mod.rs b/crates/solidity/src/solc/combined_json/mod.rs index 0dd9c15..17ca906 100644 --- a/crates/solidity/src/solc/combined_json/mod.rs +++ b/crates/solidity/src/solc/combined_json/mod.rs @@ -80,10 +80,9 @@ impl CombinedJson { file_path.push(format!("combined.{}", revive_common::EXTENSION_JSON)); if file_path.exists() && !overwrite { - eprintln!( + anyhow::bail!( "Refusing to overwrite an existing file {file_path:?} (use --overwrite to force)." ); - return Ok(()); } File::create(&file_path) diff --git a/crates/solidity/src/solc/solc_compiler.rs b/crates/solidity/src/solc/solc_compiler.rs index 2180268..ca8052c 100644 --- a/crates/solidity/src/solc/solc_compiler.rs +++ b/crates/solidity/src/solc/solc_compiler.rs @@ -152,8 +152,16 @@ impl Compiler for SolcCompiler { anyhow::anyhow!("{} subprocess error: {:?}", self.executable, error) })?; if !output.status.success() { - println!("{}", String::from_utf8_lossy(output.stdout.as_slice())); - println!("{}", String::from_utf8_lossy(output.stderr.as_slice())); + writeln!( + std::io::stdout(), + "{}", + String::from_utf8_lossy(output.stdout.as_slice()) + )?; + writeln!( + std::io::stdout(), + "{}", + String::from_utf8_lossy(output.stderr.as_slice()) + )?; anyhow::bail!( "{} error: {}", self.executable, diff --git a/crates/solidity/src/solc/standard_json/input/mod.rs b/crates/solidity/src/solc/standard_json/input/mod.rs index 86c6bed..41e7263 100644 --- a/crates/solidity/src/solc/standard_json/input/mod.rs +++ b/crates/solidity/src/solc/standard_json/input/mod.rs @@ -68,12 +68,8 @@ impl Input { paths.insert(PathBuf::from(library_file)); } - #[cfg(feature = "parallel")] - let iter = paths.into_par_iter(); // Parallel iterator - #[cfg(not(feature = "parallel"))] - let iter = paths.iter(); // Sequential iterator - - let sources = iter + let sources = paths + .iter() .map(|path| { let source = Source::try_from(path.as_path()).unwrap_or_else(|error| { panic!("Source code file {path:?} reading error: {error}") diff --git a/crates/solidity/src/tests/cli-tests/tests/common.test.ts b/crates/solidity/src/tests/cli-tests/tests/common.test.ts index 65fbb91..0ed795f 100644 --- a/crates/solidity/src/tests/cli-tests/tests/common.test.ts +++ b/crates/solidity/src/tests/cli-tests/tests/common.test.ts @@ -10,7 +10,7 @@ describe("Run resolc without any options", () => { const result = executeCommand(command); it("Info with help is presented", () => { - expect(result.output).toMatch(/(No input sources specified|Error(s) found.)/i); + expect(result.output).toMatch(/(Usage: resolc)/i); }); it("Exit code = 1", () => { @@ -28,7 +28,7 @@ describe("Run resolc without any options", () => { //#1713 describe("Default run a command from the help", () => { - const command = `resolc ${paths.pathToBasicSolContract} -O3 --bin --output-dir "${paths.pathToOutputDir}"`; // potential issue on resolc with full path on Windows cmd + const command = `resolc ${paths.pathToBasicSolContract} --overwrite -O3 --bin --output-dir "${paths.pathToOutputDir}"`; // potential issue on resolc with full path on Windows cmd const result = executeCommand(command); it("Compiler run successful", () => { @@ -54,7 +54,7 @@ describe("Default run a command from the help", () => { //#1818 describe("Default run a command from the help", () => { - const command = `resolc ${paths.pathToBasicSolContract} -O3 --bin --asm --output-dir "${paths.pathToOutputDir}"`; // potential issue on resolc with full path on Windows cmd + const command = `resolc ${paths.pathToBasicSolContract} --overwrite -O3 --bin --asm --output-dir "${paths.pathToOutputDir}"`; // potential issue on resolc with full path on Windows cmd const result = executeCommand(command); it("Compiler run successful", () => { @@ -81,8 +81,8 @@ describe("Default run a command from the help", () => { describe("Run resolc with source debug information", () => { const commands = [ - `resolc -g ${paths.pathToBasicSolContract} --bin --asm --output-dir "${paths.pathToOutputDir}"`, - `resolc --disable-solc-optimizer -g ${paths.pathToBasicSolContract} --bin --asm --output-dir "${paths.pathToOutputDir}"` + `resolc -g ${paths.pathToBasicSolContract} --overwrite --bin --asm --output-dir "${paths.pathToOutputDir}"`, + `resolc --disable-solc-optimizer -g ${paths.pathToBasicSolContract} --overwrite --bin --asm --output-dir "${paths.pathToOutputDir}"` ]; // potential issue on resolc with full path on Windows cmd`; for (var idx in commands) { @@ -114,8 +114,8 @@ describe("Run resolc with source debug information", () => { describe("Run resolc with source debug information, check LLVM debug-info", () => { const commands = [ - `resolc -g ${paths.pathToBasicSolContract} --debug-output-dir="${paths.pathToOutputDir}"`, - `resolc -g --disable-solc-optimizer ${paths.pathToBasicSolContract} --debug-output-dir="${paths.pathToOutputDir}"` + `resolc -g ${paths.pathToBasicSolContract} --overwrite --debug-output-dir="${paths.pathToOutputDir}"`, + `resolc -g --disable-solc-optimizer ${paths.pathToBasicSolContract} --overwrite --debug-output-dir="${paths.pathToOutputDir}"` ]; // potential issue on resolc with full path on Windows cmd`; for (var idx in commands) {