Skip to content

Remap target dir #11156

Open
Open
@alepez

Description

@alepez

Problem

I'm using ZFS filesystem, which has an instant snapshot feature very useful for disaster recovery. I use this feature to automatically make snapshots of my projects. The snapshot is applied to the whole ZFS dataset and there is no way to exclude certain directories.

The problem with Rust projects is that they may have many files in the target directory (which is usually inside the project directory). File under target directory change often (e.g. when re-building) and can be effortlessly recreated. So they don't fit well with ZFS automatic snapshotting system.

Similar problem exists if someone wants to always use a different disk or partition for target directories (which are frequently changed, effortlessly recoverable, unlike the source code).

I could override CARGO_TARGET_DIR environment variable, but I would need to remember to do it for every every project.

I could create an alias of cargo which automatically overrides CARGO_TARGET_DIR, but it would also override whatever is set as target directory in the config.toml, which can cause unpredictable issues.

Proposed Solution

I propose to add the environmental variable CARGO_TARGET_DIR_REMAP which let the user remap where target dir should be created.

Example: crate is in /home/user/projects/foo and default target directory is in /home/user/projects/foo/target. Setting CARGO_TARGET_DIR_REMAP=/home/user/projects=/tmp/cargo-build should change the real target directory so it becomes /tmp/cargo-build/foo/target

The syntax is inspired by rustc command line argument --remap-path-prefix

Notes

I've already experimented with a possible implementation. This is a simple working patch.

diff --git a/src/cargo/util/config/mod.rs b/src/cargo/util/config/mod.rs
index fddd47acb..5bc0c94ba 100644
--- a/src/cargo/util/config/mod.rs
+++ b/src/cargo/util/config/mod.rs
@@ -518,6 +518,13 @@ impl Config {
             }
 
             Ok(Some(Filesystem::new(path)))
+        } else if let Some(remap) = self.env.get("CARGO_TARGET_DIR_REMAP") {
+            remap_target_dir(remap, self.cwd.clone())
+                .map(|dir| Some(Filesystem::new(dir)))
+                .map_err(|err| anyhow!(
+                    "the target directory remap is not valid in the \
+                    `CARGO_TARGET_DIR_REMAP` environment variable. reason: {}", err)
+                )
         } else {
             Ok(None)
         }
@@ -2459,3 +2466,45 @@ macro_rules! drop_eprint {
         $crate::__shell_print!($config, err, false, $($arg)*)
     );
 }
+
+fn remap_target_dir(remap: &str, mut path: PathBuf) -> CargoResult<PathBuf> {
+    let (from, to) = remap.split_once('=')
+        .ok_or_else(|| anyhow!("must be a value of the form FROM=TO"))?;
+
+    // Change the path only if it starts with the `from` prefix
+    if path.starts_with(from) {
+        let relative = path.strip_prefix(from)?;
+        path = PathBuf::from(to).join(relative);
+    }
+
+    Ok(path)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_remap_target_dir_start() {
+        let original_path = PathBuf::from("/foo/bar/baz");
+        let remap = String::from("/foo/bar=/new/target/prefix");
+        let remapped = remap_target_dir(&remap, original_path);
+        assert_eq!(PathBuf::from("/new/target/prefix/baz"), remapped.unwrap());
+    }
+
+    #[test]
+    fn test_remap_target_dir_different_start_unchanged() {
+        let original_path = PathBuf::from("/baz/foo/bar/baz");
+        let remap = String::from("/foo/bar=/new/target/prefix");
+        let remapped = remap_target_dir(&remap, original_path);
+        assert_eq!(PathBuf::from("/baz/foo/bar/baz"), remapped.unwrap());
+    }
+
+    #[test]
+    fn test_remap_target_dir_invalid_syntax() {
+        let original_path = PathBuf::from("/baz/foo/bar/baz");
+        let remap = String::from("/new/target/prefix");
+        let remapped = remap_target_dir(&remap, original_path);
+        assert!(remapped.is_err());
+    }
+}
\ No newline at end of file

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-cachingArea: caching of dependencies, repositories, and build artifactsA-configurationArea: cargo config files and env varsA-layoutArea: target output directory layout, naming, and organizationC-feature-requestCategory: proposal for a feature. Before PR, ping rust-lang/cargo if this is not `Feature accepted`S-triageStatus: This issue is waiting on initial triage.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions