fix(solc): purge obsolete cached artifacts (#1273)

* fix(solc): purge obsolete cached artifacts

* fix add cfg
This commit is contained in:
Matthias Seitz 2022-05-17 01:48:47 +02:00 committed by GitHub
parent 75ba3f4a9a
commit 1271308e06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 103 additions and 21 deletions

View File

@ -294,7 +294,7 @@ impl SolFilesCache {
I: IntoIterator<Item = (&'a Path, V)>, I: IntoIterator<Item = (&'a Path, V)>,
V: IntoIterator<Item = &'a Version>, V: IntoIterator<Item = &'a Version>,
{ {
let mut files: HashMap<_, _> = files.into_iter().map(|(p, v)| (p, v)).collect(); let mut files: HashMap<_, _> = files.into_iter().collect();
self.files.retain(|file, entry| { self.files.retain(|file, entry| {
if entry.artifacts.is_empty() { if entry.artifacts.is_empty() {
@ -474,7 +474,9 @@ impl CacheEntry {
} }
} }
/// Retains only those artifacts that match the provided version. /// Retains only those artifacts that match the provided versions.
///
/// Removes an artifact entry if none of its versions is included in the `versions` set.
pub fn retain_versions<'a, I>(&mut self, versions: I) pub fn retain_versions<'a, I>(&mut self, versions: I)
where where
I: IntoIterator<Item = &'a Version>, I: IntoIterator<Item = &'a Version>,
@ -874,35 +876,54 @@ impl<'a, T: ArtifactOutput> ArtifactsCache<'a, T> {
// keep only those files that were previously filtered (not dirty, reused) // keep only those files that were previously filtered (not dirty, reused)
cache.retain(filtered.iter().map(|(p, (_, v))| (p.as_path(), v))); cache.retain(filtered.iter().map(|(p, (_, v))| (p.as_path(), v)));
// add the artifacts to the cache entries, this way we can keep a mapping from // add the written artifacts to the cache entries, this way we can keep a mapping
// solidity file to its artifacts // from solidity file to its artifacts
// this step is necessary because the concrete artifacts are only known after solc // this step is necessary because the concrete artifacts are only known after solc
// was invoked and received as output, before that we merely know the file and // was invoked and received as output, before that we merely know the file and
// the versions, so we add the artifacts on a file by file basis // the versions, so we add the artifacts on a file by file basis
for (file, artifacts) in written_artifacts.as_ref() { for (file, written_artifacts) in written_artifacts.as_ref() {
let file_path = Path::new(&file); let file_path = Path::new(&file);
if let Some((entry, versions)) = dirty_source_files.get_mut(file_path) { if let Some((cache_entry, versions)) = dirty_source_files.get_mut(file_path) {
entry.insert_artifacts(artifacts.iter().map(|(name, artifacts)| { cache_entry.insert_artifacts(written_artifacts.iter().map(
let artifacts = artifacts |(name, artifacts)| {
.iter() let artifacts = artifacts
.filter(|artifact| versions.contains(&artifact.version)) .iter()
.collect::<Vec<_>>(); .filter(|artifact| versions.contains(&artifact.version))
(name, artifacts) .collect::<Vec<_>>();
})); (name, artifacts)
},
));
} }
// cached artifacts that were overwritten also need to be removed from the // cached artifacts that were overwritten also need to be removed from the
// `cached_artifacts` set // `cached_artifacts` set
if let Some((f, mut cached)) = cached_artifacts.0.remove_entry(file) { if let Some((f, mut cached)) = cached_artifacts.0.remove_entry(file) {
cached.retain(|name, files| { tracing::trace!("checking {} for obsolete cached artifact entries", file);
if let Some(written_files) = artifacts.get(name) { cached.retain(|name, cached_artifacts| {
files.retain(|f| { if let Some(written_files) = written_artifacts.get(name) {
written_files.iter().all(|other| other.version != f.version) // written artifact clashes with a cached artifact, so we need to decide whether to keep or to remove the cached
cached_artifacts.retain(|f| {
// we only keep those artifacts that don't conflict with written artifacts and which version was a compiler target
let retain = written_files
.iter()
.all(|other| other.version != f.version) && filtered.get(
&PathBuf::from(file)).map(|(_, versions)| {
versions.contains(&f.version)
}).unwrap_or_default();
if !retain {
tracing::trace!(
"purging obsolete cached artifact for contract {} and version {}",
name,
f.version
);
}
retain
}); });
return !files.is_empty() return !cached_artifacts.is_empty()
} }
false false
}); });
if !cached.is_empty() { if !cached.is_empty() {
cached_artifacts.0.insert(f, cached); cached_artifacts.0.insert(f, cached);
} }

View File

@ -269,6 +269,29 @@ impl Solc {
Ok(Some(Solc::new(solc))) Ok(Some(Solc::new(solc)))
} }
/// Returns the path for a [svm](https://github.com/roynalnaruto/svm-rs) installed version.
///
/// If the version is not installed yet, it will install it.
///
/// # Example
/// ```no_run
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// use ethers_solc::Solc;
/// let solc = Solc::find_or_install_svm_version("0.8.9").unwrap();
/// assert_eq!(solc, Solc::new("~/.svm/0.8.9/solc-0.8.9"));
/// # Ok(())
/// # }
/// ```
#[cfg(all(not(target_arch = "wasm32"), all(feature = "svm-solc")))]
pub fn find_or_install_svm_version(version: impl AsRef<str>) -> Result<Self> {
let version = version.as_ref();
if let Some(solc) = Solc::find_svm_installed_version(version)? {
Ok(solc)
} else {
Ok(Solc::blocking_install(&version.parse::<Version>()?)?)
}
}
/// Assuming the `versions` array is sorted, it returns the first element which satisfies /// Assuming the `versions` array is sorted, it returns the first element which satisfies
/// the provided [`VersionReq`] /// the provided [`VersionReq`]
pub fn find_matching_installation( pub fn find_matching_installation(

View File

@ -9,7 +9,7 @@ use crate::{
utils, utils,
utils::tempdir, utils::tempdir,
Artifact, ArtifactOutput, Artifacts, ConfigurableArtifacts, ConfigurableContractArtifact, Artifact, ArtifactOutput, Artifacts, ConfigurableArtifacts, ConfigurableContractArtifact,
FileFilter, PathStyle, Project, ProjectCompileOutput, ProjectPathsConfig, SolFilesCache, FileFilter, PathStyle, Project, ProjectCompileOutput, ProjectPathsConfig, SolFilesCache, Solc,
SolcIoError, SolcIoError,
}; };
use fs_extra::{dir, file}; use fs_extra::{dir, file};
@ -66,6 +66,13 @@ impl<T: ArtifactOutput> TempProject<T> {
self self
} }
/// Explicitly sets the solc version for the project
pub fn set_solc(&mut self, solc: impl AsRef<str>) -> &mut Self {
self.inner.solc = Solc::find_or_install_svm_version(solc).unwrap();
self.inner.auto_detect = false;
self
}
pub fn project(&self) -> &Project<T> { pub fn project(&self) -> &Project<T> {
&self.inner &self.inner
} }

View File

@ -1333,7 +1333,7 @@ fn can_compile_model_checker_sample() {
fn remove_solc_if_exists(version: &Version) { fn remove_solc_if_exists(version: &Version) {
match Solc::find_svm_installed_version(version.to_string()).unwrap() { match Solc::find_svm_installed_version(version.to_string()).unwrap() {
Some(_) => svm::remove_version(&version).expect("failed to remove version"), Some(_) => svm::remove_version(version).expect("failed to remove version"),
None => {} None => {}
}; };
} }
@ -1351,7 +1351,7 @@ async fn can_install_solc_and_compile_version() {
pragma solidity {}; pragma solidity {};
contract Contract {{ }} contract Contract {{ }}
"#, "#,
version.to_string() version
), ),
) )
.unwrap(); .unwrap();
@ -1380,3 +1380,34 @@ async fn can_install_solc_and_compile_std_json_input_async() {
assert!(!out.has_error()); assert!(!out.has_error());
assert!(out.sources.contains_key("lib/ds-test/src/test.sol")); assert!(out.sources.contains_key("lib/ds-test/src/test.sol"));
} }
#[test]
fn can_purge_obsolete_artifacts() {
let mut project = TempProject::<ConfigurableArtifacts>::dapptools().unwrap();
project.set_solc("0.8.10");
project
.add_source(
"Contract",
r#"
pragma solidity >=0.8.10;
contract Contract {
function xyz() public {
}
}
"#,
)
.unwrap();
let compiled = project.compile().unwrap();
assert!(!compiled.has_compiler_errors());
assert!(!compiled.is_unchanged());
assert_eq!(compiled.into_artifacts().count(), 1);
project.set_solc("0.8.13");
let compiled = project.compile().unwrap();
assert!(!compiled.has_compiler_errors());
assert!(!compiled.is_unchanged());
assert_eq!(compiled.into_artifacts().count(), 1);
}