dev_prune/adapters/
pixi.rs1use super::{BloatDir, EnforcePolicy, PackageManager, dir_size, run_command_with_timeout};
12use anyhow::{Result, anyhow};
13use std::fs;
14use std::path::Path;
15
16pub struct Pixi;
18
19impl PackageManager for Pixi {
20 fn name(&self) -> &'static str {
21 "pixi"
22 }
23
24 fn detect(&self, path: &Path) -> bool {
25 if path.join("pixi.toml").is_file() || path.join("pixi.lock").is_file() {
26 return true;
27 }
28 let pyproject = path.join("pyproject.toml");
30 pyproject.is_file()
31 && fs::read_to_string(&pyproject)
32 .map(|content| content.contains("[tool.pixi"))
33 .unwrap_or(false)
34 }
35
36 fn bloat_dirs(&self, path: &Path) -> Vec<BloatDir> {
37 let mut dirs = Vec::new();
38 let pixi_dir = path.join(".pixi");
39 if pixi_dir.is_dir() {
40 dirs.push(BloatDir {
41 name: ".pixi".to_string(),
42 size_bytes: dir_size(&pixi_dir),
43 path: pixi_dir,
44 shared_bytes: 0,
45 });
46 }
47 dirs
48 }
49
50 fn enforce_lockfile(&self, path: &Path, _policy: EnforcePolicy) -> Result<()> {
57 let lockfile = path.join("pixi.lock");
58 let content = fs::read_to_string(&lockfile).map_err(|e| {
59 anyhow!(
60 "`pixi.lock` could not be read ({e}): nothing proves the environments are \
61 recoverable. Write one first: `pixi lock`."
62 )
63 })?;
64 let has_version = content
65 .lines()
66 .any(|line| line.trim_start().starts_with("version:"));
67 let has_package = content.lines().any(|line| {
68 let line = line.trim_start();
69 line.starts_with("- conda") || line.starts_with("- pypi")
70 });
71 if !has_version || !has_package {
72 return Err(anyhow!(
73 "`pixi.lock` does not read as a pixi lockfile pinning any packages: \
74 refusing to treat `.pixi` as recoverable from it. Regenerate it: \
75 `pixi lock`."
76 ));
77 }
78 Ok(())
79 }
80
81 fn restore(&self, path: &Path, timeout: std::time::Duration) -> Result<()> {
82 run_command_with_timeout("pixi", &["install"], path, timeout)
83 }
84
85 fn lockfiles(&self) -> &'static [&'static str] {
86 &["pixi.lock"]
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use tempfile::tempdir;
94
95 const LOCKFILE: &str = "version: 6\nenvironments:\n default:\n channels:\n\
97 - url: https://conda.anaconda.org/conda-forge/\n packages:\n\
98 - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025a.conda\n";
99
100 #[test]
101 fn detects_on_manifest_lockfile_or_pyproject_table() {
102 let dir = tempdir().unwrap();
103 assert!(!Pixi.detect(dir.path()));
104
105 fs::write(dir.path().join("pixi.toml"), "[project]\n").unwrap();
106 assert!(Pixi.detect(dir.path()));
107 fs::remove_file(dir.path().join("pixi.toml")).unwrap();
108
109 fs::write(dir.path().join("pixi.lock"), LOCKFILE).unwrap();
110 assert!(Pixi.detect(dir.path()));
111 fs::remove_file(dir.path().join("pixi.lock")).unwrap();
112
113 fs::write(dir.path().join("pyproject.toml"), "[tool.pixi.project]\n").unwrap();
114 assert!(Pixi.detect(dir.path()));
115 }
116
117 #[test]
118 fn a_plain_pyproject_is_not_claimed() {
119 let dir = tempdir().unwrap();
120 fs::write(dir.path().join("pyproject.toml"), "[tool.poetry]\n").unwrap();
121 assert!(!Pixi.detect(dir.path()));
122 }
123
124 #[test]
125 fn the_environment_directory_is_claimed_and_nothing_else() {
126 let dir = tempdir().unwrap();
127 fs::create_dir(dir.path().join(".pixi")).unwrap();
128 fs::create_dir(dir.path().join("src")).unwrap();
129
130 let dirs = Pixi.bloat_dirs(dir.path());
131 assert_eq!(dirs.len(), 1);
132 assert_eq!(dirs[0].name, ".pixi");
133 }
134
135 #[test]
136 fn a_missing_or_empty_lockfile_is_refused() {
137 let dir = tempdir().unwrap();
138 let policy = EnforcePolicy::default();
139 assert!(Pixi.enforce_lockfile(dir.path(), policy).is_err());
140 fs::write(
141 dir.path().join("pixi.lock"),
142 "version: 6\nenvironments: {}\n",
143 )
144 .unwrap();
145 assert!(Pixi.enforce_lockfile(dir.path(), policy).is_err());
146 fs::write(dir.path().join("pixi.lock"), LOCKFILE).unwrap();
147 assert!(Pixi.enforce_lockfile(dir.path(), policy).is_ok());
148 }
149
150 #[test]
151 fn a_foreign_file_under_the_lockfile_name_is_refused() {
152 let dir = tempdir().unwrap();
153 fs::write(dir.path().join("pixi.lock"), "hello there\n").unwrap();
154 assert!(
155 Pixi.enforce_lockfile(dir.path(), EnforcePolicy::default())
156 .is_err()
157 );
158 }
159
160 #[test]
161 fn pixi_is_not_opt_in() {
162 assert!(!Pixi.opt_in());
163 }
164}