From fd2e004ee617d51a6d176a6a6697d2948282ebd8 Mon Sep 17 00:00:00 2001 From: Omar Abdulla Date: Wed, 16 Jul 2025 00:54:17 +0300 Subject: [PATCH] Fix compilation errors related to paths --- crates/compiler/src/lib.rs | 12 ++-- crates/compiler/src/revive_resolc.rs | 30 +++++++-- crates/compiler/src/solc.rs | 38 +++++++++-- crates/core/src/driver/mod.rs | 95 +++++++++++++++++++++++----- 4 files changed, 145 insertions(+), 30 deletions(-) diff --git a/crates/compiler/src/lib.rs b/crates/compiler/src/lib.rs index 7e92cf0..e43a527 100644 --- a/crates/compiler/src/lib.rs +++ b/crates/compiler/src/lib.rs @@ -44,6 +44,8 @@ pub trait SolidityCompiler { pub struct CompilerInput { pub extra_options: T, pub input: SolcStandardJsonInput, + pub allow_paths: Vec, + pub base_path: Option, } /// The generic compilation output configuration. @@ -83,8 +85,8 @@ where pub struct Compiler { input: SolcStandardJsonInput, extra_options: T::Options, - allow_paths: Vec, - base_path: Option, + allow_paths: Vec, + base_path: Option, } impl Default for Compiler { @@ -145,12 +147,12 @@ where self } - pub fn allow_path(mut self, path: String) -> Self { + pub fn allow_path(mut self, path: PathBuf) -> Self { self.allow_paths.push(path); self } - pub fn base_path(mut self, base_path: String) -> Self { + pub fn base_path(mut self, base_path: PathBuf) -> Self { self.base_path = Some(base_path); self } @@ -159,6 +161,8 @@ where T::new(solc_path).build(CompilerInput { extra_options: self.extra_options, input: self.input, + allow_paths: self.allow_paths, + base_path: self.base_path, }) } diff --git a/crates/compiler/src/revive_resolc.rs b/crates/compiler/src/revive_resolc.rs index eb63719..851fae3 100644 --- a/crates/compiler/src/revive_resolc.rs +++ b/crates/compiler/src/revive_resolc.rs @@ -23,13 +23,24 @@ impl SolidityCompiler for Resolc { &self, input: CompilerInput, ) -> anyhow::Result> { - let mut child = Command::new(&self.resolc_path) - .arg("--standard-json") - .args(&input.extra_options) + let mut command = Command::new(&self.resolc_path); + command .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) - .spawn()?; + .arg("--standard-json"); + + if !input.allow_paths.is_empty() { + command.arg("--allow-paths").arg( + input + .allow_paths + .iter() + .map(|path| path.display().to_string()) + .collect::>() + .join(","), + ); + } + let mut child = command.spawn()?; let stdin_pipe = child.stdin.as_mut().expect("stdin must be piped"); serde_json::to_writer(stdin_pipe, &input.input)?; @@ -55,13 +66,22 @@ impl SolidityCompiler for Resolc { }); } - let parsed: SolcStandardJsonOutput = serde_json::from_slice(&stdout).map_err(|e| { + let parsed = serde_json::from_slice::(&stdout).map_err(|e| { anyhow::anyhow!( "failed to parse resolc JSON output: {e}\nstderr: {}", String::from_utf8_lossy(&stderr) ) })?; + // Detecting if the compiler output contained errors and reporting them through logs and + // errors instead of returning the compiler output that might contain errors. + for error in parsed.errors.iter().flatten() { + if error.severity == "error" { + tracing::error!(?error, ?input, "Encountered an error in the compilation"); + anyhow::bail!("Encountered an error in the compilation: {error}") + } + } + Ok(CompilerOutput { input, output: parsed, diff --git a/crates/compiler/src/solc.rs b/crates/compiler/src/solc.rs index b40d18f..aef9dd5 100644 --- a/crates/compiler/src/solc.rs +++ b/crates/compiler/src/solc.rs @@ -9,6 +9,7 @@ use std::{ use crate::{CompilerInput, CompilerOutput, SolidityCompiler}; use revive_dt_config::Arguments; use revive_dt_solc_binaries::download_solc; +use revive_solc_json_interface::SolcStandardJsonOutput; pub struct Solc { solc_path: PathBuf, @@ -21,12 +22,24 @@ impl SolidityCompiler for Solc { &self, input: CompilerInput, ) -> anyhow::Result> { - let mut child = Command::new(&self.solc_path) + let mut command = Command::new(&self.solc_path); + command .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) - .arg("--standard-json") - .spawn()?; + .arg("--standard-json"); + + if !input.allow_paths.is_empty() { + command.arg("--allow-paths").arg( + input + .allow_paths + .iter() + .map(|path| path.display().to_string()) + .collect::>() + .join(","), + ); + } + let mut child = command.spawn()?; let stdin = child.stdin.as_mut().expect("should be piped"); serde_json::to_writer(stdin, &input.input)?; @@ -42,9 +55,26 @@ impl SolidityCompiler for Solc { }); } + let parsed = + serde_json::from_slice::(&output.stdout).map_err(|e| { + anyhow::anyhow!( + "failed to parse resolc JSON output: {e}\nstderr: {}", + String::from_utf8_lossy(&output.stdout) + ) + })?; + + // Detecting if the compiler output contained errors and reporting them through logs and + // errors instead of returning the compiler output that might contain errors. + for error in parsed.errors.iter().flatten() { + if error.severity == "error" { + tracing::error!(?error, ?input, "Encountered an error in the compilation"); + anyhow::bail!("Encountered an error in the compilation: {error}") + } + } + Ok(CompilerOutput { input, - output: serde_json::from_slice(&output.stdout)?, + output: parsed, error: None, }) } diff --git a/crates/core/src/driver/mod.rs b/crates/core/src/driver/mod.rs index 1e34f3c..f5b6275 100644 --- a/crates/core/src/driver/mod.rs +++ b/crates/core/src/driver/mod.rs @@ -70,25 +70,12 @@ where }; let compiler = Compiler::::new() - .base_path(metadata.directory()?.display().to_string()) - .allow_path(metadata.directory()?.display().to_string()) + .base_path(metadata.directory()?) + .allow_path(metadata.directory()?) .solc_optimizer(mode.solc_optimize()); - let compiler = std::fs::read_dir(metadata.directory()?)? - .flat_map(|entry| match entry { - Ok(entry) => { - if entry - .path() - .extension() - .is_some_and(|ext| ext.eq_ignore_ascii_case("sol")) - { - Some(entry.path()) - } else { - None - } - } - Err(_) => None, - }) + let compiler = FilesWithExtensionIterator::new(metadata.directory()?) + .with_allowed_extension("sol") .try_fold(compiler, |compiler, path| compiler.with_source(&path))?; let mut task = CompilationTask { @@ -492,3 +479,77 @@ where Ok(()) } } + +/// An iterator that finds files of a certain extension in the provided directory. You can think of +/// this a glob pattern similar to: `${path}/**/*.md` +struct FilesWithExtensionIterator { + /// The set of allowed extensions that that match the requirement and that should be returned + /// when found. + allowed_extensions: std::collections::HashSet>, + + /// The set of directories to visit next. This iterator does BFS and so these directories will + /// only be visited if we can't find any files in our state. + directories_to_search: Vec, + + /// The set of files matching the allowed extensions that were found. If there are entries in + /// this vector then they will be returned when the [`Iterator::next`] method is called. If not + /// then we visit one of the next directories to visit. + /// + /// [`Iterator`]: std::iter::Iterator + files_matching_allowed_extensions: Vec, +} + +impl FilesWithExtensionIterator { + fn new(root_directory: std::path::PathBuf) -> Self { + Self { + allowed_extensions: Default::default(), + directories_to_search: vec![root_directory], + files_matching_allowed_extensions: Default::default(), + } + } + + fn with_allowed_extension( + mut self, + allowed_extension: impl Into>, + ) -> Self { + self.allowed_extensions.insert(allowed_extension.into()); + self + } +} + +impl Iterator for FilesWithExtensionIterator { + type Item = std::path::PathBuf; + + fn next(&mut self) -> Option { + if let Some(file_path) = self.files_matching_allowed_extensions.pop() { + return Some(file_path); + }; + + let directory_to_search = self.directories_to_search.pop()?; + + // Read all of the entries in the directory. If we failed to read this dir's entires then we + // elect to just ignore it and look in the next directory, we do that by calling the next + // method again on the iterator, which is an intentional decision that we made here instead + // of panicking. + let Ok(dir_entries) = std::fs::read_dir(directory_to_search) else { + return self.next(); + }; + + for entry in dir_entries.flatten() { + let entry_path = entry.path(); + if entry_path.is_dir() { + self.directories_to_search.push(entry_path) + } else if entry_path.is_file() + && entry_path.extension().is_some_and(|ext| { + self.allowed_extensions + .iter() + .any(|allowed| ext.eq_ignore_ascii_case(allowed.as_ref())) + }) + { + self.files_matching_allowed_extensions.push(entry_path) + } + } + + self.next() + } +}