diff --git a/ethers-solc/src/artifacts.rs b/ethers-solc/src/artifacts.rs index b0fbeceb..ddf57d56 100644 --- a/ethers-solc/src/artifacts.rs +++ b/ethers-solc/src/artifacts.rs @@ -6,12 +6,12 @@ use md5::Digest; use semver::Version; use std::{ collections::BTreeMap, - fmt, fs, io, + fmt, fs, path::{Path, PathBuf}, str::FromStr, }; -use crate::{compile::*, remappings::Remapping, utils}; +use crate::{compile::*, error::SolcIoError, remappings::Remapping, utils}; use ethers_core::abi::Address; use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; @@ -30,7 +30,7 @@ pub struct CompilerInput { impl CompilerInput { /// Reads all contracts found under the path - pub fn new(path: impl AsRef) -> io::Result { + pub fn new(path: impl AsRef) -> Result { Source::read_all_from(path.as_ref()).map(Self::with_sources) } @@ -382,17 +382,18 @@ pub struct Source { impl Source { /// Reads the file content - pub fn read(file: impl AsRef) -> io::Result { - Ok(Self { content: fs::read_to_string(file.as_ref())? }) + pub fn read(file: impl AsRef) -> Result { + let file = file.as_ref(); + Ok(Self { content: fs::read_to_string(file).map_err(|err| SolcIoError::new(err, file))? }) } /// Finds all source files under the given dir path and reads them all - pub fn read_all_from(dir: impl AsRef) -> io::Result { - Self::read_all(utils::source_files(dir)?) + pub fn read_all_from(dir: impl AsRef) -> Result { + Self::read_all(utils::source_files(dir)) } /// Reads all files - pub fn read_all(files: I) -> io::Result + pub fn read_all(files: I) -> Result where I: IntoIterator, T: Into, @@ -421,17 +422,22 @@ impl Source { #[cfg(feature = "async")] impl Source { /// async version of `Self::read` - pub async fn async_read(file: impl AsRef) -> io::Result { - Ok(Self { content: tokio::fs::read_to_string(file.as_ref()).await? }) + pub async fn async_read(file: impl AsRef) -> Result { + let file = file.as_ref(); + Ok(Self { + content: tokio::fs::read_to_string(file) + .await + .map_err(|err| SolcIoError::new(err, file))?, + }) } /// Finds all source files under the given dir path and reads them all - pub async fn async_read_all_from(dir: impl AsRef) -> io::Result { - Self::async_read_all(utils::source_files(dir.as_ref())?).await + pub async fn async_read_all_from(dir: impl AsRef) -> Result { + Self::async_read_all(utils::source_files(dir.as_ref())).await } /// async version of `Self::read_all` - pub async fn async_read_all(files: I) -> io::Result + pub async fn async_read_all(files: I) -> Result where I: IntoIterator, T: Into, diff --git a/ethers-solc/src/cache.rs b/ethers-solc/src/cache.rs index 31cc7578..fb9fb908 100644 --- a/ethers-solc/src/cache.rs +++ b/ethers-solc/src/cache.rs @@ -64,7 +64,7 @@ impl SolFilesCache { pub fn read(path: impl AsRef) -> Result { let path = path.as_ref(); tracing::trace!("reading solfiles cache at {}", path.display()); - let file = fs::File::open(path)?; + let file = fs::File::open(path).map_err(|err| SolcError::io(err, path))?; let file = std::io::BufReader::new(file); let cache = serde_json::from_reader(file)?; tracing::trace!("done"); @@ -74,7 +74,7 @@ impl SolFilesCache { /// Write the cache to json file pub fn write(&self, path: impl AsRef) -> Result<()> { let path = path.as_ref(); - let file = fs::File::create(path)?; + let file = fs::File::create(path).map_err(|err| SolcError::io(err, path))?; tracing::trace!("writing cache to json file"); serde_json::to_writer_pretty(file, self)?; tracing::trace!("cache file located: {}", path.display()); @@ -198,13 +198,16 @@ impl SolFilesCache { #[cfg(feature = "async")] impl SolFilesCache { pub async fn async_read(path: impl AsRef) -> Result { - let content = tokio::fs::read_to_string(path.as_ref()).await?; + let path = path.as_ref(); + let content = + tokio::fs::read_to_string(path).await.map_err(|err| SolcError::io(err, path))?; Ok(serde_json::from_str(&content)?) } pub async fn async_write(&self, path: impl AsRef) -> Result<()> { + let path = path.as_ref(); let content = serde_json::to_vec_pretty(self)?; - Ok(tokio::fs::write(path.as_ref(), content).await?) + Ok(tokio::fs::write(path, content).await.map_err(|err| SolcError::io(err, path))?) } } @@ -236,12 +239,18 @@ impl SolFilesCacheBuilder { let solc_config = self.solc_config.map(Ok).unwrap_or_else(|| SolcConfig::builder().build())?; - let root = self.root.map(Ok).unwrap_or_else(std::env::current_dir)?; + let root = self + .root + .map(Ok) + .unwrap_or_else(std::env::current_dir) + .map_err(|err| SolcError::io(err, "."))?; let mut files = BTreeMap::new(); for (file, source) in sources { - let last_modification_date = fs::metadata(&file)? - .modified()? + let last_modification_date = fs::metadata(&file) + .map_err(|err| SolcError::io(err, file.clone()))? + .modified() + .map_err(|err| SolcError::io(err, file.clone()))? .duration_since(UNIX_EPOCH) .map_err(|err| SolcError::solc(err.to_string()))? .as_millis() as u64; @@ -268,7 +277,9 @@ impl SolFilesCacheBuilder { if dest.exists() { // read the existing cache and extend it by the files that changed // (if we just wrote to the cache file, we'd overwrite the existing data) - let reader = std::io::BufReader::new(File::open(dest)?); + let reader = std::io::BufReader::new( + File::open(dest).map_err(|err| SolcError::io(err, dest))?, + ); let mut cache: SolFilesCache = serde_json::from_reader(reader)?; assert_eq!(cache.format, format); cache.files.extend(files); diff --git a/ethers-solc/src/compile.rs b/ethers-solc/src/compile.rs index eaaa31bd..0521bee0 100644 --- a/ethers-solc/src/compile.rs +++ b/ethers-solc/src/compile.rs @@ -247,7 +247,8 @@ impl Solc { let version = self.version_short()?; let mut version_path = svm::version_path(version.to_string().as_str()); version_path.push(format!("solc-{}", version.to_string().as_str())); - let content = std::fs::read(version_path)?; + let content = + std::fs::read(&version_path).map_err(|err| SolcError::io(err, version_path))?; use sha2::Digest; let mut hasher = sha2::Sha256::new(); @@ -265,6 +266,7 @@ impl Solc { /// Convenience function for compiling all sources under the given path pub fn compile_source(&self, path: impl AsRef) -> Result { + let path = path.as_ref(); self.compile(&CompilerInput::new(path)?) } @@ -302,11 +304,12 @@ impl Solc { .stdin(Stdio::piped()) .stderr(Stdio::piped()) .stdout(Stdio::piped()) - .spawn()?; + .spawn() + .map_err(|err| SolcError::io(err, &self.solc))?; let stdin = child.stdin.take().unwrap(); serde_json::to_writer(stdin, input)?; - compile_output(child.wait_with_output()?) + compile_output(child.wait_with_output().map_err(|err| SolcError::io(err, &self.solc))?) } pub fn version_short(&self) -> Result { @@ -322,7 +325,8 @@ impl Solc { .stdin(Stdio::piped()) .stderr(Stdio::piped()) .stdout(Stdio::piped()) - .output()?, + .output() + .map_err(|err| SolcError::io(err, &self.solc))?, ) } } @@ -363,11 +367,14 @@ impl Solc { .stdin(Stdio::piped()) .stderr(Stdio::piped()) .stdout(Stdio::piped()) - .spawn()?; + .spawn() + .map_err(|err| SolcError::io(err, &self.solc))?; let stdin = child.stdin.as_mut().unwrap(); - stdin.write_all(&content).await?; - stdin.flush().await?; - compile_output(child.wait_with_output().await?) + stdin.write_all(&content).await.map_err(|err| SolcError::io(err, &self.solc))?; + stdin.flush().await.map_err(|err| SolcError::io(err, &self.solc))?; + compile_output( + child.wait_with_output().await.map_err(|err| SolcError::io(err, &self.solc))?, + ) } pub async fn async_version(&self) -> Result { @@ -377,9 +384,11 @@ impl Solc { .stdin(Stdio::piped()) .stderr(Stdio::piped()) .stdout(Stdio::piped()) - .spawn()? + .spawn() + .map_err(|err| SolcError::io(err, &self.solc))? .wait_with_output() - .await?, + .await + .map_err(|err| SolcError::io(err, &self.solc))?, ) } @@ -470,7 +479,8 @@ fn version_from_output(output: Output) -> Result { .stdout .lines() .last() - .ok_or_else(|| SolcError::solc("version not found in solc output"))??; + .ok_or_else(|| SolcError::solc("version not found in solc output"))? + .map_err(|err| SolcError::msg(format!("Failed to read output: {}", err)))?; // NOTE: semver doesn't like `+` in g++ in build metadata which is invalid semver Ok(Version::from_str(&version.trim_start_matches("Version: ").replace(".g++", ".gcc"))?) } else { diff --git a/ethers-solc/src/config.rs b/ethers-solc/src/config.rs index dd76774b..6e1891fa 100644 --- a/ethers-solc/src/config.rs +++ b/ethers-solc/src/config.rs @@ -1,7 +1,7 @@ use crate::{ artifacts::{CompactContract, CompactContractRef, Contract, Settings}, cache::SOLIDITY_FILES_CACHE_FILENAME, - error::{Result, SolcError}, + error::{Result, SolcError, SolcIoError}, hh::HardhatArtifact, remappings::Remapping, CompilerOutput, @@ -51,12 +51,12 @@ impl ProjectPathsConfig { /// Creates a new config with the current directory as the root pub fn current_hardhat() -> Result { - Self::hardhat(std::env::current_dir()?) + Self::hardhat(std::env::current_dir().map_err(|err| SolcError::io(err, "."))?) } /// Creates a new config with the current directory as the root pub fn current_dapptools() -> Result { - Self::dapptools(std::env::current_dir()?) + Self::dapptools(std::env::current_dir().map_err(|err| SolcError::io(err, "."))?) } } @@ -68,7 +68,8 @@ pub enum PathStyle { impl PathStyle { pub fn paths(&self, root: impl AsRef) -> Result { - let root = std::fs::canonicalize(root)?; + let root = root.as_ref(); + let root = std::fs::canonicalize(root).map_err(|err| SolcError::io(err, root))?; Ok(match self { PathStyle::Dapptools => ProjectPathsConfig::builder() @@ -157,9 +158,13 @@ impl ProjectPathsConfigBuilder { self } - pub fn build(self) -> io::Result { - let root = self.root.map(Ok).unwrap_or_else(std::env::current_dir)?; - let root = std::fs::canonicalize(root)?; + pub fn build(self) -> std::result::Result { + let root = self + .root + .map(Ok) + .unwrap_or_else(std::env::current_dir) + .map_err(|err| SolcIoError::new(err, "."))?; + let root = std::fs::canonicalize(&root).map_err(|err| SolcIoError::new(err, root))?; Ok(ProjectPathsConfig { cache: self @@ -287,7 +292,8 @@ pub trait ArtifactOutput { } fn read_cached_artifact(path: impl AsRef) -> Result { - let file = fs::File::open(path.as_ref())?; + let path = path.as_ref(); + let file = fs::File::open(path).map_err(|err| SolcError::io(err, path))?; let file = io::BufReader::new(file); Ok(serde_json::from_reader(file)?) } @@ -362,7 +368,8 @@ impl ArtifactOutput for MinimalCombinedArtifacts { })?; } let min = CompactContractRef::from(contract); - fs::write(&file, serde_json::to_vec_pretty(&min)?)? + fs::write(&file, serde_json::to_vec_pretty(&min)?) + .map_err(|err| SolcError::io(err, file))? } } Ok(()) @@ -386,7 +393,8 @@ impl ArtifactOutput for MinimalCombinedArtifactsHardhatFallback { } fn read_cached_artifact(path: impl AsRef) -> Result { - let content = fs::read_to_string(path)?; + let path = path.as_ref(); + let content = fs::read_to_string(path).map_err(|err| SolcError::io(err, path))?; if let Ok(a) = serde_json::from_str(&content) { Ok(a) } else { @@ -429,17 +437,18 @@ impl fmt::Display for AllowedLibPaths { } impl> TryFrom> for AllowedLibPaths { - type Error = std::io::Error; + type Error = SolcIoError; fn try_from(libs: Vec) -> std::result::Result { let libs = libs .into_iter() .map(|lib| { let path: PathBuf = lib.into(); - let lib = std::fs::canonicalize(path)?; + let lib = + std::fs::canonicalize(&path).map_err(|err| SolcIoError::new(err, path))?; Ok(lib) }) - .collect::, std::io::Error>>()?; + .collect::, _>>()?; Ok(AllowedLibPaths(libs)) } } diff --git a/ethers-solc/src/error.rs b/ethers-solc/src/error.rs index 7fa86a5b..35299112 100644 --- a/ethers-solc/src/error.rs +++ b/ethers-solc/src/error.rs @@ -1,3 +1,4 @@ +use std::{io, path::PathBuf}; use thiserror::Error; pub type Result = std::result::Result; @@ -21,7 +22,7 @@ pub enum SolcError { SerdeJson(#[from] serde_json::Error), /// Filesystem IO error #[error(transparent)] - Io(#[from] std::io::Error), + Io(#[from] SolcIoError), #[cfg(feature = "svm")] #[error(transparent)] SvmError(#[from] svm::SolcVmError), @@ -35,6 +36,9 @@ pub enum SolcError { } impl SolcError { + pub(crate) fn io(err: io::Error, path: impl Into) -> Self { + SolcIoError::new(err, path).into() + } pub(crate) fn solc(msg: impl Into) -> Self { SolcError::SolcError(msg.into()) } @@ -42,3 +46,22 @@ impl SolcError { SolcError::Message(msg.into()) } } + +#[derive(Debug, Error)] +#[error("\"{}\": {io}", self.path.display())] +pub struct SolcIoError { + io: io::Error, + path: PathBuf, +} + +impl SolcIoError { + pub fn new(io: io::Error, path: impl Into) -> Self { + Self { io, path: path.into() } + } +} + +impl From for io::Error { + fn from(err: SolcIoError) -> Self { + err.io + } +} diff --git a/ethers-solc/src/hh.rs b/ethers-solc/src/hh.rs index bcb3b41b..5f4a47bd 100644 --- a/ethers-solc/src/hh.rs +++ b/ethers-solc/src/hh.rs @@ -73,7 +73,8 @@ impl ArtifactOutput for HardhatArtifacts { })?; } let artifact = Self::contract_to_artifact(file, name, contract.clone()); - fs::write(&artifact_file, serde_json::to_vec_pretty(&artifact)?)? + fs::write(&artifact_file, serde_json::to_vec_pretty(&artifact)?) + .map_err(|err| SolcError::io(err, artifact_file))? } } Ok(()) diff --git a/ethers-solc/src/lib.rs b/ethers-solc/src/lib.rs index 5ee6af31..1d0afc21 100644 --- a/ethers-solc/src/lib.rs +++ b/ethers-solc/src/lib.rs @@ -26,10 +26,14 @@ use crate::{artifacts::Source, cache::SolFilesCache}; pub mod error; pub mod utils; -use crate::{artifacts::Sources, cache::PathMap}; +use crate::{ + artifacts::Sources, + cache::PathMap, + error::{SolcError, SolcIoError}, +}; use error::Result; use std::{ - borrow::Cow, collections::BTreeMap, convert::TryInto, fmt, fs, io, marker::PhantomData, + borrow::Cow, collections::BTreeMap, convert::TryInto, fmt, fs, marker::PhantomData, path::PathBuf, }; @@ -117,7 +121,7 @@ impl Project { if let Some(cache_dir) = self.paths.cache.parent() { tracing::trace!("creating cache file parent directory \"{}\"", cache_dir.display()); - fs::create_dir_all(cache_dir)? + fs::create_dir_all(cache_dir).map_err(|err| SolcError::io(err, cache_dir))? } tracing::trace!("writing cache file to \"{}\"", self.paths.cache.display()); @@ -128,9 +132,9 @@ impl Project { /// Returns all sources found under the project's configured sources path #[tracing::instrument(skip_all, fields(name = "sources"))] - pub fn sources(&self) -> io::Result { + pub fn sources(&self) -> Result { tracing::trace!("reading all sources from \"{}\"", self.paths.sources.display()); - Source::read_all_from(&self.paths.sources) + Ok(Source::read_all_from(&self.paths.sources)?) } /// This emits the cargo [`rerun-if-changed`](https://doc.rust-lang.org/cargo/reference/build-scripts.html#cargorerun-if-changedpath) instruction. @@ -161,7 +165,7 @@ impl Project { fn resolved_libraries( &self, sources: &Sources, - ) -> io::Result> { + ) -> Result> { let mut libs = BTreeMap::default(); for source in sources.values() { for import in source.parse_imports() { @@ -462,14 +466,16 @@ impl Project { } /// Removes the project's artifacts and cache file - pub fn cleanup(&self) -> Result<()> { + pub fn cleanup(&self) -> std::result::Result<(), SolcIoError> { tracing::trace!("clean up project"); if self.paths.cache.exists() { - std::fs::remove_file(&self.paths.cache)?; + std::fs::remove_file(&self.paths.cache) + .map_err(|err| SolcIoError::new(err, self.paths.cache.clone()))?; tracing::trace!("removed cache file \"{}\"", self.paths.cache.display()); } if self.paths.artifacts.exists() { - std::fs::remove_dir_all(&self.paths.artifacts)?; + std::fs::remove_dir_all(&self.paths.artifacts) + .map_err(|err| SolcIoError::new(err, self.paths.artifacts.clone()))?; tracing::trace!("removed artifacts dir \"{}\"", self.paths.artifacts.display()); } Ok(()) diff --git a/ethers-solc/src/remappings.rs b/ethers-solc/src/remappings.rs index d5a4bd13..a7d197aa 100644 --- a/ethers-solc/src/remappings.rs +++ b/ethers-solc/src/remappings.rs @@ -132,16 +132,19 @@ impl Remapping { // nothing to find return Ok(Vec::new()) } - let mut paths = std::fs::read_dir(path)?.into_iter().collect::>(); + let mut paths = std::fs::read_dir(path) + .map_err(|err| SolcError::io(err, path))? + .into_iter() + .collect::>(); let mut remappings = Vec::new(); - while let Some(path) = paths.pop() { - let path = path?.path(); + while let Some(p) = paths.pop() { + let path = p.map_err(|err| SolcError::io(err, path))?.path(); // get all the directories inside a file if it's a valid dir if let Ok(dir) = std::fs::read_dir(&path) { for inner in dir { - let inner = inner?; + let inner = inner.map_err(|err| SolcError::io(err, &path))?; let path = inner.path().display().to_string(); let path = path.rsplit('/').next().unwrap().to_string(); if path != DAPPTOOLS_CONTRACTS_DIR && path != JS_CONTRACTS_DIR { diff --git a/ethers-solc/src/utils.rs b/ethers-solc/src/utils.rs index 40da5e3c..733ad6dd 100644 --- a/ethers-solc/src/utils.rs +++ b/ethers-solc/src/utils.rs @@ -49,17 +49,16 @@ pub fn find_version_pragma(contract: &str) -> Option<&str> { /// /// ```no_run /// use ethers_solc::utils; -/// let sources = utils::source_files("./contracts").unwrap(); +/// let sources = utils::source_files("./contracts"); /// ``` -pub fn source_files(root: impl AsRef) -> walkdir::Result> { - let files = WalkDir::new(root) +pub fn source_files(root: impl AsRef) -> Vec { + WalkDir::new(root) .into_iter() .filter_map(Result::ok) .filter(|e| e.file_type().is_file()) .filter(|e| e.path().extension().map(|ext| ext == "sol").unwrap_or_default()) .map(|e| e.path().into()) - .collect(); - Ok(files) + .collect() } /// Returns the source name for the given source path, the ancestors of the root path @@ -192,7 +191,7 @@ mod tests { File::create(&file_c).unwrap(); File::create(&file_d).unwrap(); - let files: HashSet<_> = source_files(tmp_dir.path()).unwrap().into_iter().collect(); + let files: HashSet<_> = source_files(tmp_dir.path()).into_iter().collect(); let expected: HashSet<_> = [file_a, file_b, file_c, file_d].into(); assert_eq!(files, expected); }