dev_prune/adapters/
cargo_adapter.rs1use super::{BloatDir, EnforcePolicy, PackageManager, dir_size, enforce_two_tier};
15use anyhow::Result;
16use std::path::{Path, PathBuf};
17
18pub struct Cargo;
20
21fn workspace_lockfile(path: &Path) -> Option<PathBuf> {
29 let mut dir = path;
30 loop {
31 let candidate = dir.join("Cargo.lock");
32 if candidate.exists() {
33 return Some(candidate);
34 }
35 if dir.join(".git").exists() {
37 return None;
38 }
39 dir = dir.parent()?;
40 }
41}
42
43impl PackageManager for Cargo {
44 fn name(&self) -> &'static str {
45 "cargo"
46 }
47
48 fn detect(&self, path: &Path) -> bool {
49 path.join("Cargo.toml").exists()
50 }
51
52 fn bloat_dirs(&self, path: &Path) -> Vec<BloatDir> {
53 let mut dirs = Vec::new();
54 let target_path = path.join("target");
55 if target_path.exists() {
56 dirs.push(BloatDir {
57 name: "target".to_string(),
58 path: target_path.clone(),
59 size_bytes: dir_size(&target_path),
60 shared_bytes: 0,
61 });
62 }
63 dirs
64 }
65
66 fn enforce_lockfile(&self, path: &Path, policy: EnforcePolicy) -> Result<()> {
74 if path.join("target").join("criterion").is_dir() {
79 crate::output::print_warning(&format!(
80 "{}: `target/criterion` holds benchmark history that a rebuild does not \
81 bring back — copy it first if the baselines matter.",
82 crate::output::clean_path(path)
83 ));
84 }
85 let lockfile = workspace_lockfile(path).unwrap_or_else(|| path.join("Cargo.lock"));
86 enforce_two_tier(
87 &lockfile,
88 "cargo",
89 &["metadata", "--locked", "--format-version", "1"],
90 &["generate-lockfile"],
91 path,
92 policy,
93 )
94 }
95
96 fn restore(&self, _path: &Path, _timeout: std::time::Duration) -> Result<()> {
97 println!("Rust target/ will regenerate on next cargo build");
98 Ok(())
99 }
100
101 fn lockfiles(&self) -> &'static [&'static str] {
102 &["Cargo.lock"]
103 }
104
105 fn opt_in(&self) -> bool {
106 true
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113 use std::fs;
114 use std::fs::File;
115 use tempfile::tempdir;
116
117 #[test]
118 fn cargo_is_opt_in() {
119 assert!(Cargo.opt_in());
122 }
123
124 #[test]
125 fn test_name() {
126 let adapter = Cargo;
127 assert_eq!(adapter.name(), "cargo");
128 }
129
130 #[test]
137 fn a_default_pass_never_rewrites_a_stale_lockfile() {
138 if !super::super::binary_available("cargo") {
139 return;
140 }
141 let dir = tempdir().unwrap();
142 fs::write(
143 dir.path().join("Cargo.toml"),
144 "[package]\nname = \"stale\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n\
145 [dependencies]\nserde = \"1\"\n",
146 )
147 .unwrap();
148 fs::create_dir(dir.path().join("src")).unwrap();
149 fs::write(dir.path().join("src").join("lib.rs"), "").unwrap();
150
151 let stale = "version = 3\n\n[[package]]\nname = \"stale\"\nversion = \"0.1.0\"\n";
154 fs::write(dir.path().join("Cargo.lock"), stale).unwrap();
155
156 let result = Cargo.enforce_lockfile(dir.path(), EnforcePolicy::default());
157
158 assert!(
159 result.is_err(),
160 "a lockfile that cannot resolve the manifest must not pass verification"
161 );
162 assert_eq!(
163 fs::read_to_string(dir.path().join("Cargo.lock")).unwrap(),
164 stale,
165 "the read-only verification rewrote Cargo.lock"
166 );
167 }
168
169 #[test]
170 fn test_detect_positive() {
171 let dir = tempdir().unwrap();
172 File::create(dir.path().join("Cargo.toml")).unwrap();
173
174 let adapter = Cargo;
175 assert!(adapter.detect(dir.path()));
176 }
177
178 #[test]
179 fn test_detect_negative() {
180 let dir = tempdir().unwrap();
181
182 let adapter = Cargo;
183 assert!(!adapter.detect(dir.path()));
184 }
185
186 #[test]
187 fn test_bloat_dirs_present() {
188 let dir = tempdir().unwrap();
189 fs::create_dir(dir.path().join("target")).unwrap();
190
191 let adapter = Cargo;
192 let dirs = adapter.bloat_dirs(dir.path());
193 assert_eq!(dirs.len(), 1);
194 assert_eq!(dirs[0].name, "target");
195 }
196
197 #[test]
198 fn test_bloat_dirs_absent() {
199 let dir = tempdir().unwrap();
200
201 let adapter = Cargo;
202 let dirs = adapter.bloat_dirs(dir.path());
203 assert!(dirs.is_empty());
204 }
205}