rez_next_solver/
conflict.rs1use crate::{ConflictResolution, ConflictStrategy, DependencyConflict};
4use rez_next_common::RezCoreError;
5use rez_next_version::Version;
6use std::collections::HashMap;
7
8#[derive(Debug)]
10pub struct ConflictResolver {
11 strategy: ConflictStrategy,
13 cache: HashMap<String, ConflictResolution>,
15}
16
17impl ConflictResolver {
18 pub fn new(strategy: ConflictStrategy) -> Self {
20 Self {
21 strategy,
22 cache: HashMap::new(),
23 }
24 }
25
26 pub async fn resolve_conflicts(
28 &self,
29 conflicts: Vec<DependencyConflict>,
30 ) -> Result<Vec<ConflictResolution>, RezCoreError> {
31 let mut resolutions = Vec::new();
32
33 for conflict in conflicts {
34 let resolution = self.resolve_single_conflict(&conflict).await?;
35 resolutions.push(resolution);
36 }
37
38 Ok(resolutions)
39 }
40
41 async fn resolve_single_conflict(
43 &self,
44 conflict: &DependencyConflict,
45 ) -> Result<ConflictResolution, RezCoreError> {
46 let cache_key = self.generate_conflict_cache_key(conflict);
48 if let Some(cached_resolution) = self.cache.get(&cache_key) {
49 return Ok(cached_resolution.clone());
50 }
51
52 let resolution = match self.strategy {
53 ConflictStrategy::LatestWins => self.resolve_latest_wins(conflict).await?,
54 ConflictStrategy::EarliestWins => self.resolve_earliest_wins(conflict).await?,
55 ConflictStrategy::FailOnConflict => {
56 return Err(RezCoreError::Solver(format!(
57 "Conflict detected for package {}: {:?}",
58 conflict.package_name, conflict.conflicting_requirements
59 )));
60 }
61 ConflictStrategy::FindCompatible => self.resolve_find_compatible(conflict).await?,
62 };
63
64 Ok(resolution)
65 }
66
67 async fn resolve_latest_wins(
69 &self,
70 conflict: &DependencyConflict,
71 ) -> Result<ConflictResolution, RezCoreError> {
72 let mut latest_version: Option<Version> = None;
73 let modified_packages = conflict.source_packages.clone();
74
75 for requirement in &conflict.conflicting_requirements {
77 if let Some(ref version_spec) = requirement.version_spec {
78 if let Ok(v) = Version::parse(version_spec) {
79 match &latest_version {
80 Some(current) if v > *current => latest_version = Some(v),
81 None => latest_version = Some(v),
82 _ => {}
83 }
84 }
85 }
86 }
87
88 Ok(ConflictResolution {
89 package_name: conflict.package_name.clone(),
90 selected_version: latest_version,
91 strategy: "latest_wins".to_string(),
92 modified_packages,
93 })
94 }
95
96 async fn resolve_earliest_wins(
98 &self,
99 conflict: &DependencyConflict,
100 ) -> Result<ConflictResolution, RezCoreError> {
101 let mut earliest_version: Option<Version> = None;
102 let modified_packages = conflict.source_packages.clone();
103
104 for requirement in &conflict.conflicting_requirements {
105 if let Some(ref version_spec) = requirement.version_spec {
106 if let Ok(v) = Version::parse(version_spec) {
107 match &earliest_version {
108 Some(current) if v < *current => earliest_version = Some(v),
109 None => earliest_version = Some(v),
110 _ => {}
111 }
112 }
113 }
114 }
115
116 Ok(ConflictResolution {
117 package_name: conflict.package_name.clone(),
118 selected_version: earliest_version,
119 strategy: "earliest_wins".to_string(),
120 modified_packages,
121 })
122 }
123
124 async fn resolve_find_compatible(
126 &self,
127 conflict: &DependencyConflict,
128 ) -> Result<ConflictResolution, RezCoreError> {
129 let mut candidate: Option<Version> = None;
132
133 for requirement in &conflict.conflicting_requirements {
134 if let Some(ref version_spec) = requirement.version_spec {
135 if let Ok(v) = Version::parse(version_spec) {
136 let satisfies_all = conflict.conflicting_requirements.iter().all(|other_req| {
138 if let Some(ref other_spec) = other_req.version_spec {
139 other_spec == version_spec || other_spec.is_empty()
141 } else {
142 true
143 }
144 });
145
146 if satisfies_all {
147 candidate = Some(v);
148 break;
149 }
150 }
151 }
152 }
153
154 if candidate.is_none() {
156 return self.resolve_latest_wins(conflict).await;
157 }
158
159 let modified_packages = conflict.source_packages.clone();
160 Ok(ConflictResolution {
161 package_name: conflict.package_name.clone(),
162 selected_version: candidate,
163 strategy: "find_compatible".to_string(),
164 modified_packages,
165 })
166 }
167
168 fn generate_conflict_cache_key(&self, conflict: &DependencyConflict) -> String {
170 use std::collections::hash_map::DefaultHasher;
171 use std::hash::{Hash, Hasher};
172
173 let mut hasher = DefaultHasher::new();
174 conflict.package_name.hash(&mut hasher);
175 for req in &conflict.conflicting_requirements {
176 req.name.hash(&mut hasher);
177 if let Some(ref spec) = req.version_spec {
178 spec.hash(&mut hasher);
179 }
180 }
181 format!("{:x}", hasher.finish())
182 }
183}