feat(solc): introduce compilation unit for files that share solc ver. and config

This commit is contained in:
alpharush 2022-12-21 13:51:57 -06:00
parent 228f9607fe
commit 6812d11b81
3 changed files with 107 additions and 16 deletions

View File

@ -9,6 +9,7 @@ use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
use std::{ use std::{
collections::{BTreeMap, HashSet}, collections::{BTreeMap, HashSet},
fmt, fs, fmt, fs,
hash::{Hash, Hasher},
path::{Path, PathBuf}, path::{Path, PathBuf},
str::FromStr, str::FromStr,
}; };
@ -497,6 +498,24 @@ impl Default for Settings {
} }
} }
impl Hash for Settings {
fn hash<H: Hasher>(&self, state: &mut H) {
self.optimizer.enabled.hash(state);
self.optimizer.runs.hash(state);
if let Some(SettingsMetadata { bytecode_hash, cbor_metadata, .. }) = self.metadata {
(bytecode_hash.unwrap_or_default() as u8).hash(state);
cbor_metadata.hash(state);
}
self.output_selection.0.keys().cloned().collect::<String>().hash(state);
(self.evm_version.unwrap_or_default() as u8).hash(state);
self.via_ir.hash(state);
if let Some(DebuggingSettings { revert_strings, debug_info, .. }) = &self.debug {
(revert_strings.unwrap_or_default() as u8).hash(state);
debug_info.hash(state);
}
}
}
/// A wrapper type for all libraries in the form of `<file>:<lib>:<addr>` /// A wrapper type for all libraries in the form of `<file>:<lib>:<addr>`
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(transparent)] #[serde(transparent)]

View File

