rez_next_solver/
dependency_resolver.rs

1//! Dependency resolution implementation - equivalent to Python's solver
2
3use crate::{SolverConfig, SolverStats};
4use rez_next_common::RezCoreError;
5use rez_next_package::{Package, Requirement, VersionConstraint};
6use rez_next_repository::simple_repository::{PackageRepository, RepositoryManager};
7use rez_next_version::Version;
8use std::collections::{HashMap, HashSet, VecDeque};
9use std::sync::Arc;
10
11/// A dependency resolver that finds compatible package combinations
12pub struct DependencyResolver {
13    /// Repository manager for package discovery
14    repository_manager: Arc<RepositoryManager>,
15
16    /// Solver configuration
17    config: SolverConfig,
18
19    /// Solver statistics
20    stats: SolverStats,
21
22    /// Cache of resolved packages
23    package_cache: HashMap<String, Vec<Arc<Package>>>,
24}
25
26/// Resolution result containing resolved packages and metadata
27#[derive(Debug, Clone)]
28pub struct ResolutionResult {
29    /// Successfully resolved packages
30    pub resolved_packages: Vec<ResolvedPackageInfo>,
31
32    /// Requirements that couldn't be satisfied
33    pub failed_requirements: Vec<Requirement>,
34
35    /// Conflicts encountered during resolution
36    pub conflicts: Vec<ResolutionConflict>,
37
38    /// Resolution statistics
39    pub stats: ResolutionStats,
40}
41
42/// Information about a resolved package
43#[derive(Debug, Clone)]
44pub struct ResolvedPackageInfo {
45    /// The resolved package
46    pub package: Arc<Package>,
47
48    /// The variant index if this package has variants
49    pub variant_index: Option<usize>,
50
51    /// Whether this package was explicitly requested
52    pub requested: bool,
53
54    /// The requirements that led to this package being included
55    pub required_by: Vec<String>,
56
57    /// The specific requirement that was satisfied
58    pub satisfying_requirement: Option<Requirement>,
59}
60
61/// A conflict encountered during resolution
62#[derive(Debug, Clone, serde::Serialize)]
63pub struct ResolutionConflict {
64    /// The package name that has conflicting requirements
65    pub package_name: String,
66
67    /// The conflicting requirements
68    pub conflicting_requirements: Vec<Requirement>,
69
70    /// The packages that introduced these requirements
71    pub source_packages: Vec<String>,
72}
73
74/// Statistics about the resolution process
75#[derive(Debug, Clone, serde::Serialize)]
76pub struct ResolutionStats {
77    /// Number of packages considered
78    pub packages_considered: usize,
79
80    /// Number of variants evaluated
81    pub variants_evaluated: usize,
82
83    /// Time spent resolving (in milliseconds)
84    pub resolution_time_ms: u64,
85
86    /// Number of conflicts encountered
87    pub conflicts_encountered: usize,
88
89    /// Number of backtracking steps
90    pub backtrack_steps: usize,
91}
92
93impl DependencyResolver {
94    /// Create a new dependency resolver
95    pub fn new(repository_manager: Arc<RepositoryManager>, config: SolverConfig) -> Self {
96        Self {
97            repository_manager,
98            config,
99            stats: SolverStats::default(),
100            package_cache: HashMap::new(),
101        }
102    }
103
104    /// Resolve a set of requirements into a consistent package set
105    pub async fn resolve(
106        &mut self,
107        requirements: Vec<Requirement>,
108    ) -> Result<ResolutionResult, RezCoreError> {
109        let start_time = std::time::Instant::now();
110
111        // Initialize resolution state
112        let mut resolution_state = ResolutionState::new(requirements.clone());
113
114        // Perform resolution
115        let result = self.resolve_recursive(&mut resolution_state).await?;
116
117        // Calculate statistics
118        let resolution_time = start_time.elapsed().as_millis() as u64;
119        let stats = ResolutionStats {
120            packages_considered: resolution_state.packages_considered,
121            variants_evaluated: resolution_state.variants_evaluated,
122            resolution_time_ms: resolution_time,
123            conflicts_encountered: resolution_state.conflicts.len(),
124            backtrack_steps: resolution_state.backtrack_steps,
125        };
126
127        Ok(ResolutionResult {
128            resolved_packages: result,
129            failed_requirements: resolution_state.failed_requirements,
130            conflicts: resolution_state.conflicts,
131            stats,
132        })
133    }
134
135    /// Recursive resolution implementation
136    async fn resolve_recursive(
137        &mut self,
138        state: &mut ResolutionState,
139    ) -> Result<Vec<ResolvedPackageInfo>, RezCoreError> {
140        // Get next requirement to resolve
141        while let Some(requirement) = state.get_next_requirement() {
142            // Check if we already have a package that satisfies this requirement
143            if let Some(existing) = state.find_satisfying_package(&requirement) {
144                // Mark this requirement as satisfied
145                state.mark_requirement_satisfied(&requirement, existing.package.name.clone());
146                continue;
147            }
148
149            // Find candidate packages for this requirement
150            let candidates = self.find_candidate_packages(&requirement).await?;
151            state.packages_considered += candidates.len();
152
153            if candidates.is_empty() {
154                state.failed_requirements.push(requirement.clone());
155                continue;
156            }
157
158            // Try each candidate
159            let mut resolved = false;
160            for candidate in candidates {
161                // Check for conflicts with existing packages
162                if let Some(conflict) = state.check_conflicts(&candidate, &requirement) {
163                    state.conflicts.push(conflict);
164                    continue;
165                }
166
167                // Try to resolve with this candidate
168                if let Ok(resolved_info) = self
169                    .try_resolve_with_candidate(state, &candidate, &requirement)
170                    .await
171                {
172                    state.add_resolved_package(resolved_info);
173                    resolved = true;
174                    break;
175                }
176            }
177
178            if !resolved {
179                state.failed_requirements.push(requirement);
180            }
181        }
182
183        Ok(state.resolved_packages.clone())
184    }
185
186    /// Find candidate packages that could satisfy a requirement
187    async fn find_candidate_packages(
188        &mut self,
189        requirement: &Requirement,
190    ) -> Result<Vec<Arc<Package>>, RezCoreError> {
191        // Check cache first
192        if let Some(cached) = self.package_cache.get(&requirement.name) {
193            return Ok(self.filter_candidates(cached, requirement));
194        }
195
196        // Search repositories
197        let packages = self
198            .repository_manager
199            .find_packages(&requirement.name)
200            .await?;
201
202        // Cache the results
203        self.package_cache
204            .insert(requirement.name.clone(), packages.clone());
205
206        Ok(self.filter_candidates(&packages, requirement))
207    }
208
209    /// Filter candidate packages based on version constraints
210    fn filter_candidates(
211        &self,
212        packages: &[Arc<Package>],
213        requirement: &Requirement,
214    ) -> Vec<Arc<Package>> {
215        packages
216            .iter()
217            .filter(|pkg| {
218                if let Some(ref version) = pkg.version {
219                    requirement.is_satisfied_by(version)
220                } else {
221                    // Package without version satisfies any requirement
222                    true
223                }
224            })
225            .cloned()
226            .collect()
227    }
228
229    /// Try to resolve using a specific candidate package
230    async fn try_resolve_with_candidate(
231        &mut self,
232        state: &mut ResolutionState,
233        candidate: &Arc<Package>,
234        requirement: &Requirement,
235    ) -> Result<ResolvedPackageInfo, RezCoreError> {
236        // Create resolved package info
237        let resolved_info = ResolvedPackageInfo {
238            package: candidate.clone(),
239            variant_index: None, // TODO: Handle variants
240            requested: state.is_original_requirement(requirement),
241            required_by: vec![requirement.name.clone()],
242            satisfying_requirement: Some(requirement.clone()),
243        };
244
245        // Add transitive dependencies to resolution queue
246        for dep_req_str in &candidate.requires {
247            let dep_requirement: Requirement = dep_req_str.parse().map_err(|e| {
248                RezCoreError::RequirementParse(format!(
249                    "Invalid requirement '{}': {}",
250                    dep_req_str, e
251                ))
252            })?;
253            state.add_requirement(dep_requirement);
254        }
255
256        Ok(resolved_info)
257    }
258}
259
260/// Internal state for the resolution process
261#[derive(Debug)]
262struct ResolutionState {
263    /// Original requirements to resolve
264    original_requirements: Vec<Requirement>,
265
266    /// Queue of requirements to process
267    requirement_queue: VecDeque<Requirement>,
268
269    /// Successfully resolved packages
270    resolved_packages: Vec<ResolvedPackageInfo>,
271
272    /// Requirements that couldn't be satisfied
273    failed_requirements: Vec<Requirement>,
274
275    /// Conflicts encountered
276    conflicts: Vec<ResolutionConflict>,
277
278    /// Satisfied requirements (to avoid duplicates)
279    satisfied_requirements: HashSet<String>,
280
281    /// Statistics
282    packages_considered: usize,
283    variants_evaluated: usize,
284    backtrack_steps: usize,
285}
286
287impl ResolutionState {
288    fn new(requirements: Vec<Requirement>) -> Self {
289        let mut queue = VecDeque::new();
290        for req in &requirements {
291            queue.push_back(req.clone());
292        }
293
294        Self {
295            original_requirements: requirements,
296            requirement_queue: queue,
297            resolved_packages: Vec::new(),
298            failed_requirements: Vec::new(),
299            conflicts: Vec::new(),
300            satisfied_requirements: HashSet::new(),
301            packages_considered: 0,
302            variants_evaluated: 0,
303            backtrack_steps: 0,
304        }
305    }
306
307    fn get_next_requirement(&mut self) -> Option<Requirement> {
308        self.requirement_queue.pop_front()
309    }
310
311    fn add_requirement(&mut self, requirement: Requirement) {
312        let req_key = format!(
313            "{}:{}",
314            requirement.name,
315            requirement
316                .version_constraint
317                .as_ref()
318                .map(|v| format!("{:?}", v))
319                .unwrap_or_default()
320        );
321        if !self.satisfied_requirements.contains(&req_key) {
322            self.requirement_queue.push_back(requirement);
323        }
324    }
325
326    fn mark_requirement_satisfied(&mut self, requirement: &Requirement, package_name: String) {
327        let req_key = format!(
328            "{}:{}",
329            requirement.name,
330            requirement
331                .version_constraint
332                .as_ref()
333                .map(|v| format!("{:?}", v))
334                .unwrap_or_default()
335        );
336        self.satisfied_requirements.insert(req_key);
337    }
338
339    fn find_satisfying_package(&self, requirement: &Requirement) -> Option<&ResolvedPackageInfo> {
340        self.resolved_packages.iter().find(|pkg| {
341            pkg.package.name == requirement.name
342                && pkg
343                    .package
344                    .version
345                    .as_ref()
346                    .map_or(true, |v| requirement.is_satisfied_by(v))
347        })
348    }
349
350    fn check_conflicts(
351        &self,
352        candidate: &Arc<Package>,
353        requirement: &Requirement,
354    ) -> Option<ResolutionConflict> {
355        // Check for version conflicts with existing packages
356        for existing in &self.resolved_packages {
357            if existing.package.name == candidate.name {
358                if let (Some(existing_version), Some(candidate_version)) =
359                    (&existing.package.version, &candidate.version)
360                {
361                    if existing_version != candidate_version {
362                        return Some(ResolutionConflict {
363                            package_name: candidate.name.clone(),
364                            conflicting_requirements: vec![requirement.clone()],
365                            source_packages: vec![existing.package.name.clone()],
366                        });
367                    }
368                }
369            }
370        }
371
372        None
373    }
374
375    fn add_resolved_package(&mut self, package: ResolvedPackageInfo) {
376        self.resolved_packages.push(package);
377    }
378
379    fn is_original_requirement(&self, requirement: &Requirement) -> bool {
380        self.original_requirements
381            .iter()
382            .any(|orig| orig.name == requirement.name)
383    }
384}
385
386impl Default for ResolutionStats {
387    fn default() -> Self {
388        Self {
389            packages_considered: 0,
390            variants_evaluated: 0,
391            resolution_time_ms: 0,
392            conflicts_encountered: 0,
393            backtrack_steps: 0,
394        }
395    }
396}