feat(solc): store source files with their solc version (#1231)
* feat(solc): add versioned sources * feat(solc): support versioned sources
This commit is contained in:
parent
c7765e1721
commit
44cbbc769a
|
@ -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::{
|
||||
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<String, SourceFile>,
|
||||
sources: &VersionedSourceFiles,
|
||||
layout: &ProjectPathsConfig,
|
||||
) -> Result<Artifacts<Self::Artifact>> {
|
||||
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<String, SourceFile>,
|
||||
sources: &VersionedSourceFiles,
|
||||
) -> Artifacts<Self::Artifact> {
|
||||
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<String, SourceFile>,
|
||||
sources: &VersionedSourceFiles,
|
||||
layout: &ProjectPathsConfig,
|
||||
) -> Result<Artifacts<Self::Artifact>> {
|
||||
MinimalCombinedArtifacts::default().on_output(output, sources, layout)
|
||||
|
|
|
@ -46,10 +46,10 @@ pub type Contracts = FileToContractsMap<Contract>;
|
|||
pub type Sources = BTreeMap<PathBuf, Source>;
|
||||
|
||||
/// A set of different Solc installations with their version and the sources to be compiled
|
||||
pub type VersionedSources = BTreeMap<Solc, (Version, Sources)>;
|
||||
pub(crate) type VersionedSources = BTreeMap<Solc, (Version, Sources)>;
|
||||
|
||||
/// A set of different Solc installations with their version and the sources to be compiled
|
||||
pub type VersionedFilteredSources = BTreeMap<Solc, (Version, FilteredSources)>;
|
||||
pub(crate) type VersionedFilteredSources = BTreeMap<Solc, (Version, FilteredSources)>;
|
||||
|
||||
const SOLIDITY: &str = "Solidity";
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<T: ArtifactOutput> ProjectCompileOutput<T> {
|
|||
}
|
||||
|
||||
/// All artifacts together with their ID and the sources of the project.
|
||||
pub fn into_artifacts_with_sources(self) -> (BTreeMap<ArtifactId, T::Artifact>, SourceFiles) {
|
||||
pub fn into_artifacts_with_sources(
|
||||
self,
|
||||
) -> (BTreeMap<ArtifactId, T::Artifact>, VersionedSourceFiles) {
|
||||
let Self { cached_artifacts, compiled_artifacts, compiler_output, .. } = self;
|
||||
|
||||
(
|
||||
|
@ -77,7 +83,7 @@ impl<T: ArtifactOutput> ProjectCompileOutput<T> {
|
|||
.into_artifacts::<T>()
|
||||
.chain(compiled_artifacts.into_artifacts::<T>())
|
||||
.collect(),
|
||||
SourceFiles(compiler_output.sources),
|
||||
compiler_output.sources,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -228,8 +234,8 @@ impl<T: ArtifactOutput> fmt::Display for ProjectCompileOutput<T> {
|
|||
pub struct AggregatedCompilerOutput {
|
||||
/// all errors from all `CompilerOutput`
|
||||
pub errors: Vec<Error>,
|
||||
/// All source files
|
||||
pub sources: BTreeMap<String, SourceFile>,
|
||||
/// 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 (file_name, new_contracts) in output.contracts {
|
||||
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 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String, Vec<VersionedSourceFile>>);
|
||||
|
||||
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<Item = &String> + '_ {
|
||||
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<u32,String> = files.into_ids().collect();
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn into_ids(self) -> impl Iterator<Item = (u32, String)> {
|
||||
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<String, u32> = files.into_paths().collect();
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn into_paths(self) -> impl Iterator<Item = (String, u32)> {
|
||||
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<Item = (u32, String, Version)> {
|
||||
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<str>) -> 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<str>) -> Option<SourceFile> {
|
||||
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<SourceFile> {
|
||||
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<Item = (&String, &SourceFile)> {
|
||||
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<Item = (&String, &SourceFile, &Version)> {
|
||||
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<String, SourceFile> = sources
|
||||
/// .into_sources()
|
||||
/// .collect();
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn into_sources(self) -> impl Iterator<Item = (String, SourceFile)> {
|
||||
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<Item = (String, SourceFile, Version)> {
|
||||
self.0.into_iter().flat_map(|(path, sources)| {
|
||||
sources
|
||||
.into_iter()
|
||||
.map(move |source| (path.clone(), source.source_file, source.version))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<BTreeMap<String, Vec<VersionedSourceFile>>> for VersionedSourceFiles {
|
||||
fn as_ref(&self) -> &BTreeMap<String, Vec<VersionedSourceFile>> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<BTreeMap<String, Vec<VersionedSourceFile>>> for VersionedSourceFiles {
|
||||
fn as_mut(&mut self) -> &mut BTreeMap<String, Vec<VersionedSourceFile>> {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for VersionedSourceFiles {
|
||||
type Item = (String, Vec<VersionedSourceFile>);
|
||||
type IntoIter = std::collections::btree_map::IntoIter<String, Vec<VersionedSourceFile>>;
|
||||
|
||||
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,
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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<T: ArtifactOutput> ArtifactOutput for Project<T> {
|
|||
fn on_output(
|
||||
&self,
|
||||
contracts: &VersionedContracts,
|
||||
sources: &BTreeMap<String, SourceFile>,
|
||||
sources: &VersionedSourceFiles,
|
||||
layout: &ProjectPathsConfig,
|
||||
) -> Result<Artifacts<Self::Artifact>> {
|
||||
self.artifacts_handler().on_output(contracts, sources, layout)
|
||||
|
@ -825,7 +826,7 @@ impl<T: ArtifactOutput> ArtifactOutput for Project<T> {
|
|||
fn output_to_artifacts(
|
||||
&self,
|
||||
contracts: &VersionedContracts,
|
||||
sources: &BTreeMap<String, SourceFile>,
|
||||
sources: &VersionedSourceFiles,
|
||||
) -> Artifacts<Self::Artifact> {
|
||||
self.artifacts_handler().output_to_artifacts(contracts, sources)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue