Skip to main content

canic_host/canister_build/
context.rs

1use std::{fs, path::PathBuf, process::Command};
2
3use canic_core::ids::{BuildNetwork, RELEASE_BUILD_ID_ENV, ReleaseBuildId};
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    pub release_build_id: Option<ReleaseBuildId>,
27}
28
29impl WorkspaceBuildContext {
30    #[must_use]
31    pub fn lines(&self) -> Vec<String> {
32        let mut lines = vec![
33            "Canic build:".to_string(),
34            format!("role: {}", self.role),
35            format!("profile: {}", self.profile.target_dir_name()),
36            format!("environment: {}", self.environment),
37            format!("build network: {}", self.build_network),
38            format!("workspace: {}", self.workspace_root.display()),
39        ];
40
41        if self.icp_root != self.workspace_root {
42            lines.push(format!("icp root: {}", self.icp_root.display()));
43        }
44        if let Some(release_build_id) = self.release_build_id {
45            lines.push(format!("release build: {release_build_id}"));
46        }
47
48        lines
49    }
50
51    /// Return a copy using a different Cargo profile for one child build.
52    #[must_use]
53    pub fn with_profile(&self, profile: CanisterBuildProfile) -> Self {
54        let mut context = self.clone();
55        context.profile = profile;
56        context
57    }
58
59    /// Return a copy selecting another role in the same workspace authority.
60    #[must_use]
61    pub fn with_role(&self, role: impl Into<String>) -> Self {
62        let mut context = self.clone();
63        context.role = role.into();
64        context
65    }
66
67    /// Return a copy bound to one already-durable release-build plan.
68    #[must_use]
69    pub fn with_release_build_id(&self, release_build_id: ReleaseBuildId) -> Self {
70        let mut context = self.clone();
71        context.release_build_id = Some(release_build_id);
72        context
73    }
74
75    /// Apply the exact Canic build authority to one child command.
76    pub fn apply_to_command(&self, command: &mut Command) {
77        command
78            .env_remove(RELEASE_BUILD_ID_ENV)
79            .env("ICP_ENVIRONMENT", self.build_network.as_str())
80            .env(
81                canic_core::role_contract::CANONICAL_BUILD_ICP_ROOT_ENV,
82                &self.icp_root,
83            )
84            .env(
85                canic_core::role_contract::CANONICAL_BUILD_CONFIG_PATH_ENV,
86                &self.config_path,
87            );
88        if let Some(release_build_id) = self.release_build_id {
89            command.env(RELEASE_BUILD_ID_ENV, release_build_id.to_string());
90        }
91    }
92}
93
94// Print the current build context once per caller session so caller builds
95// stay readable without repeating root/profile diagnostics for every canister.
96pub fn print_workspace_build_context_once(
97    context: &WorkspaceBuildContext,
98) -> Result<(), Box<dyn std::error::Error>> {
99    if workspace_build_context_once(context)? {
100        eprintln!("{}", context.lines().join("\n"));
101    }
102
103    Ok(())
104}
105
106// Return whether this caller should print its explicit build context.
107pub fn workspace_build_context_once(
108    context: &WorkspaceBuildContext,
109) -> Result<bool, Box<dyn std::error::Error>> {
110    let marker_dir = context.icp_root.join(".icp");
111    fs::create_dir_all(&marker_dir)?;
112
113    let marker_key = icp_ancestor_process_id()
114        .or_else(parent_process_id)
115        .unwrap_or_else(std::process::id)
116        .to_string();
117    let marker_file = marker_dir.join(format!(".canic-build-context-{marker_key}"));
118
119    if marker_file.exists() {
120        return Ok(false);
121    }
122
123    fs::write(&marker_file, [])?;
124    Ok(true)
125}