dev_prune/adapters/
mix.rs1use super::{
16 BloatDir, EnforcePolicy, PackageManager, dir_size, refuse_if_manifest_stale,
17 run_command_with_timeout,
18};
19use anyhow::{Result, anyhow};
20use std::fs;
21use std::path::Path;
22
23pub struct Mix;
25
26impl PackageManager for Mix {
27 fn name(&self) -> &'static str {
28 "mix"
29 }
30
31 fn detect(&self, path: &Path) -> bool {
32 path.join("mix.exs").exists()
33 }
34
35 fn bloat_dirs(&self, path: &Path) -> Vec<BloatDir> {
36 let deps = path.join("deps");
37 if !deps.is_dir() {
38 return Vec::new();
39 }
40 vec![BloatDir {
41 name: "deps".to_string(),
42 path: deps.clone(),
43 size_bytes: dir_size(&deps),
44 shared_bytes: 0,
45 }]
46 }
47
48 fn enforce_lockfile(&self, path: &Path, _policy: EnforcePolicy) -> Result<()> {
49 let lock = path.join("mix.lock");
50 let content = fs::read_to_string(&lock).map_err(|e| {
51 anyhow!(
52 "`mix.lock` could not be read ({e}) — without it `mix deps.get` resolves \
53 afresh instead of restoring the versions being deleted."
54 )
55 })?;
56 if !content.contains("%{") {
59 return Err(anyhow!(
60 "`mix.lock` is not an Elixir map literal — it is not a complete Mix \
61 lockfile, so `deps/` cannot be proven rebuildable from it."
62 ));
63 }
64 refuse_if_manifest_stale(&path.join("mix.exs"), &lock, "mix deps.get")
65 }
66
67 fn restore(&self, path: &Path, timeout: std::time::Duration) -> Result<()> {
68 run_command_with_timeout("mix", &["deps.get"], path, timeout)
69 }
70
71 fn lockfiles(&self) -> &'static [&'static str] {
72 &["mix.lock"]
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79 use tempfile::tempdir;
80
81 #[test]
82 fn detects_on_the_mix_manifest() {
83 let dir = tempdir().unwrap();
84 assert!(!Mix.detect(dir.path()));
85 fs::write(dir.path().join("mix.exs"), "defmodule X.MixProject do end").unwrap();
86 assert!(Mix.detect(dir.path()));
87 }
88
89 #[test]
90 fn claims_deps_and_never_the_build_tree() {
91 let dir = tempdir().unwrap();
92 fs::create_dir(dir.path().join("deps")).unwrap();
93 fs::create_dir(dir.path().join("_build")).unwrap();
94 let names: Vec<String> = Mix
95 .bloat_dirs(dir.path())
96 .into_iter()
97 .map(|b| b.name)
98 .collect();
99 assert_eq!(names, vec!["deps"]);
100 }
101
102 #[test]
103 fn a_missing_or_malformed_lockfile_is_refused() {
104 let dir = tempdir().unwrap();
105 assert!(
106 Mix.enforce_lockfile(dir.path(), EnforcePolicy::default())
107 .is_err()
108 );
109 fs::write(dir.path().join("mix.lock"), "<<<<<<< HEAD\n").unwrap();
110 assert!(
111 Mix.enforce_lockfile(dir.path(), EnforcePolicy::default())
112 .is_err()
113 );
114 fs::write(dir.path().join("mix.lock"), "%{\n \"jason\": {:hex},\n}\n").unwrap();
115 assert!(
116 Mix.enforce_lockfile(dir.path(), EnforcePolicy::default())
117 .is_ok()
118 );
119 }
120}