Skip to main content

canic_host/release_set/
paths.rs

1use crate::workspace_discovery::{
2    discover_canister_manifest_from_metadata, discover_icp_root_from, discover_workspace_root_from,
3    normalize_workspace_path,
4};
5use std::{
6    fs,
7    path::{Path, PathBuf},
8};
9use toml::Value as TomlValue;
10
11use super::{
12    CANISTERS_ROOT_RELATIVE, ROOT_CONFIG_FILE, ROOT_RELEASE_SET_MANIFEST_FILE,
13    WORKSPACE_MANIFEST_RELATIVE,
14};
15
16// Resolve the downstream Cargo workspace root from the current directory,
17// config hints, or an explicit override.
18pub fn workspace_root() -> Result<PathBuf, Box<dyn std::error::Error>> {
19    if let Ok(path) = std::env::var("CANIC_WORKSPACE_ROOT") {
20        return Ok(PathBuf::from(path).canonicalize()?);
21    }
22
23    if let Some(root) = std::env::var_os("CANIC_WORKSPACE_MANIFEST_PATH")
24        .map(PathBuf::from)
25        .and_then(|path| discover_workspace_root_from(&path))
26    {
27        return Ok(root);
28    }
29
30    if let Some(root) = std::env::var_os("CANIC_CONFIG_PATH")
31        .map(PathBuf::from)
32        .and_then(|path| discover_workspace_root_from(&path))
33    {
34        return Ok(root);
35    }
36
37    if let Some(root) = discover_workspace_root_from(&std::env::current_dir()?) {
38        return Ok(root);
39    }
40
41    Ok(std::env::current_dir()?.canonicalize()?)
42}
43
44// Resolve the downstream ICP CLI/project root from the current directory or an
45// explicit override.
46pub fn icp_root() -> Result<PathBuf, Box<dyn std::error::Error>> {
47    if let Ok(path) = std::env::var("CANIC_ICP_ROOT") {
48        return Ok(PathBuf::from(path).canonicalize()?);
49    }
50
51    let current_dir = std::env::current_dir()?.canonicalize()?;
52    if let Some(root) = discover_icp_root_from(&current_dir) {
53        return Ok(root);
54    }
55
56    if let Ok(path) = std::env::var("CANIC_WORKSPACE_ROOT") {
57        let workspace_root = PathBuf::from(path).canonicalize()?;
58        if let Some(root) = discover_icp_root_from(&workspace_root) {
59            return Ok(root);
60        }
61        return Ok(workspace_root);
62    }
63
64    Ok(current_dir)
65}
66
67// Resolve the downstream Canic config path.
68#[must_use]
69pub fn config_path(workspace_root: &Path) -> PathBuf {
70    std::env::var_os("CANIC_CONFIG_PATH").map_or_else(
71        || canisters_root(workspace_root).join(ROOT_CONFIG_FILE),
72        |path| normalize_workspace_path(workspace_root, PathBuf::from(path)),
73    )
74}
75
76// Resolve the downstream canister-manifest root.
77#[must_use]
78pub fn canisters_root(workspace_root: &Path) -> PathBuf {
79    if let Some(path) = std::env::var_os("CANIC_CANISTERS_ROOT") {
80        return normalize_workspace_path(workspace_root, PathBuf::from(path));
81    }
82
83    if let Some(path) = std::env::var_os("CANIC_CONFIG_PATH") {
84        let config_path = normalize_workspace_path(workspace_root, PathBuf::from(path));
85        if let Some(parent) = config_path.parent() {
86            return parent.to_path_buf();
87        }
88    }
89
90    if let Some(manifest_path) = discover_canister_manifest_from_metadata(workspace_root, "root")
91        && let Some(parent) = manifest_path.parent().and_then(Path::parent)
92    {
93        return parent.to_path_buf();
94    }
95
96    workspace_root.join(CANISTERS_ROOT_RELATIVE)
97}
98
99// Resolve the downstream root canister manifest path.
100#[must_use]
101pub fn root_manifest_path(workspace_root: &Path) -> PathBuf {
102    std::env::var_os("CANIC_ROOT_MANIFEST_PATH").map_or_else(
103        || {
104            discover_canister_manifest_from_metadata(workspace_root, "root").unwrap_or_else(|| {
105                canisters_root(workspace_root)
106                    .join("root")
107                    .join("Cargo.toml")
108            })
109        },
110        |path| normalize_workspace_path(workspace_root, PathBuf::from(path)),
111    )
112}
113
114// Resolve the downstream manifest path for one visible canister role.
115#[must_use]
116pub fn canister_manifest_path(workspace_root: &Path, canister_name: &str) -> PathBuf {
117    discover_canister_manifest_from_metadata(workspace_root, canister_name).unwrap_or_else(|| {
118        canisters_root(workspace_root)
119            .join(canister_name)
120            .join("Cargo.toml")
121    })
122}
123
124// Resolve the downstream workspace manifest path.
125#[must_use]
126pub fn workspace_manifest_path(workspace_root: &Path) -> PathBuf {
127    std::env::var_os("CANIC_WORKSPACE_MANIFEST_PATH").map_or_else(
128        || workspace_root.join(WORKSPACE_MANIFEST_RELATIVE),
129        |path| normalize_workspace_path(workspace_root, PathBuf::from(path)),
130    )
131}
132
133// Render a path relative to the workspace root when possible.
134#[must_use]
135pub fn display_workspace_path(workspace_root: &Path, path: &Path) -> String {
136    path.strip_prefix(workspace_root)
137        .unwrap_or(path)
138        .display()
139        .to_string()
140}
141
142// Resolve the built artifact directory for the selected ICP environment.
143pub fn resolve_artifact_root(
144    icp_root: &Path,
145    network: &str,
146) -> Result<PathBuf, Box<dyn std::error::Error>> {
147    let preferred = icp_root.join(".icp").join(network).join("canisters");
148    if preferred.is_dir() {
149        return Ok(preferred);
150    }
151
152    let local_artifact_root = icp_root.join(".icp/local/canisters");
153    if local_artifact_root.is_dir() {
154        return Ok(local_artifact_root);
155    }
156
157    Err(format!(
158        "missing built ICP artifacts under {} or {}",
159        preferred.display(),
160        local_artifact_root.display()
161    )
162    .into())
163}
164
165// Return the canonical manifest path for the staged root release set.
166pub fn root_release_set_manifest_path(
167    artifact_root: &Path,
168) -> Result<PathBuf, Box<dyn std::error::Error>> {
169    let manifest_path = artifact_root
170        .join("root")
171        .join(ROOT_RELEASE_SET_MANIFEST_FILE);
172
173    if let Some(parent) = manifest_path.parent() {
174        fs::create_dir_all(parent)?;
175    }
176
177    Ok(manifest_path)
178}
179
180// Read the reference root canister version so staged release versions match the install.
181pub fn load_root_package_version(
182    root_manifest_path: &Path,
183    workspace_manifest_path: &Path,
184) -> Result<String, Box<dyn std::error::Error>> {
185    let manifest_source = fs::read_to_string(root_manifest_path)?;
186    let manifest = toml::from_str::<TomlValue>(&manifest_source)?;
187    let version_value = manifest
188        .get("package")
189        .and_then(TomlValue::as_table)
190        .and_then(|package| package.get("version"))
191        .ok_or_else(|| {
192            format!(
193                "missing package.version in {}",
194                root_manifest_path.display()
195            )
196        })?;
197
198    if let Some(version) = version_value.as_str() {
199        return Ok(version.to_string());
200    }
201
202    if version_value
203        .as_table()
204        .and_then(|value| value.get("workspace"))
205        .and_then(TomlValue::as_bool)
206        == Some(true)
207    {
208        return load_workspace_package_version(workspace_manifest_path);
209    }
210
211    Err(format!(
212        "unsupported package.version format in {}",
213        root_manifest_path.display()
214    )
215    .into())
216}
217
218// Resolve the shared workspace package version used by reference canisters.
219pub fn load_workspace_package_version(
220    workspace_manifest_path: &Path,
221) -> Result<String, Box<dyn std::error::Error>> {
222    let manifest_source = fs::read_to_string(workspace_manifest_path)?;
223    let manifest = toml::from_str::<TomlValue>(&manifest_source)?;
224    let version = manifest
225        .get("workspace")
226        .and_then(TomlValue::as_table)
227        .and_then(|workspace| workspace.get("package"))
228        .and_then(TomlValue::as_table)
229        .and_then(|package| package.get("version"))
230        .and_then(TomlValue::as_str)
231        .ok_or_else(|| {
232            format!(
233                "missing workspace.package.version in {}",
234                workspace_manifest_path.display()
235            )
236        })?;
237
238    Ok(version.to_string())
239}