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,
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,
}
impl Manifest {
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(),
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());
}
}