pkgcraft/
pkg.rs

1use std::fmt;
2
3use enum_as_inner::EnumAsInner;
4use scallop::ExecStatus;
5
6use crate::dep::{Cpn, Cpv, Dep, Version};
7use crate::eapi::{Eapi, Restrict as EapiRestrict};
8use crate::repo::{Repo, Repository};
9use crate::restrict::str::Restrict as StrRestrict;
10use crate::restrict::{Restrict as BaseRestrict, Restriction};
11use crate::traits::Intersects;
12
13pub mod ebuild;
14pub mod fake;
15
16#[allow(clippy::large_enum_variant)]
17#[derive(EnumAsInner, Debug, Clone)]
18pub enum Pkg {
19    Configured(ebuild::EbuildConfiguredPkg),
20    Ebuild(ebuild::EbuildPkg),
21    Fake(fake::Pkg),
22}
23
24make_pkg_traits!(Pkg);
25
26pub trait Package:
27    fmt::Debug + fmt::Display + Intersects<Dep> + Intersects<Cpv> + Intersects<Cpn>
28{
29    /// Return a package's EAPI.
30    fn eapi(&self) -> &'static Eapi;
31
32    /// Return a package's Cpv.
33    fn cpv(&self) -> &Cpv;
34
35    /// Return the unversioned package.
36    fn cpn(&self) -> &Cpn {
37        self.cpv().cpn()
38    }
39
40    /// Return a package's category.
41    fn category(&self) -> &str {
42        self.cpv().category()
43    }
44
45    /// Return a package's name.
46    fn package(&self) -> &str {
47        self.cpv().package()
48    }
49
50    /// Return a package's version.
51    fn version(&self) -> &Version {
52        self.cpv().version()
53    }
54
55    /// Return a package's name and version.
56    fn p(&self) -> String {
57        self.cpv().p()
58    }
59
60    /// Return a package's name, version, and revision.
61    fn pf(&self) -> String {
62        self.cpv().pf()
63    }
64
65    /// Return a package's revision.
66    fn pr(&self) -> String {
67        self.cpv().pr()
68    }
69
70    /// Return a package's version.
71    fn pv(&self) -> String {
72        self.cpv().pv()
73    }
74
75    /// Returna package's version and revision.
76    fn pvr(&self) -> String {
77        self.cpv().pvr()
78    }
79}
80
81pub trait RepoPackage: Package + Ord {
82    type Repo: Repository;
83
84    /// Return a package's repo.
85    fn repo(&self) -> Self::Repo;
86}
87
88#[allow(dead_code)]
89pub(crate) trait Build: Package {
90    /// Run the build operations for a package.
91    fn build(&self) -> scallop::Result<()>;
92}
93
94pub(crate) trait PkgPretend: Package {
95    /// Run the pkg_pretend operation for a package.
96    fn pkg_pretend(&self) -> scallop::Result<Option<String>>;
97}
98
99pub trait Source: Package {
100    /// Source a package.
101    fn source(&self) -> scallop::Result<ExecStatus>;
102}
103
104macro_rules! make_pkg_traits {
105    ($($x:ty),+) => {$(
106        impl PartialEq for $x {
107            fn eq(&self, other: &Self) -> bool {
108                self.repo() == other.repo() && self.cpv() == other.cpv()
109            }
110        }
111
112        impl Eq for $x {}
113
114        impl std::hash::Hash for $x {
115            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
116                self.repo().hash(state);
117                self.cpv().hash(state);
118            }
119        }
120
121        impl PartialOrd for $x {
122            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
123                Some(self.cmp(other))
124            }
125        }
126
127        impl Ord for $x {
128            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
129                self.cpv().cmp(other.cpv()).then_with(|| self.repo().cmp(&other.repo()))
130            }
131        }
132
133        impl std::fmt::Display for $x {
134            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
135                write!(f, "{}::{}", self.cpv(), self.repo())
136            }
137        }
138
139        impl From<&$x> for $crate::restrict::Restrict {
140            fn from(pkg: &$x) -> Self {
141                let r1 = pkg.cpv().into();
142                let r2 = $crate::restrict::Restrict::Dep(pkg.repo().into());
143                $crate::restrict::Restrict::and([r1, r2])
144            }
145        }
146
147        impl $crate::traits::Intersects<$x> for $crate::dep::Dep {
148            fn intersects(&self, other: &$x) -> bool {
149                other.intersects(self)
150            }
151        }
152
153        impl $crate::traits::Intersects<$crate::dep::Cpv> for $x {
154            fn intersects(&self, cpv: &$crate::dep::Cpv) -> bool {
155                self.cpv() == cpv
156            }
157        }
158
159        impl $crate::traits::Intersects<$x> for $crate::dep::Cpv {
160            fn intersects(&self, other: &$x) -> bool {
161                other.intersects(self)
162            }
163        }
164
165        impl $crate::traits::Intersects<$crate::dep::Cpn> for $x {
166            fn intersects(&self, cpn: &$crate::dep::Cpn) -> bool {
167                self.cpn() == cpn
168            }
169        }
170
171        impl $crate::traits::Intersects<$x> for $crate::dep::Cpn {
172            fn intersects(&self, other: &$x) -> bool {
173                other.intersects(self)
174            }
175        }
176
177        impl From<&$x> for $crate::dep::Cpv {
178            fn from(value: &$x) -> Self {
179                value.cpv().clone()
180            }
181        }
182    )+};
183}
184use make_pkg_traits;
185
186impl Package for Pkg {
187    fn eapi(&self) -> &'static Eapi {
188        match self {
189            Self::Configured(pkg) => pkg.eapi(),
190            Self::Ebuild(pkg) => pkg.eapi(),
191            Self::Fake(pkg) => pkg.eapi(),
192        }
193    }
194
195    fn cpv(&self) -> &Cpv {
196        match self {
197            Self::Configured(pkg) => pkg.cpv(),
198            Self::Ebuild(pkg) => pkg.cpv(),
199            Self::Fake(pkg) => pkg.cpv(),
200        }
201    }
202}
203
204impl RepoPackage for Pkg {
205    type Repo = Repo;
206
207    fn repo(&self) -> Self::Repo {
208        match self {
209            Self::Configured(pkg) => pkg.repo().into(),
210            Self::Ebuild(pkg) => pkg.repo().into(),
211            Self::Fake(pkg) => pkg.repo().into(),
212        }
213    }
214}
215
216impl Intersects<Dep> for Pkg {
217    fn intersects(&self, dep: &Dep) -> bool {
218        match self {
219            Self::Configured(pkg) => pkg.intersects(dep),
220            Self::Ebuild(pkg) => pkg.intersects(dep),
221            Self::Fake(pkg) => pkg.intersects(dep),
222        }
223    }
224}
225
226impl<T> Package for &T
227where
228    T: Package,
229{
230    fn eapi(&self) -> &'static Eapi {
231        (*self).eapi()
232    }
233    fn cpv(&self) -> &Cpv {
234        (*self).cpv()
235    }
236}
237
238impl<T> RepoPackage for &T
239where
240    T: RepoPackage,
241{
242    type Repo = T::Repo;
243
244    fn repo(&self) -> Self::Repo {
245        (*self).repo()
246    }
247}
248
249impl<T> Intersects<Dep> for &T
250where
251    T: Package,
252{
253    fn intersects(&self, dep: &Dep) -> bool {
254        (*self).intersects(dep)
255    }
256}
257
258impl<T> Intersects<Cpv> for &T
259where
260    T: Package,
261{
262    fn intersects(&self, cpv: &Cpv) -> bool {
263        (*self).intersects(cpv)
264    }
265}
266
267impl<T> Intersects<Cpn> for &T
268where
269    T: Package,
270{
271    fn intersects(&self, cpn: &Cpn) -> bool {
272        (*self).intersects(cpn)
273    }
274}
275
276#[derive(Debug, Clone, PartialEq, Eq, Hash)]
277pub enum Restrict {
278    Eapi(EapiRestrict),
279    Ebuild(ebuild::Restrict),
280    Repo(StrRestrict),
281}
282
283impl Restrict {
284    pub fn eapi(s: &str) -> Self {
285        Self::Eapi(EapiRestrict::Id(StrRestrict::equal(s)))
286    }
287
288    pub fn repo(s: &str) -> Self {
289        Self::Repo(StrRestrict::equal(s))
290    }
291}
292
293impl From<Restrict> for BaseRestrict {
294    fn from(r: Restrict) -> Self {
295        Self::Pkg(r)
296    }
297}
298
299impl Restriction<&Pkg> for Restrict {
300    fn matches(&self, pkg: &Pkg) -> bool {
301        match self {
302            Self::Eapi(r) => r.matches(pkg.eapi()),
303            Self::Repo(r) => r.matches(pkg.repo().id()),
304            Self::Ebuild(r) => match pkg {
305                Pkg::Ebuild(p) => r.matches(p),
306                _ => false,
307            },
308        }
309    }
310}
311
312impl Restriction<&Pkg> for BaseRestrict {
313    fn matches(&self, pkg: &Pkg) -> bool {
314        crate::restrict::restrict_match! {self, pkg,
315            Self::Dep(r) => r.matches(pkg),
316            Self::Pkg(r) => r.matches(pkg),
317        }
318    }
319}
320
321impl Restriction<&Repo> for Restrict {
322    fn matches(&self, repo: &Repo) -> bool {
323        match self {
324            Self::Repo(r) => r.matches(repo.id()),
325            _ => false,
326        }
327    }
328}
329
330#[cfg(test)]
331mod tests {
332    use itertools::Itertools;
333
334    use crate::eapi::EAPI_LATEST_OFFICIAL;
335    use crate::repo::{PkgRepository, fake};
336    use crate::test::assert_ordered_eq;
337
338    use super::*;
339
340    #[test]
341    fn ordering() {
342        // unmatching pkgs sorted by dep attributes
343        let r1: Repo = fake::FakeRepo::new("b", 0)
344            .pkgs(["cat/pkg-1"])
345            .unwrap()
346            .into();
347        let r2: Repo = fake::FakeRepo::new("a", 0)
348            .pkgs(["cat/pkg-0"])
349            .unwrap()
350            .into();
351        let pkgs: Vec<_> = r1.iter().chain(r2.iter()).try_collect().unwrap();
352        let sorted_pkgs: Vec<_> = pkgs.iter().sorted().collect();
353        assert_ordered_eq!(pkgs.iter().rev(), sorted_pkgs);
354
355        // matching pkgs sorted by repo priority
356        let r1: Repo = fake::FakeRepo::new("a", -1)
357            .pkgs(["cat/pkg-0"])
358            .unwrap()
359            .into();
360        let r2: Repo = fake::FakeRepo::new("b", 0)
361            .pkgs(["cat/pkg-0"])
362            .unwrap()
363            .into();
364        let pkgs: Vec<_> = r1.iter().chain(r2.iter()).try_collect().unwrap();
365        let sorted_pkgs: Vec<_> = pkgs.iter().sorted().collect();
366        assert_ordered_eq!(pkgs.iter().rev(), sorted_pkgs);
367
368        // matching pkgs sorted by repo id since repos have matching priorities
369        let r1: Repo = fake::FakeRepo::new("2", 0)
370            .pkgs(["cat/pkg-0"])
371            .unwrap()
372            .into();
373        let r2: Repo = fake::FakeRepo::new("1", 0)
374            .pkgs(["cat/pkg-0"])
375            .unwrap()
376            .into();
377        let pkgs: Vec<_> = r1.iter().chain(r2.iter()).try_collect().unwrap();
378        let sorted_pkgs: Vec<_> = pkgs.iter().sorted().collect();
379        assert_ordered_eq!(pkgs.iter().rev(), sorted_pkgs);
380    }
381
382    #[test]
383    fn package_trait() {
384        let cpv = Cpv::try_new("cat/pkg-1-r2").unwrap();
385        let cpn = Cpn::try_new("cat/pkg").unwrap();
386        let dep = Dep::try_new(">=cat/pkg-1").unwrap();
387        let r: Repo = fake::FakeRepo::new("test", 0).pkgs([&cpv]).unwrap().into();
388        let pkg = r.iter_restrict(&cpv).next().unwrap().unwrap();
389        assert_eq!(pkg.eapi(), *EAPI_LATEST_OFFICIAL);
390        assert_eq!(pkg.cpv(), &cpv);
391        assert_eq!(pkg.cpn(), &cpn);
392        assert_eq!(pkg.category(), "cat");
393        assert_eq!(pkg.package(), "pkg");
394        assert_eq!(pkg.version().to_string(), "1-r2");
395        assert_eq!(pkg.p(), "pkg-1");
396        assert_eq!(pkg.pf(), "pkg-1-r2");
397        assert_eq!(pkg.pr(), "r2");
398        assert_eq!(pkg.pv(), "1");
399        assert_eq!(pkg.pvr(), "1-r2");
400
401        // intersects
402        assert!(pkg.intersects(&dep));
403        assert!(dep.intersects(&pkg));
404        assert!(pkg.intersects(&cpv));
405        assert!(cpv.intersects(&pkg));
406        assert!(pkg.intersects(&cpn));
407        assert!(cpn.intersects(&pkg));
408
409        let pkg = &&pkg;
410        assert!(pkg.intersects(&dep));
411        assert!(pkg.intersects(&cpv));
412        assert!(pkg.intersects(&cpn));
413    }
414
415    #[test]
416    fn intersects_dep() {
417        let cpv = Cpv::try_new("cat/pkg-1-r2").unwrap();
418        let r: Repo = fake::FakeRepo::new("test", 0).pkgs([&cpv]).unwrap().into();
419        let pkg = r.iter_restrict(&cpv).next().unwrap().unwrap();
420
421        for (s, expected) in [
422            ("cat/pkg", true),
423            ("a/b", false),
424            ("=cat/pkg-1-r2", true),
425            (">cat/pkg-1-r2", false),
426            ("~cat/pkg-1", true),
427        ] {
428            let dep: Dep = s.parse().unwrap();
429            assert_eq!(pkg.intersects(&dep), expected, "failed for {s}");
430        }
431    }
432}