Skip to main content

mars_agents/resolve/
context.rs

1use std::collections::{HashMap, HashSet};
2
3use indexmap::IndexMap;
4use semver::Version;
5
6use super::filter::push_filter_constraint;
7use super::{
8    PackageResolutionState, PackageVersions, PendingItem, RegisteredPackage, ResolvedGraph,
9    ResolvedNode, RootedSourceRef, VersionConstraint, VisitedSet,
10};
11use crate::config::FilterMode;
12use crate::source::ResolvedRef;
13use crate::types::{SourceId, SourceName};
14
15/// Mutable resolver state threaded through bottom-up resolution and DFS traversal.
16pub struct ResolverContext {
17    registry: IndexMap<SourceName, RegisteredPackage>,
18    package_states: HashMap<SourceName, PackageResolutionState>,
19    id_index: HashMap<SourceId, SourceName>,
20    version_constraints: HashMap<SourceName, Vec<(String, VersionConstraint)>>,
21    materialization_filters: HashMap<SourceName, Vec<FilterMode>>,
22    stack: Vec<PendingItem>,
23    visited: VisitedSet,
24    package_versions: PackageVersions,
25    /// Pre-computed set of source names that are direct dependencies in mars.toml.
26    /// Used by `resolve_single_source` to determine whether to replay the consumer
27    /// lock — a source-level fact, not per-request, so ordering cannot affect it.
28    direct_source_names: HashSet<SourceName>,
29    /// Version overrides carried from a prior restart pass.
30    ///
31    /// When a restart is triggered because package X would resolve to a different
32    /// version under the full accumulated constraint set, the driver carries the
33    /// correct (new) ref into the fresh context via this map. The first-resolution
34    /// branch in `resolve_package_bottom_up` checks this map and uses the override
35    /// directly, so the same constraint-accumulation pattern does NOT re-trigger a
36    /// restart on the next pass.
37    version_overrides: HashMap<SourceName, (ResolvedRef, RootedSourceRef, Option<Version>)>,
38    /// Pending restart info set by `resolve_package_bottom_up` just before it returns
39    /// `ResolutionRestartNeeded`. The driver reads this before discarding the context.
40    pending_restart: Option<(SourceName, ResolvedRef, RootedSourceRef, Option<Version>)>,
41}
42
43impl Default for ResolverContext {
44    fn default() -> Self {
45        Self::new()
46    }
47}
48
49impl ResolverContext {
50    pub fn new() -> Self {
51        Self {
52            registry: IndexMap::new(),
53            package_states: HashMap::new(),
54            id_index: HashMap::new(),
55            version_constraints: HashMap::new(),
56            materialization_filters: HashMap::new(),
57            stack: Vec::new(),
58            visited: VisitedSet::new(),
59            package_versions: PackageVersions::new(),
60            direct_source_names: HashSet::new(),
61            version_overrides: HashMap::new(),
62            pending_restart: None,
63        }
64    }
65
66    /// Populate the set of direct dependency source names before resolution begins.
67    /// Must be called once in `resolve()` before any `resolve_package_bottom_up` call.
68    pub(super) fn set_direct_sources(&mut self, names: HashSet<SourceName>) {
69        self.direct_source_names = names;
70    }
71
72    /// Returns true if `name` is a direct dependency in mars.toml.
73    /// Used by `resolve_single_source` to decide whether to replay the consumer lock.
74    pub(super) fn is_direct_source(&self, name: &SourceName) -> bool {
75        self.direct_source_names.contains(name)
76    }
77
78    /// Set version overrides from a prior restart pass.
79    /// These are used by `resolve_package_bottom_up` to skip re-resolution for
80    /// packages where the correct version was already computed.
81    pub(super) fn set_version_overrides(
82        &mut self,
83        overrides: HashMap<SourceName, (ResolvedRef, RootedSourceRef, Option<Version>)>,
84    ) {
85        self.version_overrides = overrides;
86    }
87
88    /// Look up an override for the first resolution of `name`.
89    /// Returns the pre-computed (ResolvedRef, RootedSourceRef, latest_version) if present.
90    pub(super) fn version_override(
91        &self,
92        name: &SourceName,
93    ) -> Option<&(ResolvedRef, RootedSourceRef, Option<Version>)> {
94        self.version_overrides.get(name)
95    }
96
97    /// Record the restart info: the package that triggered a restart and the ref
98    /// it should be resolved to on the next pass. Called by `resolve_package_bottom_up`
99    /// just before returning `ResolutionRestartNeeded`.
100    pub(super) fn set_pending_restart(
101        &mut self,
102        package: SourceName,
103        new_ref: ResolvedRef,
104        new_rooted: RootedSourceRef,
105        latest_version: Option<Version>,
106    ) {
107        self.pending_restart = Some((package, new_ref, new_rooted, latest_version));
108    }
109
110    /// Drain the pending restart info. Called by the driver after catching the signal.
111    pub(super) fn take_pending_restart(
112        &mut self,
113    ) -> Option<(SourceName, ResolvedRef, RootedSourceRef, Option<Version>)> {
114        self.pending_restart.take()
115    }
116
117    pub(super) fn registry(&self) -> &IndexMap<SourceName, RegisteredPackage> {
118        &self.registry
119    }
120
121    pub(super) fn registry_mut(&mut self) -> &mut IndexMap<SourceName, RegisteredPackage> {
122        &mut self.registry
123    }
124
125    pub(super) fn package_states(&self) -> &HashMap<SourceName, PackageResolutionState> {
126        &self.package_states
127    }
128
129    pub(super) fn package_states_mut(
130        &mut self,
131    ) -> &mut HashMap<SourceName, PackageResolutionState> {
132        &mut self.package_states
133    }
134
135    pub(super) fn id_index(&self) -> &HashMap<SourceId, SourceName> {
136        &self.id_index
137    }
138
139    pub(super) fn id_index_mut(&mut self) -> &mut HashMap<SourceId, SourceName> {
140        &mut self.id_index
141    }
142
143    pub(super) fn version_constraints(
144        &self,
145    ) -> &HashMap<SourceName, Vec<(String, VersionConstraint)>> {
146        &self.version_constraints
147    }
148
149    pub(super) fn materialization_filters(&self) -> &HashMap<SourceName, Vec<FilterMode>> {
150        &self.materialization_filters
151    }
152
153    pub(super) fn visited(&self) -> &VisitedSet {
154        &self.visited
155    }
156
157    pub(super) fn visited_mut(&mut self) -> &mut VisitedSet {
158        &mut self.visited
159    }
160
161    pub(super) fn package_versions_mut(&mut self) -> &mut PackageVersions {
162        &mut self.package_versions
163    }
164
165    pub fn add_version_constraint(
166        &mut self,
167        package: &SourceName,
168        requester: &str,
169        constraint: VersionConstraint,
170    ) {
171        self.version_constraints
172            .entry(package.clone())
173            .or_default()
174            .push((requester.to_string(), constraint));
175    }
176
177    pub fn add_filter(&mut self, package: &SourceName, filter: FilterMode) {
178        push_filter_constraint(&mut self.materialization_filters, package, &filter);
179    }
180
181    pub fn push_pending(&mut self, item: PendingItem) {
182        self.stack.push(item);
183    }
184
185    pub fn pop_pending(&mut self) -> Option<PendingItem> {
186        self.stack.pop()
187    }
188
189    pub fn into_graph(self) -> ResolvedGraph {
190        let mut nodes: IndexMap<SourceName, ResolvedNode> = IndexMap::new();
191        for (name, package) in self.registry {
192            nodes.insert(name, package.node);
193        }
194
195        let mut order: Vec<SourceName> = nodes.keys().cloned().collect();
196        order.sort();
197
198        ResolvedGraph {
199            nodes,
200            order,
201            filters: self.materialization_filters,
202        }
203    }
204}