greentic_component/scaffold/
deps.rs

1use std::env;
2use std::fs;
3use std::io;
4use std::path::{Path, PathBuf};
5
6use pathdiff::diff_paths;
7use serde::Serialize;
8use thiserror::Error;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
11#[serde(rename_all = "kebab-case")]
12pub enum DependencyMode {
13    Local,
14    CratesIo,
15}
16
17impl DependencyMode {
18    pub fn from_env() -> Self {
19        match env::var("GREENTIC_DEP_MODE") {
20            Ok(value) => match value.trim().to_ascii_lowercase().as_str() {
21                "cratesio" | "crates-io" | "crates_io" => DependencyMode::CratesIo,
22                "local" | "" => DependencyMode::Local,
23                _ => {
24                    eprintln!("Unknown GREENTIC_DEP_MODE='{value}', defaulting to local mode");
25                    DependencyMode::Local
26                }
27            },
28            Err(_) => DependencyMode::Local,
29        }
30    }
31
32    pub fn as_str(&self) -> &'static str {
33        match self {
34            DependencyMode::Local => "local",
35            DependencyMode::CratesIo => "cratesio",
36        }
37    }
38}
39
40const GREENTIC_TYPES_VERSION: &str = "0.4";
41const GREENTIC_INTERFACES_GUEST_VERSION: &str = "0.4";
42const GREENTIC_INTERFACES_VERSION: &str = "0.4";
43
44#[derive(Debug, Clone)]
45pub struct DependencyTemplates {
46    pub greentic_interfaces: String,
47    pub greentic_interfaces_guest: String,
48    pub greentic_types: String,
49    pub relative_patch_path: Option<String>,
50}
51
52#[derive(Debug, Error)]
53pub enum DependencyError {
54    #[error("crates.io dependency mode forbids `path =` entries in {manifest}")]
55    PathDependency { manifest: PathBuf },
56    #[error("failed to read manifest {manifest}: {source}")]
57    Io {
58        manifest: PathBuf,
59        #[source]
60        source: io::Error,
61    },
62}
63
64pub fn resolve_dependency_templates(
65    mode: DependencyMode,
66    target_path: &Path,
67) -> DependencyTemplates {
68    match mode {
69        DependencyMode::Local => DependencyTemplates {
70            greentic_interfaces: format!("version = \"{GREENTIC_INTERFACES_VERSION}\""),
71            greentic_interfaces_guest: format!("version = \"{GREENTIC_INTERFACES_GUEST_VERSION}\""),
72            greentic_types: format!("version = \"{GREENTIC_TYPES_VERSION}\""),
73            relative_patch_path: local_patch_path(target_path),
74        },
75        DependencyMode::CratesIo => DependencyTemplates {
76            greentic_interfaces: format!("version = \"{GREENTIC_INTERFACES_VERSION}\""),
77            greentic_interfaces_guest: format!("version = \"{GREENTIC_INTERFACES_GUEST_VERSION}\""),
78            greentic_types: format!("version = \"{GREENTIC_TYPES_VERSION}\""),
79            relative_patch_path: None,
80        },
81    }
82}
83
84fn local_patch_path(scaffold_root: &Path) -> Option<String> {
85    let repo_root = workspace_root();
86    let crate_root = repo_root.join("crates/greentic-component");
87    if !crate_root.exists() {
88        return None;
89    }
90    Some(greentic_component_patch_path(scaffold_root, &repo_root))
91}
92
93fn workspace_root() -> PathBuf {
94    let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
95    manifest_dir
96        .parent()
97        .and_then(|p| p.parent())
98        .unwrap_or(manifest_dir)
99        .to_path_buf()
100}
101
102fn greentic_component_patch_path(scaffold_root: &Path, repo_root: &Path) -> String {
103    let abs = repo_root.join("crates/greentic-component");
104    let rel = diff_paths(&abs, scaffold_root).unwrap_or(abs);
105    format!(r#"path = "{}""#, rel.display())
106}
107
108pub fn ensure_cratesio_manifest_clean(root: &Path) -> Result<(), DependencyError> {
109    let manifest = root.join("Cargo.toml");
110    let contents = fs::read_to_string(&manifest).map_err(|source| DependencyError::Io {
111        manifest: manifest.clone(),
112        source,
113    })?;
114    if contents.contains("path =") {
115        return Err(DependencyError::PathDependency { manifest });
116    }
117    Ok(())
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123    use assert_fs::TempDir;
124
125    #[test]
126    fn cratesio_manifest_rejects_path_dependencies() {
127        let temp = TempDir::new().unwrap();
128        let manifest = temp.path().join("Cargo.toml");
129        std::fs::write(&manifest, "[dependencies]\nfoo = { path = \"../foo\" }\n").unwrap();
130        let err = ensure_cratesio_manifest_clean(temp.path()).unwrap_err();
131        match err {
132            DependencyError::PathDependency { manifest: path } => assert_eq!(path, manifest),
133            other => panic!("unexpected error {other:?}"),
134        }
135    }
136
137    #[test]
138    fn cratesio_manifest_accepts_version_dependencies() {
139        let temp = TempDir::new().unwrap();
140        std::fs::write(
141            temp.path().join("Cargo.toml"),
142            "[dependencies]\nfoo = \"0.1\"\n",
143        )
144        .unwrap();
145        ensure_cratesio_manifest_clean(temp.path()).unwrap();
146    }
147}