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
This commit is contained in:
korboismoe 2021-11-26 16:49:19 +00:00 committed by GitHub
parent ca558b08d6
commit d77068e26a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 105 additions and 10 deletions

View File

@ -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

View File

@ -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<Artifacts: ArtifactOutput> Project<Artifacts> {
}
// 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::<Artifacts>(
sources,
Some(&self.solc_config),
&self.paths.artifacts,
);
let cached_artifacts = if self.paths.artifacts.exists() {
cache.read_artifacts::<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::<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<Artifacts: ArtifactOutput> Project<Artifacts> {
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<T: ArtifactOutput> ProjectCompileOutput<T> {
}
}
pub fn from_compiler_output_and_cache(
compiler_output: CompilerOutput,
cache: BTreeMap<PathBuf, T::Artifact>,
ignored_error_codes: Vec<u64>,
) -> 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<T: ArtifactOutput> fmt::Display for ProjectCompileOutput<T> {
#[cfg(test)]
mod tests {
#[test]
#[cfg(all(feature = "svm", feature = "async"))]
fn test_build_all_versions() {

View File

@ -0,0 +1,5 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.6.6;
contract NewContract {
}

View File

@ -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<Path>, dst: impl AsRef<Path>) -> 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(())
}