Skip to main content

canic_host/canister_build/
context.rs

1use std::{fs, path::PathBuf, process::Command};
2
3use canic_core::ids::BuildNetwork;
4
5use crate::icp::LocalReplicaTarget;
6
7use super::{
8    CanisterBuildProfile,
9    process::{icp_ancestor_process_id, parent_process_id},
10};
11
12/// Exact authority for one canister artifact build.
13///
14
15#[derive(Clone, Debug, Eq, PartialEq)]
16pub struct WorkspaceBuildContext {
17    pub role: String,
18    pub profile: CanisterBuildProfile,
19    pub environment: String,
20    pub build_network: BuildNetwork,
21    pub workspace_root: PathBuf,
22    pub icp_root: PathBuf,
23    pub config_path: PathBuf,
24    pub local_replica: Option<LocalReplicaTarget>,
25    pub refresh_canonical_wasm_store_did: bool,
26}
27
28impl WorkspaceBuildContext {
29    #[must_use]
30    pub fn lines(&self) -> Vec<String> {
31        let mut lines = vec![
32            "Canic build:".to_string(),
33            format!("role: {}", self.role),
34            format!("profile: {}", self.profile.target_dir_name()),
35            format!("environment: {}", self.environment),
36            format!("build network: {}", self.build_network),
37            format!("workspace: {}", self.workspace_root.display()),
38        ];
39
40        if self.icp_root != self.workspace_root {
41            lines.push(format!("icp root: {}", self.icp_root.display()));
42        }
43
44        lines
45    }
46
47    /// Return a copy using a different Cargo profile for one child build.
48    #[must_use]
49    pub fn with_profile(&self, profile: CanisterBuildProfile) -> Self {
50        let mut context = self.clone();
51        context.profile = profile;
52        context
53    }
54
55    /// Return a copy selecting another role in the same workspace authority.
56    #[must_use]
57    pub fn with_role(&self, role: impl Into<String>) -> Self {
58        let mut context = self.clone();
59        context.role = role.into();
60        context
61    }
62
63    /// Apply the exact Canic build authority to one child command.
64    pub fn apply_to_command(&self, command: &mut Command) {
65        command
66            .env("ICP_ENVIRONMENT", self.build_network.as_str())
67            .env(
68                canic_core::role_contract::CANONICAL_BUILD_ICP_ROOT_ENV,
69                &self.icp_root,
70            )
71            .env(
72                canic_core::role_contract::CANONICAL_BUILD_CONFIG_PATH_ENV,
73                &self.config_path,
74            );
75    }
76}
77
78// Print the current build context once per caller session so caller builds
79// stay readable without repeating root/profile diagnostics for every canister.
80pub fn print_workspace_build_context_once(
81    context: &WorkspaceBuildContext,
82) -> Result<(), Box<dyn std::error::Error>> {
83    if workspace_build_context_once(context)? {
84        eprintln!("{}", context.lines().join("\n"));
85    }
86
87    Ok(())
88}
89
90// Return whether this caller should print its explicit build context.
91pub fn workspace_build_context_once(
92    context: &WorkspaceBuildContext,
93) -> Result<bool, Box<dyn std::error::Error>> {
94    let marker_dir = context.icp_root.join(".icp");
95    fs::create_dir_all(&marker_dir)?;
96
97    let marker_key = icp_ancestor_process_id()
98        .or_else(parent_process_id)
99        .unwrap_or_else(std::process::id)
100        .to_string();
101    let marker_file = marker_dir.join(format!(".canic-build-context-{marker_key}"));
102
103    if marker_file.exists() {
104        return Ok(false);
105    }
106
107    fs::write(&marker_file, [])?;
108    Ok(true)
109}