Description
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