@ -16,6 +16,7 @@ use std::{
hash_map, BTreeSet, HashMap, HashSet, hash_map, BTreeSet, HashMap, HashSet,
}, },
fs::{self}, fs::{self},
hash::{Hash, Hasher},
path::{Path, PathBuf}, path::{Path, PathBuf},
time::{Duration, UNIX_EPOCH}, time::{Duration, UNIX_EPOCH},
}; };
@ -25,11 +26,34 @@ use std::{
/// `ethers-solc` uses a different format version id, but the actual format is consistent with /// `ethers-solc` uses a different format version id, but the actual format is consistent with
/// hardhat This allows ethers-solc to detect if the cache file was written by hardhat or /// hardhat This allows ethers-solc to detect if the cache file was written by hardhat or
/// `ethers-solc` /// `ethers-solc`
const ETHERS_FORMAT_VERSION: &str = "ethers-rs-sol-cache-3"; const ETHERS_FORMAT_VERSION: &str = "ethers-rs-sol-cache-4";
/// The file name of the default cache file /// The file name of the default cache file
pub const SOLIDITY_FILES_CACHE_FILENAME: &str = "solidity-files-cache.json"; pub const SOLIDITY_FILES_CACHE_FILENAME: &str = "solidity-files-cache.json";
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
pub struct CompilationUnitId(String);
impl CompilationUnitId {
/// Create a unique id for each compilation unit based on compiler version and settings
pub fn new(version: Version, solc_config: SolcConfig) -> Self {
let mut hasher = hash_map::DefaultHasher::new();
version.hash(&mut hasher);
solc_config.hash(&mut hasher);
let result = hasher.finish();
Self(format!("{:x}", result))
}
}
/// A unit of source files that are compiled together with the same compiler version and settings
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CompilationUnit {
pub solc_config: SolcConfig,
pub version: Version,
pub source_units: Vec<PathBuf>,
}
/// A multi version cache file /// A multi version cache file
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct SolFilesCache { pub struct SolFilesCache {
@ -38,12 +62,18 @@ pub struct SolFilesCache {
/// contains all directories used for the project /// contains all directories used for the project
pub paths: ProjectPaths, pub paths: ProjectPaths,
pub files: BTreeMap<PathBuf, CacheEntry>, pub files: BTreeMap<PathBuf, CacheEntry>,
#[serde(rename = "compilationUnits")]
pub compilation_units: HashMap<CompilationUnitId, CompilationUnit>,
} }
impl SolFilesCache { impl SolFilesCache {
/// Create a new cache instance with the given files /// Create a new cache instance with the given files
pub fn new(files: BTreeMap<PathBuf, CacheEntry>, paths: ProjectPaths) -> Self { pub fn new(
Self { format: ETHERS_FORMAT_VERSION.to_string(), files, paths } files: BTreeMap<PathBuf, CacheEntry>,
paths: ProjectPaths,
compilation_units: HashMap<CompilationUnitId, CompilationUnit>,
) -> Self {
Self { format: ETHERS_FORMAT_VERSION.to_string(), files, paths, compilation_units }
} }
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
@ -386,6 +416,7 @@ impl Default for SolFilesCache {
format: ETHERS_FORMAT_VERSION.to_string(), format: ETHERS_FORMAT_VERSION.to_string(),
files: Default::default(), files: Default::default(),
paths: Default::default(), paths: Default::default(),
compilation_units: Default::default(),
} }
} }
} }
@ -393,7 +424,7 @@ impl Default for SolFilesCache {
impl<'a> From<&'a ProjectPathsConfig> for SolFilesCache { impl<'a> From<&'a ProjectPathsConfig> for SolFilesCache {
fn from(config: &'a ProjectPathsConfig) -> Self { fn from(config: &'a ProjectPathsConfig) -> Self {
let paths = config.paths_relative(); let paths = config.paths_relative();
SolFilesCache::new(Default::default(), paths) SolFilesCache::new(Default::default(), paths, Default::default())
} }
} }
@ -411,8 +442,8 @@ pub struct CacheEntry {
pub content_hash: String, pub content_hash: String,
/// identifier name see [`crate::utils::source_name()`] /// identifier name see [`crate::utils::source_name()`]
pub source_name: PathBuf, pub source_name: PathBuf,
/// what config was set when compiling this file /// what compilation unit this file was a part of
pub solc_config: SolcConfig, pub compilation_unit: CompilationUnitId,
/// fully resolved imports of the file /// fully resolved imports of the file
/// ///
/// all paths start relative from the project's root: `src/importedFile.sol` /// all paths start relative from the project's root: `src/importedFile.sol`
@ -605,20 +636,19 @@ pub(crate) struct ArtifactsCacheInner<'a, T: ArtifactOutput> {
impl<'a, T: ArtifactOutput> ArtifactsCacheInner<'a, T> { impl<'a, T: ArtifactOutput> ArtifactsCacheInner<'a, T> {
/// Creates a new cache entry for the file /// Creates a new cache entry for the file
fn create_cache_entry(&self, file: &Path, source: &Source) -> CacheEntry { fn create_cache_entry(&self, file: &Path, source: &Source, version: Version) -> CacheEntry {
let imports = self let imports = self
.edges .edges
.imports(file) .imports(file)
.into_iter() .into_iter()
.map(|import| utils::source_name(import, self.project.root()).to_path_buf()) .map(|import| utils::source_name(import, self.project.root()).to_path_buf())
.collect(); .collect();
let entry = CacheEntry { let entry = CacheEntry {
last_modification_date: CacheEntry::read_last_modification_date(file) last_modification_date: CacheEntry::read_last_modification_date(file)
.unwrap_or_default(), .unwrap_or_default(),
content_hash: source.content_hash(), content_hash: source.content_hash(),
source_name: utils::source_name(file, self.project.root()).into(), source_name: utils::source_name(file, self.project.root()).into(),
solc_config: self.project.solc_config.clone(), compilation_unit: CompilationUnitId::new(version, self.project.solc_config.clone()),
imports, imports,
version_requirement: self.edges.version_requirement(file).map(|v| v.to_string()), version_requirement: self.edges.version_requirement(file).map(|v| v.to_string()),
// artifacts remain empty until we received the compiler output // artifacts remain empty until we received the compiler output
@ -628,15 +658,41 @@ impl<'a, T: ArtifactOutput> ArtifactsCacheInner<'a, T> {
entry entry
} }
/// insert a new compilation unit into the cache
///
/// If there is already a compilation unit for a given version and solc config, the file is
/// added to the unit
fn insert_compilation_unit(
&mut self,
file: PathBuf,
solc_config: SolcConfig,
version: Version,
) {
let id = CompilationUnitId::new(version.clone(), solc_config.clone());
if let Some(CompilationUnit { source_units, .. }) =
self.cache.compilation_units.get_mut(&id)
{
source_units.push(file);
} else {
self.cache.compilation_units.insert(
id,
CompilationUnit { solc_config, version, source_units: vec![file.to_path_buf()] },
);
}
}
/// inserts a new cache entry for the given file /// inserts a new cache entry for the given file
/// ///
/// If there is already an entry available for the file the given version is added to the set /// If there is already an entry available for the file the given version is added to the set
fn insert_new_cache_entry(&mut self, file: &Path, source: &Source, version: Version) { fn insert_new_cache_entry(&mut self, file: &Path, source: &Source, version: Version) {
if let Some((_, versions)) = self.dirty_source_files.get_mut(file) { if let Some((_, versions)) = self.dirty_source_files.get_mut(file) {
versions.insert(version); versions.insert(version.clone());
} else { } else {
let entry = self.create_cache_entry(file, source); let entry = self.create_cache_entry(file, source, version.clone());
self.dirty_source_files.insert(file.to_path_buf(), (entry, HashSet::from([version]))); let path = file.to_path_buf();
self.dirty_source_files
.insert(path.clone(), (entry.clone(), HashSet::from([version.clone()])));
self.insert_compilation_unit(path, self.project.solc_config.clone(), version);
} }
} }
@ -733,10 +789,19 @@ impl<'a, T: ArtifactOutput> ArtifactsCacheInner<'a, T> {
tracing::trace!("changed content hash for source file \"{}\"", file.display()); tracing::trace!("changed content hash for source file \"{}\"", file.display());
return true return true
} }
if self.project.solc_config != entry.solc_config { if let Some(compilation_unit) = self
tracing::trace!("changed solc config for source file \"{}\"", file.display()); .cache
.compilation_units
.get(&CompilationUnitId::new(version.clone(), self.project.solc_config.clone()))
{
if self.project.solc_config != compilation_unit.solc_config {
tracing::trace!(
"changed solc config for source file \"{}\"",
file.display()
);
return true return true
} }
}
// only check artifact's existence if the file generated artifacts. // only check artifact's existence if the file generated artifacts.
// e.g. a solidity file consisting only of import statements (like interfaces that // e.g. a solidity file consisting only of import statements (like interfaces that
@ -813,7 +878,7 @@ impl<'a, T: ArtifactOutput> ArtifactsCache<'a, T> {
} }
// new empty cache // new empty cache
SolFilesCache::new(Default::default(), paths) SolFilesCache::new(Default::default(), paths, Default::default())
} }
let cache = if project.cached { let cache = if project.cached {

View File

@ -11,6 +11,7 @@ use std::{
collections::{BTreeSet, HashSet}, collections::{BTreeSet, HashSet},
fmt::{self, Formatter}, fmt::{self, Formatter},
fs, fs,
hash::{Hash, Hasher},
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
path::{Component, Path, PathBuf}, path::{Component, Path, PathBuf},
}; };
@ -745,6 +746,12 @@ impl From<SolcConfig> for Settings {
} }
} }
impl Hash for SolcConfig {
fn hash<H: Hasher>(&self, state: &mut H) {
self.settings.hash(state);
}
}
#[derive(Default)] #[derive(Default)]
pub struct SolcConfigBuilder { pub struct SolcConfigBuilder {
settings: Option<Settings>, settings: Option<Settings>,