Skip to main content

callisto_graph/
lib.rs

1//! Dependency graph, cascade, aggregation, and config resolution for callisto.
2
3#![allow(clippy::result_large_err)]
4
5use std::collections::BTreeMap;
6use std::path::PathBuf;
7
8use callisto_model::{CommandRunner, PackageId, Version};
9
10pub mod aggregate;
11pub mod apply;
12pub mod cascade;
13pub mod changed;
14pub mod commands;
15pub mod config;
16pub mod crosscheck;
17pub mod error;
18pub mod groups;
19pub mod identity;
20pub mod infer;
21pub mod locate;
22pub mod napi;
23pub mod plan;
24pub mod resolver;
25pub mod tags;
26pub mod toposort;
27pub mod walk;
28
29pub use aggregate::{aggregate, load_changesets, Aggregation, LoadedChangeset, NamedBy};
30pub use apply::{apply_version_plan, ApplyOptions, ApplyOutcome};
31pub use cascade::{
32    cascade_action, coverage, rewrite_spec, run_cascade, CascadeDecision, CascadeInput,
33    CascadeOutcome, DepWriteTarget, RewriteKey, RewriteOutcome, SpecRewrite,
34};
35pub use config::{load as load_config, GroupDef, GroupTable, ResolvedConfig};
36pub use error::{ConfigError, GraphError};
37pub use groups::{fixed_group_target, pre_mutation_checks, GroupCheckOutcome};
38pub use identity::{IdentityIndex, IdentityResolver};
39pub use infer::{InferenceOutcome, InferenceWindowSpec, NoInference, SeverityInference};
40pub use locate::{find_workspace_root, IgnoreWalkLocator, LocateError, ProjectLocator};
41pub use napi::{napi_drift, role_to_triple, triple_to_role, NapiTargetsIndex};
42pub use plan::{PlannedBump, VersionPlan, VersionWriteTarget};
43pub use resolver::{DependencyResolver, ManifestWalkResolver};
44pub use tags::{last_tag_for, TagIndex};
45pub use toposort::toposort_impl;
46
47pub struct Workspace<'a, R: CommandRunner, D: DependencyResolver = ManifestWalkResolver> {
48    pub root: PathBuf,
49    pub config: ResolvedConfig,
50    pub graph: D,
51    pub tags: TagIndex,
52    pub runner: &'a R,
53}
54
55impl<'a, R: CommandRunner> Workspace<'a, R, ManifestWalkResolver> {
56    pub fn load<L: ProjectLocator>(
57        root: PathBuf,
58        locator: &L,
59        runner: &'a R,
60    ) -> Result<Self, GraphError> {
61        let config = config::load(&root)?;
62        let graph = ManifestWalkResolver::build(&root, locator, runner, &config)?;
63        let tags = TagIndex::build(runner, &root, &graph, &config)?;
64
65        Ok(Workspace {
66            root,
67            config,
68            graph,
69            tags,
70            runner,
71        })
72    }
73}
74
75impl<'a, R: CommandRunner, D: DependencyResolver> Workspace<'a, R, D> {
76    pub fn base_versions(&self) -> Result<BTreeMap<PackageId, Version>, GraphError> {
77        let cargo_workspace = if self.root.join("Cargo.toml").exists() {
78            if let Ok(resolver) =
79                callisto_manifests::WorkspaceCargoResolver::load(&self.root.join("Cargo.toml"))
80            {
81                resolver.inheritance().ok().map(std::sync::Arc::new)
82            } else {
83                None
84            }
85        } else {
86            None
87        };
88        let npm_workspace_kind = callisto_manifests::detect_npm_workspace_kind(&self.root)
89            .ok()
90            .flatten();
91        let ctx = callisto_manifests::OpenContext {
92            workspace_root: &self.root,
93            cargo_workspace,
94            npm_workspace_kind,
95        };
96
97        let mut versions = BTreeMap::new();
98        for pkg in self.graph.packages() {
99            let mut found_version = None;
100            for decl in &pkg.manifests {
101                if decl.role == callisto_model::ManifestRole::Canonical {
102                    let handle = callisto_manifests::open(decl, &ctx)?;
103                    let v = handle.current_version()?;
104                    found_version = Some(v);
105                    break;
106                }
107            }
108            if let Some(version) = found_version {
109                versions.insert(pkg.id.clone(), version);
110            } else {
111                return Err(GraphError::Manifest(
112                    callisto_model::ManifestError::MissingField {
113                        path: pkg
114                            .manifests
115                            .first()
116                            .map(|m| m.path.clone())
117                            .unwrap_or_default(),
118                        field: "version",
119                    },
120                ));
121            }
122        }
123        Ok(versions)
124    }
125
126    pub fn pre_json_key<'b>(&self, id: &'b PackageId) -> Result<&'b str, GraphError> {
127        Ok(id.name())
128    }
129
130    pub fn initial_versions(&self) -> Result<Vec<(String, Version)>, GraphError> {
131        let base = self.base_versions()?;
132        Ok(base
133            .into_iter()
134            .map(|(id, v)| (id.name().to_string(), v))
135            .collect())
136    }
137}