From 26b6472f9bc34b4bfad7518554ae15dcd63f9a9e Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 30 Mar 2022 21:14:29 +0200 Subject: [PATCH] feat(solc): include project paths in cache (#1097) * feat: add project paths struct * feat: add project paths to cache file * chore: bump format version * update cache creation api * feat: add cache condition --- ethers-solc/src/cache.rs | 46 ++++++++++++++++++----- ethers-solc/src/config.rs | 78 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 112 insertions(+), 12 deletions(-) diff --git a/ethers-solc/src/cache.rs b/ethers-solc/src/cache.rs index a7048329..e1f2ed58 100644 --- a/ethers-solc/src/cache.rs +++ b/ethers-solc/src/cache.rs @@ -1,7 +1,7 @@ //! Support for compiling contracts use crate::{ artifacts::Sources, - config::SolcConfig, + config::{ProjectPaths, SolcConfig}, error::{Result, SolcError}, filter::{FilteredSource, FilteredSourceInfo, FilteredSources}, resolver::GraphEdges, @@ -25,7 +25,7 @@ use std::{ /// `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 /// `ethers-solc` -const ETHERS_FORMAT_VERSION: &str = "ethers-rs-sol-cache-2"; +const ETHERS_FORMAT_VERSION: &str = "ethers-rs-sol-cache-3"; /// The file name of the default cache file pub const SOLIDITY_FILES_CACHE_FILENAME: &str = "solidity-files-cache.json"; @@ -35,13 +35,15 @@ pub const SOLIDITY_FILES_CACHE_FILENAME: &str = "solidity-files-cache.json"; pub struct SolFilesCache { #[serde(rename = "_format")] pub format: String, + /// contains all directories used for the project + pub paths: ProjectPaths, pub files: BTreeMap, } impl SolFilesCache { /// Create a new cache instance with the given files - pub fn new(files: BTreeMap) -> Self { - Self { format: ETHERS_FORMAT_VERSION.to_string(), files } + pub fn new(files: BTreeMap, paths: ProjectPaths) -> Self { + Self { format: ETHERS_FORMAT_VERSION.to_string(), files, paths } } pub fn is_empty(&self) -> bool { @@ -348,7 +350,18 @@ impl SolFilesCache { impl Default for SolFilesCache { fn default() -> Self { - SolFilesCache { format: ETHERS_FORMAT_VERSION.to_string(), files: Default::default() } + SolFilesCache { + format: ETHERS_FORMAT_VERSION.to_string(), + files: Default::default(), + paths: Default::default(), + } + } +} + +impl<'a> From<&'a ProjectPathsConfig> for SolFilesCache { + fn from(config: &'a ProjectPathsConfig) -> Self { + let paths = config.paths_relative(); + SolFilesCache::new(Default::default(), paths) } } @@ -741,13 +754,26 @@ pub(crate) enum ArtifactsCache<'a, T: ArtifactOutput> { impl<'a, T: ArtifactOutput> ArtifactsCache<'a, T> { pub fn new(project: &'a Project, edges: GraphEdges) -> Result { + /// returns the [SolFilesCache] to use + fn get_cache(project: &Project) -> SolFilesCache { + // the currently configured paths + let paths = project.paths.paths_relative(); + + if project.cache_path().exists() { + if let Ok(cache) = SolFilesCache::read_joined(&project.paths) { + if cache.paths == paths { + // unchanged project paths + return cache + } + } + } + // new empty cache + SolFilesCache::new(Default::default(), paths) + } + let cache = if project.cached { // read the cache file if it already exists - let mut cache = if project.cache_path().exists() { - SolFilesCache::read_joined(&project.paths).unwrap_or_default() - } else { - SolFilesCache::default() - }; + let mut cache = get_cache(project); cache.remove_missing_files(); diff --git a/ethers-solc/src/config.rs b/ethers-solc/src/config.rs index c7865cd8..6ffda54c 100644 --- a/ethers-solc/src/config.rs +++ b/ethers-solc/src/config.rs @@ -10,14 +10,14 @@ use crate::{ use crate::artifacts::output_selection::ContractOutputSelection; use serde::{Deserialize, Serialize}; use std::{ - collections::HashSet, + collections::{BTreeSet, HashSet}, fmt::{self, Formatter}, fs, path::{Component, Path, PathBuf}, }; /// Where to find all files or where to write them -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProjectPathsConfig { /// Project root pub root: PathBuf, @@ -60,6 +60,25 @@ impl ProjectPathsConfig { Self::dapptools(std::env::current_dir().map_err(|err| SolcError::io(err, "."))?) } + /// Returns a new [ProjectPaths] instance that contains all directories configured for this + /// project + pub fn paths(&self) -> ProjectPaths { + ProjectPaths { + artifacts: self.artifacts.clone(), + sources: self.sources.clone(), + tests: self.tests.clone(), + libraries: self.libraries.iter().cloned().collect(), + } + } + + /// Same as [Self::paths()] but strips the `root` form all paths, + /// [ProjectPaths::strip_prefix_all()] + pub fn paths_relative(&self) -> ProjectPaths { + let mut paths = self.paths(); + paths.strip_prefix_all(&self.root); + paths + } + /// Creates all configured dirs and files pub fn create_all(&self) -> std::result::Result<(), SolcIoError> { if let Some(parent) = self.cache.parent() { @@ -316,6 +335,61 @@ impl fmt::Display for ProjectPathsConfig { } } +/// This is a subset of [ProjectPathsConfig] that contains all relevant folders in the project +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct ProjectPaths { + pub artifacts: PathBuf, + pub sources: PathBuf, + pub tests: PathBuf, + pub libraries: BTreeSet, +} + +impl ProjectPaths { + /// Joins the folders' location with `root` + pub fn join_all(&mut self, root: impl AsRef) -> &mut Self { + let root = root.as_ref(); + self.artifacts = root.join(&self.artifacts); + self.sources = root.join(&self.sources); + self.tests = root.join(&self.tests); + let libraries = std::mem::take(&mut self.libraries); + self.libraries.extend(libraries.into_iter().map(|p| root.join(p))); + self + } + + /// Removes `base` from all folders + pub fn strip_prefix_all(&mut self, base: impl AsRef) -> &mut Self { + let base = base.as_ref(); + + if let Ok(prefix) = self.artifacts.strip_prefix(base) { + self.artifacts = prefix.to_path_buf(); + } + if let Ok(prefix) = self.sources.strip_prefix(base) { + self.sources = prefix.to_path_buf(); + } + if let Ok(prefix) = self.tests.strip_prefix(base) { + self.tests = prefix.to_path_buf(); + } + let libraries = std::mem::take(&mut self.libraries); + self.libraries.extend( + libraries + .into_iter() + .map(|p| p.strip_prefix(base).map(|p| p.to_path_buf()).unwrap_or(p)), + ); + self + } +} + +impl Default for ProjectPaths { + fn default() -> Self { + Self { + artifacts: "out".into(), + sources: "src".into(), + tests: "tests".into(), + libraries: Default::default(), + } + } +} + #[derive(Debug, Clone, Eq, PartialEq)] pub enum PathStyle { HardHat,