canic_host/canister_build/
context.rs1use 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#[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_infrastructure_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 #[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 #[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 #[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 #[must_use]
77 pub(crate) fn artifact_root(&self) -> PathBuf {
78 self.release_build_id.map_or_else(
79 || self.icp_root.join(".icp/local/canisters"),
80 |release_build_id| {
81 self.icp_root
82 .join(".canic/release-builds")
83 .join(release_build_id.to_string())
84 .join("artifacts")
85 },
86 )
87 }
88
89 pub fn apply_to_command(&self, command: &mut Command) {
91 crate::build_environment::apply(command);
92 command
93 .env_remove(canic_core::role_contract::CANONICAL_CANDID_BUILD_ENV)
94 .env_remove(RELEASE_BUILD_ID_ENV)
95 .env_remove(canic_core::role_contract::build_context::PROTOCOL_BUILD_CONTEXT_ENV)
96 .env("ICP_ENVIRONMENT", self.build_network.as_str())
97 .env(
98 canic_core::role_contract::CANONICAL_BUILD_ICP_ROOT_ENV,
99 &self.icp_root,
100 )
101 .env(
102 canic_core::role_contract::CANONICAL_BUILD_CONFIG_PATH_ENV,
103 &self.config_path,
104 );
105 if let Some(release_build_id) = self.release_build_id {
106 command.env(RELEASE_BUILD_ID_ENV, release_build_id.to_string());
107 }
108 }
109}
110
111pub fn print_workspace_build_context_once(
114 context: &WorkspaceBuildContext,
115) -> Result<(), Box<dyn std::error::Error>> {
116 if workspace_build_context_once(context)? {
117 eprintln!("{}", context.lines().join("\n"));
118 }
119
120 Ok(())
121}
122
123pub fn workspace_build_context_once(
125 context: &WorkspaceBuildContext,
126) -> Result<bool, Box<dyn std::error::Error>> {
127 let marker_dir = context.icp_root.join(".icp");
128 fs::create_dir_all(&marker_dir)?;
129
130 let marker_key = icp_ancestor_process_id()
131 .or_else(parent_process_id)
132 .unwrap_or_else(std::process::id)
133 .to_string();
134 let marker_file = marker_dir.join(format!(".canic-build-context-{marker_key}"));
135
136 if marker_file.exists() {
137 return Ok(false);
138 }
139
140 fs::write(&marker_file, [])?;
141 Ok(true)
142}