diff --git a/ethers-solc/src/artifact_output/mod.rs b/ethers-solc/src/artifact_output/mod.rs index e5bfde6cd..f08756432 100644 --- a/ethers-solc/src/artifact_output/mod.rs +++ b/ethers-solc/src/artifact_output/mod.rs @@ -1,8 +1,8 @@ //! Output artifact handling use crate::{ - artifacts::FileToContractsMap, contracts::VersionedContracts, error::Result, utils, - HardhatArtifact, ProjectPathsConfig, SolcError, + artifacts::FileToContractsMap, error::Result, utils, HardhatArtifact, ProjectPathsConfig, + SolcError, }; use ethers_core::{abi::Abi, types::Bytes}; use semver::Version; @@ -15,10 +15,13 @@ use std::{ }; mod configurable; -use crate::artifacts::{ - contract::{CompactContract, CompactContractBytecode, Contract}, - BytecodeObject, CompactBytecode, CompactContractBytecodeCow, CompactDeployedBytecode, - SourceFile, +use crate::{ + artifacts::{ + contract::{CompactContract, CompactContractBytecode, Contract}, + BytecodeObject, CompactBytecode, CompactContractBytecodeCow, CompactDeployedBytecode, + SourceFile, + }, + compile::output::{contracts::VersionedContracts, sources::VersionedSourceFiles}, }; pub use configurable::*; @@ -447,7 +450,7 @@ pub trait ArtifactOutput { fn on_output( &self, contracts: &VersionedContracts, - sources: &BTreeMap, + sources: &VersionedSourceFiles, layout: &ProjectPathsConfig, ) -> Result> { let mut artifacts = self.output_to_artifacts(contracts, sources); @@ -609,16 +612,17 @@ pub trait ArtifactOutput { fn output_to_artifacts( &self, contracts: &VersionedContracts, - sources: &BTreeMap, + sources: &VersionedSourceFiles, ) -> Artifacts { let mut artifacts = ArtifactsMap::new(); for (file, contracts) in contracts.as_ref().iter() { - let source_file = sources.get(file); let mut entries = BTreeMap::new(); for (name, versioned_contracts) in contracts { let mut contracts = Vec::with_capacity(versioned_contracts.len()); // check if the same contract compiled with multiple solc versions for contract in versioned_contracts { + let source_file = sources.find_file_and_version(file, &contract.version); + let artifact_path = if versioned_contracts.len() > 1 { Self::output_file_versioned(file, name, &contract.version) } else { @@ -689,7 +693,7 @@ impl ArtifactOutput for MinimalCombinedArtifactsHardhatFallback { fn on_output( &self, output: &VersionedContracts, - sources: &BTreeMap, + sources: &VersionedSourceFiles, layout: &ProjectPathsConfig, ) -> Result> { MinimalCombinedArtifacts::default().on_output(output, sources, layout) diff --git a/ethers-solc/src/artifacts/mod.rs b/ethers-solc/src/artifacts/mod.rs index cd5e0f404..3001716d8 100644 --- a/ethers-solc/src/artifacts/mod.rs +++ b/ethers-solc/src/artifacts/mod.rs @@ -46,10 +46,10 @@ pub type Contracts = FileToContractsMap; pub type Sources = BTreeMap; /// A set of different Solc installations with their version and the sources to be compiled -pub type VersionedSources = BTreeMap; +pub(crate) type VersionedSources = BTreeMap; /// A set of different Solc installations with their version and the sources to be compiled -pub type VersionedFilteredSources = BTreeMap; +pub(crate) type VersionedFilteredSources = BTreeMap; const SOLIDITY: &str = "Solidity"; diff --git a/ethers-solc/src/compile/mod.rs b/ethers-solc/src/compile/mod.rs index 5a096829a..384f9c75a 100644 --- a/ethers-solc/src/compile/mod.rs +++ b/ethers-solc/src/compile/mod.rs @@ -5,7 +5,6 @@ use crate::{ }; use semver::{Version, VersionReq}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; - use std::{ fmt, io::BufRead, @@ -13,10 +12,9 @@ use std::{ process::{Command, Output, Stdio}, str::FromStr, }; - -pub mod contracts; pub mod many; pub mod output; +pub use output::{contracts, sources}; pub mod project; /// The name of the `solc` binary on the system diff --git a/ethers-solc/src/compile/contracts.rs b/ethers-solc/src/compile/output/contracts.rs similarity index 100% rename from ethers-solc/src/compile/contracts.rs rename to ethers-solc/src/compile/output/contracts.rs diff --git a/ethers-solc/src/compile/output.rs b/ethers-solc/src/compile/output/mod.rs similarity index 94% rename from ethers-solc/src/compile/output.rs rename to ethers-solc/src/compile/output/mod.rs index 2ee3eddb4..57d121bb3 100644 --- a/ethers-solc/src/compile/output.rs +++ b/ethers-solc/src/compile/output/mod.rs @@ -3,14 +3,18 @@ use crate::{ artifacts::{ contract::{CompactContractBytecode, CompactContractRef, Contract}, - Error, SourceFile, SourceFiles, + Error, }, - contracts::{VersionedContract, VersionedContracts}, + sources::{VersionedSourceFile, VersionedSourceFiles}, ArtifactId, ArtifactOutput, Artifacts, CompilerOutput, ConfigurableArtifacts, }; +use contracts::{VersionedContract, VersionedContracts}; use semver::Version; use std::{collections::BTreeMap, fmt, path::Path}; +pub mod contracts; +pub mod sources; + /// Contains a mixture of already compiled/cached artifacts and the input set of sources that still /// need to be compiled. #[derive(Debug, Clone, PartialEq, Default)] @@ -69,7 +73,9 @@ impl ProjectCompileOutput { } /// All artifacts together with their ID and the sources of the project. - pub fn into_artifacts_with_sources(self) -> (BTreeMap, SourceFiles) { + pub fn into_artifacts_with_sources( + self, + ) -> (BTreeMap, VersionedSourceFiles) { let Self { cached_artifacts, compiled_artifacts, compiler_output, .. } = self; ( @@ -77,7 +83,7 @@ impl ProjectCompileOutput { .into_artifacts::() .chain(compiled_artifacts.into_artifacts::()) .collect(), - SourceFiles(compiler_output.sources), + compiler_output.sources, ) } @@ -228,8 +234,8 @@ impl fmt::Display for ProjectCompileOutput { pub struct AggregatedCompilerOutput { /// all errors from all `CompilerOutput` pub errors: Vec, - /// All source files - pub sources: BTreeMap, + /// All source files combined with the solc version used to compile them + pub sources: VersionedSourceFiles, /// All compiled contracts combined with the solc version used to compile them pub contracts: VersionedContracts, } @@ -274,10 +280,15 @@ impl AggregatedCompilerOutput { /// adds a new `CompilerOutput` to the aggregated output pub fn extend(&mut self, version: Version, output: CompilerOutput) { - self.errors.extend(output.errors); - self.sources.extend(output.sources); + let CompilerOutput { errors, sources, contracts } = output; + self.errors.extend(errors); + + for (path, source_file) in sources { + let sources = self.sources.as_mut().entry(path).or_default(); + sources.push(VersionedSourceFile { source_file, version: version.clone() }); + } - for (file_name, new_contracts) in output.contracts { + for (file_name, new_contracts) in contracts { let contracts = self.contracts.as_mut().entry(file_name).or_default(); for (contract_name, contract) in new_contracts { let versioned = contracts.entry(contract_name).or_default(); @@ -346,8 +357,8 @@ impl AggregatedCompilerOutput { /// let (sources, contracts) = output.split(); /// # } /// ``` - pub fn split(self) -> (SourceFiles, VersionedContracts) { - (SourceFiles(self.sources), self.contracts) + pub fn split(self) -> (VersionedSourceFiles, VersionedContracts) { + (self.sources, self.contracts) } } diff --git a/ethers-solc/src/compile/output/sources.rs b/ethers-solc/src/compile/output/sources.rs new file mode 100644 index 000000000..811690fd9 --- /dev/null +++ b/ethers-solc/src/compile/output/sources.rs @@ -0,0 +1,254 @@ +use crate::SourceFile; +use semver::Version; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +/// (source_file path -> `SourceFile` + solc version) +#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] +#[serde(transparent)] +pub struct VersionedSourceFiles(pub BTreeMap>); + +impl VersionedSourceFiles { + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn len(&self) -> usize { + self.0.len() + } + + /// Returns an iterator over all files + pub fn files(&self) -> impl Iterator + '_ { + self.0.keys() + } + + /// Returns an iterator over the source files' ids and path + /// + /// ``` + /// use std::collections::BTreeMap; + /// use ethers_solc::sources::VersionedSourceFiles; + /// # fn demo(files: VersionedSourceFiles) { + /// let sources: BTreeMap = files.into_ids().collect(); + /// # } + /// ``` + pub fn into_ids(self) -> impl Iterator { + self.into_sources().map(|(path, source)| (source.id, path)) + } + + /// Returns an iterator over the source files' paths and ids + /// + /// ``` + /// use std::collections::BTreeMap; + /// use ethers_solc::artifacts::SourceFiles; + /// # fn demo(files: SourceFiles) { + /// let sources :BTreeMap = files.into_paths().collect(); + /// # } + /// ``` + pub fn into_paths(self) -> impl Iterator { + self.into_ids().map(|(id, path)| (path, id)) + } + + /// Returns an iterator over the source files' ids and path + /// + /// ``` + /// use std::collections::BTreeMap; + /// use semver::Version; + /// use ethers_solc::sources::VersionedSourceFiles; + /// # fn demo(files: VersionedSourceFiles) { + /// let sources: BTreeMap<(u32, Version) ,String> = files.into_ids_with_version().map(|(id, source, version)|((id, version), source)).collect(); + /// # } + /// ``` + pub fn into_ids_with_version(self) -> impl Iterator { + self.into_sources_with_version().map(|(path, source, version)| (source.id, path, version)) + } + + /// Finds the _first_ source file with the given path + /// + /// # Example + /// + /// ``` + /// use ethers_solc::Project; + /// use ethers_solc::artifacts::*; + /// # fn demo(project: Project) { + /// let output = project.compile().unwrap().output(); + /// let source_file = output.sources.find_file("src/Greeter.sol").unwrap(); + /// # } + /// ``` + pub fn find_file(&self, source_file: impl AsRef) -> Option<&SourceFile> { + let source_file_name = source_file.as_ref(); + self.sources().find_map( + |(path, source_file)| { + if path == source_file_name { + Some(source_file) + } else { + None + } + }, + ) + } + + /// Same as [Self::find_file] but also checks for version + pub fn find_file_and_version(&self, path: &str, version: &Version) -> Option<&SourceFile> { + self.0.get(path).and_then(|contracts| { + contracts.iter().find_map(|source| { + if source.version == *version { + Some(&source.source_file) + } else { + None + } + }) + }) + } + + /// Finds the _first_ source file with the given id + /// + /// # Example + /// + /// ``` + /// use ethers_solc::Project; + /// use ethers_solc::artifacts::*; + /// # fn demo(project: Project) { + /// let output = project.compile().unwrap().output(); + /// let source_file = output.sources.find_id(0).unwrap(); + /// # } + /// ``` + pub fn find_id(&self, id: u32) -> Option<&SourceFile> { + self.sources().filter(|(_, source)| source.id == id).map(|(_, source)| source).next() + } + + /// Same as [Self::find_id] but also checks for version + pub fn find_id_and_version(&self, id: u32, version: &Version) -> Option<&SourceFile> { + self.sources_with_version() + .filter(|(_, source, v)| source.id == id && *v == version) + .map(|(_, source, _)| source) + .next() + } + + /// Removes the _first_ source_file with the given path from the set + /// + /// # Example + /// + /// ``` + /// use ethers_solc::Project; + /// use ethers_solc::artifacts::*; + /// # fn demo(project: Project) { + /// let (mut sources, _) = project.compile().unwrap().output().split(); + /// let source_file = sources.remove_by_path("src/Greeter.sol").unwrap(); + /// # } + /// ``` + pub fn remove_by_path(&mut self, source_file: impl AsRef) -> Option { + let source_file_path = source_file.as_ref(); + self.0.get_mut(source_file_path).and_then(|all_sources| { + if !all_sources.is_empty() { + Some(all_sources.remove(0).source_file) + } else { + None + } + }) + } + + /// Removes the _first_ source_file with the given id from the set + /// + /// # Example + /// + /// ``` + /// use ethers_solc::Project; + /// use ethers_solc::artifacts::*; + /// # fn demo(project: Project) { + /// let (mut sources, _) = project.compile().unwrap().output().split(); + /// let source_file = sources.remove_by_id(0).unwrap(); + /// # } + /// ``` + pub fn remove_by_id(&mut self, id: u32) -> Option { + self.0 + .values_mut() + .filter_map(|sources| { + sources + .iter() + .position(|source| source.source_file.id == id) + .map(|pos| sources.remove(pos).source_file) + }) + .next() + } + + /// Iterate over all contracts and their names + pub fn sources(&self) -> impl Iterator { + self.0.iter().flat_map(|(path, sources)| { + sources.iter().map(move |source| (path, &source.source_file)) + }) + } + + /// Returns an iterator over (`file`, `SourceFile`, `Version`) + pub fn sources_with_version(&self) -> impl Iterator { + self.0.iter().flat_map(|(file, sources)| { + sources.iter().map(move |c| (file, &c.source_file, &c.version)) + }) + } + + /// Returns an iterator over all contracts and their source names. + /// + /// ``` + /// use std::collections::BTreeMap; + /// use ethers_solc::{ artifacts::* }; + /// use ethers_solc::sources::VersionedSourceFiles; + /// # fn demo(sources: VersionedSourceFiles) { + /// let sources: BTreeMap = sources + /// .into_sources() + /// .collect(); + /// # } + /// ``` + pub fn into_sources(self) -> impl Iterator { + self.0.into_iter().flat_map(|(path, sources)| { + sources.into_iter().map(move |source| (path.clone(), source.source_file)) + }) + } + + /// Returns an iterator over all contracts and their source names. + /// + /// ``` + /// use std::collections::BTreeMap; + /// use semver::Version; + /// use ethers_solc::{ artifacts::* }; + /// use ethers_solc::sources::VersionedSourceFiles; + /// # fn demo(sources: VersionedSourceFiles) { + /// let sources: BTreeMap<(String,Version), SourceFile> = sources + /// .into_sources_with_version().map(|(path, source, version)|((path,version), source)) + /// .collect(); + /// # } + /// ``` + pub fn into_sources_with_version(self) -> impl Iterator { + self.0.into_iter().flat_map(|(path, sources)| { + sources + .into_iter() + .map(move |source| (path.clone(), source.source_file, source.version)) + }) + } +} + +impl AsRef>> for VersionedSourceFiles { + fn as_ref(&self) -> &BTreeMap> { + &self.0 + } +} + +impl AsMut>> for VersionedSourceFiles { + fn as_mut(&mut self) -> &mut BTreeMap> { + &mut self.0 + } +} + +impl IntoIterator for VersionedSourceFiles { + type Item = (String, Vec); + type IntoIter = std::collections::btree_map::IntoIter>; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +/// A [SourceFile] and the compiler version used to compile it +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct VersionedSourceFile { + pub source_file: SourceFile, + pub version: Version, +} diff --git a/ethers-solc/src/compile/project.rs b/ethers-solc/src/compile/project.rs index 0a0595c48..22126ca6d 100644 --- a/ethers-solc/src/compile/project.rs +++ b/ethers-solc/src/compile/project.rs @@ -700,7 +700,7 @@ mod tests { let state = state.compile().unwrap(); assert_eq!(state.output.sources.len(), 3); - for (f, source) in &state.output.sources { + for (f, source) in state.output.sources.sources() { if f.ends_with("A.sol") { assert!(source.ast.is_some()); } else { diff --git a/ethers-solc/src/lib.rs b/ethers-solc/src/lib.rs index 56ec5cbe3..ac0e88275 100644 --- a/ethers-solc/src/lib.rs +++ b/ethers-solc/src/lib.rs @@ -35,10 +35,11 @@ pub use filter::{FileFilter, TestFileFilter}; use crate::{ artifacts::Sources, cache::SolFilesCache, - contracts::VersionedContracts, error::{SolcError, SolcIoError}, + sources::VersionedSourceFiles, }; use artifacts::contract::Contract; +use compile::output::contracts::VersionedContracts; use error::Result; use semver::Version; use std::path::{Path, PathBuf}; @@ -750,7 +751,7 @@ impl ArtifactOutput for Project { fn on_output( &self, contracts: &VersionedContracts, - sources: &BTreeMap, + sources: &VersionedSourceFiles, layout: &ProjectPathsConfig, ) -> Result> { self.artifacts_handler().on_output(contracts, sources, layout) @@ -825,7 +826,7 @@ impl ArtifactOutput for Project { fn output_to_artifacts( &self, contracts: &VersionedContracts, - sources: &BTreeMap, + sources: &VersionedSourceFiles, ) -> Artifacts { self.artifacts_handler().output_to_artifacts(contracts, sources) }