Skip to main content

doido_generators/
dev_workspace.rs

1//! Runtime detection of whether the running `doido` / `doido-generators` binary
2//! lives inside a local Doido framework checkout (path deps) or is an isolated /
3//! published install (crates.io version deps matching this binary's release).
4
5use std::path::{Path, PathBuf};
6
7/// How generated apps should depend on first-party `doido-*` crates.
8#[derive(Clone, Debug, PartialEq, Eq)]
9pub struct DependencyMode {
10    /// Use `path = "…"` deps against the discovered workspace root.
11    pub use_path: bool,
12    /// Absolute workspace root when [`Self::use_path`] is true.
13    pub workspace_path: String,
14    /// Crates.io version pinned when [`Self::use_path`] is false.
15    pub version: &'static str,
16}
17
18impl DependencyMode {
19    /// Resolve dependency style from the running binary's location on disk.
20    pub fn resolve() -> Self {
21        let version = env!("CARGO_PKG_VERSION");
22        if let Some(root) = find_dev_workspace_root() {
23            Self {
24                use_path: true,
25                workspace_path: root.display().to_string(),
26                version,
27            }
28        } else {
29            Self {
30                use_path: false,
31                workspace_path: String::new(),
32                version,
33            }
34        }
35    }
36}
37
38/// Returns the workspace root when `path` looks like a Doido framework checkout.
39pub fn is_doido_workspace(path: &Path) -> bool {
40    path.join("doido").join("Cargo.toml").is_file()
41        && path.join("doido-generators").join("Cargo.toml").is_file()
42        && path.join("doido-core").join("Cargo.toml").is_file()
43}
44
45/// Walks upward from the running executable until a Doido workspace is found.
46pub fn find_dev_workspace_root() -> Option<PathBuf> {
47    let exe = std::env::current_exe().ok()?;
48    for ancestor in exe.ancestors() {
49        if is_doido_workspace(ancestor) {
50            return ancestor
51                .canonicalize()
52                .ok()
53                .or_else(|| Some(ancestor.to_path_buf()));
54        }
55    }
56    None
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62    use std::fs;
63    use tempfile::tempdir;
64
65    #[test]
66    fn is_doido_workspace_requires_core_crates() {
67        let dir = tempdir().unwrap();
68        assert!(!is_doido_workspace(dir.path()));
69
70        fs::create_dir_all(dir.path().join("doido")).unwrap();
71        fs::write(
72            dir.path().join("doido/Cargo.toml"),
73            "[package]\nname = \"doido\"\n",
74        )
75        .unwrap();
76        assert!(!is_doido_workspace(dir.path()));
77
78        fs::create_dir_all(dir.path().join("doido-generators")).unwrap();
79        fs::write(
80            dir.path().join("doido-generators/Cargo.toml"),
81            "[package]\nname = \"doido-generators\"\n",
82        )
83        .unwrap();
84        fs::create_dir_all(dir.path().join("doido-core")).unwrap();
85        fs::write(
86            dir.path().join("doido-core/Cargo.toml"),
87            "[package]\nname = \"doido-core\"\n",
88        )
89        .unwrap();
90        assert!(is_doido_workspace(dir.path()));
91    }
92
93    #[test]
94    fn resolve_uses_path_when_test_binary_runs_inside_checkout() {
95        let mode = DependencyMode::resolve();
96        // Integration/unit tests execute from `<workspace>/target/debug/deps/…`.
97        if find_dev_workspace_root().is_some() {
98            assert!(mode.use_path);
99            assert!(mode.workspace_path.contains("doido"));
100        } else {
101            assert!(!mode.use_path);
102        }
103    }
104}