nornir 0.4.42

Companion to cargo: dependency tracking, release gating, deploy, benchmarks, and documentation assembly. Project-agnostic.
//! Multi-repo workspace descriptor.
//!
//! A *workspace* in nornir's sense is a collection of independent
//! repositories (each typically a cargo workspace of its own) that the
//! release pipeline treats as one unit. The descriptor is a single
//! `nornir-workspace.toml` listing every repo by name and giving its
//! source — either a local path or a git URL.
//!
//! The dependency-graph computation built from this descriptor lives
//! in [`crate::warehouse::dep_graph`] — graphs are historical artefacts
//! Urðr remembers, so the analysis + persistence are co-located there.
//! This module only parses the toml.

pub mod descriptor;
pub mod resolve;

pub use descriptor::{RepoSource, RepoSpec, WorkspaceDescriptor, WorkspaceMeta};
pub use resolve::{git_cache_dir, resolve_sources};

use std::path::{Path, PathBuf};

/// Walk up from `start` looking for a `nornir-workspace.toml`, returning the
/// first one found. This is the CLIENT-side convenience that lets a user stand
/// in a workspace dir (or any sub-directory of it) and run
/// `nornir workspace add <name>` without a `--descriptor` flag: we simply check
/// the current directory for a descriptor, then walk up a directory, repeating
/// to the filesystem root. Returns `None` when no descriptor is found anywhere
/// on the way up.
pub fn discover_descriptor(start: &Path) -> Option<PathBuf> {
    let mut cur = start
        .canonicalize()
        .unwrap_or_else(|_| start.to_path_buf());
    loop {
        let cand = cur.join("nornir-workspace.toml");
        if cand.is_file() {
            return Some(cand);
        }
        if !cur.pop() {
            return None;
        }
    }
}

#[cfg(test)]
mod discover_tests {
    use super::*;

    #[test]
    fn discovers_descriptor_walking_up_from_subdir() {
        let tmp = std::env::temp_dir().join(format!(
            "nornir-discover-{}-{}",
            std::process::id(),
            std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap()
                .as_nanos()
        ));
        let ws = tmp.join("workspace_demo");
        let deep = ws.join("sub/a/b");
        std::fs::create_dir_all(&deep).unwrap();
        let desc = ws.join("nornir-workspace.toml");
        std::fs::write(&desc, "[workspace]\nname = \"demo\"\n").unwrap();

        // From the workspace dir itself.
        let found = discover_descriptor(&ws).expect("found at ws root");
        assert_eq!(found.canonicalize().unwrap(), desc.canonicalize().unwrap());
        // From a deep sub-directory — walks up to the same descriptor.
        let found_deep = discover_descriptor(&deep).expect("found walking up");
        assert_eq!(
            found_deep.canonicalize().unwrap(),
            desc.canonicalize().unwrap()
        );

        let _ = std::fs::remove_dir_all(&tmp);
    }

    #[test]
    fn returns_none_when_no_descriptor() {
        let tmp = std::env::temp_dir().join(format!(
            "nornir-discover-none-{}-{}",
            std::process::id(),
            std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap()
                .as_nanos()
        ));
        std::fs::create_dir_all(&tmp).unwrap();
        // A temp dir tree with no descriptor on the way up to it — but / may have
        // none either, so just assert the immediate dir yields None up to where a
        // real descriptor could exist. We assert the descriptor in THIS subtree
        // is absent (parents are system dirs without nornir-workspace.toml).
        let sub = tmp.join("empty/here");
        std::fs::create_dir_all(&sub).unwrap();
        // There is no descriptor in tmp/empty/here or tmp/empty or tmp; whether a
        // parent system dir has one is environment-dependent, so we only assert
        // that the descriptor we did NOT create is not spuriously found within.
        assert!(!sub.join("nornir-workspace.toml").exists());
        let _ = std::fs::remove_dir_all(&tmp);
    }
}