canic_core/macros/start.rs
1//! # Canic Lifecycle Macros
2//!
3//! These macros define **compile-time lifecycle entry points** (`init` and `post_upgrade`)
4//! for Canic canisters. Lifecycle hooks must exist at the crate root with fixed names,
5//! so they cannot be registered dynamically — macros are therefore used to generate the
6//! boilerplate pre- and post-initialization logic automatically.
7//!
8//! Each macro sets up configuration, memory, timers, and TLS before calling user-defined
9//! async setup functions (`canic_setup`, `canic_install`, `canic_upgrade`), and then
10//! exposes the standard Canic endpoint suites.
11//!
12//! ## When to use which
13//!
14//! - [`macro@canic::start`] — for **non-root** canisters (standard services, workers, etc.).
15//! - [`macro@canic::start_root`] — for the **root orchestrator**, which performs
16//! additional initialization for global registries and root-only extensions.
17
18/// Configure lifecycle hooks for **non-root Canic canisters**.
19///
20/// This macro wires up the `init` and `post_upgrade` entry points required by the IC,
21/// performing pre-initialization steps (config, memory, TLS, environment) before invoking
22/// user async functions:
23///
24/// ```ignore
25/// async fn canic_setup() { /* shared setup */ }
26/// async fn canic_install(args: Option<Vec<u8>>) { /* called after init */ }
27/// async fn canic_upgrade() { /* called after post_upgrade */ }
28/// ```
29///
30/// These functions are spawned asynchronously after bootstrap completes.
31/// The macro also exposes the standard non-root Canic endpoint suites.
32///
33/// This macro must be used instead of a normal function because the IC runtime requires
34/// `init` and `post_upgrade` to be declared at the top level.
35
36#[macro_export]
37macro_rules! start {
38 ($canister_role:expr) => {
39 #[::canic::cdk::init]
40 fn init(payload: ::canic::core::ops::storage::CanisterInitPayload, args: Option<Vec<u8>>) {
41 ::canic::core::__canic_load_config!();
42
43 // ops
44 ::canic::core::ops::runtime::nonroot_init($canister_role, payload);
45
46 // timers — async body, no spawn()
47 let install_args = args;
48 let _ = ::canic::core::ops::ic::timer::TimerOps::set(
49 ::std::time::Duration::ZERO,
50 "startup:init",
51 async move {
52 canic_setup().await;
53 canic_install(install_args).await;
54 },
55 );
56 }
57
58 #[::canic::cdk::post_upgrade]
59 fn post_upgrade() {
60 ::canic::core::__canic_load_config!();
61
62 // ops
63 ::canic::core::ops::runtime::nonroot_post_upgrade($canister_role);
64
65 // timers — async body, no spawn()
66 let _ = ::canic::core::ops::ic::timer::TimerOps::set(
67 ::std::time::Duration::ZERO,
68 "startup:upgrade",
69 async move {
70 canic_setup().await;
71 canic_upgrade().await;
72 },
73 );
74 }
75
76 ::canic::core::canic_endpoints!();
77 ::canic::core::canic_endpoints_nonroot!();
78 };
79}
80
81///
82/// Configure lifecycle hooks for the **root Canic orchestrator canister**.
83///
84/// This macro behaves like [`macro@canic::start`], but includes additional
85/// root-only initialization for:
86///
87/// - the global subnet registry
88/// - root-only memory extensions and cycle tracking
89/// - the root endpoint suite
90///
91/// It generates the `init` and `post_upgrade` hooks required by the IC, loads embedded
92/// configuration, imports the root `WASMS` bundle, and runs pre- and post-upgrade logic
93/// in [`ops::runtime_lifecycle`].
94///
95/// Use this for the root orchestrator canister only. Other canisters should use
96/// [`macro@canic::start`].
97
98#[macro_export]
99macro_rules! start_root {
100 () => {
101 #[::canic::cdk::init]
102 fn init(identity: ::canic::core::ops::storage::topology::SubnetIdentity) {
103 ::canic::core::__canic_load_config!();
104
105 // ops
106 ::canic::core::ops::runtime::root_init(identity);
107
108 // import wasms
109 ::canic::core::ops::wasm::WasmOps::import_static(WASMS);
110
111 // timers
112 let _ = ::canic::core::ops::ic::timer::TimerOps::set(
113 std::time::Duration::ZERO,
114 "startup:root",
115 async move {
116 ::canic::core::ops::bootstrap::root::root_set_subnet_id().await;
117 ::canic::core::ops::bootstrap::root::root_import_pool_from_config().await;
118
119 // attempt to create canisters
120 if let Err(err) =
121 ::canic::core::ops::bootstrap::root::root_create_canisters().await
122 {
123 $crate::log!(
124 $crate::log::Topic::Init,
125 Error,
126 "root_create_canisters failed: {err}"
127 );
128 return;
129 }
130
131 canic_setup().await;
132 canic_install().await;
133 },
134 );
135 }
136
137 #[::canic::cdk::post_upgrade]
138 fn post_upgrade() {
139 ::canic::core::__canic_load_config!();
140 ::canic::core::ops::wasm::WasmOps::import_static(WASMS);
141
142 // ops
143 ::canic::core::ops::runtime::root_post_upgrade();
144
145 // timers
146 let _ = ::canic::core::ops::ic::timer::TimerOps::set(
147 ::std::time::Duration::ZERO,
148 "startup:root-upgrade",
149 async move {
150 canic_setup().await;
151 canic_upgrade().await;
152 },
153 );
154 }
155
156 ::canic::core::canic_endpoints!();
157 ::canic::core::canic_endpoints_root!();
158 };
159}
160
161//
162// Private helpers
163//
164
165///
166/// Load the embedded configuration during init and upgrade hooks.
167///
168/// This macro exists solely to embed and load the TOML configuration file
169/// at compile time (`CANIC_CONFIG_PATH`). It is used internally by
170/// [`macro@canic::start`] and [`macro@canic::start_root`].
171
172#[doc(hidden)]
173#[macro_export]
174macro_rules! __canic_load_config {
175 () => {{
176 let config_str = include_str!(env!("CANIC_CONFIG_PATH"));
177 if let Err(err) = $crate::config::Config::init_from_toml(config_str) {
178 $crate::cdk::println!(
179 "[canic] FATAL: config init failed (CANIC_CONFIG_PATH={}): {err}",
180 env!("CANIC_CONFIG_PATH")
181 );
182 let msg = format!(
183 "canic init failed: config init failed (CANIC_CONFIG_PATH={}): {err}",
184 env!("CANIC_CONFIG_PATH")
185 );
186 $crate::cdk::api::trap(&msg);
187 }
188 }};
189}