dev_prune/adapters/
sbt.rs1use super::{BloatDir, EnforcePolicy, PackageManager, dir_size};
21use anyhow::{Result, anyhow};
22use std::fs;
23use std::path::Path;
24
25const MANIFEST: &str = "build.sbt";
27
28pub struct Sbt;
30
31impl PackageManager for Sbt {
32 fn name(&self) -> &'static str {
33 "sbt"
34 }
35
36 fn detect(&self, path: &Path) -> bool {
37 path.join(MANIFEST).is_file()
38 }
39
40 fn bloat_dirs(&self, path: &Path) -> Vec<BloatDir> {
41 let mut dirs = Vec::new();
42 let target = path.join("target");
43 if target.is_dir() {
44 dirs.push(BloatDir {
45 name: "target".to_string(),
46 size_bytes: dir_size(&target),
47 path: target,
48 shared_bytes: 0,
49 });
50 }
51 let project_target = path.join("project").join("target");
52 if project_target.is_dir() {
53 dirs.push(BloatDir {
54 name: "project/target".to_string(),
57 size_bytes: dir_size(&project_target),
58 path: project_target,
59 shared_bytes: 0,
60 });
61 }
62 dirs
63 }
64
65 fn enforce_lockfile(&self, path: &Path, _policy: EnforcePolicy) -> Result<()> {
72 let manifest = path.join(MANIFEST);
73 let content = fs::read_to_string(&manifest).map_err(|e| {
74 anyhow!("`{MANIFEST}` could not be read ({e}): nothing to rebuild the targets from.")
75 })?;
76 if content.contains(":=") {
77 return Ok(());
78 }
79 let properties = path.join("project").join("build.properties");
80 if fs::read_to_string(&properties)
81 .map(|p| p.contains("sbt.version"))
82 .unwrap_or(false)
83 {
84 return Ok(());
85 }
86 Err(anyhow!(
87 "`{MANIFEST}` assigns no settings and `project/build.properties` pins no \
88 `sbt.version`: refusing to treat the build trees as regenerable from it."
89 ))
90 }
91
92 fn restore(&self, _path: &Path, _timeout: std::time::Duration) -> Result<()> {
93 println!("sbt's build trees regenerate on the next `sbt compile`");
94 Ok(())
95 }
96
97 fn lockfiles(&self) -> &'static [&'static str] {
98 &[MANIFEST]
99 }
100
101 fn opt_in(&self) -> bool {
102 true
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109 use std::path::PathBuf;
110 use tempfile::tempdir;
111
112 fn project(dir: &Path) -> PathBuf {
114 fs::write(
115 dir.join(MANIFEST),
116 "name := \"demo\"\n\nscalaVersion := \"3.4.2\"\n",
117 )
118 .unwrap();
119 dir.to_path_buf()
120 }
121
122 fn claimed(project: &Path) -> Vec<String> {
123 Sbt.bloat_dirs(project)
124 .into_iter()
125 .map(|b| b.name)
126 .collect()
127 }
128
129 #[test]
130 fn detects_on_the_build_definition() {
131 let dir = tempdir().unwrap();
132 assert!(!Sbt.detect(dir.path()));
133 project(dir.path());
134 assert!(Sbt.detect(dir.path()));
135 }
136
137 #[test]
138 fn both_target_trees_are_claimed_and_nothing_else() {
139 let dir = tempdir().unwrap();
140 let root = project(dir.path());
141 fs::create_dir(root.join("target")).unwrap();
142 fs::create_dir_all(root.join("project").join("target")).unwrap();
143 fs::create_dir(root.join("src")).unwrap();
144
145 assert_eq!(claimed(&root), vec!["target", "project/target"]);
146 }
147
148 #[test]
149 fn a_settingless_root_passes_when_build_properties_pins_sbt() {
150 let dir = tempdir().unwrap();
151 fs::write(dir.path().join(MANIFEST), "// aggregation root\n").unwrap();
152 fs::create_dir(dir.path().join("project")).unwrap();
153 fs::write(
154 dir.path().join("project").join("build.properties"),
155 "sbt.version=1.10.1\n",
156 )
157 .unwrap();
158 assert!(
159 Sbt.enforce_lockfile(dir.path(), EnforcePolicy::default())
160 .is_ok()
161 );
162 }
163
164 #[test]
165 fn a_missing_or_bogus_build_definition_is_refused() {
166 let dir = tempdir().unwrap();
167 let policy = EnforcePolicy::default();
168 assert!(Sbt.enforce_lockfile(dir.path(), policy).is_err());
169 fs::write(dir.path().join(MANIFEST), "// nothing assigned\n").unwrap();
170 assert!(Sbt.enforce_lockfile(dir.path(), policy).is_err());
171 project(dir.path());
172 assert!(Sbt.enforce_lockfile(dir.path(), policy).is_ok());
173 }
174
175 #[test]
176 fn sbt_is_opt_in() {
177 assert!(Sbt.opt_in());
178 }
179}