use super::{BloatDir, EnforcePolicy, PackageManager, dir_size};
use anyhow::{Result, anyhow};
use std::fs;
use std::path::Path;
pub struct Vcpkg;
impl PackageManager for Vcpkg {
fn name(&self) -> &'static str {
"vcpkg"
}
fn detect(&self, path: &Path) -> bool {
path.join("vcpkg.json").exists()
}
fn bloat_dirs(&self, path: &Path) -> Vec<BloatDir> {
let installed = path.join("vcpkg_installed");
if !installed.is_dir() {
return Vec::new();
}
vec![BloatDir {
name: "vcpkg_installed".to_string(),
path: installed.clone(),
size_bytes: dir_size(&installed),
shared_bytes: 0,
}]
}
fn enforce_lockfile(&self, path: &Path, _policy: EnforcePolicy) -> Result<()> {
let manifest = path.join("vcpkg.json");
let raw = fs::read_to_string(&manifest).map_err(|e| {
anyhow!(
"`vcpkg.json` could not be read ({e}) — nothing to rebuild `vcpkg_installed/` from."
)
})?;
let json: serde_json::Value = serde_json::from_str(&raw).map_err(|e| {
anyhow!(
"`vcpkg.json` is not valid JSON ({e}) — `vcpkg install` could not read it either."
)
})?;
let declares_dependencies = json
.get("dependencies")
.and_then(|d| d.as_array())
.is_some_and(|d| !d.is_empty());
if !declares_dependencies {
return Err(anyhow!(
"`vcpkg.json` declares no `dependencies` — refusing to treat \
`vcpkg_installed/` as rebuildable from it."
));
}
Ok(())
}
fn restore(&self, _path: &Path, _timeout: std::time::Duration) -> Result<()> {
println!("vcpkg vcpkg_installed/ will regenerate on the next `vcpkg install`");
Ok(())
}
fn lockfiles(&self) -> &'static [&'static str] {
&["vcpkg.json"]
}
fn opt_in(&self) -> bool {
true
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
fn manifest(dir: &Path, body: &str) {
fs::write(dir.join("vcpkg.json"), body).unwrap();
}
fn enforced(dir: &Path) -> Result<()> {
Vcpkg.enforce_lockfile(dir, EnforcePolicy::default())
}
#[test]
fn detects_on_the_manifest() {
let dir = tempdir().unwrap();
assert!(!Vcpkg.detect(dir.path()));
manifest(dir.path(), r#"{"dependencies":["fmt"]}"#);
assert!(Vcpkg.detect(dir.path()));
}
#[test]
fn claims_the_manifest_install_tree_and_nothing_else() {
let dir = tempdir().unwrap();
fs::create_dir(dir.path().join("vcpkg_installed")).unwrap();
fs::create_dir(dir.path().join("build")).unwrap();
let names: Vec<String> = Vcpkg
.bloat_dirs(dir.path())
.into_iter()
.map(|b| b.name)
.collect();
assert_eq!(names, vec!["vcpkg_installed"]);
}
#[test]
fn a_port_manifest_is_not_an_installation() {
let dir = tempdir().unwrap();
manifest(dir.path(), r#"{"name":"fmt","version":"10.1.1"}"#);
assert!(enforced(dir.path()).is_err());
}
#[test]
fn a_missing_empty_or_unreadable_manifest_is_refused() {
let dir = tempdir().unwrap();
assert!(enforced(dir.path()).is_err(), "no manifest at all");
manifest(dir.path(), "{ not json");
assert!(enforced(dir.path()).is_err(), "unparseable manifest");
manifest(dir.path(), r#"{"dependencies":[]}"#);
assert!(
enforced(dir.path()).is_err(),
"an empty dependency list rebuilds nothing"
);
}
#[test]
fn a_manifest_with_dependencies_is_the_proof() {
let dir = tempdir().unwrap();
manifest(
dir.path(),
r#"{"dependencies":["fmt","zlib"],
"builtin-baseline":"3426db05b996481ca31e95fff3734cf23e0f51bc"}"#,
);
assert!(enforced(dir.path()).is_ok());
manifest(
dir.path(),
r#"{"dependencies":[{"name":"boost-asio","features":["ssl"]}]}"#,
);
assert!(enforced(dir.path()).is_ok());
}
#[test]
fn vcpkg_is_opt_in() {
assert!(Vcpkg.opt_in());
}
}