debian_analyzer/
relations.rs

1//! Functions for working with package relations.
2use debian_control::lossless::relations::{Entry, Relation, Relations};
3use debian_control::relations::VersionConstraint;
4use debversion::Version;
5
6/// Check if one dependency is implied by another.
7///
8/// Is dep implied by outer?
9pub fn is_dep_implied(dep: &Relation, outer: &Relation) -> bool {
10    if dep.name() != outer.name() {
11        return false;
12    }
13
14    let (v1, v2) = match (dep.version(), outer.version()) {
15        (Some(v1), Some(v2)) => (v1, v2),
16        (None, _) => return true,
17        (_, None) => return false,
18    };
19
20    match (v1, v2) {
21        ((VersionConstraint::GreaterThanEqual, v1), (VersionConstraint::GreaterThan, v2)) => {
22            v2 > v1
23        }
24        (
25            (VersionConstraint::GreaterThanEqual, v1),
26            (VersionConstraint::GreaterThanEqual, v2) | (VersionConstraint::Equal, v2),
27        ) => v2 >= v1,
28        (
29            (VersionConstraint::GreaterThanEqual, _v1),
30            (VersionConstraint::LessThanEqual, _v2) | (VersionConstraint::LessThan, _v2),
31        ) => false,
32        ((VersionConstraint::Equal, v1), (VersionConstraint::Equal, v2)) => v2 == v1,
33        ((VersionConstraint::Equal, _), (_, _)) => false,
34        ((VersionConstraint::LessThan, v1), (VersionConstraint::LessThan, v2)) => v2 <= v1,
35        (
36            (VersionConstraint::LessThan, v1),
37            (VersionConstraint::LessThanEqual, v2) | (VersionConstraint::Equal, v2),
38        ) => v2 < v1,
39        (
40            (VersionConstraint::LessThan, _v1),
41            (VersionConstraint::GreaterThanEqual, _v2) | (VersionConstraint::GreaterThan, _v2),
42        ) => false,
43        (
44            (VersionConstraint::LessThanEqual, v1),
45            (VersionConstraint::LessThanEqual, v2)
46            | (VersionConstraint::Equal, v2)
47            | (VersionConstraint::LessThan, v2),
48        ) => v2 <= v1,
49        (
50            (VersionConstraint::LessThanEqual, _v1),
51            (VersionConstraint::GreaterThanEqual, _v2) | (VersionConstraint::GreaterThan, _v2),
52        ) => false,
53        ((VersionConstraint::GreaterThan, v1), (VersionConstraint::GreaterThan, v2)) => v2 >= v1,
54        (
55            (VersionConstraint::GreaterThan, v1),
56            (VersionConstraint::GreaterThanEqual, v2) | (VersionConstraint::Equal, v2),
57        ) => v2 > v1,
58        (
59            (VersionConstraint::GreaterThan, _v1),
60            (VersionConstraint::LessThanEqual, _v2) | (VersionConstraint::LessThan, _v2),
61        ) => false,
62    }
63}
64
65/// Check if one relation implies another.
66///
67/// # Arguments
68/// * `inner` - Inner relation (e.g. "bzr (>= 1.3)")
69/// * `outer` - Outer relation (e.g. "bzr (>= 1.3) | libc6")
70pub fn is_relation_implied(inner: &Entry, outer: &Entry) -> bool {
71    if inner == outer {
72        return true;
73    }
74
75    // "bzr >= 1.3" implied by "bzr >= 1.3 | libc6"
76    for inner_dep in inner.relations() {
77        if outer
78            .relations()
79            .any(|outer_dep| is_dep_implied(&inner_dep, &outer_dep))
80        {
81            return true;
82        }
83    }
84
85    false
86}
87
88/// Ensure that a relation exists.
89///
90/// This is done by either verifying that there is an existing
91/// relation that satisfies the specified relation, or
92/// by upgrading an existing relation.
93pub fn ensure_relation(rels: &mut Relations, newrel: Entry) {
94    let mut obsolete = vec![];
95    for (i, relation) in rels.entries().enumerate() {
96        if is_relation_implied(&newrel, &relation) {
97            // The new relation is already implied by an existing one.
98            return;
99        }
100        if is_relation_implied(&relation, &newrel) {
101            obsolete.push(i);
102        }
103    }
104
105    if let Some(pos) = obsolete.pop() {
106        rels.replace(pos, newrel);
107    } else {
108        rels.push(newrel);
109    }
110
111    for i in obsolete.into_iter().rev() {
112        rels.remove_entry(i);
113    }
114}
115
116/// Update a relation string to ensure a particular version is required.
117///
118/// # Arguments
119/// * `relations` - Package relation
120/// * `package` - Package name
121/// * `minimum_version` - Minimum version
122///
123/// # Returns
124/// True if the relation was changed
125///
126/// # Examples
127/// ```rust
128/// use debian_control::lossless::relations::Relations;
129/// use debian_analyzer::relations::ensure_minimum_version;
130/// let mut rels: Relations = "".parse().unwrap();
131/// ensure_minimum_version(&mut rels, "foo", &"1.0".parse().unwrap());
132/// assert_eq!("foo (>= 1.0)", rels.to_string());
133/// ensure_minimum_version(&mut rels, "foo", &"2.0".parse().unwrap());
134/// assert_eq!("foo (>= 2.0)", rels.to_string());
135///
136/// let mut rels: Relations = "foo (= 1.0)".parse().unwrap();
137/// ensure_minimum_version(&mut rels, "foo", &"2.0".parse().unwrap());
138/// assert_eq!("foo (>= 2.0)", rels.to_string());
139/// ```
140pub fn ensure_minimum_version(
141    relations: &mut Relations,
142    package: &str,
143    minimum_version: &Version,
144) -> bool {
145    let is_obsolete = |entry: &Entry| -> bool {
146        for r in entry.relations() {
147            if r.name() != package {
148                continue;
149            }
150            if let Some((vc, v)) = r.version().as_ref() {
151                if *vc == VersionConstraint::GreaterThan && v < minimum_version {
152                    return true;
153                }
154                if *vc == VersionConstraint::GreaterThanEqual && v <= minimum_version {
155                    return true;
156                }
157            }
158        }
159        false
160    };
161
162    let mut found = false;
163    let mut changed = false;
164    let mut obsolete_relations = vec![];
165    let mut relevant_relations = vec![];
166    for (i, entry) in relations.entries().enumerate() {
167        let names = entry
168            .relations()
169            .map(|r| r.name().to_string())
170            .collect::<Vec<_>>();
171        if names.len() > 1 && names.contains(&package.to_string()) && is_obsolete(&entry) {
172            obsolete_relations.push(i);
173        }
174        if names != [package] {
175            continue;
176        }
177        found = true;
178        if entry
179            .relations()
180            .next()
181            .and_then(|r| r.version())
182            .map(|(_vc, v)| &v < minimum_version)
183            .unwrap_or(false)
184        {
185            relevant_relations.push(i);
186        }
187    }
188    if !found {
189        changed = true;
190        relations.push(
191            Relation::new(
192                package,
193                Some((VersionConstraint::GreaterThanEqual, minimum_version.clone())),
194            )
195            .into(),
196        );
197    } else {
198        for i in relevant_relations.into_iter().rev() {
199            relations.replace(
200                i,
201                Relation::new(
202                    package,
203                    Some((VersionConstraint::GreaterThanEqual, minimum_version.clone())),
204                )
205                .into(),
206            );
207            changed = true;
208        }
209    }
210    for i in obsolete_relations.into_iter().rev() {
211        relations.remove_entry(i);
212    }
213    changed
214}
215
216/// Update a relation string to depend on a specific version.
217///
218/// # Arguments
219/// * `relation` - Package relations
220/// * `package` - Package name
221/// * `version` - Exact version to depend on
222/// * `position` - Optional position in the list to insert any new entries
223///
224/// # Examples
225/// ```rust
226/// use debian_control::lossless::relations::Relations;
227/// use debian_analyzer::relations::ensure_exact_version;
228/// let mut rels: Relations = "".parse().unwrap();
229/// ensure_exact_version(&mut rels, "foo", &"1.0".parse().unwrap(), None);
230/// assert_eq!("foo (= 1.0)", rels.to_string());
231///
232/// let mut rels: Relations = "foo (= 1.0)".parse().unwrap();
233/// ensure_exact_version(&mut rels, "foo", &"2.0".parse().unwrap(), None);
234/// assert_eq!("foo (= 2.0)", rels.to_string());
235/// ```
236pub fn ensure_exact_version(
237    relations: &mut Relations,
238    package: &str,
239    version: &Version,
240    position: Option<usize>,
241) -> bool {
242    let mut changed = false;
243    let mut found = vec![];
244
245    for (i, entry) in relations.entries().enumerate() {
246        let names = entry
247            .relations()
248            .map(|r| r.name().to_string())
249            .collect::<Vec<_>>();
250        if names != [package] {
251            continue;
252        }
253        let relation = entry.relations().next().unwrap();
254        if relation
255            .version()
256            .is_none_or(|(vc, v)| vc != VersionConstraint::Equal || &v != version)
257        {
258            found.push(i);
259        }
260    }
261    if found.is_empty() {
262        changed = true;
263        let relation = Relation::new(package, Some((VersionConstraint::Equal, version.clone())));
264        if let Some(position) = position {
265            relations.insert(position, relation.into());
266        } else {
267            relations.push(relation.into());
268        }
269    } else {
270        for i in found.into_iter().rev() {
271            relations.replace(
272                i,
273                Relation::new(package, Some((VersionConstraint::Equal, version.clone()))).into(),
274            );
275            changed = true;
276        }
277    }
278    changed
279}
280
281/// Ensure that a relation exists for a particular package.
282///
283/// # Arguments
284/// * `relations` - Relations to update
285/// * `package` - Package name
286///
287/// # Examples
288/// ```rust
289/// use debian_control::lossless::relations::Relations;
290/// use debian_analyzer::relations::ensure_some_version;
291///
292/// let mut rels: Relations = "".parse().unwrap();
293/// ensure_some_version(&mut rels, "foo");
294/// assert_eq!("foo", rels.to_string());
295///
296/// let mut rels: Relations = "foo".parse().unwrap();
297/// ensure_some_version(&mut rels, "foo");
298/// assert_eq!("foo", rels.to_string());
299///
300/// let mut rels: Relations = "foo (>= 1), bar".parse().unwrap();
301/// ensure_some_version(&mut rels, "foo");
302/// assert_eq!("foo (>= 1), bar", rels.to_string());
303/// ```
304pub fn ensure_some_version(relations: &mut Relations, package: &str) -> bool {
305    for entry in relations.entries() {
306        let names = entry
307            .relations()
308            .map(|r| r.name().to_string())
309            .collect::<Vec<_>>();
310        if names == [package] {
311            return false;
312        }
313    }
314    relations.push(Relation::simple(package).into());
315    true
316}
317
318#[cfg(test)]
319mod tests {
320    use super::*;
321    use debian_control::lossless::relations::{Relation, Relations};
322
323    mod is_dep_implied {
324        use super::*;
325        fn parse(s: &str) -> Relation {
326            let rs: Relations = s.parse().unwrap();
327            let mut entries = rs.entries();
328            let entry = entries.next().unwrap();
329            assert_eq!(entries.next(), None);
330            let mut relations = entry.relations();
331            let relation = relations.next().unwrap();
332            assert_eq!(relations.next(), None);
333            relation
334        }
335
336        fn is_dep_implied(inner: &str, outer: &str) -> bool {
337            super::is_dep_implied(&parse(inner), &parse(outer))
338        }
339
340        #[test]
341        fn test_no_version() {
342            assert!(is_dep_implied("bzr", "bzr"));
343            assert!(is_dep_implied("bzr", "bzr (>= 3)"));
344            assert!(is_dep_implied("bzr", "bzr (<< 3)"));
345        }
346
347        #[test]
348        fn test_wrong_package() {
349            assert!(!is_dep_implied("bzr", "foo (<< 3)"));
350        }
351
352        #[test]
353        fn test_version() {
354            assert!(!is_dep_implied("bzr (>= 3)", "bzr (<< 3)"));
355            assert!(is_dep_implied("bzr (>= 3)", "bzr (= 3)"));
356            assert!(!is_dep_implied("bzr (= 3)", "bzr (>= 3)"));
357            assert!(!is_dep_implied("bzr (>= 3)", "bzr (>> 3)"));
358            assert!(!is_dep_implied("bzr (= 3)", "bzr (= 4)"));
359            assert!(!is_dep_implied("bzr (>= 3)", "bzr (>= 2)"));
360            assert!(is_dep_implied("bzr (>= 3)", "bzr (>= 3)"));
361            assert!(is_dep_implied("bzr", "bzr (<< 3)"));
362            assert!(is_dep_implied("bzr (<< 3)", "bzr (<< 3)"));
363            assert!(is_dep_implied("bzr (<= 3)", "bzr (<< 3)"));
364            assert!(!is_dep_implied("bzr (>= 2)", "bzr (<< 3)"));
365            assert!(!is_dep_implied("bzr (<< 2)", "bzr (<< 3)"));
366            assert!(!is_dep_implied("bzr (<= 2)", "bzr (<< 3)"));
367            assert!(is_dep_implied("bzr (<= 5)", "bzr (<< 3)"));
368            assert!(is_dep_implied("bzr (<= 5)", "bzr (= 3)"));
369            assert!(!is_dep_implied("bzr (<= 5)", "bzr (>= 3)"));
370            assert!(is_dep_implied("bzr (>> 5)", "bzr (>> 6)"));
371            assert!(is_dep_implied("bzr (>> 5)", "bzr (>> 5)"));
372            assert!(!is_dep_implied("bzr (>> 5)", "bzr (>> 4)"));
373            assert!(is_dep_implied("bzr (>> 5)", "bzr (= 6)"));
374            assert!(!is_dep_implied("bzr (>> 5)", "bzr (= 5)"));
375            assert!(is_dep_implied("bzr:any (>> 5)", "bzr:any (= 6)"));
376        }
377    }
378
379    mod is_relation_implied {
380        use debian_control::lossless::relations::Relations;
381
382        fn parse(s: &str) -> super::Entry {
383            let r: Relations = s.parse().unwrap();
384            let mut entries = r.entries();
385            let entry = entries.next().unwrap();
386            assert_eq!(entries.next(), None);
387            entry
388        }
389
390        fn is_relation_implied(inner: &str, outer: &str) -> bool {
391            super::is_relation_implied(&parse(inner), &parse(outer))
392        }
393
394        #[test]
395        fn test_unrelated() {
396            assert!(!is_relation_implied("bzr", "bar"));
397            assert!(!is_relation_implied("bzr (= 3)", "bar"));
398            assert!(!is_relation_implied("bzr (= 3) | foo", "bar"));
399        }
400
401        #[test]
402        fn test_too_old() {
403            assert!(!is_relation_implied("bzr (= 3)", "bzr"));
404            assert!(!is_relation_implied("bzr (= 3)", "bzr (= 2)"));
405            assert!(!is_relation_implied("bzr (= 3)", "bzr (>= 2)"));
406        }
407
408        #[test]
409        fn test_ors() {
410            assert!(!is_relation_implied("bzr (= 3)", "bzr | foo"));
411            assert!(is_relation_implied("bzr", "bzr | foo"));
412            assert!(is_relation_implied("bzr | foo", "bzr | foo"));
413        }
414
415        #[test]
416        fn test_implied() {
417            assert!(is_relation_implied("bzr (= 3)", "bzr (= 3)"));
418            assert!(is_relation_implied("bzr (>= 3)", "bzr (>= 4)"));
419            assert!(is_relation_implied("bzr (>= 4)", "bzr (>= 4)"));
420            assert!(is_relation_implied("bzr", "bzr"));
421            assert!(is_relation_implied("bzr | foo", "bzr"));
422            assert!(!is_relation_implied("bzr (= 3)", "bzr (>= 3)"));
423            assert!(is_relation_implied(
424                "python3:any | dh-sequence-python3",
425                "python3:any"
426            ));
427            assert!(is_relation_implied(
428                "python3:any | python3-dev:any | dh-sequence-python3",
429                "python3:any | python3-dev:any"
430            ));
431        }
432    }
433
434    #[test]
435    fn test_ensure_relation() {
436        let mut rels: Relations = "".parse().unwrap();
437        let rel = Relation::simple("foo");
438        ensure_relation(&mut rels, rel.into());
439        assert_eq!("foo", rels.to_string());
440    }
441
442    #[test]
443    fn test_ensure_relation_upgrade() {
444        let mut rels = "foo".parse().unwrap();
445        let newrel: Entry = Relation::new(
446            "foo",
447            Some((VersionConstraint::Equal, "1.0".parse().unwrap())),
448        )
449        .into();
450        ensure_relation(&mut rels, newrel);
451        assert_eq!("foo (= 1.0)", rels.to_string());
452    }
453
454    #[test]
455    fn test_ensure_relation_new() {
456        let mut rels = "bar (= 1.0)".parse().unwrap();
457        let newrel: Entry = Relation::new(
458            "foo",
459            Some((VersionConstraint::Equal, "2.0".parse().unwrap())),
460        )
461        .into();
462        ensure_relation(&mut rels, newrel);
463        assert_eq!("bar (= 1.0), foo (= 2.0)", rels.to_string());
464    }
465
466    #[test]
467    fn test_drops_obsolete() {
468        let mut rels = "bar (= 1.0), foo (>= 2.0), foo (>= 1.0)".parse().unwrap();
469        let newrel: Entry = Relation::new(
470            "foo",
471            Some((VersionConstraint::GreaterThanEqual, "3.0".parse().unwrap())),
472        )
473        .into();
474        ensure_relation(&mut rels, newrel);
475        assert_eq!("bar (= 1.0), foo (>= 3.0)", rels.to_string());
476    }
477
478    #[test]
479    fn test_ensure_relation_with_error() {
480        let mut rels = Relations::parse_relaxed("@cdbs@, debhelper (>= 9)", false).0;
481        let newrel: Entry = Relation::new("foo", None).into();
482
483        ensure_relation(&mut rels, newrel);
484        assert_eq!("@cdbs@, debhelper (>= 9), foo", rels.to_string());
485    }
486
487    #[test]
488    fn test_ensure_minimum_version() {
489        let mut rels = "".parse().unwrap();
490        ensure_minimum_version(&mut rels, "foo", &"1.0".parse().unwrap());
491        assert_eq!("foo (>= 1.0)", rels.to_string());
492    }
493
494    #[test]
495    fn test_ensure_minimum_version_upgrade() {
496        let mut rels = "foo (>= 1.0)".parse().unwrap();
497        ensure_minimum_version(&mut rels, "foo", &"2.0".parse().unwrap());
498        assert_eq!("foo (>= 2.0)", rels.to_string());
499    }
500
501    #[test]
502    fn test_ensure_minimum_version_upgrade_with_or() {
503        let mut rels = "foo (>= 1.0) | bar".parse().unwrap();
504        ensure_minimum_version(&mut rels, "foo", &"2.0".parse().unwrap());
505        assert_eq!("foo (>= 2.0)", rels.to_string());
506    }
507
508    #[test]
509    fn test_ensure_exact_version() {
510        let mut rels = "".parse().unwrap();
511        ensure_exact_version(&mut rels, "foo", &"1.0".parse().unwrap(), None);
512        assert_eq!("foo (= 1.0)", rels.to_string());
513    }
514
515    #[test]
516    fn test_ensure_exact_version_upgrade() {
517        let mut rels = "foo (= 1.0)".parse().unwrap();
518        ensure_exact_version(&mut rels, "foo", &"2.0".parse().unwrap(), None);
519        assert_eq!("foo (= 2.0)", rels.to_string());
520    }
521
522    #[test]
523    fn test_ensure_exact_version_upgrade_with_position() {
524        let mut rels = "foo (= 1.0)".parse().unwrap();
525        ensure_exact_version(&mut rels, "foo", &"2.0".parse().unwrap(), Some(0));
526        assert_eq!("foo (= 2.0)", rels.to_string());
527    }
528}