debian_packaging/
dependency.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5/*! Debian package dependency syntax handling.
6
7See <https://www.debian.org/doc/debian-policy/ch-relationships.html> for the specification.
8 */
9
10use {
11    crate::{
12        control::ControlParagraph,
13        error::{DebianError, Result},
14        package_version::PackageVersion,
15    },
16    once_cell::sync::Lazy,
17    regex::Regex,
18    std::{
19        cmp::Ordering,
20        fmt::{Display, Formatter},
21        ops::{Deref, DerefMut},
22        str::FromStr,
23    },
24};
25
26/// Regular expression to parse dependency expressions.
27pub static RE_DEPENDENCY: Lazy<Regex> = Lazy::new(|| {
28    // TODO <> is a legacy syntax.
29    Regex::new(
30        r"(?x)
31        # Package name is alphanumeric, terminating at whitespace, [ or (
32        (?P<package>[^\s\[(]+)
33        # Any number of optional spaces.
34        \s*
35        # Relationships are within an optional parenthesis.
36        (?:\(
37            # Optional spaces after (
38            \s*
39            # The relationship operator.
40            (?P<relop>(<<|<=|=|>=|>>))
41            # Optional spaces after the operator.
42            \s*
43            # Version string is everything up to space or closing parenthesis.
44            (?P<version>[^\s)]+)
45            # Trailing space before ).
46            \s*
47        \))?
48        # Any amount of space after optional relationship definition.
49        \s*
50        # Architecture restrictions are within an optional [..] field.
51        (?:\[
52            # Optional whitespace after [
53            \s*
54            # Optional negation operator.
55            (?P<arch_negate>!)?
56            \s*
57            # The architecture. May have spaces to delimit multiple values.
58            (?P<arch>[^\]]+)
59        \])?
60        ",
61    )
62    .unwrap()
63});
64
65#[derive(Clone, Copy, Debug, PartialEq)]
66pub enum VersionRelationship {
67    StrictlyEarlier,
68    EarlierOrEqual,
69    ExactlyEqual,
70    LaterOrEqual,
71    StrictlyLater,
72}
73
74impl Display for VersionRelationship {
75    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
76        match self {
77            Self::StrictlyEarlier => write!(f, "<<"),
78            Self::EarlierOrEqual => write!(f, "<="),
79            Self::ExactlyEqual => write!(f, "="),
80            Self::LaterOrEqual => write!(f, ">="),
81            Self::StrictlyLater => write!(f, ">>"),
82        }
83    }
84}
85
86/// Represents a version constraint on a given package.
87#[derive(Clone, Debug, PartialEq)]
88pub struct DependencyVersionConstraint {
89    pub relationship: VersionRelationship,
90    pub version: PackageVersion,
91}
92
93/// A dependency of a package.
94#[derive(Clone, Debug, PartialEq)]
95pub struct SingleDependency {
96    /// Package the dependency is on.
97    pub package: String,
98    pub version_constraint: Option<DependencyVersionConstraint>,
99    pub architectures: Option<(bool, Vec<String>)>,
100}
101
102impl Display for SingleDependency {
103    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
104        write!(f, "{}", self.package)?;
105        if let Some(constraint) = &self.version_constraint {
106            write!(f, " ({} {})", constraint.relationship, constraint.version)?;
107        }
108        if let Some((negate, arch)) = &self.architectures {
109            write!(f, " [{}{}]", if *negate { "!" } else { "" }, arch.join(" "))?;
110        }
111
112        Ok(())
113    }
114}
115
116impl SingleDependency {
117    /// Parse a single package dependency expression into a [SingleDependency].
118    pub fn parse(s: &str) -> Result<Self> {
119        let caps = RE_DEPENDENCY
120            .captures(s)
121            .ok_or_else(|| DebianError::DependencyParse(s.to_string()))?;
122
123        let package = caps["package"].to_string();
124        let dependency = match (caps.name("relop"), caps.name("version")) {
125            (Some(relop), Some(version)) => {
126                let relationship = match relop.as_str() {
127                    "<<" => VersionRelationship::StrictlyEarlier,
128                    "<=" => VersionRelationship::EarlierOrEqual,
129                    "=" => VersionRelationship::ExactlyEqual,
130                    ">=" => VersionRelationship::LaterOrEqual,
131                    ">>" => VersionRelationship::StrictlyLater,
132                    v => panic!("unexpected version relationship: {}", v),
133                };
134
135                let version = PackageVersion::parse(version.as_str())?;
136
137                Some(DependencyVersionConstraint {
138                    relationship,
139                    version,
140                })
141            }
142            _ => None,
143        };
144
145        let architectures = match (caps.name("arch_negate"), caps.name("arch")) {
146            (Some(_), Some(arch)) => Some((
147                true,
148                arch.as_str()
149                    .split_ascii_whitespace()
150                    .map(|x| x.to_string())
151                    .collect::<Vec<_>>(),
152            )),
153            (None, Some(arch)) => Some((
154                false,
155                arch.as_str()
156                    .split_ascii_whitespace()
157                    .map(|x| x.to_string())
158                    .collect::<Vec<_>>(),
159            )),
160            _ => None,
161        };
162
163        Ok(Self {
164            package,
165            version_constraint: dependency,
166            architectures,
167        })
168    }
169
170    /// Evaluate whether a package satisfies the requirements of this parsed expression.
171    ///
172    /// This takes as arguments the low-level package components needed for checking.
173    pub fn package_satisfies(
174        &self,
175        package: &str,
176        version: &PackageVersion,
177        architecture: &str,
178    ) -> bool {
179        if self.package == package {
180            if let Some((negate, arches)) = &self.architectures {
181                let contains = arches.iter().any(|x| x == architecture);
182
183                // Requesting an arch mismatch.
184                if (*negate && contains) || (!*negate && !contains) {
185                    return false;
186                }
187            }
188
189            // Package and arch requirements match. Go on to version compare.
190            if let Some(constaint) = &self.version_constraint {
191                matches!(
192                    (version.cmp(&constaint.version), constaint.relationship),
193                    (
194                        Ordering::Equal,
195                        VersionRelationship::ExactlyEqual
196                            | VersionRelationship::LaterOrEqual
197                            | VersionRelationship::EarlierOrEqual,
198                    ) | (
199                        Ordering::Less,
200                        VersionRelationship::StrictlyEarlier | VersionRelationship::EarlierOrEqual,
201                    ) | (
202                        Ordering::Greater,
203                        VersionRelationship::StrictlyLater | VersionRelationship::LaterOrEqual,
204                    )
205                )
206            } else {
207                // No version constraint means yes.
208                true
209            }
210        } else {
211            false
212        }
213    }
214
215    /// Whether a package satisfies a virtual package constraint.
216    ///
217    /// These are processed a bit differently in that architecture doesn't come into play and
218    /// version constraints in the source package are optional.
219    pub fn package_satisfies_virtual(
220        &self,
221        package: &str,
222        provides: Option<&DependencyVersionConstraint>,
223    ) -> bool {
224        if self.package == package {
225            // If we don't provide a constraint, all provided versions match.
226            // If the incoming constraint isn't defined, it matches all our constraints.
227            // In either case, all variants other than (Some, Some) satisfy the requirements.
228            if let (Some(wanted_constraint), Some(provides)) =
229                (&self.version_constraint.as_ref(), provides)
230            {
231                matches!(
232                    (
233                        provides.version.cmp(&wanted_constraint.version),
234                        wanted_constraint.relationship,
235                        provides.relationship,
236                    ),
237                    // If provided versions are equal, we satisfy if our constraint contains equal
238                    // and equal is provided.
239                    (
240                        Ordering::Equal,
241                        VersionRelationship::ExactlyEqual
242                        | VersionRelationship::LaterOrEqual
243                        | VersionRelationship::EarlierOrEqual,
244                        VersionRelationship::ExactlyEqual
245                        | VersionRelationship::LaterOrEqual
246                        | VersionRelationship::EarlierOrEqual,
247                    )
248                |
249                    // TODO this is probably subtly wrong. Add tests!
250                    (
251                        Ordering::Less,
252                        VersionRelationship::EarlierOrEqual | VersionRelationship::StrictlyEarlier,
253                        VersionRelationship::EarlierOrEqual | VersionRelationship::StrictlyEarlier,
254                    ) |
255                    (
256                        Ordering::Greater,
257                        VersionRelationship::LaterOrEqual | VersionRelationship::StrictlyLater,
258                        VersionRelationship::LaterOrEqual | VersionRelationship::StrictlyLater,
259                    )
260                )
261            } else {
262                true
263            }
264        } else {
265            false
266        }
267    }
268}
269
270#[derive(Clone, Debug, Default, PartialEq)]
271pub struct DependencyVariants(Vec<SingleDependency>);
272
273impl Display for DependencyVariants {
274    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
275        write!(
276            f,
277            "{}",
278            self.0
279                .iter()
280                .map(|x| format!("{}", x))
281                .collect::<Vec<_>>()
282                .join(" | ")
283        )
284    }
285}
286
287impl Deref for DependencyVariants {
288    type Target = Vec<SingleDependency>;
289
290    fn deref(&self) -> &Self::Target {
291        &self.0
292    }
293}
294
295impl DerefMut for DependencyVariants {
296    fn deref_mut(&mut self) -> &mut Self::Target {
297        &mut self.0
298    }
299}
300
301impl DependencyVariants {
302    /// Evaluate whether a package satisfies the requirements of this set of variants.
303    ///
304    /// This calls [SingleDependency.satisfies()] for each tracked variant. Returns true
305    /// if the given package satisfies any variant.
306    pub fn package_satisfies(&self, package: &str, version: &PackageVersion, arch: &str) -> bool {
307        self.0
308            .iter()
309            .any(|variant| variant.package_satisfies(package, version, arch))
310    }
311}
312
313/// Represents an ordered list of dependencies, delimited by commas (`,`).
314#[derive(Clone, Debug, PartialEq)]
315pub struct DependencyList {
316    dependencies: Vec<DependencyVariants>,
317}
318
319impl Display for DependencyList {
320    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
321        write!(
322            f,
323            "{}",
324            self.dependencies
325                .iter()
326                .map(|x| format!("{}", x))
327                .collect::<Vec<_>>()
328                .join(", ")
329        )
330    }
331}
332
333impl DependencyList {
334    /// Parse a dependency list from a string.
335    ///
336    /// A dependency list is a comma-delimited list of expressions. Each expression is a
337    /// `|` delimited list of expressions of the form
338    /// `package (version_relationship version) [arch]`.
339    pub fn parse(s: &str) -> Result<Self> {
340        let mut els = vec![];
341
342        for el in s.split(',') {
343            // Interior whitespace doesn't matter.
344            let el = el.trim();
345
346            // Each dependency consists of alternatives split by |.
347            let mut variants = DependencyVariants::default();
348
349            for alt in el.split('|') {
350                let alt = alt.trim();
351
352                variants.push(SingleDependency::parse(alt)?);
353            }
354
355            els.push(variants);
356        }
357
358        Ok(Self { dependencies: els })
359    }
360
361    /// Evaluate whether a package satisfies at least one expression in this list.
362    pub fn package_satisfies(&self, package: &str, version: &PackageVersion, arch: &str) -> bool {
363        self.dependencies
364            .iter()
365            .any(|variants| variants.package_satisfies(package, version, arch))
366    }
367
368    /// Obtain the individual requirements constituting this list of dependencies.
369    ///
370    /// Each requirement is itself a set of expressions to match against. The length of
371    /// this set is commonly 1.
372    pub fn requirements(&self) -> impl Iterator<Item = &DependencyVariants> {
373        self.dependencies.iter()
374    }
375}
376
377/// Describes the dependency relationship for a binary package.
378///
379/// Variants correspond to fields in binary control file, as described at
380/// <https://www.debian.org/doc/debian-policy/ch-relationships.html#binary-dependencies-depends-recommends-suggests-enhances-pre-depends>.
381#[derive(Clone, Copy, Debug)]
382pub enum BinaryDependency {
383    Depends,
384    Recommends,
385    Suggests,
386    Enhances,
387    PreDepends,
388}
389
390impl FromStr for BinaryDependency {
391    type Err = DebianError;
392
393    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
394        match s {
395            "Depends" => Ok(Self::Depends),
396            "Recommends" => Ok(Self::Recommends),
397            "Suggests" => Ok(Self::Suggests),
398            "Enhances" => Ok(Self::Enhances),
399            "Pre-Depends" => Ok(Self::PreDepends),
400            _ => Err(Self::Err::UnknownBinaryDependencyField(s.to_string())),
401        }
402    }
403}
404
405impl Display for BinaryDependency {
406    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
407        write!(
408            f,
409            "{}",
410            match self {
411                Self::Depends => "Depends",
412                Self::Recommends => "Recommends",
413                Self::Suggests => "Suggests",
414                Self::Enhances => "Enhances",
415                Self::PreDepends => "Pre-Depends",
416            }
417        )
418    }
419}
420
421impl BinaryDependency {
422    /// Obtain all variants of this enum.
423    pub fn values() -> &'static [Self] {
424        &[
425            Self::Depends,
426            Self::Recommends,
427            Self::Suggests,
428            Self::Enhances,
429            Self::PreDepends,
430        ]
431    }
432}
433
434/// Holds all fields related to package dependency metadata.
435///
436/// Instances of this type effectively describe the relationships between the package it
437/// describes and other packages.
438///
439/// See <https://www.debian.org/doc/debian-policy/ch-relationships.html> for a list of all the
440/// fields and what they mean.
441#[derive(Clone, Debug)]
442pub struct PackageDependencyFields {
443    /// `Depends`.
444    pub depends: Option<DependencyList>,
445
446    /// `Recommends`.
447    pub recommends: Option<DependencyList>,
448
449    /// `Suggests`.
450    pub suggests: Option<DependencyList>,
451
452    /// `Enhances`.
453    pub enhances: Option<DependencyList>,
454
455    /// `Pre-Depends`.
456    pub pre_depends: Option<DependencyList>,
457
458    /// `Breaks`.
459    pub breaks: Option<DependencyList>,
460
461    /// `Conflicts`.
462    pub conflicts: Option<DependencyList>,
463
464    /// `Provides`.
465    pub provides: Option<DependencyList>,
466
467    /// `Replaces`.
468    pub replaces: Option<DependencyList>,
469
470    /// `Build-Depends`.
471    pub build_depends: Option<DependencyList>,
472
473    /// `Build-Depends-Indep`.
474    pub build_depends_indep: Option<DependencyList>,
475
476    /// `Build-Depends-Arch`.
477    pub build_depends_arch: Option<DependencyList>,
478
479    /// `Build-Conflicts`.
480    pub build_conflicts: Option<DependencyList>,
481
482    /// `Build-Conflicts-Indep`.
483    pub build_conflicts_indep: Option<DependencyList>,
484
485    /// `Build-Conflicts-Arch`.
486    pub build_conflicts_arch: Option<DependencyList>,
487
488    /// `Built-Using`.
489    pub built_using: Option<DependencyList>,
490}
491
492impl PackageDependencyFields {
493    /// Construct an instance from a control paragraph.
494    pub fn from_paragraph(para: &ControlParagraph) -> Result<Self> {
495        let get_field = |field| -> Result<Option<DependencyList>> {
496            if let Some(value) = para.field_str(field) {
497                Ok(Some(DependencyList::parse(value)?))
498            } else {
499                Ok(None)
500            }
501        };
502
503        Ok(Self {
504            depends: get_field("Depends")?,
505            recommends: get_field("Recommends")?,
506            suggests: get_field("Suggests")?,
507            enhances: get_field("Enhances")?,
508            pre_depends: get_field("Pre-Depends")?,
509            breaks: get_field("Breaks")?,
510            conflicts: get_field("Conflicts")?,
511            provides: get_field("Provides")?,
512            replaces: get_field("Replaces")?,
513            build_depends: get_field("Build-Depends")?,
514            build_depends_indep: get_field("Build-Depends-Indep")?,
515            build_depends_arch: get_field("Build-Depends-Arch")?,
516            build_conflicts: get_field("Build-Conflicts")?,
517            build_conflicts_indep: get_field("Build-Conflicts-Indep")?,
518            build_conflicts_arch: get_field("Build-Conflicts-Arch")?,
519            built_using: get_field("Built-Using")?,
520        })
521    }
522
523    /// Resolve the value of a given [BinaryDependency] field.
524    pub fn binary_dependency(&self, field: BinaryDependency) -> Option<&DependencyList> {
525        match field {
526            BinaryDependency::Depends => self.depends.as_ref(),
527            BinaryDependency::Recommends => self.recommends.as_ref(),
528            BinaryDependency::Suggests => self.suggests.as_ref(),
529            BinaryDependency::Enhances => self.enhances.as_ref(),
530            BinaryDependency::PreDepends => self.pre_depends.as_ref(),
531        }
532    }
533}
534
535#[cfg(test)]
536mod test {
537    use super::*;
538
539    #[test]
540    fn parse_depends() -> Result<()> {
541        let dl = DependencyList::parse("libc6 (>= 2.4), libx11-6")?;
542        assert_eq!(dl.dependencies.len(), 2);
543        assert_eq!(dl.dependencies[0].0.len(), 1);
544        assert_eq!(dl.dependencies[1].0.len(), 1);
545
546        assert_eq!(
547            dl.dependencies[0].0[0],
548            SingleDependency {
549                package: "libc6".into(),
550                version_constraint: Some(DependencyVersionConstraint {
551                    relationship: VersionRelationship::LaterOrEqual,
552                    version: PackageVersion::parse("2.4").unwrap()
553                }),
554                architectures: None,
555            }
556        );
557        assert_eq!(
558            dl.dependencies[1].0[0],
559            SingleDependency {
560                package: "libx11-6".into(),
561                version_constraint: None,
562                architectures: None,
563            }
564        );
565
566        let dl = DependencyList::parse("libc [amd64]")?;
567        assert_eq!(dl.dependencies.len(), 1);
568        assert_eq!(dl.dependencies[0].0.len(), 1);
569        assert_eq!(
570            dl.dependencies[0].0[0],
571            SingleDependency {
572                package: "libc".into(),
573                version_constraint: None,
574                architectures: Some((false, vec!["amd64".into()])),
575            }
576        );
577
578        let dl = DependencyList::parse("libc [!amd64 i386]")?;
579        assert_eq!(dl.dependencies.len(), 1);
580        assert_eq!(dl.dependencies[0].0.len(), 1);
581        assert_eq!(
582            dl.dependencies[0].0[0],
583            SingleDependency {
584                package: "libc".into(),
585                version_constraint: None,
586                architectures: Some((true, vec!["amd64".into(), "i386".into()])),
587            }
588        );
589
590        Ok(())
591    }
592
593    #[test]
594    fn satisfies_version_constraints() -> Result<()> {
595        let dl = DependencyList::parse("libc (= 2.4)")?;
596        assert!(dl.dependencies[0].package_satisfies(
597            "libc",
598            &PackageVersion::parse("2.4")?,
599            "ignored"
600        ));
601        assert!(!dl.dependencies[0].package_satisfies(
602            "libc",
603            &PackageVersion::parse("2.3")?,
604            "ignored"
605        ));
606        assert!(!dl.dependencies[0].package_satisfies(
607            "libc",
608            &PackageVersion::parse("2.5")?,
609            "ignored"
610        ));
611        assert!(!dl.dependencies[0].package_satisfies(
612            "other",
613            &PackageVersion::parse("2.4")?,
614            "ignored"
615        ));
616
617        let dl = DependencyList::parse("libc (<= 2.4)")?;
618        assert!(dl.dependencies[0].package_satisfies(
619            "libc",
620            &PackageVersion::parse("2.3")?,
621            "ignored"
622        ));
623        assert!(dl.dependencies[0].package_satisfies(
624            "libc",
625            &PackageVersion::parse("2.4")?,
626            "ignored"
627        ));
628        assert!(!dl.dependencies[0].package_satisfies(
629            "libc",
630            &PackageVersion::parse("2.5")?,
631            "ignored"
632        ));
633        assert!(!dl.dependencies[0].package_satisfies(
634            "other",
635            &PackageVersion::parse("2.4")?,
636            "ignored"
637        ));
638
639        let dl = DependencyList::parse("libc (>= 2.4)")?;
640        assert!(!dl.dependencies[0].package_satisfies(
641            "libc",
642            &PackageVersion::parse("2.3")?,
643            "ignored"
644        ));
645        assert!(dl.dependencies[0].package_satisfies(
646            "libc",
647            &PackageVersion::parse("2.4")?,
648            "ignored"
649        ));
650        assert!(dl.dependencies[0].package_satisfies(
651            "libc",
652            &PackageVersion::parse("2.5")?,
653            "ignored"
654        ));
655        assert!(!dl.dependencies[0].package_satisfies(
656            "other",
657            &PackageVersion::parse("2.4")?,
658            "ignored"
659        ));
660
661        let dl = DependencyList::parse("libc (<< 2.4)")?;
662        assert!(dl.dependencies[0].package_satisfies(
663            "libc",
664            &PackageVersion::parse("2.3")?,
665            "ignored"
666        ));
667        assert!(!dl.dependencies[0].package_satisfies(
668            "libc",
669            &PackageVersion::parse("2.4")?,
670            "ignored"
671        ));
672        assert!(!dl.dependencies[0].package_satisfies(
673            "libc",
674            &PackageVersion::parse("2.5")?,
675            "ignored"
676        ));
677        assert!(!dl.dependencies[0].package_satisfies(
678            "other",
679            &PackageVersion::parse("2.3")?,
680            "ignored"
681        ));
682
683        let dl = DependencyList::parse("libc (>> 2.4)")?;
684        assert!(!dl.dependencies[0].package_satisfies(
685            "libc",
686            &PackageVersion::parse("2.3")?,
687            "ignored"
688        ));
689        assert!(!dl.dependencies[0].package_satisfies(
690            "libc",
691            &PackageVersion::parse("2.4")?,
692            "ignored"
693        ));
694        assert!(dl.dependencies[0].package_satisfies(
695            "libc",
696            &PackageVersion::parse("2.5")?,
697            "ignored"
698        ));
699        assert!(!dl.dependencies[0].package_satisfies(
700            "other",
701            &PackageVersion::parse("2.5")?,
702            "ignored"
703        ));
704
705        Ok(())
706    }
707
708    #[test]
709    fn satisfies_architecture_constraints() -> Result<()> {
710        let dl = DependencyList::parse("libc [amd64]")?;
711        assert!(dl.dependencies[0].package_satisfies(
712            "libc",
713            &PackageVersion::parse("2.4")?,
714            "amd64"
715        ));
716        assert!(!dl.dependencies[0].package_satisfies(
717            "libc",
718            &PackageVersion::parse("2.3")?,
719            "x86"
720        ));
721
722        let dl = DependencyList::parse("libc [amd64 i386]")?;
723        assert!(dl.dependencies[0].package_satisfies(
724            "libc",
725            &PackageVersion::parse("2.4")?,
726            "amd64"
727        ));
728        assert!(dl.dependencies[0].package_satisfies(
729            "libc",
730            &PackageVersion::parse("2.3")?,
731            "i386"
732        ));
733        assert!(!dl.dependencies[0].package_satisfies(
734            "libc",
735            &PackageVersion::parse("2.3")?,
736            "arm64"
737        ));
738
739        let dl = DependencyList::parse("libc [!amd64]")?;
740        assert!(!dl.dependencies[0].package_satisfies(
741            "libc",
742            &PackageVersion::parse("2.4")?,
743            "amd64"
744        ));
745        assert!(dl.dependencies[0].package_satisfies(
746            "libc",
747            &PackageVersion::parse("2.3")?,
748            "x86"
749        ));
750
751        let dl = DependencyList::parse("libc [!amd64 i386]")?;
752        assert!(!dl.dependencies[0].package_satisfies(
753            "libc",
754            &PackageVersion::parse("2.4")?,
755            "amd64"
756        ));
757        assert!(!dl.dependencies[0].package_satisfies(
758            "libc",
759            &PackageVersion::parse("2.3")?,
760            "i386"
761        ));
762        assert!(dl.dependencies[0].package_satisfies(
763            "libc",
764            &PackageVersion::parse("2.3")?,
765            "arm64"
766        ));
767
768        Ok(())
769    }
770}