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        let conflicting = Conflicting { pkg, conflict };
127        if !self.conflicting.contains(&conflicting) {
128            self.conflicting.push(conflicting);
129        }
130    }
131}
132
133/// Describes a package in the package stack.
134#[derive(Debug, Clone, Default)]
135pub struct DepMissing {
136    /// The name of the package
137    pub pkg: String,
138    /// The dep string that pulled in the package. If it was different
139    /// from the package name.
140    pub dep: Option<String>,
141}
142
143impl DepMissing {
144    pub(crate) fn new(pkg: String, dep: String) -> DepMissing {
145        DepMissing {
146            dep: (pkg != dep).then_some(dep),
147            pkg,
148        }
149    }
150}
151
152/// A package that could not be resolved.
153#[derive(Debug, Clone, Default)]
154pub struct Missing {
155    /// The Dependency we failed to satisfy.
156    pub dep: String,
157    /// The dependency path leadsing to pkg.
158    pub stack: Vec<DepMissing>,
159}
160
161impl<'a> Actions<'a> {
162    fn has_pkg<S: AsRef<str>>(&self, name: S) -> bool {
163        let name = name.as_ref();
164        let install = &self.install;
165        self.iter_aur_pkgs().any(|pkg| pkg.pkg.name == name)
166            || self.iter_pkgbuilds().any(|pkg| pkg.1.pkg.pkgname == name)
167            || install.iter().any(|pkg| pkg.pkg.name() == name)
168    }
169
170    fn arch(&self) -> &str {
171        // Assume first alpm arch is our makepkg carch because we can't parse the makepkg config
172        // there's always one configured realisticly. If there's not this resolves to empty string
173        // and then the call to ArchVecs::arch will just give us the any fields and this still works
174        // even if it's not ideal.
175        // TODO make the user explicitly configure an arch and pawn the problem off.
176        self.alpm.architectures().first().unwrap_or_default()
177    }
178
179    // check a conflict from locally installed pkgs, against install+build
180    fn check_reverse_conflict<S: AsRef<str>>(
181        &self,
182        name: S,
183        runtime: bool,
184        conflict: &Dep,
185        conflicts: &mut ConflictMap,
186    ) {
187        let name = name.as_ref();
188
189        self.install
190            .iter()
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
202        self.iter_aur_pkgs()
203            .filter(|pkg| !runtime || !pkg.make)
204            .map(|pkg| &pkg.pkg)
205            .filter(|pkg| pkg.name != name)
206            .filter(|pkg| pkg.satisfies_dep(conflict, false))
207            .for_each(|pkg| {
208                conflicts
209                    .entry(pkg.name.to_string())
210                    .or_insert_with(|| Conflict::new(pkg.name.to_string()))
211                    .push(name.to_string(), conflict);
212            });
213        self.iter_pkgbuilds()
214            .filter(|(_, pkg)| !runtime || !pkg.make)
215            .filter(|(_, pkg)| pkg.pkg.pkgname != name)
216            .filter(|(base, pkg)| (*base, &pkg.pkg).satisfies_dep(conflict, false))
217            .map(|pkg| &pkg.1.pkg)
218            .for_each(|pkg| {
219                conflicts
220                    .entry(pkg.pkgname.clone())
221                    .or_insert_with(|| Conflict::new(pkg.pkgname.to_string()))
222                    .push(name.to_string(), conflict);
223            });
224    }
225
226    // check a conflict from install+build against all locally installed pkgs
227    fn check_forward_conflict<S: AsRef<str>>(
228        &self,
229        name: S,
230        conflict: &Dep,
231        conflicts: &mut ConflictMap,
232    ) {
233        let name = name.as_ref();
234        self.alpm
235            .localdb()
236            .pkgs()
237            .iter()
238            .filter(|pkg| !self.has_pkg(pkg.name()))
239            .filter(|pkg| pkg.name() != name)
240            .filter(|pkg| pkg.satisfies_dep(conflict, false))
241            .for_each(|pkg| {
242                conflicts
243                    .entry(name.to_string())
244                    .or_insert_with(|| Conflict::new(name.to_string()))
245                    .push(pkg.name().to_string(), conflict);
246            });
247    }
248
249    fn check_forward_conflicts(&self, runtime: bool, conflicts: &mut ConflictMap) {
250        for pkg in self.install.iter() {
251            if runtime && pkg.make {
252                continue;
253            }
254
255            for conflict in pkg.pkg.conflicts() {
256                self.check_forward_conflict(pkg.pkg.name(), conflict, conflicts);
257            }
258        }
259
260        for pkg in self.iter_aur_pkgs() {
261            if runtime && pkg.make {
262                continue;
263            }
264
265            for conflict in &pkg.pkg.conflicts {
266                self.check_forward_conflict(
267                    &pkg.pkg.name,
268                    &Depend::new(conflict.to_string()),
269                    conflicts,
270                );
271            }
272        }
273        for (_, pkg) in self.iter_pkgbuilds() {
274            if runtime && pkg.make {
275                continue;
276            }
277
278            for conflict in pkg.pkg.conflicts.arch(self.arch()) {
279                self.check_forward_conflict(
280                    &pkg.pkg.pkgname,
281                    &Depend::new(conflict),
282                    conflicts,
283                );
284            }
285        }
286    }
287
288    fn check_inner_conflicts(&self, runtime: bool, conflicts: &mut ConflictMap) {
289        for pkg in self.install.iter() {
290            if runtime && pkg.make {
291                continue;
292            }
293
294            for conflict in pkg.pkg.conflicts() {
295                self.check_reverse_conflict(pkg.pkg.name(), runtime, conflict, conflicts)
296            }
297        }
298
299        for pkg in self.iter_aur_pkgs() {
300            if runtime && pkg.make {
301                continue;
302            }
303
304            for conflict in pkg.pkg.conflicts.iter() {
305                self.check_reverse_conflict(
306                    &pkg.pkg.name,
307                    runtime,
308                    &Depend::new(conflict.to_string()),
309                    conflicts,
310                )
311            }
312        }
313
314        for (_, pkg) in self.iter_pkgbuilds() {
315            if runtime && pkg.make {
316                continue;
317            }
318
319            for conflict in pkg
320                .pkg
321                .conflicts
322                .arch(self.arch())
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        self.install
399            .iter()
400            .map(|pkg| pkg.pkg.name())
401            .chain(build)
402            .chain(pkgbuilds)
403            .filter(|&name| !names.insert(name))
404            .map(Into::into)
405            .collect::<Vec<_>>()
406    }
407}