use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use crate::error::Error;
use crate::util;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Manifest {
pub lihaaf_version: String,
#[serde(default = "default_suite_name_field")]
pub suite_name: String,
pub rustc_release: String,
pub rustc_commit_hash: String,
pub host_triple: String,
pub sysroot: PathBuf,
pub dylib_crate: String,
pub cargo_dylib_path: PathBuf,
pub managed_dylib_path: PathBuf,
pub dylib_sha256: String,
pub dylib_mtime_unix_secs: i64,
pub use_symlink: bool,
#[serde(default)]
pub features: Vec<String>,
pub extern_crates: Vec<String>,
pub edition: String,
pub metadata_snapshot: serde_json::Value,
}
fn default_suite_name_field() -> String {
crate::config::DEFAULT_SUITE_NAME.to_string()
}
pub fn manifest_path_for_suite(workspace_target: &Path, suite_name: &str) -> PathBuf {
let lihaaf_dir = workspace_target.join("lihaaf");
if suite_name == crate::config::DEFAULT_SUITE_NAME {
lihaaf_dir.join("manifest.json")
} else {
lihaaf_dir.join(format!("manifest-{suite_name}.json"))
}
}
impl Manifest {
#[allow(dead_code)] pub fn try_read(path: &Path) -> Option<Self> {
let text = std::fs::read_to_string(path).ok()?;
serde_json::from_str(&text).ok()
}
pub fn write(&self, path: &Path) -> Result<(), Error> {
let text = serde_json::to_vec_pretty(self).map_err(|e| Error::JsonParse {
context: "serializing manifest".into(),
message: e.to_string(),
})?;
let mut text = text;
text.push(b'\n');
util::write_file_atomic(path, &text)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
use tempfile::tempdir;
fn sample() -> Manifest {
Manifest {
lihaaf_version: "0.1.0".into(),
suite_name: crate::config::DEFAULT_SUITE_NAME.into(),
rustc_release: "rustc 1.95.0 (abc 2026-01-01)".into(),
rustc_commit_hash: "abc".into(),
host_triple: "x86_64-unknown-linux-gnu".into(),
sysroot: PathBuf::from("/home/u/.rustup/toolchains/stable-x86_64-unknown-linux-gnu"),
dylib_crate: "consumer".into(),
cargo_dylib_path: PathBuf::from("/p/target/release/deps/libconsumer-abc.so"),
managed_dylib_path: PathBuf::from("/p/target/lihaaf/libconsumer-current-abc.so"),
dylib_sha256: "deadbeef".into(),
dylib_mtime_unix_secs: 1746883200,
use_symlink: false,
features: vec!["testing".into()],
extern_crates: vec!["consumer".into(), "consumer-macros".into()],
edition: "2021".into(),
metadata_snapshot: json!({"dylib_crate": "consumer"}),
}
}
#[test]
fn round_trips_through_disk() {
let tmp = tempdir().unwrap();
let path = tmp.path().join("manifest.json");
let m = sample();
m.write(&path).unwrap();
let read = Manifest::try_read(&path).unwrap();
assert_eq!(read, m);
}
#[test]
fn try_read_returns_none_on_corrupt_manifest() {
let tmp = tempdir().unwrap();
let path = tmp.path().join("manifest.json");
std::fs::write(&path, "not json").unwrap();
assert!(Manifest::try_read(&path).is_none());
}
#[test]
fn try_read_returns_none_on_missing_manifest() {
let tmp = tempdir().unwrap();
let path = tmp.path().join("nope.json");
assert!(Manifest::try_read(&path).is_none());
}
#[test]
fn manifest_without_suite_name_round_trips_with_default_suite() {
let default_suite_json = r#"{
"lihaaf_version": "0.1.0-alpha.2",
"rustc_release": "rustc 1.95.0 (abc 2026-01-01)",
"rustc_commit_hash": "abc",
"host_triple": "x86_64-unknown-linux-gnu",
"sysroot": "/r",
"dylib_crate": "consumer",
"cargo_dylib_path": "/p/target/release/deps/libconsumer-abc.so",
"managed_dylib_path": "/p/target/lihaaf/libconsumer-current-abc.so",
"dylib_sha256": "deadbeef",
"dylib_mtime_unix_secs": 1746883200,
"use_symlink": false,
"features": [],
"extern_crates": ["consumer"],
"edition": "2021",
"metadata_snapshot": {"dylib_crate": "consumer"}
}"#;
let m: Manifest =
serde_json::from_str(default_suite_json).expect("default-suite manifest must parse");
assert_eq!(m.suite_name, crate::config::DEFAULT_SUITE_NAME);
}
#[test]
fn manifest_path_for_default_suite_uses_default_name() {
let p = manifest_path_for_suite(Path::new("/p/target"), crate::config::DEFAULT_SUITE_NAME);
assert_eq!(p, PathBuf::from("/p/target/lihaaf/manifest.json"));
}
#[test]
fn manifest_path_for_named_suite_includes_name() {
let p = manifest_path_for_suite(Path::new("/p/target"), "spatial");
assert_eq!(p, PathBuf::from("/p/target/lihaaf/manifest-spatial.json"));
}
}