Repository: gakonst/ethers-rs
Pull request #1307 (2022-05-24 14:29:38)

feat(solc): make cache entries relative to root dir
<!--
Thank you for your Pull Request. Please provide a description above and review
the requirements below.

Bug fixes and new features should include tests.

Contributors guide: https://github.com/gakonst/ethers-rs/blob/master/CONTRIBUTING.md

The contributors guide includes instructions for running rustfmt and building the
documentation.
-->

## Motivation
store all paths relative in the cache file
* cache entry objects relative to root, (can be in src, lib, etc..)
* artifacts relative to artifacts output dir
<!--
Explain the context and why you're making that change. What is the problem
you're trying to solve? In some cases there is not a problem and this can be
thought of as being the motivation for your change.
-->

## Solution
strip paths before saving and join paths before loading
<!--
Summarize the solution and provide any necessary context needed to understand
the code change.
-->

## PR Checklist

- [x] Added Tests
- [x] Added Documentation
- [x] Updated the changelog



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 28a1c459..63ff4482 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -95,6 +95,8 @@
 
 ### Unreleased
 
+- Save cache entry objects with relative paths
+  [#1307](https://github.com/gakonst/ethers-rs/pull/1307)
 - Bundle svm, svm-builds and sha2 dependencies in new `svm-solc` feature
   [#1071](https://github.com/gakonst/ethers-rs/pull/1071)
 - Emit artifact files for source files without any ContractDefinition
diff --git a/ethers-solc/src/cache.rs b/ethers-solc/src/cache.rs
index f9af00e3..087c38de 100644
--- a/ethers-solc/src/cache.rs
+++ b/ethers-solc/src/cache.rs
@@ -104,7 +104,12 @@ impl SolFilesCache {
         Ok(cache)
     }
 
-    /// Reads the cache json file from the given path and returns the cache with modified paths
+    /// Reads the cache json file from the given path and returns the cache with paths adjoined to
+    /// the `ProjectPathsConfig`.
+    ///
+    /// This expects the `artifact` files to be relative to the artifacts dir of the `paths` and the
+    /// `CachEntry` paths to be relative to the root dir of the `paths`
+    ///
     ///
     ///
     /// # Example
@@ -120,7 +125,7 @@ impl SolFilesCache {
     /// ```
     pub fn read_joined(paths: &ProjectPathsConfig) -> Result<Self> {
         let mut cache = SolFilesCache::read(&paths.cache)?;
-        cache.join_artifacts_files(&paths.artifacts);
+        cache.join_entries(&paths.root).join_artifacts_files(&paths.artifacts);
         Ok(cache)
     }
 
@@ -139,6 +144,26 @@ impl SolFilesCache {
         Ok(())
     }
 
+    /// Sets the `CacheEntry`'s file paths to `root` adjoined to `self.file`.
+    pub fn join_entries(&mut self, root: impl AsRef<Path>) -> &mut Self {
+        let root = root.as_ref();
+        self.files = std::mem::take(&mut self.files)
+            .into_iter()
+            .map(|(path, entry)| (root.join(path), entry))
+            .collect();
+        self
+    }
+
+    /// Removes `base` from all `CacheEntry` paths
+    pub fn strip_entries_prefix(&mut self, base: impl AsRef<Path>) -> &mut Self {
+        let base = base.as_ref();
+        self.files = std::mem::take(&mut self.files)
+            .into_iter()
+            .map(|(path, entry)| (path.strip_prefix(base).map(Into::into).unwrap_or(path), entry))
+            .collect();
+        self
+    }
+
     /// Sets the artifact files location to `base` adjoined to the `CachEntries` artifacts.
     pub fn join_artifacts_files(&mut self, base: impl AsRef<Path>) -> &mut Self {
         let base = base.as_ref();
@@ -182,7 +207,7 @@ impl SolFilesCache {
     /// # Example
     ///
     /// ```
-    /// fn t() {
+    /// # fn t() {
     /// use ethers_solc::artifacts::contract::CompactContract;
     /// use ethers_solc::cache::SolFilesCache;
     /// use ethers_solc::Project;
@@ -934,10 +959,13 @@ impl<'a, T: ArtifactOutput> ArtifactsCache<'a, T> {
                 cache
                     .extend(dirty_source_files.into_iter().map(|(file, (entry, _))| (file, entry)));
 
-                cache.strip_artifact_files_prefixes(project.artifacts_path());
-
                 // write to disk
                 if write_to_disk {
+                    // make all `CacheEntry` paths relative to the project root and all artifact
+                    // paths relative to the artifact's directory
+                    cache
+                        .strip_entries_prefix(project.root())
+                        .strip_artifact_files_prefixes(project.artifacts_path());
                     cache.write(project.cache_path())?;
                 }
 


Test patch:
diff --git a/ethers-solc/tests/project.rs b/ethers-solc/tests/project.rs
index 3a3ee65b..8da70148 100644
--- a/ethers-solc/tests/project.rs
+++ b/ethers-solc/tests/project.rs
@@ -1708,3 +1708,65 @@ fn can_parse_notice() {
         })
     );
 }
+
+#[test]
+fn test_relative_cache_entries() {
+    let project = TempProject::dapptools().unwrap();
+    let _a = project
+        .add_source(
+            "A",
+            r#"
+pragma solidity ^0.8.10;
+contract A { }
+"#,
+        )
+        .unwrap();
+    let _b = project
+        .add_source(
+            "B",
+            r#"
+pragma solidity ^0.8.10;
+contract B { }
+"#,
+        )
+        .unwrap();
+    let _c = project
+        .add_source(
+            "C",
+            r#"
+pragma solidity ^0.8.10;
+contract C { }
+"#,
+        )
+        .unwrap();
+    let _d = project
+        .add_source(
+            "D",
+            r#"
+pragma solidity ^0.8.10;
+contract D { }
+"#,
+        )
+        .unwrap();
+
+    let compiled = project.compile().unwrap();
+    println!("{}", compiled);
+    assert!(!compiled.has_compiler_errors());
+
+    let cache = SolFilesCache::read(project.cache_path()).unwrap();
+
+    let entries = vec![
+        PathBuf::from("src/A.sol"),
+        PathBuf::from("src/B.sol"),
+        PathBuf::from("src/C.sol"),
+        PathBuf::from("src/D.sol"),
+    ];
+    assert_eq!(entries, cache.files.keys().cloned().collect::<Vec<_>>());
+
+    let cache = SolFilesCache::read_joined(project.paths()).unwrap();
+
+    assert_eq!(
+        entries.into_iter().map(|p| project.root().join(p)).collect::<Vec<_>>(),
+        cache.files.keys().cloned().collect::<Vec<_>>()
+    );
+}