aur_depends/
actions.rs

1use crate::{satisfies::Satisfies, Base};
2
3use std::collections::{HashMap, HashSet};
4
5use alpm::{Alpm, Dep, DepMod, Depend};
6use raur::ArcPackage;
7
8type ConflictMap = HashMap<String, Conflict>;
9
10/// The response from resolving dependencies.
11///
12/// Note that just because resolving returned Ok() does not mean it is safe to bindly start
13/// installing these packages.
14#[derive(Debug)]
15pub struct Actions<'a> {
16    pub(crate) alpm: &'a Alpm,
17    /// Some of the targets or dependencies could not be satisfied. This should be treated as
18    /// a hard error.
19    pub missing: Vec<Missing>,
20    /// Targets that are up to date.
21    pub unneeded: Vec<Unneeded>,
22    /// Aur packages to build.
23    pub build: Vec<Base>,
24    /// Repo packages to install.
25    pub install: Vec<RepoPackage<'a>>,
26}
27
28impl<'a> Actions<'a> {
29    /// An iterator over each AUR package in self.build.
30    pub fn iter_aur_pkgs(&self) -> impl Iterator<Item = &AurPackage> {
31        self.build
32            .iter()
33            .filter_map(|b| match b {
34                Base::Aur(pkg) => Some(&pkg.pkgs),
35                Base::Pkgbuild(_) => None,
36            })
37            .flatten()
38    }
39
40    /// An iterator over each pkgbuild in self.build.
41    pub fn iter_pkgbuilds(&self) -> impl Iterator<Item = (&srcinfo::Srcinfo, &Pkgbuild)> {
42        self.build
43            .iter()
44            .filter_map(|b| match b {
45                Base::Aur(_) => None,
46                Base::Pkgbuild(base) => Some((&base.srcinfo, &base.pkgs)),
47            })
48            .flat_map(|(base, pkgs)| pkgs.iter().map(move |p| (base.as_ref(), p)))
49    }
50}
51
52/// Information about an up to date package
53#[derive(Debug, Eq, Clone, PartialEq, Ord, PartialOrd, Hash)]
54pub struct Unneeded {
55    /// Package name
56    pub name: String,
57    /// Package version
58    pub version: String,
59}
60
61impl Unneeded {
62    /// Create a new Unneeded
63    pub fn new<S: Into<String>>(name: S, version: S) -> Self {
64        Unneeded {
65            name: name.into(),
66            version: version.into(),
67        }
68    }
69}
70
71/// Wrapper around a package for extra metadata.
72#[derive(Debug, Eq, Clone, PartialEq, Ord, PartialOrd, Hash)]
73pub struct Package<T> {
74    /// The underlying package
75    pub pkg: T,
76    /// If the package is only needed to build the targets.
77    pub make: bool,
78    /// If the package is a target.
79    pub target: bool,
80}
81
82/// Wrapper around ArcPackage for extra metadata.
83pub type AurPackage = Package<ArcPackage>;
84
85/// Wrapper around Srcinfo for extra metadata.
86pub type Pkgbuild = Package<srcinfo::Package>;
87
88/// Wrapper around alpm::Package for extra metadata.
89pub type RepoPackage<'a> = Package<&'a alpm::Package>;
90
91/// A conflict
92#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone)]
93pub struct Conflict {
94    /// The name of the package.
95    pub pkg: String,
96    /// The packages conflicting with it.
97    pub conflicting: Vec<Conflicting>,
98}
99
100/// A package that has conflicted with something
101#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone)]
102pub struct Conflicting {
103    /// The name of the package.
104    pub pkg: String,
105    /// The conflict that cause the confliction if it is different from the pkgname.
106    pub conflict: Option<String>,
107}
108
109impl Conflict {
110    /// Crate a new conflict.
111    pub fn new(pkg: String) -> Self {
112        Conflict {
113            pkg,
114            conflicting: Vec::with_capacity(1),
115        }
116    }
117
118    /// Push a new conflicting to the conflict.
119    pub fn push(&mut self, pkg: String, conflict: &Dep) {
120        let conflict = if pkg != conflict.name() || conflict.depmod() != DepMod::Any {
121            Some(conflict.to_string())
122        } else {
123            None
124        };
125
126        self.conflicting.push(Conflicting { pkg, conflict });
127    }
128}
129
130/// Describes a package in the package stack.
131#[derive(Debug, Clone, Default)]
132pub struct DepMissing {
133    /// The name of the package
134    pub pkg: String,
135    /// The dep string that pulled in the package. If it was different
136    /// from the package name.
137    pub dep: Option<String>,
138}
139
140impl DepMissing {
141    pub(crate) fn new(pkg: String, dep: String) -> DepMissing {
142        DepMissing {
143            dep: (pkg != dep).then_some(dep),
144            pkg,
145        }
146    }
147}
148
149/// A package that could not be resolved.
150#[derive(Debug, Clone, Default)]
151pub struct Missing {
152    /// The Dependency we failed to satisfy.
153    pub dep: String,
154    /// The dependency path leadsing to pkg.
155    pub stack: Vec<DepMissing>,
156}
157
158impl<'a> Actions<'a> {
159    fn has_pkg<S: AsRef<str>>(&self, name: S) -> bool {
160        let name = name.as_ref();
161        let install = &self.install;
162        self.iter_aur_pkgs().any(|pkg| pkg.pkg.name == name)
163            || self.iter_pkgbuilds().any(|pkg| pkg.1.pkg.pkgname == name)
164            || install.iter().any(|pkg| pkg.pkg.name() == name)
165    }
166
167    // check a conflict from locally installed pkgs, against install+build
168    fn check_reverse_conflict<S: AsRef<str>>(
169        &self,
170        name: S,
171        runtime: bool,
172        conflict: &Dep,
173        conflicts: &mut ConflictMap,
174    ) {
175        let name = name.as_ref();
176
177        self.install
178            .iter()
179            .filter(|pkg| !runtime || !pkg.make)
180            .map(|pkg| &pkg.pkg)
181            .filter(|pkg| pkg.name() != name)
182            .filter(|pkg| pkg.satisfies_dep(conflict, false))
183            .for_each(|pkg| {
184                conflicts
185                    .entry(pkg.name().to_string())
186                    .or_insert_with(|| Conflict::new(pkg.name().to_string()))
187                    .push(name.to_string(), conflict);
188            });
189
190        self.iter_aur_pkgs()
191            .filter(|pkg| !runtime || !pkg.make)
192            .map(|pkg| &pkg.pkg)
193            .filter(|pkg| pkg.name != name)
194            .filter(|pkg| pkg.satisfies_dep(conflict, false))
195            .for_each(|pkg| {
196                conflicts
197                    .entry(pkg.name.to_string())
198                    .or_insert_with(|| Conflict::new(pkg.name.to_string()))
199                    .push(name.to_string(), conflict);
200            });
201        self.iter_pkgbuilds()
202            .filter(|(_, pkg)| !runtime || !pkg.make)
203            .filter(|(_, pkg)| pkg.pkg.pkgname != name)
204            .filter(|(base, pkg)| (*base, &pkg.pkg).satisfies_dep(conflict, false))
205            .map(|pkg| &pkg.1.pkg)
206            .for_each(|pkg| {
207                conflicts
208                    .entry(pkg.pkgname.clone())
209                    .or_insert_with(|| Conflict::new(pkg.pkgname.to_string()))
210                    .push(name.to_string(), conflict);
211            });
212    }
213
214    // check a conflict from install+build against all locally installed pkgs
215    fn check_forward_conflict<S: AsRef<str>>(
216        &self,
217        name: S,
218        conflict: &Dep,
219        conflicts: &mut ConflictMap,
220    ) {
221        let name = name.as_ref();
222        self.alpm
223            .localdb()
224            .pkgs()
225            .iter()
226            .filter(|pkg| !self.has_pkg(pkg.name()))
227            .filter(|pkg| pkg.name() != name)
228            .filter(|pkg| pkg.satisfies_dep(conflict, false))
229            .for_each(|pkg| {
230                conflicts
231                    .entry(name.to_string())
232                    .or_insert_with(|| Conflict::new(name.to_string()))
233                    .push(pkg.name().to_string(), conflict);
234            });
235    }
236
237    fn check_forward_conflicts(&self, runtime: bool, conflicts: &mut ConflictMap) {
238        for pkg in self.install.iter() {
239            if runtime && pkg.make {
240                continue;
241            }
242
243            for conflict in pkg.pkg.conflicts() {
244                self.check_forward_conflict(pkg.pkg.name(), &conflict, conflicts);
245            }
246        }
247
248        for pkg in self.iter_aur_pkgs() {
249            if runtime && pkg.make {
250                continue;
251            }
252
253            for conflict in &pkg.pkg.conflicts {
254                self.check_forward_conflict(
255                    &pkg.pkg.name,
256                    &Depend::new(conflict.to_string()),
257                    conflicts,
258                );
259            }
260        }
261        for (_, pkg) in self.iter_pkgbuilds() {
262            if runtime && pkg.make {
263                continue;
264            }
265
266            for conflict in pkg
267                .pkg
268                .conflicts
269                .iter()
270                .filter(|c| {
271                    c.arch.is_none() || c.arch.as_deref() == self.alpm.architectures().first()
272                })
273                .flat_map(|c| &c.vec)
274            {
275                self.check_forward_conflict(
276                    &pkg.pkg.pkgname,
277                    &Depend::new(conflict.clone()),
278                    conflicts,
279                );
280            }
281        }
282    }
283
284    fn check_inner_conflicts(&self, runtime: bool, conflicts: &mut ConflictMap) {
285        for pkg in self.install.iter() {
286            if runtime && pkg.make {
287                continue;
288            }
289
290            for conflict in pkg.pkg.conflicts() {
291                self.check_reverse_conflict(pkg.pkg.name(), runtime, &conflict, conflicts)
292            }
293        }
294
295        for pkg in self.iter_aur_pkgs() {
296            if runtime && pkg.make {
297                continue;
298            }
299
300            for conflict in pkg.pkg.conflicts.iter() {
301                self.check_reverse_conflict(
302                    &pkg.pkg.name,
303                    runtime,
304                    &Depend::new(conflict.to_string()),
305                    conflicts,
306                )
307            }
308        }
309
310        for (_, pkg) in self.iter_pkgbuilds() {
311            if runtime && pkg.make {
312                continue;
313            }
314
315            for conflict in pkg
316                .pkg
317                .conflicts
318                .iter()
319                .filter(|c| {
320                    c.arch.is_none() || c.arch.as_deref() == self.alpm.architectures().first()
321                })
322                .flat_map(|c| &c.vec)
323            {
324                self.check_reverse_conflict(
325                    &pkg.pkg.pkgname,
326                    runtime,
327                    &Depend::new(conflict.to_string()),
328                    conflicts,
329                )
330            }
331        }
332    }
333
334    fn check_reverse_conflicts(&self, runtime: bool, conflicts: &mut ConflictMap) {
335        self.alpm
336            .localdb()
337            .pkgs()
338            .iter()
339            .filter(|pkg| !self.has_pkg(pkg.name()))
340            .for_each(|pkg| {
341                pkg.conflicts().iter().for_each(|conflict| {
342                    self.check_reverse_conflict(pkg.name(), runtime, &conflict, conflicts)
343                })
344            });
345    }
346
347    /// Calculate conflicts. Do note that even with conflicts it can still be possible to continue and
348    /// install the packages. Although that is not checked here.
349    ///
350    /// For example installing pacman-git will conflict with pacman. But the install will still
351    /// succeed as long as the user hits yes to pacman's prompt to remove pacman.
352    ///
353    /// However other cases are more complex and can not be automatically resolved. So it is up to
354    /// the user to decide how to handle these.
355    ///
356    /// makedeps: if true, include make dependencies in the conflict calculation.
357    pub fn calculate_conflicts(&self, makedeps: bool) -> Vec<Conflict> {
358        let mut conflicts = ConflictMap::new();
359
360        self.check_reverse_conflicts(!makedeps, &mut conflicts);
361        self.check_forward_conflicts(!makedeps, &mut conflicts);
362
363        let mut conflicts = conflicts.into_values().collect::<Vec<Conflict>>();
364
365        conflicts.sort();
366        conflicts
367    }
368
369    /// Calculate inner conflicts. Do note that even with conflicts it can still be possible to continue and
370    /// install the packages. Although that is not checked here.
371    ///
372    /// For example installing pacman-git will conflict with pacman. But the install will still
373    /// succeed as long as the user hits yes to pacman's prompt to remove pacman.
374    ///
375    /// However other cases are more complex and can not be automatically resolved. So it is up to
376    /// the user to decide how to handle these.
377    ///
378    /// makedeps: if true, include make dependencies in the conflict calculation.
379    pub fn calculate_inner_conflicts(&self, makedeps: bool) -> Vec<Conflict> {
380        let mut inner_conflicts = ConflictMap::new();
381
382        self.check_inner_conflicts(!makedeps, &mut inner_conflicts);
383
384        let mut inner_conflicts = inner_conflicts.into_values().collect::<Vec<Conflict>>();
385
386        inner_conflicts.sort();
387        inner_conflicts
388    }
389
390    /// Find duplicate targets. It is possible to have duplicate targets if packages with the same
391    /// name exist across repos.
392    pub fn duplicate_targets(&self) -> Vec<String> {
393        let mut names = HashSet::new();
394
395        let build = self.iter_aur_pkgs().map(|pkg| pkg.pkg.name.as_str());
396        let pkgbuilds = self.iter_pkgbuilds().map(|pkg| pkg.1.pkg.pkgname.as_str());
397
398        let duplicates = self
399            .install
400            .iter()
401            .map(|pkg| pkg.pkg.name())
402            .chain(build)
403            .chain(pkgbuilds)
404            .filter(|&name| !names.insert(name))
405            .map(Into::into)
406            .collect::<Vec<_>>();
407
408        duplicates
409    }
410}