Skip to main content

cargo_airbender/commands/new/
deps.rs

1use crate::error::{CliError, Result};
2use std::path::{Path, PathBuf};
3
4const DEFAULT_SDK_GIT_REPOSITORY: &str = "https://github.com/matter-labs/airbender-platform";
5const DEFAULT_SDK_GIT_BRANCH: &str = "main";
6
7pub(super) fn resolve_crate_dependency(
8    destination_crate_dir: &Path,
9    sdk_path: Option<&Path>,
10    sdk_version: Option<&str>,
11    crate_name: &str,
12) -> Result<String> {
13    if let Some(version) = sdk_version {
14        if version.is_empty() {
15            return Err(CliError::new("`--sdk-version` cannot be empty"));
16        }
17
18        return Ok(format!("version = \"{version}\""));
19    }
20
21    if let Some(sdk_path) = sdk_path {
22        if !sdk_path.exists() {
23            return Err(
24                CliError::new(format!("SDK path `{}` does not exist", sdk_path.display()))
25                    .with_hint(
26                        "pass `--sdk-path` pointing to an existing airbender-platform checkout",
27                    ),
28            );
29        }
30
31        let crate_path = resolve_dependency_crate_path(sdk_path, crate_name)?;
32        let sdk_relative = relative_path(destination_crate_dir, &crate_path)?;
33        return Ok(format!("path = \"{}\"", sdk_relative.to_string_lossy()));
34    }
35
36    Ok(format!(
37        "git = \"{DEFAULT_SDK_GIT_REPOSITORY}\", branch = \"{DEFAULT_SDK_GIT_BRANCH}\""
38    ))
39}
40
41fn resolve_dependency_crate_path(sdk_path: &Path, crate_name: &str) -> Result<PathBuf> {
42    let mut candidates = Vec::new();
43
44    if sdk_path
45        .file_name()
46        .map(|name| name == crate_name)
47        .unwrap_or(false)
48    {
49        candidates.push(sdk_path.to_path_buf());
50    }
51    candidates.push(sdk_path.join(crate_name));
52    candidates.push(sdk_path.join("crates").join(crate_name));
53    if let Some(parent) = sdk_path.parent() {
54        candidates.push(parent.join(crate_name));
55    }
56
57    for candidate in candidates {
58        if candidate.join("Cargo.toml").exists() {
59            return candidate.canonicalize().map_err(|err| {
60                CliError::with_source(
61                    format!("failed to canonicalize `{}`", candidate.display()),
62                    err,
63                )
64            });
65        }
66    }
67
68    Err(CliError::new(format!(
69        "failed to locate `{crate_name}` under `{}`",
70        sdk_path.display()
71    ))
72    .with_hint("point `--sdk-path` to the workspace root or crate directory"))
73}
74
75fn relative_path(from: &Path, to: &Path) -> Result<PathBuf> {
76    let from = from.canonicalize().map_err(|err| {
77        CliError::with_source(format!("failed to canonicalize `{}`", from.display()), err)
78    })?;
79    let to = to.canonicalize().map_err(|err| {
80        CliError::with_source(format!("failed to canonicalize `{}`", to.display()), err)
81    })?;
82
83    let from_components: Vec<_> = from.components().collect();
84    let to_components: Vec<_> = to.components().collect();
85
86    let mut common_len = 0usize;
87    while common_len < from_components.len()
88        && common_len < to_components.len()
89        && from_components[common_len] == to_components[common_len]
90    {
91        common_len += 1;
92    }
93
94    let mut result = PathBuf::new();
95    for _ in common_len..from_components.len() {
96        result.push("..");
97    }
98    for component in &to_components[common_len..] {
99        result.push(component.as_os_str());
100    }
101
102    Ok(result)
103}