//! Package Dependency Resolver for APT-OSTree //! //! This module implements dependency resolution for DEB packages in the context //! of OSTree commits, ensuring proper layering order and conflict resolution. use std::collections::{HashMap, HashSet, VecDeque}; use tracing::{info, warn, debug}; use serde::{Serialize, Deserialize}; use crate::error::{AptOstreeError, AptOstreeResult}; use crate::apt_ostree_integration::DebPackageMetadata; /// Dependency relationship types #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum DependencyRelation { Depends, Recommends, Suggests, Conflicts, Breaks, Provides, Replaces, } /// Dependency constraint #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DependencyConstraint { pub package_name: String, pub version_constraint: Option, pub relation: DependencyRelation, } /// Version constraint #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VersionConstraint { pub operator: VersionOperator, pub version: String, } /// Version comparison operators #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum VersionOperator { LessThan, LessThanOrEqual, Equal, GreaterThanOrEqual, GreaterThan, NotEqual, } /// Resolved dependency graph #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DependencyGraph { pub nodes: HashMap, pub edges: Vec, } /// Package node in dependency graph #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PackageNode { pub name: String, pub metadata: DebPackageMetadata, pub dependencies: Vec, pub level: usize, pub visited: bool, } /// Dependency edge in graph #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DependencyEdge { pub from: String, pub to: String, pub relation: DependencyRelation, } /// Dependency resolver for OSTree packages pub struct DependencyResolver { available_packages: HashMap, } impl DependencyResolver { /// Create a new dependency resolver pub fn new() -> Self { Self { available_packages: HashMap::new(), } } /// Add available packages to the resolver pub fn add_available_packages(&mut self, packages: Vec) { for package in packages { self.available_packages.insert(package.name.clone(), package); } info!("Added {} available packages to resolver", self.available_packages.len()); } /// Resolve dependencies for a list of packages pub fn resolve_dependencies(&self, package_names: &[String]) -> AptOstreeResult { info!("Resolving dependencies for {} packages", package_names.len()); // Build dependency graph let graph = self.build_dependency_graph(package_names)?; // Check for conflicts let conflicts = self.check_conflicts(&graph)?; if !conflicts.is_empty() { return Err(AptOstreeError::DependencyConflict( format!("Dependency conflicts found: {:?}", conflicts) )); } // Topological sort for layering order let layering_order = self.topological_sort(&graph)?; // Calculate dependency levels let leveled_packages = self.calculate_dependency_levels(&graph, &layering_order)?; Ok(ResolvedDependencies { packages: layering_order, levels: leveled_packages, graph, }) } /// Build dependency graph from package names fn build_dependency_graph(&self, package_names: &[String]) -> AptOstreeResult { let mut graph = DependencyGraph { nodes: HashMap::new(), edges: Vec::new(), }; // Add requested packages for package_name in package_names { if let Some(metadata) = self.available_packages.get(package_name) { let node = PackageNode { name: package_name.clone(), metadata: metadata.clone(), dependencies: self.parse_dependencies(&metadata.depends), level: 0, visited: false, }; graph.nodes.insert(package_name.clone(), node); } else { return Err(AptOstreeError::PackageNotFound(package_name.clone())); } } // Add dependencies recursively let mut to_process: VecDeque = package_names.iter().cloned().collect(); let mut processed = HashSet::new(); while let Some(package_name) = to_process.pop_front() { if processed.contains(&package_name) { continue; } processed.insert(package_name.clone()); if let Some(node) = graph.nodes.get(&package_name) { // Collect dependencies to avoid borrow checker issues let dependencies = node.dependencies.clone(); for dep_constraint in &dependencies { let dep_name = &dep_constraint.package_name; // Add dependency node if not already present if !graph.nodes.contains_key(dep_name) { if let Some(dep_metadata) = self.available_packages.get(dep_name) { let dep_node = PackageNode { name: dep_name.clone(), metadata: dep_metadata.clone(), dependencies: self.parse_dependencies(&dep_metadata.depends), level: 0, visited: false, }; graph.nodes.insert(dep_name.clone(), dep_node); to_process.push_back(dep_name.clone()); } else { warn!("Dependency not found: {}", dep_name); } } // Add edge graph.edges.push(DependencyEdge { from: package_name.clone(), to: dep_name.clone(), relation: dep_constraint.relation.clone(), }); } } } info!("Built dependency graph with {} nodes and {} edges", graph.nodes.len(), graph.edges.len()); Ok(graph) } /// Parse dependency strings into structured constraints fn parse_dependencies(&self, deps_str: &[String]) -> Vec { let mut constraints = Vec::new(); for dep_str in deps_str { // Simple parsing - in real implementation, this would be more sophisticated let parts: Vec<&str> = dep_str.split_whitespace().collect(); if !parts.is_empty() { let package_name = parts[0].to_string(); let version_constraint = if parts.len() > 1 { self.parse_version_constraint(&parts[1..]) } else { None }; constraints.push(DependencyConstraint { package_name, version_constraint, relation: DependencyRelation::Depends, }); } } constraints } /// Parse version constraint from string parts fn parse_version_constraint(&self, parts: &[&str]) -> Option { if parts.is_empty() { return None; } let constraint_str = parts.join(" "); // Simple version constraint parsing // In real implementation, this would handle complex Debian version constraints if constraint_str.starts_with(">=") { Some(VersionConstraint { operator: VersionOperator::GreaterThanOrEqual, version: constraint_str[2..].trim().to_string(), }) } else if constraint_str.starts_with("<=") { Some(VersionConstraint { operator: VersionOperator::LessThanOrEqual, version: constraint_str[2..].trim().to_string(), }) } else if constraint_str.starts_with(">") { Some(VersionConstraint { operator: VersionOperator::GreaterThan, version: constraint_str[1..].trim().to_string(), }) } else if constraint_str.starts_with("<") { Some(VersionConstraint { operator: VersionOperator::LessThan, version: constraint_str[1..].trim().to_string(), }) } else if constraint_str.starts_with("=") { Some(VersionConstraint { operator: VersionOperator::Equal, version: constraint_str[1..].trim().to_string(), }) } else { // Assume exact version match Some(VersionConstraint { operator: VersionOperator::Equal, version: constraint_str.to_string(), }) } } /// Check for dependency conflicts fn check_conflicts(&self, graph: &DependencyGraph) -> AptOstreeResult> { let mut conflicts = Vec::new(); // Check for direct conflicts for node in graph.nodes.values() { for conflict in &node.metadata.conflicts { if graph.nodes.contains_key(conflict) { conflicts.push(format!("{} conflicts with {}", node.name, conflict)); } } } // Check for circular dependencies if self.has_circular_dependencies(graph)? { conflicts.push("Circular dependency detected".to_string()); } if !conflicts.is_empty() { warn!("Found {} conflicts", conflicts.len()); } Ok(conflicts) } /// Check for circular dependencies using DFS fn has_circular_dependencies(&self, graph: &DependencyGraph) -> AptOstreeResult { let mut visited = HashSet::new(); let mut rec_stack = HashSet::new(); for node_name in graph.nodes.keys() { if !visited.contains(node_name) { if self.is_cyclic_util(graph, node_name, &mut visited, &mut rec_stack)? { return Ok(true); } } } Ok(false) } /// Utility function for cycle detection fn is_cyclic_util( &self, graph: &DependencyGraph, node_name: &str, visited: &mut HashSet, rec_stack: &mut HashSet, ) -> AptOstreeResult { visited.insert(node_name.to_string()); rec_stack.insert(node_name.to_string()); for edge in &graph.edges { if edge.from == *node_name { let neighbor = &edge.to; if !visited.contains(neighbor) { if self.is_cyclic_util(graph, neighbor, visited, rec_stack)? { return Ok(true); } } else if rec_stack.contains(neighbor) { return Ok(true); } } } rec_stack.remove(node_name); Ok(false) } /// Perform topological sort for layering order fn topological_sort(&self, graph: &DependencyGraph) -> AptOstreeResult> { let mut in_degree: HashMap = HashMap::new(); let mut queue: VecDeque = VecDeque::new(); let mut result = Vec::new(); // Initialize in-degrees for node_name in graph.nodes.keys() { in_degree.insert(node_name.clone(), 0); } // Calculate in-degrees for edge in &graph.edges { *in_degree.get_mut(&edge.to).unwrap() += 1; } // Add nodes with no dependencies to queue for (node_name, degree) in &in_degree { if *degree == 0 { queue.push_back(node_name.clone()); } } // Process queue while let Some(node_name) = queue.pop_front() { result.push(node_name.clone()); // Reduce in-degree of neighbors for edge in &graph.edges { if edge.from == *node_name { let neighbor = &edge.to; if let Some(degree) = in_degree.get_mut(neighbor) { *degree -= 1; if *degree == 0 { queue.push_back(neighbor.clone()); } } } } } // Check if all nodes were processed if result.len() != graph.nodes.len() { return Err(AptOstreeError::DependencyConflict( "Circular dependency detected during topological sort".to_string() )); } info!("Topological sort completed: {:?}", result); Ok(result) } /// Calculate dependency levels for layering fn calculate_dependency_levels( &self, graph: &DependencyGraph, layering_order: &[String], ) -> AptOstreeResult>> { let mut levels: Vec> = Vec::new(); let mut node_levels: HashMap = HashMap::new(); for node_name in layering_order { let mut max_dep_level = 0; // Find maximum level of dependencies for edge in &graph.edges { if edge.from == *node_name { if let Some(dep_level) = node_levels.get(&edge.to) { max_dep_level = max_dep_level.max(*dep_level + 1); } } } node_levels.insert(node_name.clone(), max_dep_level); // Add to appropriate level while levels.len() <= max_dep_level { levels.push(Vec::new()); } levels[max_dep_level].push(node_name.clone()); } info!("Calculated {} dependency levels", levels.len()); for (i, level) in levels.iter().enumerate() { debug!("Level {}: {:?}", i, level); } Ok(levels) } } /// Resolved dependencies result #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ResolvedDependencies { pub packages: Vec, pub levels: Vec>, pub graph: DependencyGraph, } impl ResolvedDependencies { /// Get packages in layering order pub fn layering_order(&self) -> &[String] { &self.packages } /// Get packages grouped by dependency level pub fn by_level(&self) -> &[Vec] { &self.levels } /// Get total number of packages pub fn package_count(&self) -> usize { self.packages.len() } /// Get number of dependency levels pub fn level_count(&self) -> usize { self.levels.len() } }