greentic_component/scaffold/
deps.rs1use 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.70";
42const GREENTIC_INTERFACES_VERSION: &str = "0.4.70";
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}