1use crate::{
2 canister_build::cache::DefaultCanisterBuildCacheCleanup,
3 release_set::{icp_root, workspace_root},
4};
5use config_selection::resolve_install_config_path;
6use std::{path::PathBuf, time::Instant};
7use thiserror::Error as ThisError;
8
9mod activation;
10mod artifact_promotion;
11mod build_environment;
12mod build_snapshot;
13mod build_targets;
14mod capabilities;
15mod clock;
16mod commands;
17mod config_selection;
18mod current_execution;
19mod deployment_registration;
20mod deployment_truth_gate;
21mod execution_preflight;
22mod identity;
23mod install_state;
24mod operations;
25mod options;
26mod output;
27mod phase_receipts;
28mod plan_artifacts;
29mod preparation;
30mod readiness;
31mod receipt_io;
32mod root_canister;
33mod root_cycles;
34mod root_verification;
35mod staging;
36mod state;
37mod timing;
38mod truth_check;
39
40use activation::run_root_activation_phases;
41use artifact_promotion::write_artifact_promotion_execution_receipt_for_install;
42use build_environment::resolve_install_build_context;
43use build_snapshot::resolve_install_snapshot;
44pub use config_selection::{
45 ConfigDiscoveryError, current_canic_project_root, discover_canic_config_choices,
46 discover_canic_project_root_from, discover_project_canic_config_choices, project_fleet_roots,
47 select_discovered_fleet_config_path,
48};
49use current_execution::current_install_execution_context;
50pub use deployment_registration::{
51 RegisterDeploymentStateOptions, VerifyDeploymentRootOptions, register_deployment_state,
52 verify_registered_deployment_root,
53};
54use identity::resolve_install_identity;
55use install_state::{build_install_state, write_install_state_with_deployment_truth_receipt};
56pub use options::InstallRootOptions;
57use output::{print_install_result_summary, print_install_timing_summary};
58use phase_receipts::InstallReceiptScope;
59use plan_artifacts::emit_manifest_with_deployment_truth_receipt;
60use preparation::prepare_install_deployment_truth;
61pub use receipt_io::latest_deployment_truth_receipt_path_from_root;
62pub use state::{
63 InstallState, InstallStateError, RootVerificationStatus, read_named_deployment_install_state,
64 read_named_deployment_install_state_from_root,
65};
66pub(crate) use state::{decode_install_state, validate_network_name};
67use timing::InstallTimingSummary as CurrentInstallTimingSummary;
68pub use truth_check::{check_install_deployment_truth, check_install_execution_preflight};
69
70#[cfg(test)]
71mod tests;
72
73#[derive(Clone, Copy, Debug, Eq, PartialEq)]
80pub enum InstallRootBlockKind {
81 DeploymentExecutionPreflight,
82 DeploymentTruth,
83}
84
85#[derive(Debug, ThisError)]
92#[error("{message}")]
93pub struct InstallRootBlockedError {
94 kind: InstallRootBlockKind,
95 message: String,
96}
97
98impl InstallRootBlockedError {
99 pub(super) const fn new(kind: InstallRootBlockKind, message: String) -> Self {
100 Self { kind, message }
101 }
102
103 #[must_use]
104 pub const fn kind(&self) -> InstallRootBlockKind {
105 self.kind
106 }
107}
108
109pub fn discover_current_canic_config_choices() -> Result<Vec<PathBuf>, ConfigDiscoveryError> {
111 let project_root = current_canic_project_root()?;
112 let choices = config_selection::discover_workspace_canic_config_choices(&project_root)?;
113 if !choices.is_empty() {
114 return Ok(choices);
115 }
116
117 if let Ok(icp_root) = icp_root()
118 && icp_root != project_root
119 {
120 return config_selection::discover_workspace_canic_config_choices(&icp_root);
121 }
122
123 Ok(choices)
124}
125
126pub fn install_root(options: InstallRootOptions) -> Result<(), Box<dyn std::error::Error>> {
128 let workspace_root = workspace_root()?;
129 let _build_cache_cleanup = DefaultCanisterBuildCacheCleanup::for_install(&workspace_root);
130 let icp_root = match &options.icp_root {
131 Some(path) => path.canonicalize()?,
132 None => icp_root()?,
133 };
134 let config_path = resolve_install_config_path(
135 &icp_root,
136 options.config_path.as_deref(),
137 options.interactive_config_selection,
138 )?;
139 let (build_context, install_snapshot) =
140 current_install_build_inputs(&workspace_root, &icp_root, &config_path, &options)?;
141 let (fleet_name, deployment_name) =
142 resolve_install_identity(&options, &config_path, &install_snapshot.fleet_name)?;
143 let total_started_at = Instant::now();
144 let mut timings = CurrentInstallTimingSummary::default();
145 let network = options.network.as_str();
146 let execution_context = current_install_execution_context(&workspace_root, &icp_root, network);
147
148 println!("Installing deployment {deployment_name}");
149 println!("Fleet template {fleet_name}");
150 println!();
151 let prepared = prepare_install_deployment_truth(
152 &options,
153 &icp_root,
154 &config_path,
155 &deployment_name,
156 &execution_context,
157 &build_context,
158 &install_snapshot,
159 )?;
160 timings.create_canisters = prepared.timings.create_canisters;
161 timings.build_all = prepared.timings.build_all;
162
163 let (manifest_path, emit_manifest_duration) = emit_manifest_with_deployment_truth_receipt(
164 &icp_root,
165 &options,
166 &deployment_name,
167 &prepared.deployment_truth_check,
168 &execution_context,
169 &install_snapshot,
170 &prepared.build_outputs,
171 )?;
172 timings.emit_manifest = emit_manifest_duration;
173 let activation_timings = run_root_activation_phases(
174 InstallReceiptScope {
175 icp_root: &icp_root,
176 network,
177 deployment_name: &deployment_name,
178 check: &prepared.deployment_truth_check,
179 execution_context: Some(&execution_context),
180 },
181 &options,
182 &prepared.root_canister_id,
183 &manifest_path,
184 total_started_at,
185 &build_context,
186 )?;
187 timings.install_root = activation_timings.install_root;
188 timings.fund_root = activation_timings.fund_root;
189 timings.stage_release_set = activation_timings.stage_release_set;
190 timings.resume_bootstrap = activation_timings.resume_bootstrap;
191 timings.wait_ready = activation_timings.wait_ready;
192 timings.finalize_root_funding = activation_timings.finalize_root_funding;
193
194 print_install_timing_summary(&timings, total_started_at.elapsed());
195 let state = build_install_state(
196 &options,
197 &workspace_root,
198 &icp_root,
199 &config_path,
200 &manifest_path,
201 (&deployment_name, &fleet_name),
202 &prepared.root_canister_id,
203 )?;
204 let state_path = write_install_state_with_deployment_truth_receipt(
205 InstallReceiptScope {
206 icp_root: &icp_root,
207 network,
208 deployment_name: &deployment_name,
209 check: &prepared.deployment_truth_check,
210 execution_context: Some(&execution_context),
211 },
212 &options.network,
213 &state,
214 )?;
215 write_artifact_promotion_execution_receipt_for_install(
216 &options,
217 &icp_root,
218 network,
219 &deployment_name,
220 &prepared.deployment_truth_check,
221 &execution_context,
222 )?;
223 print_install_result_summary(
224 &options.network,
225 &state.deployment_name,
226 &state.fleet_template,
227 &state_path,
228 );
229 Ok(())
230}
231
232fn current_install_build_inputs(
233 workspace_root: &std::path::Path,
234 icp_root: &std::path::Path,
235 config_path: &std::path::Path,
236 options: &InstallRootOptions,
237) -> Result<
238 (
239 crate::canister_build::WorkspaceBuildContext,
240 build_snapshot::ValidatedInstallSnapshot,
241 ),
242 Box<dyn std::error::Error>,
243> {
244 let context = resolve_install_build_context(
245 workspace_root,
246 icp_root,
247 config_path,
248 &options.network,
249 &options.root_build_target,
250 options.build_profile,
251 )?;
252 let snapshot = resolve_install_snapshot(
253 &context,
254 &options.root_build_target,
255 options.deployment_plan_override.is_some(),
256 )?;
257 Ok((context, snapshot))
258}