use serde::{Deserialize, Serialize};
pub const SYSTEM_MANIFEST_SCHEMA_VERSION: u32 = 1;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct HwInfo {
#[serde(default)]
pub sku: String,
#[serde(default)]
pub som_hw_rev: Option<String>,
#[serde(default)]
pub board_name: Option<String>,
#[serde(default)]
pub board_hw_rev: Option<String>,
#[serde(default)]
pub silicon: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Slice {
pub core_id: String,
pub os: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub app: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub image: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub machine: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub board: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub toolchain: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub build_dir: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub output_artefact: Option<String>,
#[serde(default)]
pub status: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub log_path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub flash_method: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub flash_args: Option<serde_yaml::Value>,
}
impl Slice {
pub fn is_active(&self) -> bool {
self.os != "off"
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct IpcLink {
pub name: String,
pub kind: String,
#[serde(default)]
pub endpoints: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HelperMcu {
pub name: String,
pub chip: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub firmware_path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub flash_method: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub flash_args: Option<serde_yaml::Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SystemManifest {
pub schema_version: u32,
#[serde(default)]
pub generated_by: String,
#[serde(default)]
pub hw_info: HwInfo,
#[serde(default)]
pub slices: Vec<Slice>,
#[serde(default)]
pub ipc: Vec<IpcLink>,
#[serde(default)]
pub helper_mcus: Vec<HelperMcu>,
#[serde(default)]
pub boot_order: Vec<serde_yaml::Value>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub storage: Vec<serde_yaml::Value>,
}
impl SystemManifest {
pub fn active_slices(&self) -> impl Iterator<Item = &Slice> {
self.slices.iter().filter(|s| s.is_active())
}
pub fn slice_for_core(&self, core_id: &str) -> Option<&Slice> {
self.slices.iter().find(|s| s.core_id == core_id)
}
}
#[derive(Debug, thiserror::Error)]
pub enum SystemManifestError {
#[error("system-manifest is not valid YAML: {0}")]
Parse(String),
#[error(
"unsupported system-manifest schema_version {found} (this CLI consumes v{supported}); \
upgrade the CLI or the SDK so the versions match"
)]
UnsupportedSchemaVersion { found: u32, supported: u32 },
}
pub fn parse_system_manifest(yaml: &str) -> Result<SystemManifest, SystemManifestError> {
let manifest: SystemManifest =
serde_yaml::from_str(yaml).map_err(|e| SystemManifestError::Parse(e.to_string()))?;
if manifest.schema_version != SYSTEM_MANIFEST_SCHEMA_VERSION {
return Err(SystemManifestError::UnsupportedSchemaVersion {
found: manifest.schema_version,
supported: SYSTEM_MANIFEST_SCHEMA_VERSION,
});
}
Ok(manifest)
}
pub fn summarize_manifest(m: &SystemManifest) -> Vec<String> {
let mut lines = vec![format!(
"system-manifest: {} — {} slice(s), {} active, {} ipc, {} helper mcu(s)",
m.hw_info.sku,
m.slices.len(),
m.active_slices().count(),
m.ipc.len(),
m.helper_mcus.len(),
)];
for s in &m.slices {
let target = s
.board
.as_deref()
.or(s.machine.as_deref())
.or(s.image.as_deref())
.unwrap_or("-");
lines.push(format!(
" {} [{}] {} ({})",
s.core_id, s.os, s.status, target
));
}
lines
}
#[cfg(test)]
mod tests {
use super::*;
const AEN701: &str = r#"
schema_version: 1
generated_by: scripts/alp_orchestrate.py
hw_info:
sku: E1M-AEN701
som_hw_rev: r1
board_name: E1M-EVK
board_hw_rev: r1
silicon: alif:ensemble:e7
slices:
- core_id: a32_cluster
os: 'off'
app: alp-image-edge
machine: e1m-aen701-a32
toolchain: poky-glibc
status: pending
- core_id: m55_hp
os: zephyr
app: ./src
board: alp_e1m_aen701_m55_hp
toolchain: arm-zephyr-eabi
status: pending
flash_method: zephyr_west_flash
flash_args:
runner: openocd
- core_id: m55_he
os: zephyr
app: alp-stock-shim
board: alp_e1m_aen701_m55_he
toolchain: arm-zephyr-eabi
status: pending
flash_method: zephyr_west_flash
flash_args:
runner: openocd
ipc: []
helper_mcus:
- name: cc3501e_otp
chip: cc3501e
firmware_path: TBD
flash_method: TBD
flash_args: TBD
note: firmware_path TBD; populated when the upstream firmware release lands
boot_order: []
"#;
#[test]
fn parses_the_real_aen701_manifest() {
let m = parse_system_manifest(AEN701).expect("valid manifest");
assert_eq!(m.schema_version, 1);
assert_eq!(m.generated_by, "scripts/alp_orchestrate.py");
assert_eq!(m.hw_info.sku, "E1M-AEN701");
assert_eq!(m.hw_info.silicon.as_deref(), Some("alif:ensemble:e7"));
assert_eq!(m.slices.len(), 3);
let a32 = m.slice_for_core("a32_cluster").expect("a32 slice");
assert_eq!(a32.os, "off");
assert!(a32.flash_method.is_none());
assert!(!a32.is_active());
let hp = m.slice_for_core("m55_hp").expect("m55_hp slice");
assert_eq!(hp.flash_method.as_deref(), Some("zephyr_west_flash"));
assert!(hp.is_active());
assert_eq!(m.active_slices().count(), 2);
assert_eq!(m.helper_mcus.len(), 1);
assert_eq!(m.helper_mcus[0].chip, "cc3501e");
}
#[test]
fn rejects_unsupported_schema_version() {
let yaml = AEN701.replace("schema_version: 1", "schema_version: 2");
let err = parse_system_manifest(&yaml).unwrap_err();
assert!(matches!(
err,
SystemManifestError::UnsupportedSchemaVersion { found: 2, .. }
));
}
#[test]
fn tolerates_unknown_additive_fields() {
let yaml = format!("{AEN701}\nfuture_block:\n anything: 1\n");
let m = parse_system_manifest(&yaml).expect("unknown fields tolerated");
assert_eq!(m.slices.len(), 3);
}
#[test]
fn round_trips_through_serde() {
let m = parse_system_manifest(AEN701).unwrap();
let yaml = serde_yaml::to_string(&m).unwrap();
let reparsed = parse_system_manifest(&yaml).unwrap();
assert_eq!(m, reparsed);
}
}