dev_prune/adapters/
vcpkg.rs1use super::{BloatDir, EnforcePolicy, PackageManager, dir_size};
19use anyhow::{Result, anyhow};
20use std::fs;
21use std::path::Path;
22
23pub struct Vcpkg;
25
26impl PackageManager for Vcpkg {
27 fn name(&self) -> &'static str {
28 "vcpkg"
29 }
30
31 fn detect(&self, path: &Path) -> bool {
32 path.join("vcpkg.json").exists()
33 }
34
35 fn bloat_dirs(&self, path: &Path) -> Vec<BloatDir> {
38 let installed = path.join("vcpkg_installed");
39 if !installed.is_dir() {
40 return Vec::new();
41 }
42 vec![BloatDir {
43 name: "vcpkg_installed".to_string(),
44 path: installed.clone(),
45 size_bytes: dir_size(&installed),
46 shared_bytes: 0,
47 }]
48 }
49
50 fn enforce_lockfile(&self, path: &Path, _policy: EnforcePolicy) -> Result<()> {
62 let manifest = path.join("vcpkg.json");
63 let raw = fs::read_to_string(&manifest).map_err(|e| {
64 anyhow!(
65 "`vcpkg.json` could not be read ({e}) — nothing to rebuild `vcpkg_installed/` from."
66 )
67 })?;
68 let json: serde_json::Value = serde_json::from_str(&raw).map_err(|e| {
69 anyhow!(
70 "`vcpkg.json` is not valid JSON ({e}) — `vcpkg install` could not read it either."
71 )
72 })?;
73 let declares_dependencies = json
74 .get("dependencies")
75 .and_then(|d| d.as_array())
76 .is_some_and(|d| !d.is_empty());
77 if !declares_dependencies {
78 return Err(anyhow!(
79 "`vcpkg.json` declares no `dependencies` — refusing to treat \
80 `vcpkg_installed/` as rebuildable from it."
81 ));
82 }
83 Ok(())
84 }
85
86 fn restore(&self, _path: &Path, _timeout: std::time::Duration) -> Result<()> {
87 println!("vcpkg vcpkg_installed/ will regenerate on the next `vcpkg install`");
88 Ok(())
89 }
90
91 fn lockfiles(&self) -> &'static [&'static str] {
92 &["vcpkg.json"]
93 }
94
95 fn opt_in(&self) -> bool {
96 true
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103 use tempfile::tempdir;
104
105 fn manifest(dir: &Path, body: &str) {
106 fs::write(dir.join("vcpkg.json"), body).unwrap();
107 }
108
109 fn enforced(dir: &Path) -> Result<()> {
110 Vcpkg.enforce_lockfile(dir, EnforcePolicy::default())
111 }
112
113 #[test]
114 fn detects_on_the_manifest() {
115 let dir = tempdir().unwrap();
116 assert!(!Vcpkg.detect(dir.path()));
117 manifest(dir.path(), r#"{"dependencies":["fmt"]}"#);
118 assert!(Vcpkg.detect(dir.path()));
119 }
120
121 #[test]
122 fn claims_the_manifest_install_tree_and_nothing_else() {
123 let dir = tempdir().unwrap();
127 fs::create_dir(dir.path().join("vcpkg_installed")).unwrap();
128 fs::create_dir(dir.path().join("build")).unwrap();
129 let names: Vec<String> = Vcpkg
130 .bloat_dirs(dir.path())
131 .into_iter()
132 .map(|b| b.name)
133 .collect();
134 assert_eq!(names, vec!["vcpkg_installed"]);
135 }
136
137 #[test]
138 fn a_port_manifest_is_not_an_installation() {
139 let dir = tempdir().unwrap();
143 manifest(dir.path(), r#"{"name":"fmt","version":"10.1.1"}"#);
144 assert!(enforced(dir.path()).is_err());
145 }
146
147 #[test]
148 fn a_missing_empty_or_unreadable_manifest_is_refused() {
149 let dir = tempdir().unwrap();
150 assert!(enforced(dir.path()).is_err(), "no manifest at all");
151 manifest(dir.path(), "{ not json");
152 assert!(enforced(dir.path()).is_err(), "unparseable manifest");
153 manifest(dir.path(), r#"{"dependencies":[]}"#);
154 assert!(
155 enforced(dir.path()).is_err(),
156 "an empty dependency list rebuilds nothing"
157 );
158 }
159
160 #[test]
161 fn a_manifest_with_dependencies_is_the_proof() {
162 let dir = tempdir().unwrap();
163 manifest(
164 dir.path(),
165 r#"{"dependencies":["fmt","zlib"],
166 "builtin-baseline":"3426db05b996481ca31e95fff3734cf23e0f51bc"}"#,
167 );
168 assert!(enforced(dir.path()).is_ok());
169
170 manifest(
174 dir.path(),
175 r#"{"dependencies":[{"name":"boost-asio","features":["ssl"]}]}"#,
176 );
177 assert!(enforced(dir.path()).is_ok());
178 }
179
180 #[test]
181 fn vcpkg_is_opt_in() {
182 assert!(Vcpkg.opt_in());
183 }
184}