1mod aur;
4mod parse;
5mod query;
6mod resolve;
7mod reverse;
8mod source;
9mod srcinfo;
10mod status;
11mod utils;
12
13use crate::state::modal::{DependencyInfo, DependencyStatus};
14use crate::state::types::{PackageItem, Source};
15use parse::parse_dep_spec;
16use resolve::{batch_fetch_official_deps, fetch_package_conflicts, resolve_package_deps};
17use source::{determine_dependency_source, is_system_package};
18use status::determine_status;
19use std::collections::{HashMap, HashSet};
20use utils::dependency_priority;
21
22pub use query::{
23 get_installed_packages, get_provided_packages, get_upgradable_packages,
24 is_package_installed_or_provided,
25};
26pub use reverse::{
27 ReverseDependencyReport, get_installed_required_by, has_installed_required_by,
28 resolve_reverse_dependencies,
29};
30pub use status::{get_installed_version, version_satisfies};
31
32fn process_conflicts(
48 item: &PackageItem,
49 root_names: &HashSet<String>,
50 installed: &HashSet<String>,
51 provided: &HashSet<String>,
52 deps: &mut HashMap<String, DependencyInfo>,
53) {
54 let conflicts = fetch_package_conflicts(&item.name, &item.source);
55 if conflicts.is_empty() {
56 return;
57 }
58
59 tracing::debug!("Package {} conflicts with: {:?}", item.name, conflicts);
60
61 for conflict_name in conflicts {
62 if conflict_name.eq_ignore_ascii_case(&item.name) {
64 tracing::debug!(
65 "Skipping self-conflict: {} conflicts with itself",
66 item.name
67 );
68 continue;
69 }
70
71 let is_installed = crate::logic::deps::query::is_package_installed_or_provided(
73 &conflict_name,
74 installed,
75 provided,
76 );
77
78 let is_in_install_list = root_names.contains(&conflict_name);
80
81 if !is_installed && !is_in_install_list {
82 continue;
83 }
84
85 let reason = if is_installed && is_in_install_list {
86 format!("conflicts with {conflict_name} (installed and in install list)")
87 } else if is_installed {
88 format!("conflicts with installed package {conflict_name}")
89 } else {
90 format!("conflicts with package {conflict_name} in install list")
91 };
92
93 let entry = deps.entry(conflict_name.clone()).or_insert_with(|| {
95 let (source, is_core) =
97 crate::logic::deps::source::determine_dependency_source(&conflict_name, installed);
98 let is_system =
99 is_core || crate::logic::deps::source::is_system_package(&conflict_name);
100
101 DependencyInfo {
102 name: conflict_name.clone(),
103 version: String::new(),
104 status: DependencyStatus::Conflict {
105 reason: reason.clone(),
106 },
107 source,
108 required_by: vec![item.name.clone()],
109 depends_on: Vec::new(),
110 is_core,
111 is_system,
112 }
113 });
114
115 if !matches!(entry.status, DependencyStatus::Conflict { .. }) {
117 entry.status = DependencyStatus::Conflict { reason };
118 }
119
120 if !entry.required_by.contains(&item.name) {
122 entry.required_by.push(item.name.clone());
123 }
124
125 if is_in_install_list {
129 let reverse_reason = format!("conflicts with package {conflict_name} in install list");
130 let current_entry = deps.entry(item.name.clone()).or_insert_with(|| {
131 let (dep_source, is_core) =
133 crate::logic::deps::source::determine_dependency_source(&item.name, installed);
134 let is_system =
135 is_core || crate::logic::deps::source::is_system_package(&item.name);
136
137 DependencyInfo {
138 name: item.name.clone(),
139 version: String::new(),
140 status: DependencyStatus::Conflict {
141 reason: reverse_reason.clone(),
142 },
143 source: dep_source,
144 required_by: vec![conflict_name.clone()],
145 depends_on: Vec::new(),
146 is_core,
147 is_system,
148 }
149 });
150
151 if !matches!(current_entry.status, DependencyStatus::Conflict { .. }) {
153 current_entry.status = DependencyStatus::Conflict {
154 reason: reverse_reason,
155 };
156 }
157
158 if !current_entry.required_by.contains(&conflict_name) {
160 current_entry.required_by.push(conflict_name.clone());
161 }
162 }
163 }
164}
165
166#[allow(clippy::case_sensitive_file_extension_comparisons)]
181fn process_batched_dependencies(
182 name: &str,
183 dep_names: Vec<String>,
184 installed: &HashSet<String>,
185 provided: &HashSet<String>,
186 upgradable: &HashSet<String>,
187) -> Vec<DependencyInfo> {
188 let mut deps = Vec::new();
189 for dep_spec in dep_names {
190 let (pkg_name, version_req) = parse_dep_spec(&dep_spec);
191 if pkg_name == name {
192 continue;
193 }
194 let pkg_lower = pkg_name.to_lowercase();
195 if pkg_lower.ends_with(".so") || pkg_lower.contains(".so.") || pkg_lower.contains(".so=") {
196 continue;
197 }
198 let status = determine_status(&pkg_name, &version_req, installed, provided, upgradable);
199 let (dep_source, is_core) = determine_dependency_source(&pkg_name, installed);
200 let is_system = is_core || is_system_package(&pkg_name);
201 deps.push(DependencyInfo {
202 name: pkg_name,
203 version: version_req,
204 status,
205 source: dep_source,
206 required_by: vec![name.to_string()],
207 depends_on: Vec::new(),
208 is_core,
209 is_system,
210 });
211 }
212 deps
213}
214
215fn merge_dependency(
231 dep: &DependencyInfo,
232 parent_name: &str,
233 installed: &HashSet<String>,
234 provided: &HashSet<String>,
235 upgradable: &HashSet<String>,
236 deps: &mut HashMap<String, DependencyInfo>,
237) {
238 let dep_name = dep.name.clone();
239
240 let existing_dep = deps.get(&dep_name).cloned();
242 let needs_required_by_update = existing_dep
243 .as_ref()
244 .is_none_or(|e| !e.required_by.contains(&parent_name.to_string()));
245
246 let entry = deps
248 .entry(dep_name.clone())
249 .or_insert_with(|| DependencyInfo {
250 name: dep_name.clone(),
251 version: dep.version.clone(),
252 status: dep.status.clone(),
253 source: dep.source.clone(),
254 required_by: vec![parent_name.to_string()],
255 depends_on: Vec::new(),
256 is_core: dep.is_core,
257 is_system: dep.is_system,
258 });
259
260 if needs_required_by_update {
262 entry.required_by.push(parent_name.to_string());
263 }
264
265 if !matches!(entry.status, DependencyStatus::Conflict { .. }) {
268 let existing_priority = dependency_priority(&entry.status);
269 let new_priority = dependency_priority(&dep.status);
270 if new_priority < existing_priority {
271 entry.status = dep.status.clone();
272 }
273 }
274
275 if !dep.version.is_empty() && dep.version != entry.version {
278 if matches!(entry.status, DependencyStatus::Conflict { .. }) {
280 if entry.version.is_empty() {
282 entry.version.clone_from(&dep.version);
283 }
284 return;
285 }
286
287 if entry.version.is_empty() {
288 entry.version.clone_from(&dep.version);
289 } else {
290 let existing_status =
292 determine_status(&entry.name, &entry.version, installed, provided, upgradable);
293 let new_status =
294 determine_status(&entry.name, &dep.version, installed, provided, upgradable);
295 let existing_req_priority = dependency_priority(&existing_status);
296 let new_req_priority = dependency_priority(&new_status);
297
298 if new_req_priority < existing_req_priority {
299 entry.version.clone_from(&dep.version);
300 entry.status = new_status;
301 }
302 }
303 }
304}
305
306fn resolve_single_package_deps(
321 item: &PackageItem,
322 batched_deps_cache: &HashMap<String, Vec<String>>,
323 installed: &HashSet<String>,
324 provided: &HashSet<String>,
325 upgradable: &HashSet<String>,
326) -> Result<Vec<DependencyInfo>, String> {
327 let name = &item.name;
328 let source = &item.source;
329
330 tracing::debug!(
331 "Resolving direct dependencies for {} (source: {:?})",
332 name,
333 source
334 );
335
336 let use_batched = matches!(source, Source::Official { repo, .. } if repo != "local")
338 && batched_deps_cache.contains_key(name.as_str());
339
340 if use_batched {
341 let dep_names = batched_deps_cache
343 .get(name.as_str())
344 .cloned()
345 .unwrap_or_default();
346 let deps = process_batched_dependencies(name, dep_names, installed, provided, upgradable);
347 Ok(deps)
348 } else {
349 resolve_package_deps(name, source, installed, provided, upgradable)
350 }
351}
352
353pub fn resolve_dependencies(items: &[PackageItem]) -> Vec<DependencyInfo> {
366 let _span = tracing::info_span!(
367 "resolve_dependencies",
368 stage = "dependencies",
369 item_count = items.len()
370 )
371 .entered();
372 let start_time = std::time::Instant::now();
373 let backtrace = std::backtrace::Backtrace::force_capture();
376 let backtrace_str = format!("{backtrace:?}");
377 let is_blocking_task = backtrace_str.contains("blocking::task")
380 || backtrace_str.contains("blocking::pool")
381 || backtrace_str.contains("spawn_blocking");
382 if !is_blocking_task {
383 tracing::warn!(
384 "[Deps] resolve_dependencies called synchronously from UI thread! This will block! Backtrace:\n{}",
385 backtrace_str
386 );
387 }
388
389 if items.is_empty() {
390 tracing::warn!("No packages provided for dependency resolution");
391 return Vec::new();
392 }
393
394 let mut deps: HashMap<String, DependencyInfo> = HashMap::new();
395
396 tracing::info!("Fetching list of installed packages...");
398 let installed = get_installed_packages();
399 tracing::info!("Found {} installed packages", installed.len());
400
401 tracing::debug!(
404 "Provides will be checked lazily on-demand (not building full set for performance)"
405 );
406 let provided = get_provided_packages(&installed);
407
408 let upgradable = get_upgradable_packages();
410 tracing::info!("Found {} upgradable packages", upgradable.len());
411
412 let root_names: HashSet<String> = items.iter().map(|i| i.name.clone()).collect();
414
415 tracing::info!("Checking conflicts for {} package(s)", items.len());
419 for item in items {
420 process_conflicts(item, &root_names, &installed, &provided, &mut deps);
421 }
422
423 let official_packages: Vec<&str> = items
439 .iter()
440 .filter_map(|item| {
441 if let Source::Official { repo, .. } = &item.source {
442 if *repo == "local" {
443 None
444 } else {
445 Some(item.name.as_str())
446 }
447 } else {
448 None
449 }
450 })
451 .collect();
452 let batched_deps_cache = if official_packages.is_empty() {
453 std::collections::HashMap::new()
454 } else {
455 batch_fetch_official_deps(&official_packages)
456 };
457
458 for item in items {
461 match resolve_single_package_deps(
462 item,
463 &batched_deps_cache,
464 &installed,
465 &provided,
466 &upgradable,
467 ) {
468 Ok(resolved_deps) => {
469 tracing::debug!(
470 " Found {} dependencies for {}",
471 resolved_deps.len(),
472 item.name
473 );
474
475 for dep in resolved_deps {
476 merge_dependency(
477 &dep,
478 &item.name,
479 &installed,
480 &provided,
481 &upgradable,
482 &mut deps,
483 );
484
485 }
488 }
489 Err(e) => {
490 tracing::warn!(" Failed to resolve dependencies for {}: {}", item.name, e);
491 }
492 }
493 }
494
495 let mut result: Vec<DependencyInfo> = deps.into_values().collect();
496 tracing::info!("Total unique dependencies found: {}", result.len());
497
498 result.sort_by(|a, b| {
500 let priority_a = dependency_priority(&a.status);
501 let priority_b = dependency_priority(&b.status);
502 priority_a
503 .cmp(&priority_b)
504 .then_with(|| a.name.cmp(&b.name))
505 });
506
507 let elapsed = start_time.elapsed();
508 let duration_ms = u64::try_from(elapsed.as_millis()).unwrap_or(u64::MAX);
509 tracing::info!(
510 stage = "dependencies",
511 item_count = items.len(),
512 result_count = result.len(),
513 duration_ms = duration_ms,
514 "Dependency resolution complete"
515 );
516 result
517}