1use crate::resolution_state::ResolutionState;
4use crate::SolverConfig;
5use rez_next_common::RezCoreError;
6use rez_next_package::{Package, Requirement};
7use rez_next_repository::simple_repository::RepositoryManager;
8use std::collections::HashMap;
9use std::sync::Arc;
10
11pub struct DependencyResolver {
13 repository_manager: Arc<RepositoryManager>,
15
16 config: SolverConfig,
18
19 package_cache: HashMap<String, Vec<Arc<Package>>>,
21}
22
23#[derive(Debug, Clone)]
25pub struct DetailedResolutionResult {
26 pub resolved_packages: Vec<ResolvedPackageInfo>,
28
29 pub failed_requirements: Vec<Requirement>,
31
32 pub conflicts: Vec<ResolutionConflict>,
34
35 pub stats: ResolutionStats,
37}
38
39#[derive(Debug, Clone)]
41pub struct ResolvedPackageInfo {
42 pub package: Arc<Package>,
44
45 pub variant_index: Option<usize>,
47
48 pub requested: bool,
50
51 pub required_by: Vec<String>,
53
54 pub satisfying_requirement: Option<Requirement>,
56}
57
58#[derive(Debug, Clone, serde::Serialize)]
60pub struct ResolutionConflict {
61 pub package_name: String,
63
64 pub conflicting_requirements: Vec<Requirement>,
66
67 pub source_packages: Vec<String>,
69}
70
71#[derive(Debug, Clone, serde::Serialize, Default)]
73pub struct ResolutionStats {
74 pub packages_considered: usize,
76
77 pub variants_evaluated: usize,
79
80 pub resolution_time_ms: u64,
82
83 pub conflicts_encountered: usize,
85
86 pub backtrack_steps: usize,
88}
89
90impl DependencyResolver {
91 pub fn new(repository_manager: Arc<RepositoryManager>, config: SolverConfig) -> Self {
93 Self {
94 repository_manager,
95 config,
96 package_cache: HashMap::new(),
97 }
98 }
99
100 pub async fn resolve(
102 &mut self,
103 requirements: Vec<Requirement>,
104 ) -> Result<DetailedResolutionResult, RezCoreError> {
105 let start_time = std::time::Instant::now();
106
107 let mut resolution_state = ResolutionState::new(requirements.clone());
109
110 let result = self.resolve_recursive(&mut resolution_state).await?;
112
113 if let Some(cycle) = resolution_state.detect_cycle() {
115 return Err(RezCoreError::Solver(format!(
116 "Cyclic dependency detected: {}",
117 cycle.join(" -> ")
118 )));
119 }
120
121 let resolution_time = start_time.elapsed().as_millis() as u64;
123 let stats = ResolutionStats {
124 packages_considered: resolution_state.packages_considered,
125 variants_evaluated: resolution_state.variants_evaluated,
126 resolution_time_ms: resolution_time,
127 conflicts_encountered: resolution_state.conflicts.len(),
128 backtrack_steps: resolution_state.backtrack_steps,
129 };
130
131 let failed = resolution_state.failed_requirements;
132
133 if self.config.strict_mode && !failed.is_empty() {
135 let names: Vec<String> = failed.iter().map(|r| r.to_string()).collect();
136 return Err(RezCoreError::Solver(format!(
137 "Strict mode: failed to satisfy requirements: {}",
138 names.join(", ")
139 )));
140 }
141
142 Ok(DetailedResolutionResult {
143 resolved_packages: result,
144 failed_requirements: failed,
145 conflicts: resolution_state.conflicts,
146 stats,
147 })
148 }
149
150 async fn resolve_recursive(
152 &mut self,
153 state: &mut ResolutionState,
154 ) -> Result<Vec<ResolvedPackageInfo>, RezCoreError> {
155 while let Some(requirement) = state.get_next_requirement() {
157 if let Some(existing) = state.find_satisfying_package(&requirement) {
159 state.mark_requirement_satisfied(&requirement, existing.package.name.clone());
161 continue;
162 }
163
164 let candidates = self.find_candidate_packages(&requirement).await?;
166 state.packages_considered += candidates.len();
167
168 if candidates.is_empty() {
169 state.failed_requirements.push(requirement.clone());
170 continue;
171 }
172
173 let mut resolved = false;
175 for candidate in candidates {
176 if let Some(conflict) = state.check_conflicts(&candidate, &requirement) {
178 state.conflicts.push(conflict);
179 continue;
180 }
181
182 if let Ok(resolved_info) = self
184 .try_resolve_with_candidate(state, &candidate, &requirement)
185 .await
186 {
187 state.add_resolved_package(resolved_info);
188 resolved = true;
189 break;
190 }
191 }
192
193 if !resolved {
194 state.failed_requirements.push(requirement);
195 }
196 }
197
198 Ok(state.resolved_packages.clone())
199 }
200
201 async fn find_candidate_packages(
203 &mut self,
204 requirement: &Requirement,
205 ) -> Result<Vec<Arc<Package>>, RezCoreError> {
206 if let Some(cached) = self.package_cache.get(&requirement.name) {
208 return Ok(self.filter_and_sort_candidates(cached, requirement));
209 }
210
211 let packages = self
213 .repository_manager
214 .find_packages(&requirement.name)
215 .await?;
216
217 self.package_cache
219 .insert(requirement.name.clone(), packages.clone());
220
221 Ok(self.filter_and_sort_candidates(&packages, requirement))
222 }
223
224 fn filter_and_sort_candidates(
226 &self,
227 packages: &[Arc<Package>],
228 requirement: &Requirement,
229 ) -> Vec<Arc<Package>> {
230 let mut candidates: Vec<Arc<Package>> = packages
231 .iter()
232 .filter(|pkg| {
233 if let Some(ref version) = pkg.version {
234 if !self.config.allow_prerelease && version.is_prerelease() {
236 return false;
237 }
238 requirement.is_satisfied_by(version)
239 } else {
240 true
241 }
242 })
243 .cloned()
244 .collect();
245
246 if self.config.prefer_latest {
248 candidates.sort_by(|a, b| match (&b.version, &a.version) {
249 (Some(bv), Some(av)) => bv.cmp(av),
250 (Some(_), None) => std::cmp::Ordering::Less,
251 (None, Some(_)) => std::cmp::Ordering::Greater,
252 (None, None) => std::cmp::Ordering::Equal,
253 });
254 } else {
255 candidates.sort_by(|a, b| match (&a.version, &b.version) {
257 (Some(av), Some(bv)) => av.cmp(bv),
258 (Some(_), None) => std::cmp::Ordering::Less,
259 (None, Some(_)) => std::cmp::Ordering::Greater,
260 (None, None) => std::cmp::Ordering::Equal,
261 });
262 }
263
264 candidates
265 }
266
267 async fn try_resolve_with_candidate(
269 &mut self,
270 state: &mut ResolutionState,
271 candidate: &Arc<Package>,
272 requirement: &Requirement,
273 ) -> Result<ResolvedPackageInfo, RezCoreError> {
274 let variant_index = self.select_variant(candidate, state);
276 state.variants_evaluated += candidate.variants.len().max(1);
277
278 let effective_requires = self.get_effective_requires(candidate, variant_index);
280
281 let resolved_info = ResolvedPackageInfo {
283 package: candidate.clone(),
284 variant_index,
285 requested: state.is_original_requirement(requirement),
286 required_by: vec![requirement.name.clone()],
287 satisfying_requirement: Some(requirement.clone()),
288 };
289
290 for dep_req_str in &effective_requires {
292 let dep_requirement: Requirement = dep_req_str.parse().map_err(|e| {
293 RezCoreError::RequirementParse(format!(
294 "Invalid requirement '{}': {}",
295 dep_req_str, e
296 ))
297 })?;
298 state.record_dependency(&candidate.name, &dep_requirement.name);
300 state.add_requirement(dep_requirement);
301 }
302
303 Ok(resolved_info)
304 }
305
306 fn select_variant(&self, package: &Package, state: &ResolutionState) -> Option<usize> {
308 if package.variants.is_empty() {
309 return None;
310 }
311
312 for (i, variant_requires) in package.variants.iter().enumerate() {
314 let mut compatible = true;
315 for req_str in variant_requires {
316 if let Ok(req) = req_str.parse::<Requirement>() {
317 for resolved in &state.resolved_packages {
319 if resolved.package.name == req.name {
320 if let Some(ref version) = resolved.package.version {
321 if !req.is_satisfied_by(version) {
322 compatible = false;
323 break;
324 }
325 }
326 }
327 }
328 }
329 if !compatible {
330 break;
331 }
332 }
333 if compatible {
334 return Some(i);
335 }
336 }
337
338 Some(0)
340 }
341
342 fn get_effective_requires(
344 &self,
345 package: &Package,
346 variant_index: Option<usize>,
347 ) -> Vec<String> {
348 let mut requires = package.requires.clone();
349
350 if let Some(idx) = variant_index {
351 if let Some(variant_reqs) = package.variants.get(idx) {
352 for vreq in variant_reqs {
354 if !requires.contains(vreq) {
355 requires.push(vreq.clone());
356 }
357 }
358 }
359 }
360
361 requires
362 }
363}