1use async_trait::async_trait;
4use std::collections::{HashMap, HashSet};
5use tracing::{debug, info};
6
7use crate::core::{
8 ContextMetadata, DependencyResolver, Package, PlatformInfo, Requirement, ResolutionStats,
9 ResolvedContext, ResolverError, Result, Version, VersionConstraint,
10};
11
12pub struct DependencyResolverImpl {
14 packages: HashMap<String, Vec<Package>>,
16 resolution_cache: HashMap<Vec<Requirement>, Option<ResolvedContext>>,
18}
19
20impl DependencyResolverImpl {
21 pub fn new() -> Self {
23 Self {
24 packages: HashMap::new(),
25 resolution_cache: HashMap::new(),
26 }
27 }
28
29 pub fn set_packages(&mut self, packages: HashMap<String, Vec<Package>>) {
31 self.packages = packages;
32 self.resolution_cache.clear(); }
34
35 fn find_best_version(&self, name: &str, constraint: &VersionConstraint) -> Option<&Package> {
37 let versions = self.packages.get(name)?;
38
39 let mut candidates: Vec<&Package> = versions
41 .iter()
42 .filter(|pkg| constraint.satisfies(&pkg.version))
43 .collect();
44
45 if candidates.is_empty() {
46 return None;
47 }
48
49 candidates.sort_by(|a, b| b.version.cmp(&a.version));
51
52 candidates.first().copied()
53 }
54
55 fn check_conflicts(&self, requirements: &[Requirement]) -> Vec<String> {
57 let mut conflicts = Vec::new();
58 let mut package_constraints: HashMap<String, Vec<&Requirement>> = HashMap::new();
59
60 for req in requirements {
62 package_constraints
63 .entry(req.name.clone())
64 .or_default()
65 .push(req);
66 }
67
68 for (package_name, reqs) in package_constraints {
70 if reqs.len() > 1 {
71 if let Some(versions) = self.packages.get(&package_name) {
73 let satisfying_versions: Vec<&Package> = versions
74 .iter()
75 .filter(|pkg| {
76 reqs.iter().all(|req| {
77 if req.conflict {
78 !req.constraint.satisfies(&pkg.version)
79 } else {
80 req.constraint.satisfies(&pkg.version)
81 }
82 })
83 })
84 .collect();
85
86 if satisfying_versions.is_empty() {
87 conflicts.push(format!(
88 "No version of '{}' satisfies all constraints: {}",
89 package_name,
90 reqs.iter()
91 .map(|r| r.to_string())
92 .collect::<Vec<_>>()
93 .join(", ")
94 ));
95 }
96 }
97 }
98
99 for req in &reqs {
101 if req.conflict {
102 if let Some(best_version) =
104 self.find_best_version(&req.name, &VersionConstraint::Any)
105 {
106 if req.constraint.satisfies(&best_version.version) {
107 conflicts.push(format!(
108 "Conflict: package '{}' version '{}' is explicitly forbidden",
109 req.name, best_version.version
110 ));
111 }
112 }
113 }
114 }
115 }
116
117 conflicts
118 }
119
120 fn resolve_recursive(
122 &self,
123 requirements: &[Requirement],
124 resolved: &mut HashMap<String, Package>,
125 visited: &mut HashSet<String>,
126 ) -> Result<()> {
127 for req in requirements {
128 if req.conflict {
129 continue;
131 }
132
133 if visited.contains(&req.name) {
134 return Err(ResolverError::CircularDependency(format!(
136 "Circular dependency detected involving package '{}'",
137 req.name
138 ))
139 .into());
140 }
141
142 if resolved.contains_key(&req.name) {
143 let existing = &resolved[&req.name];
145 if !req.constraint.satisfies(&existing.version) {
146 return Err(ResolverError::Conflict(format!(
147 "Version conflict for package '{}': existing version '{}' does not satisfy constraint '{}'",
148 req.name, existing.version, req.constraint
149 )).into());
150 }
151 continue;
152 }
153
154 let package = self
156 .find_best_version(&req.name, &req.constraint)
157 .ok_or_else(|| {
158 ResolverError::PackageNotFound(format!(
159 "No version of package '{}' satisfies constraint '{}'",
160 req.name, req.constraint
161 ))
162 })?;
163
164 debug!("Resolved '{}' to version '{}'", req.name, package.version);
165
166 visited.insert(req.name.clone());
168
169 self.resolve_recursive(&package.requires, resolved, visited)?;
171
172 visited.remove(&req.name);
174
175 resolved.insert(req.name.clone(), package.clone());
177 }
178
179 Ok(())
180 }
181}
182
183impl Default for DependencyResolverImpl {
184 fn default() -> Self {
185 Self::new()
186 }
187}
188
189#[async_trait]
190impl DependencyResolver for DependencyResolverImpl {
191 async fn resolve(&self, requirements: &[Requirement]) -> Result<ResolvedContext> {
192 let start_time = std::time::Instant::now();
193 info!(
194 "Starting dependency resolution for {} requirements",
195 requirements.len()
196 );
197
198 let conflicts = self.check_conflicts(requirements);
200 if !conflicts.is_empty() {
201 return Err(ResolverError::Conflict(conflicts.join("; ")).into());
202 }
203
204 let mut resolved = HashMap::new();
205 let mut visited = HashSet::new();
206
207 self.resolve_recursive(requirements, &mut resolved, &mut visited)?;
209
210 let resolution_time = start_time.elapsed();
211 let packages: Vec<Package> = resolved.into_values().collect();
212 let packages_count = packages.len();
213
214 info!(
215 "Dependency resolution completed: {} packages resolved in {:?}",
216 packages_count, resolution_time
217 );
218
219 Ok(ResolvedContext {
220 packages,
221 metadata: ContextMetadata {
222 timestamp: chrono::Utc::now(),
223 resolver_version: env!("CARGO_PKG_VERSION").to_string(),
224 platform: PlatformInfo {
225 os: std::env::consts::OS.to_string(),
226 arch: std::env::consts::ARCH.to_string(),
227 platform: format!("{}-{}", std::env::consts::OS, std::env::consts::ARCH),
228 },
229 stats: ResolutionStats {
230 packages_considered: self.packages.values().map(|v| v.len()).sum(),
231 packages_resolved: packages_count,
232 resolution_time_ms: resolution_time.as_millis() as u64,
233 conflicts: conflicts.len(),
234 },
235 },
236 })
237 }
238
239 async fn can_resolve(&self, requirements: &[Requirement]) -> Result<bool> {
240 match self.resolve(requirements).await {
241 Ok(_) => Ok(true),
242 Err(_) => Ok(false),
243 }
244 }
245
246 async fn find_conflicts(
247 &self,
248 requirements: &[Requirement],
249 ) -> Result<Vec<crate::core::DependencyConflict>> {
250 let conflicts = self.check_conflicts(requirements);
251 Ok(conflicts
252 .into_iter()
253 .map(|description| crate::core::DependencyConflict {
254 package: "unknown".to_string(), requirements: requirements.to_vec(),
256 description,
257 })
258 .collect())
259 }
260
261 async fn get_latest_version(
262 &self,
263 name: &str,
264 constraint: &VersionConstraint,
265 ) -> Result<Option<Version>> {
266 Ok(self
267 .find_best_version(name, constraint)
268 .map(|pkg| pkg.version.clone()))
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275 use crate::core::{Package, Requirement, Version, VersionConstraint};
276 use std::collections::HashMap;
277 use std::path::PathBuf;
278
279 fn create_test_package(name: &str, version: &str, requires: Vec<Requirement>) -> Package {
280 Package {
281 name: name.to_string(),
282 version: Version::new(version),
283 description: Some(format!("Test package {}", name)),
284 authors: vec!["Test Author".to_string()],
285 requires,
286 tools: vec![],
287 variants: vec![],
288 path: PathBuf::from("/test"),
289 metadata: HashMap::new(),
290 }
291 }
292
293 #[tokio::test]
294 async fn test_simple_resolution() {
295 let mut resolver = DependencyResolverImpl::new();
296
297 let mut packages = HashMap::new();
299 packages.insert(
300 "python".to_string(),
301 vec![create_test_package("python", "3.9.0", vec![])],
302 );
303
304 resolver.set_packages(packages);
305
306 let requirements = vec![Requirement::new("python", VersionConstraint::Any)];
308 let result = resolver.resolve(&requirements).await;
309
310 assert!(result.is_ok());
311 let context = result.unwrap();
312 assert_eq!(context.packages.len(), 1);
313 assert_eq!(context.packages[0].name, "python");
314 }
315
316 #[tokio::test]
317 async fn test_version_constraint() {
318 let mut resolver = DependencyResolverImpl::new();
319
320 let mut packages = HashMap::new();
322 packages.insert(
323 "python".to_string(),
324 vec![
325 create_test_package("python", "3.7.0", vec![]),
326 create_test_package("python", "3.8.0", vec![]),
327 create_test_package("python", "3.9.0", vec![]),
328 ],
329 );
330
331 resolver.set_packages(packages);
332
333 let requirements = vec![Requirement::new(
335 "python",
336 VersionConstraint::GreaterEqual(Version::new("3.8")),
337 )];
338 let result = resolver.resolve(&requirements).await;
339
340 assert!(result.is_ok());
341 let context = result.unwrap();
342 assert_eq!(context.packages.len(), 1);
343 assert_eq!(context.packages[0].version, Version::new("3.9.0")); }
345
346 #[tokio::test]
347 async fn test_conflict_detection() {
348 let mut resolver = DependencyResolverImpl::new();
349
350 let mut packages = HashMap::new();
352 packages.insert(
353 "python".to_string(),
354 vec![create_test_package("python", "3.9.0", vec![])],
355 );
356
357 resolver.set_packages(packages);
358
359 let requirements = vec![
361 Requirement::new("python", VersionConstraint::Exact(Version::new("3.9.0"))),
362 Requirement::new("python", VersionConstraint::Exact(Version::new("3.8.0"))),
363 ];
364
365 let result = resolver.resolve(&requirements).await;
366 assert!(result.is_err());
367 }
368}