rez_next_solver/
graph.rs

1//! Dependency graph implementation
2
3use rez_next_common::RezCoreError;
4use rez_next_package::{Package, PackageRequirement};
5use rez_next_version::Version;
6use serde::{Deserialize, Serialize};
7use std::collections::{HashMap, HashSet, VecDeque};
8
9/// Dependency graph node
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct GraphNode {
12    /// Package information
13    pub package: Package,
14    /// Direct dependencies (package names)
15    pub dependencies: HashSet<String>,
16    /// Packages that depend on this one
17    pub dependents: HashSet<String>,
18    /// Node metadata
19    pub metadata: HashMap<String, String>,
20}
21
22impl GraphNode {
23    /// Create a new graph node
24    pub fn new(package: Package) -> Self {
25        Self {
26            package,
27            dependencies: HashSet::new(),
28            dependents: HashSet::new(),
29            metadata: HashMap::new(),
30        }
31    }
32
33    /// Get the package key (name-version)
34    pub fn key(&self) -> String {
35        match &self.package.version {
36            Some(version) => format!("{}-{}", self.package.name, version.as_str()),
37            None => self.package.name.clone(),
38        }
39    }
40
41    /// Add a dependency
42    pub fn add_dependency(&mut self, dependency_key: String) {
43        self.dependencies.insert(dependency_key);
44    }
45
46    /// Add a dependent
47    pub fn add_dependent(&mut self, dependent_key: String) {
48        self.dependents.insert(dependent_key);
49    }
50
51    /// Remove a dependency
52    pub fn remove_dependency(&mut self, dependency_key: &str) {
53        self.dependencies.remove(dependency_key);
54    }
55
56    /// Remove a dependent
57    pub fn remove_dependent(&mut self, dependent_key: &str) {
58        self.dependents.remove(dependent_key);
59    }
60}
61
62/// Dependency conflict information
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct DependencyConflict {
65    /// Package name that has conflicting requirements
66    pub package_name: String,
67    /// Conflicting requirements
68    pub conflicting_requirements: Vec<PackageRequirement>,
69    /// Packages that introduced these requirements
70    pub source_packages: Vec<String>,
71    /// Conflict severity
72    pub severity: ConflictSeverity,
73}
74
75/// Conflict severity levels
76#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd)]
77pub enum ConflictSeverity {
78    /// Minor version conflict
79    Minor,
80    /// Major version conflict
81    Major,
82    /// Incompatible requirements
83    Incompatible,
84}
85
86/// Conflict resolution
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct ConflictResolution {
89    /// Package name being resolved
90    pub package_name: String,
91    /// Selected package version
92    pub selected_version: Option<Version>,
93    /// Resolution strategy used
94    pub strategy: String,
95    /// Packages that were modified/removed
96    pub modified_packages: Vec<String>,
97}
98
99/// High-performance dependency graph
100#[derive(Debug, Clone)]
101pub struct DependencyGraph {
102    /// Graph nodes indexed by package key
103    nodes: HashMap<String, GraphNode>,
104    /// Requirements indexed by package name
105    requirements: HashMap<String, Vec<PackageRequirement>>,
106    /// Constraints that must be satisfied
107    constraints: Vec<PackageRequirement>,
108    /// Packages to exclude
109    exclusions: HashSet<String>,
110    /// Graph metadata
111    metadata: HashMap<String, String>,
112}
113
114impl DependencyGraph {
115    /// Create a new dependency graph
116    pub fn new() -> Self {
117        Self {
118            nodes: HashMap::new(),
119            requirements: HashMap::new(),
120            constraints: Vec::new(),
121            exclusions: HashSet::new(),
122            metadata: HashMap::new(),
123        }
124    }
125
126    /// Clear the graph
127    pub fn clear(&mut self) {
128        self.nodes.clear();
129        self.requirements.clear();
130        self.constraints.clear();
131        self.exclusions.clear();
132        self.metadata.clear();
133    }
134
135    /// Add a package to the graph
136    pub fn add_package(&mut self, package: Package) -> Result<(), RezCoreError> {
137        let key = match &package.version {
138            Some(version) => format!("{}-{}", package.name, version.as_str()),
139            None => package.name.clone(),
140        };
141
142        // Check if package is excluded
143        if self.exclusions.contains(&package.name) {
144            return Err(RezCoreError::Solver(format!(
145                "Package {} is excluded",
146                package.name
147            )));
148        }
149
150        let node = GraphNode::new(package);
151        self.nodes.insert(key, node);
152
153        Ok(())
154    }
155
156    /// Add a requirement to the graph
157    pub fn add_requirement(&mut self, requirement: PackageRequirement) -> Result<(), RezCoreError> {
158        self.requirements
159            .entry(requirement.name.clone())
160            .or_insert_with(Vec::new)
161            .push(requirement);
162
163        Ok(())
164    }
165
166    /// Add a constraint
167    pub fn add_constraint(&mut self, constraint: PackageRequirement) -> Result<(), RezCoreError> {
168        self.constraints.push(constraint);
169        Ok(())
170    }
171
172    /// Add an exclusion
173    pub fn add_exclusion(&mut self, package_name: String) -> Result<(), RezCoreError> {
174        self.exclusions.insert(package_name);
175        Ok(())
176    }
177
178    /// Add a dependency edge between two packages
179    pub fn add_dependency_edge(
180        &mut self,
181        from_key: &str,
182        to_key: &str,
183    ) -> Result<(), RezCoreError> {
184        // Add dependency to the from node
185        if let Some(from_node) = self.nodes.get_mut(from_key) {
186            from_node.add_dependency(to_key.to_string());
187        } else {
188            return Err(RezCoreError::Solver(format!(
189                "Package {} not found in graph",
190                from_key
191            )));
192        }
193
194        // Add dependent to the to node
195        if let Some(to_node) = self.nodes.get_mut(to_key) {
196            to_node.add_dependent(from_key.to_string());
197        } else {
198            return Err(RezCoreError::Solver(format!(
199                "Package {} not found in graph",
200                to_key
201            )));
202        }
203
204        Ok(())
205    }
206
207    /// Remove a package from the graph
208    pub fn remove_package(&mut self, package_key: &str) -> Result<(), RezCoreError> {
209        if let Some(node) = self.nodes.remove(package_key) {
210            // Remove all edges involving this package
211            for dep_key in &node.dependencies {
212                if let Some(dep_node) = self.nodes.get_mut(dep_key) {
213                    dep_node.remove_dependent(package_key);
214                }
215            }
216
217            for dependent_key in &node.dependents {
218                if let Some(dependent_node) = self.nodes.get_mut(dependent_key) {
219                    dependent_node.remove_dependency(package_key);
220                }
221            }
222        }
223
224        Ok(())
225    }
226
227    /// Detect conflicts in the dependency graph
228    pub fn detect_conflicts(&self) -> Vec<DependencyConflict> {
229        let mut conflicts = Vec::new();
230
231        // Group requirements by package name
232        for (package_name, requirements) in &self.requirements {
233            if requirements.len() > 1 {
234                // Check if requirements are compatible
235                let mut incompatible_groups = Vec::new();
236
237                for (i, req1) in requirements.iter().enumerate() {
238                    for req2 in requirements.iter().skip(i + 1) {
239                        // TODO: Implement is_compatible_with method for PackageRequirement
240                        // if !req1.is_compatible_with(req2) {
241                        //     incompatible_groups.push((req1.clone(), req2.clone()));
242                        // }
243                    }
244                }
245
246                if !incompatible_groups.is_empty() {
247                    let severity = self.determine_conflict_severity(&incompatible_groups);
248                    let source_packages = self.find_requirement_sources(package_name);
249
250                    conflicts.push(DependencyConflict {
251                        package_name: package_name.clone(),
252                        conflicting_requirements: requirements.clone(),
253                        source_packages,
254                        severity,
255                    });
256                }
257            }
258        }
259
260        conflicts
261    }
262
263    /// Determine the severity of a conflict
264    fn determine_conflict_severity(
265        &self,
266        incompatible_groups: &[(PackageRequirement, PackageRequirement)],
267    ) -> ConflictSeverity {
268        // Simple heuristic: if any requirements are completely incompatible, it's incompatible
269        // If major versions differ, it's major
270        // Otherwise, it's minor
271
272        // TODO: Implement range checking when PackageRequirement has range field
273        // for (req1, req2) in incompatible_groups {
274        //     if let (Some(range1), Some(range2)) = (&req1.range, &req2.range) {
275        //         if !range1.intersects(range2) {
276        //             return ConflictSeverity::Incompatible;
277        //         }
278        //     }
279        // }
280
281        ConflictSeverity::Major // Default to major for now
282    }
283
284    /// Find which packages introduced requirements for a given package
285    fn find_requirement_sources(&self, package_name: &str) -> Vec<String> {
286        let mut sources = Vec::new();
287
288        for node in self.nodes.values() {
289            for req_str in &node.package.requires {
290                if let Ok(req) = PackageRequirement::parse(req_str) {
291                    if req.name == package_name {
292                        sources.push(node.key());
293                        break;
294                    }
295                }
296            }
297        }
298
299        sources
300    }
301
302    /// Apply a conflict resolution to the graph
303    pub fn apply_conflict_resolution(
304        &mut self,
305        resolution: ConflictResolution,
306    ) -> Result<(), RezCoreError> {
307        // Remove packages that were modified
308        for package_key in &resolution.modified_packages {
309            self.remove_package(package_key)?;
310        }
311
312        // Update requirements for the resolved package
313        if let Some(requirements) = self.requirements.get_mut(&resolution.package_name) {
314            // Create a new requirement based on the resolution
315            if let Some(ref version) = resolution.selected_version {
316                // TODO: Implement exact requirement creation when method is available
317                let new_requirement = PackageRequirement::with_version(
318                    resolution.package_name.clone(),
319                    version.as_str().to_string(),
320                );
321                requirements.clear();
322                requirements.push(new_requirement);
323            }
324        }
325
326        Ok(())
327    }
328
329    /// Get all resolved packages in topological order
330    pub fn get_resolved_packages(&self) -> Result<Vec<Package>, RezCoreError> {
331        let sorted_keys = self.topological_sort()?;
332        let mut packages = Vec::new();
333
334        for key in sorted_keys {
335            if let Some(node) = self.nodes.get(&key) {
336                packages.push(node.package.clone());
337            }
338        }
339
340        Ok(packages)
341    }
342
343    /// Perform topological sort of the dependency graph
344    fn topological_sort(&self) -> Result<Vec<String>, RezCoreError> {
345        let mut in_degree: HashMap<String, usize> = HashMap::new();
346        let mut result = Vec::new();
347        let mut queue = VecDeque::new();
348
349        // Calculate in-degrees
350        for (key, node) in &self.nodes {
351            in_degree.insert(key.clone(), node.dependents.len());
352        }
353
354        // Find nodes with no incoming edges
355        for (key, &degree) in &in_degree {
356            if degree == 0 {
357                queue.push_back(key.clone());
358            }
359        }
360
361        // Process nodes
362        while let Some(current_key) = queue.pop_front() {
363            result.push(current_key.clone());
364
365            if let Some(current_node) = self.nodes.get(&current_key) {
366                // Reduce in-degree of dependent nodes
367                for dep_key in &current_node.dependencies {
368                    if let Some(degree) = in_degree.get_mut(dep_key) {
369                        *degree -= 1;
370                        if *degree == 0 {
371                            queue.push_back(dep_key.clone());
372                        }
373                    }
374                }
375            }
376        }
377
378        // Check for cycles
379        if result.len() != self.nodes.len() {
380            return Err(RezCoreError::Solver(
381                "Circular dependency detected in package graph".to_string(),
382            ));
383        }
384
385        Ok(result)
386    }
387
388    /// Get graph statistics
389    pub fn get_stats(&self) -> GraphStats {
390        let mut total_dependencies = 0;
391        let mut max_depth = 0;
392
393        for node in self.nodes.values() {
394            total_dependencies += node.dependencies.len();
395            let depth = self.calculate_node_depth(&node.key());
396            max_depth = max_depth.max(depth);
397        }
398
399        GraphStats {
400            node_count: self.nodes.len(),
401            edge_count: total_dependencies,
402            max_depth,
403            conflict_count: self.detect_conflicts().len(),
404            constraint_count: self.constraints.len(),
405            exclusion_count: self.exclusions.len(),
406        }
407    }
408
409    /// Calculate the depth of a node in the graph
410    fn calculate_node_depth(&self, node_key: &str) -> usize {
411        let mut visited = HashSet::new();
412        self.calculate_depth_recursive(node_key, &mut visited)
413    }
414
415    /// Recursive helper for depth calculation
416    fn calculate_depth_recursive(&self, node_key: &str, visited: &mut HashSet<String>) -> usize {
417        if visited.contains(node_key) {
418            return 0; // Avoid infinite recursion
419        }
420
421        visited.insert(node_key.to_string());
422
423        if let Some(node) = self.nodes.get(node_key) {
424            let mut max_dep_depth = 0;
425            for dep_key in &node.dependencies {
426                let dep_depth = self.calculate_depth_recursive(dep_key, visited);
427                max_dep_depth = max_dep_depth.max(dep_depth);
428            }
429            max_dep_depth + 1
430        } else {
431            0
432        }
433    }
434}
435
436/// Graph statistics
437#[derive(Debug, Clone, Serialize, Deserialize)]
438pub struct GraphStats {
439    /// Number of nodes in the graph
440    pub node_count: usize,
441    /// Number of edges in the graph
442    pub edge_count: usize,
443    /// Maximum depth of the graph
444    pub max_depth: usize,
445    /// Number of conflicts detected
446    pub conflict_count: usize,
447    /// Number of constraints
448    pub constraint_count: usize,
449    /// Number of exclusions
450    pub exclusion_count: usize,
451}
452
453impl Default for DependencyGraph {
454    fn default() -> Self {
455        Self::new()
456    }
457}