use crate::version::Ver;
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Platforms {
#[serde(default, rename = "platform")]
pub list: Vec<Platform>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Platform {
pub pci_id: String,
pub kernel_name: String,
#[serde(default)]
pub codename: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub min_openvino: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub min_npu_driver: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pinned_note: Option<String>,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct NpuDrivers {
#[serde(default, rename = "driver")]
pub list: Vec<NpuDriver>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NpuDriver {
pub version: String,
pub openvino: String,
pub platforms: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub measured_openvino: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub measured_note: Option<String>,
}
impl NpuDriver {
pub fn max_openvino(&self) -> Option<Ver> {
let paired = Ver::parse(&self.openvino);
let measured = self.measured_openvino.as_deref().and_then(Ver::parse);
paired.max(measured)
}
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Ledger {
#[serde(default, rename = "artifact")]
pub list: Vec<Entry>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Entry {
pub id: String,
pub digest: String,
pub first_seen: String,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub provenance: bool,
}
impl Ledger {
pub fn get(&self, id: &str) -> Option<&Entry> {
self.list.iter().find(|e| e.id == id)
}
}
pub struct Data {
pub dir: Option<PathBuf>,
pub platforms: Platforms,
pub npu_drivers: NpuDrivers,
pub ledger: Ledger,
}
fn parse<T: for<'de> Deserialize<'de>>(name: &str, text: &str) -> Result<T> {
toml::from_str(text).with_context(|| format!("parsing data/{name}"))
}
impl Data {
pub fn load(dir: Option<&Path>) -> Result<Data> {
let read = |name: &str, embedded: &'static str| -> Result<String> {
match dir {
Some(d) => std::fs::read_to_string(d.join(name))
.with_context(|| format!("reading {}", d.join(name).display())),
None => Ok(embedded.to_owned()),
}
};
Ok(Data {
dir: dir.map(Path::to_path_buf),
platforms: parse(
"platforms.toml",
&read("platforms.toml", include_str!("../data/platforms.toml"))?,
)?,
npu_drivers: parse(
"npu_drivers.toml",
&read("npu_drivers.toml", include_str!("../data/npu_drivers.toml"))?,
)?,
ledger: parse(
"ledger.toml",
&read("ledger.toml", include_str!("../data/ledger.toml"))?,
)?,
})
}
pub fn save(&self) -> Result<()> {
let dir = self.dir.as_ref().context("saving needs --data-dir")?;
let header =
"# Generated by `ovfetch ci discover`; human edits are reviewed in its PRs.\n\n";
let write =
|name: &str, body: String| std::fs::write(dir.join(name), format!("{header}{body}"));
write("platforms.toml", toml::to_string(&self.platforms)?)?;
write("npu_drivers.toml", toml::to_string(&self.npu_drivers)?)?;
write("ledger.toml", toml::to_string(&self.ledger)?)?;
Ok(())
}
}