From d77068e26a0c3385dcf64c96427378e9c40b30d9 Mon Sep 17 00:00:00 2001 From: korboismoe <82175143+korboismoe@users.noreply.github.com> Date: Fri, 26 Nov 2021 16:49:19 +0000 Subject: [PATCH] fix: pass partial artifact cache to project compiler output (#623) * fix: pass partial artifact cache to project compiler output If the cache contains some artifacts but not all, the project compiler output now contains the cached artifacts in addition to the newly compiled artifacts. * chore: update changelog * fix: remove missing cache files before getting changed files * fix: propagate error if reading cached artifacts fails --- CHANGELOG.md | 3 + ethers-solc/src/lib.rs | 38 +++++++--- .../test-data/cache-sample/NewContract.sol | 5 ++ ethers-solc/tests/project.rs | 69 ++++++++++++++++++- 4 files changed, 105 insertions(+), 10 deletions(-) create mode 100644 ethers-solc/test-data/cache-sample/NewContract.sol diff --git a/CHANGELOG.md b/CHANGELOG.md index 6385b314..29c82cf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ [597](https://github.com/gakonst/ethers-rs/pull/597) - Implement hex display format for `ethers::core::Bytes` [#624](https://github.com/gakonst/ethers-rs/pull/624). +## ethers-solc +- Return cached artifacts from project `compile` when the cache only contains some files + ### Unreleased ### 0.6.0 diff --git a/ethers-solc/src/lib.rs b/ethers-solc/src/lib.rs index d678521b..38644689 100644 --- a/ethers-solc/src/lib.rs +++ b/ethers-solc/src/lib.rs @@ -1,4 +1,4 @@ -#![doc = include_str!("../README.md")] +#![doc = include_str ! ("../README.md")] pub mod artifacts; @@ -8,9 +8,11 @@ use std::collections::btree_map::Entry; pub mod cache; mod compile; + pub use compile::*; mod config; + pub use config::{ AllowedLibPaths, Artifact, ArtifactOutput, MinimalCombinedArtifacts, ProjectPathsConfig, SolcConfig, @@ -22,6 +24,7 @@ use crate::{artifacts::Source, cache::SolFilesCache}; pub mod error; pub mod utils; + use crate::artifacts::Sources; use error::Result; use std::{ @@ -248,22 +251,28 @@ impl Project { } // If there's a cache set, filter to only re-compile the files which were changed - let sources = if self.cached && self.paths.cache.exists() { - let cache = SolFilesCache::read(&self.paths.cache)?; + let (sources, cached_artifacts) = if self.cached && self.paths.cache.exists() { + let mut cache = SolFilesCache::read(&self.paths.cache)?; + cache.remove_missing_files(); let changed_files = cache.get_changed_or_missing_artifacts_files::( sources, Some(&self.solc_config), &self.paths.artifacts, ); + let cached_artifacts = if self.paths.artifacts.exists() { + cache.read_artifacts::(&self.paths.artifacts)? + } else { + BTreeMap::default() + }; // if nothing changed and all artifacts still exist if changed_files.is_empty() { - let artifacts = cache.read_artifacts::(&self.paths.artifacts)?; - return Ok(ProjectCompileOutput::from_unchanged(artifacts)) + return Ok(ProjectCompileOutput::from_unchanged(cached_artifacts)) } - changed_files + // There are changed files and maybe some cached files + (changed_files, cached_artifacts) } else { - sources + (sources, BTreeMap::default()) }; // replace absolute path with source name to make solc happy @@ -303,7 +312,11 @@ impl Project { if !self.no_artifacts { Artifacts::on_output(&output, &self.paths)?; } - Ok(ProjectCompileOutput::from_compiler_output(output, self.ignored_error_codes.clone())) + Ok(ProjectCompileOutput::from_compiler_output_and_cache( + output, + cached_artifacts, + self.ignored_error_codes.clone(), + )) } } @@ -512,6 +525,14 @@ impl ProjectCompileOutput { } } + pub fn from_compiler_output_and_cache( + compiler_output: CompilerOutput, + cache: BTreeMap, + ignored_error_codes: Vec, + ) -> Self { + Self { compiler_output: Some(compiler_output), artifacts: cache, ignored_error_codes } + } + /// Get the (merged) solc compiler output /// ```no_run /// use std::collections::BTreeMap; @@ -648,7 +669,6 @@ impl fmt::Display for ProjectCompileOutput { #[cfg(test)] mod tests { - #[test] #[cfg(all(feature = "svm", feature = "async"))] fn test_build_all_versions() { diff --git a/ethers-solc/test-data/cache-sample/NewContract.sol b/ethers-solc/test-data/cache-sample/NewContract.sol new file mode 100644 index 00000000..8c5031e8 --- /dev/null +++ b/ethers-solc/test-data/cache-sample/NewContract.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity >=0.6.6; + +contract NewContract { +} diff --git a/ethers-solc/tests/project.rs b/ethers-solc/tests/project.rs index b5d90b7b..6caebe3e 100644 --- a/ethers-solc/tests/project.rs +++ b/ethers-solc/tests/project.rs @@ -1,7 +1,10 @@ //! project tests use ethers_solc::{cache::SOLIDITY_FILES_CACHE_FILENAME, Project, ProjectPathsConfig}; -use std::path::PathBuf; +use std::{ + io, + path::{Path, PathBuf}, +}; use tempdir::TempDir; #[test] @@ -75,3 +78,67 @@ fn can_compile_dapp_sample() { assert!(compiled.find("Dapp").is_some()); assert!(!compiled.is_unchanged()); } + +#[test] +fn can_compile_dapp_sample_with_cache() { + let tmp_dir = TempDir::new("root").unwrap(); + let root = tmp_dir.path(); + let cache = root.join("cache").join(SOLIDITY_FILES_CACHE_FILENAME); + let artifacts = root.join("out"); + + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let orig_root = manifest_dir.join("test-data/dapp-sample"); + let new_file = manifest_dir.join("test-data/cache-sample/NewContract.sol"); + copy_dir_all(orig_root, &tmp_dir).unwrap(); + let paths = ProjectPathsConfig::builder() + .cache(cache) + .sources(root.join("src")) + .artifacts(artifacts) + .lib(root.join("lib")) + .root(root) + .build() + .unwrap(); + + // first compile + let project = Project::builder().paths(paths).build().unwrap(); + let compiled = project.compile().unwrap(); + assert!(compiled.find("Dapp").is_some()); + assert!(!compiled.has_compiler_errors()); + + // cache is used when nothing to compile + let compiled = project.compile().unwrap(); + assert!(compiled.find("Dapp").is_some()); + assert!(compiled.is_unchanged()); + + // deleted artifacts cause recompile even with cache + std::fs::remove_dir_all(&project.paths.artifacts).unwrap(); + let compiled = project.compile().unwrap(); + assert!(compiled.find("Dapp").is_some()); + assert!(!compiled.is_unchanged()); + + // new file is compiled even with partial cache + std::fs::copy(new_file, root.join("src/NewContract.sol")).unwrap(); + let compiled = project.compile().unwrap(); + assert!(compiled.find("Dapp").is_some()); + assert!(compiled.find("NewContract").is_some()); + assert!(!compiled.is_unchanged()); + + // deleted artifact is not taken from the cache + std::fs::remove_file(&project.paths.sources.join("Dapp.sol")).unwrap(); + let compiled = project.compile().unwrap(); + assert!(compiled.find("Dapp").is_none()); +} + +fn copy_dir_all(src: impl AsRef, dst: impl AsRef) -> io::Result<()> { + std::fs::create_dir_all(&dst)?; + for entry in std::fs::read_dir(src)? { + let entry = entry?; + let ty = entry.file_type()?; + if ty.is_dir() { + copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?; + } else { + std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?; + } + } + Ok(()) +}