Skip to main content

dev_prune/adapters/
maven.rs

1// Copyright 2026 VKrishna04
2// SPDX-License-Identifier: Apache-2.0
3
4// Maven build-tool adapter for Java projects.
5//
6// Opt-in (`devp config set enable_maven true`), unlike every package-manager adapter:
7// `target/` is rebuilt by *recompiling the whole project*, not by re-downloading a
8// dependency tree, so deleting it trades disk for a full `mvn package` — minutes, not
9// seconds. The engine also holds these directories to the longer `build_idle_days`
10// window for the same reason.
11//
12// Recoverability rests on a different proof than the lockfile adapters use: `target/`
13// is derived entirely from the sources and `pom.xml` sitting in the repository, and
14// Maven refuses to build a module whose `pom.xml` declares dependencies without literal
15// versions resolvable from a repository. There is nothing inside `target/` that a
16// rebuild does not regenerate.
17
18use super::{BloatDir, EnforcePolicy, PackageManager, dir_size};
19use anyhow::{Result, anyhow};
20use std::fs;
21use std::path::Path;
22
23/// Adapter for Maven-based Java projects. Opt-in; see the module comment.
24pub struct Maven;
25
26impl PackageManager for Maven {
27    fn name(&self) -> &'static str {
28        "maven"
29    }
30
31    fn detect(&self, path: &Path) -> bool {
32        path.join("pom.xml").exists()
33    }
34
35    fn bloat_dirs(&self, path: &Path) -> Vec<BloatDir> {
36        let mut dirs = Vec::new();
37        let target = path.join("target");
38        if target.exists() {
39            dirs.push(BloatDir {
40                name: "target".to_string(),
41                path: target.clone(),
42                size_bytes: dir_size(&target),
43                shared_bytes: 0,
44            });
45        }
46        dirs
47    }
48
49    /// The proof here is the manifest, not a lockfile: `target/` is derived from the
50    /// working tree, so what must exist is a readable `pom.xml` for the rebuild to
51    /// start from. Running `mvn validate` instead would resolve plugins over the
52    /// network — a download in the middle of a delete pass, for no stronger answer.
53    fn enforce_lockfile(&self, path: &Path, _policy: EnforcePolicy) -> Result<()> {
54        let pom = path.join("pom.xml");
55        let content = fs::read_to_string(&pom).map_err(|e| {
56            anyhow!("`pom.xml` could not be read ({e}) — nothing to rebuild `target/` from.")
57        })?;
58        if !content.contains("<project") {
59            return Err(anyhow!(
60                "`pom.xml` does not look like a Maven manifest (no `<project` element) — \
61                 refusing to treat `target/` as rebuildable from it."
62            ));
63        }
64        Ok(())
65    }
66
67    fn restore(&self, _path: &Path, _timeout: std::time::Duration) -> Result<()> {
68        println!("Maven target/ will regenerate on the next `mvn package` (or `mvn compile`)");
69        Ok(())
70    }
71
72    fn lockfiles(&self) -> &'static [&'static str] {
73        &["pom.xml"]
74    }
75
76    fn opt_in(&self) -> bool {
77        true
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    use tempfile::tempdir;
85
86    #[test]
87    fn detects_on_pom_xml_only() {
88        let dir = tempdir().unwrap();
89        assert!(!Maven.detect(dir.path()));
90        fs::write(dir.path().join("pom.xml"), "<project/>").unwrap();
91        assert!(Maven.detect(dir.path()));
92    }
93
94    #[test]
95    fn claims_target_when_present() {
96        let dir = tempdir().unwrap();
97        assert!(Maven.bloat_dirs(dir.path()).is_empty());
98        fs::create_dir(dir.path().join("target")).unwrap();
99        let dirs = Maven.bloat_dirs(dir.path());
100        assert_eq!(dirs.len(), 1);
101        assert_eq!(dirs[0].name, "target");
102    }
103
104    #[test]
105    fn a_missing_or_bogus_manifest_is_refused() {
106        let dir = tempdir().unwrap();
107        assert!(
108            Maven
109                .enforce_lockfile(dir.path(), EnforcePolicy::default())
110                .is_err()
111        );
112        fs::write(dir.path().join("pom.xml"), "not xml at all").unwrap();
113        assert!(
114            Maven
115                .enforce_lockfile(dir.path(), EnforcePolicy::default())
116                .is_err()
117        );
118        fs::write(dir.path().join("pom.xml"), "<project></project>").unwrap();
119        assert!(
120            Maven
121                .enforce_lockfile(dir.path(), EnforcePolicy::default())
122                .is_ok()
123        );
124    }
125
126    #[test]
127    fn maven_is_opt_in() {
128        assert!(Maven.opt_in());
129    }
130}