Skip to main content

canic_host/release_set/
manifest.rs

1use serde::{Deserialize, Serialize};
2use std::{fs, path::Path};
3
4use crate::role_contract::{declared_role_manifest_path, finding_detail};
5
6use super::{
7    build_release_set_entry, config_path, configured_release_roles, load_root_package_version,
8    resolve_artifact_root, root_release_set_manifest_path, workspace_manifest_path,
9};
10
11///
12/// RootReleaseSetManifest
13///
14
15#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
16pub struct RootReleaseSetManifest {
17    pub release_version: String,
18    pub entries: Vec<ReleaseSetEntry>,
19}
20
21///
22/// ReleaseSetEntry
23///
24
25#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
26pub struct ReleaseSetEntry {
27    pub role: String,
28    pub template_id: String,
29    pub artifact_relative_path: String,
30    pub payload_size_bytes: u64,
31    pub payload_sha256_hex: String,
32    pub chunk_size_bytes: u64,
33    pub chunk_sha256_hex: Vec<String>,
34}
35
36// Build and persist the current root release-set manifest from built `.wasm.gz` artifacts.
37pub fn emit_root_release_set_manifest(
38    workspace_root: &Path,
39    icp_root: &Path,
40    network: &str,
41) -> Result<std::path::PathBuf, Box<dyn std::error::Error>> {
42    let config_path = config_path(workspace_root);
43    emit_root_release_set_manifest_with_config(workspace_root, icp_root, network, &config_path)
44}
45
46// Build and persist the current root release-set manifest with an explicit config path.
47pub fn emit_root_release_set_manifest_with_config(
48    workspace_root: &Path,
49    icp_root: &Path,
50    network: &str,
51    config_path: &Path,
52) -> Result<std::path::PathBuf, Box<dyn std::error::Error>> {
53    let artifact_root = resolve_artifact_root(icp_root, network)?;
54    let manifest_path = root_release_set_manifest_path(&artifact_root)?;
55    let root_manifest_path =
56        declared_role_manifest_path(config_path, &canic_core::ids::CanisterRole::ROOT)
57            .map_err(|finding| finding_detail(&finding))?;
58    let release_version = load_root_package_version(
59        &root_manifest_path,
60        &workspace_manifest_path(workspace_root),
61    )?;
62    let entries = configured_release_roles(config_path)?
63        .into_iter()
64        .map(|role_name| build_release_set_entry(icp_root, &artifact_root, &role_name))
65        .collect::<Result<Vec<_>, _>>()?;
66    let manifest = RootReleaseSetManifest {
67        release_version,
68        entries,
69    };
70
71    fs::write(&manifest_path, serde_json::to_vec_pretty(&manifest)?)?;
72    Ok(manifest_path)
73}
74
75// Emit the root release-set manifest only once every required ordinary artifact exists.
76pub fn emit_root_release_set_manifest_if_ready(
77    workspace_root: &Path,
78    icp_root: &Path,
79    network: &str,
80) -> Result<Option<std::path::PathBuf>, Box<dyn std::error::Error>> {
81    let artifact_root = resolve_artifact_root(icp_root, network)?;
82    let roles = configured_release_roles(&config_path(workspace_root))?;
83
84    for role_name in roles {
85        let artifact_path = artifact_root
86            .join(&role_name)
87            .join(format!("{role_name}.wasm.gz"));
88        if !artifact_path.is_file() {
89            return Ok(None);
90        }
91    }
92
93    emit_root_release_set_manifest(workspace_root, icp_root, network).map(Some)
94}
95
96// Load one previously emitted root release-set manifest from disk.
97pub fn load_root_release_set_manifest(
98    manifest_path: &Path,
99) -> Result<RootReleaseSetManifest, Box<dyn std::error::Error>> {
100    let source = fs::read(manifest_path)?;
101    Ok(serde_json::from_slice(&source)?)
102}