Skip to main content

canic_host/canister_build/
context.rs

1use std::{fs, path::PathBuf, process::Command};
2
3use crate::icp::LocalReplicaTarget;
4
5use super::{
6    CanisterBuildProfile,
7    process::{icp_ancestor_process_id, parent_process_id},
8};
9
10const LOCAL_NETWORK_URL_ENV: &str = "CANIC_ICP_LOCAL_NETWORK_URL";
11const LOCAL_ROOT_KEY_ENV: &str = "CANIC_ICP_LOCAL_ROOT_KEY";
12
13/// Exact authority for one canister artifact build.
14///
15
16#[derive(Clone, Debug, Eq, PartialEq)]
17pub struct WorkspaceBuildContext {
18    pub role: String,
19    pub profile: CanisterBuildProfile,
20    pub requested_profile: String,
21    pub environment: String,
22    pub build_network: String,
23    pub workspace_root: PathBuf,
24    pub icp_root: PathBuf,
25    pub config_path: PathBuf,
26    pub local_replica: Option<LocalReplicaTarget>,
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.requested_profile != "unset" {
42            lines.push(format!("requested profile: {}", self.requested_profile));
43        }
44        if self.icp_root != self.workspace_root {
45            lines.push(format!("icp root: {}", self.icp_root.display()));
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    /// Apply the exact Canic build authority to one child command.
68    pub fn apply_to_command(&self, command: &mut Command) {
69        command
70            .env("ICP_ENVIRONMENT", &self.build_network)
71            .env_remove("CANIC_ICP_BUILD_ENVIRONMENT")
72            .env("CANIC_WORKSPACE_ROOT", &self.workspace_root)
73            .env("CANIC_ICP_ROOT", &self.icp_root)
74            .env("CANIC_CONFIG_PATH", &self.config_path);
75        if let Some(local_replica) = &self.local_replica {
76            command
77                .env(LOCAL_NETWORK_URL_ENV, &local_replica.url)
78                .env(LOCAL_ROOT_KEY_ENV, &local_replica.root_key);
79        } else {
80            command
81                .env_remove(LOCAL_NETWORK_URL_ENV)
82                .env_remove(LOCAL_ROOT_KEY_ENV);
83        }
84    }
85}
86
87// Print the current build context once per caller session so caller builds
88// stay readable without repeating root/profile diagnostics for every canister.
89pub fn print_workspace_build_context_once(
90    context: &WorkspaceBuildContext,
91) -> Result<(), Box<dyn std::error::Error>> {
92    if workspace_build_context_once(context)? {
93        eprintln!("{}", context.lines().join("\n"));
94    }
95
96    Ok(())
97}
98
99// Return whether this caller should print its explicit build context.
100pub fn workspace_build_context_once(
101    context: &WorkspaceBuildContext,
102) -> Result<bool, Box<dyn std::error::Error>> {
103    let marker_dir = context.icp_root.join(".icp");
104    fs::create_dir_all(&marker_dir)?;
105
106    let marker_key = icp_ancestor_process_id()
107        .or_else(parent_process_id)
108        .unwrap_or_else(std::process::id)
109        .to_string();
110    let marker_file = marker_dir.join(format!(".canic-build-context-{marker_key}"));
111
112    if marker_file.exists() {
113        return Ok(false);
114    }
115
116    fs::write(&marker_file, [])?;
117    Ok(true)
118}