Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

fix(solc): purge obsolete cached artifacts #1273

Merged
merged 2 commits into from
May 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 39 additions & 18 deletions ethers-solc/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ impl SolFilesCache {
I: IntoIterator<Item = (&'a Path, V)>,
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| {
if entry.artifacts.is_empty() {
Expand Down Expand Up @@ -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)
where
I: IntoIterator<Item = &'a Version>,
Expand Down Expand Up @@ -874,35 +876,54 @@ impl<'a, T: ArtifactOutput> ArtifactsCache<'a, T> {
// keep only those files that were previously filtered (not dirty, reused)
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
// solidity file to its artifacts
// add the written artifacts to the cache entries, this way we can keep a mapping
// from solidity file to its artifacts
// 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
// 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);
if let Some((entry, versions)) = dirty_source_files.get_mut(file_path) {
entry.insert_artifacts(artifacts.iter().map(|(name, artifacts)| {
let artifacts = artifacts
.iter()
.filter(|artifact| versions.contains(&artifact.version))
.collect::<Vec<_>>();
(name, artifacts)
}));
if let Some((cache_entry, versions)) = dirty_source_files.get_mut(file_path) {
cache_entry.insert_artifacts(written_artifacts.iter().map(
|(name, artifacts)| {
let artifacts = artifacts
.iter()
.filter(|artifact| versions.contains(&artifact.version))
.collect::<Vec<_>>();
(name, artifacts)
},
));
}

// cached artifacts that were overwritten also need to be removed from the
// `cached_artifacts` set
if let Some((f, mut cached)) = cached_artifacts.0.remove_entry(file) {
cached.retain(|name, files| {
if let Some(written_files) = artifacts.get(name) {
files.retain(|f| {
written_files.iter().all(|other| other.version != f.version)
tracing::trace!("checking {} for obsolete cached artifact entries", file);
cached.retain(|name, cached_artifacts| {
if let Some(written_files) = written_artifacts.get(name) {
// 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
});

if !cached.is_empty() {
cached_artifacts.0.insert(f, cached);
}
Expand Down
23 changes: 23 additions & 0 deletions ethers-solc/src/compile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,29 @@ impl 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
/// the provided [`VersionReq`]
pub fn find_matching_installation(
Expand Down
9 changes: 8 additions & 1 deletion ethers-solc/src/project_util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
utils,
utils::tempdir,
Artifact, ArtifactOutput, Artifacts, ConfigurableArtifacts, ConfigurableContractArtifact,
FileFilter, PathStyle, Project, ProjectCompileOutput, ProjectPathsConfig, SolFilesCache,
FileFilter, PathStyle, Project, ProjectCompileOutput, ProjectPathsConfig, SolFilesCache, Solc,
SolcIoError,
};
use fs_extra::{dir, file};
Expand Down Expand Up @@ -66,6 +66,13 @@ impl<T: ArtifactOutput> TempProject<T> {
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> {
&self.inner
}
Expand Down
35 changes: 33 additions & 2 deletions ethers-solc/tests/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1333,7 +1333,7 @@ fn can_compile_model_checker_sample() {

fn remove_solc_if_exists(version: &Version) {
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 => {}
};
}
Expand All @@ -1351,7 +1351,7 @@ async fn can_install_solc_and_compile_version() {
pragma solidity {};
contract Contract {{ }}
"#,
version.to_string()
version
),
)
.unwrap();
Expand Down Expand Up @@ -1380,3 +1380,34 @@ async fn can_install_solc_and_compile_std_json_input_async() {
assert!(!out.has_error());
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);
}