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.add_dependency(newrel, None);
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.add_dependency(
191            Relation::new(
192                package,
193                Some((VersionConstraint::GreaterThanEqual, minimum_version.clone())),
194            )
195            .into(),
196            None,
197        );
198    } else {
199        for i in relevant_relations.into_iter().rev() {
200            relations.replace(
201                i,
202                Relation::new(
203                    package,
204                    Some((VersionConstraint::GreaterThanEqual, minimum_version.clone())),
205                )
206                .into(),
207            );
208            changed = true;
209        }
210    }
211    for i in obsolete_relations.into_iter().rev() {
212        relations.remove_entry(i);
213    }
214    changed
215}
216
217/// Update a relation string to depend on a specific version.
218///
219/// # Arguments
220/// * `relation` - Package relations
221/// * `package` - Package name
222/// * `version` - Exact version to depend on
223/// * `position` - Optional position in the list to insert any new entries
224///
225/// # Examples
226/// ```rust
227/// use debian_control::lossless::relations::Relations;
228/// use debian_analyzer::relations::ensure_exact_version;
229/// let mut rels: Relations = "".parse().unwrap();
230/// ensure_exact_version(&mut rels, "foo", &"1.0".parse().unwrap(), None);
231/// assert_eq!("foo (= 1.0)", rels.to_string());
232///
233/// let mut rels: Relations = "foo (= 1.0)".parse().unwrap();
234/// ensure_exact_version(&mut rels, "foo", &"2.0".parse().unwrap(), None);
235/// assert_eq!("foo (= 2.0)", rels.to_string());
236/// ```
237pub fn ensure_exact_version(
238    relations: &mut Relations,
239    package: &str,
240    version: &Version,
241    position: Option<usize>,
242) -> bool {
243    let mut changed = false;
244    let mut found = vec![];
245
246    for (i, entry) in relations.entries().enumerate() {
247        let names = entry
248            .relations()
249            .map(|r| r.name().to_string())
250            .collect::<Vec<_>>();
251        if names != [package] {
252            continue;
253        }
254        let relation = entry.relations().next().unwrap();
255        if relation
256            .version()
257            .is_none_or(|(vc, v)| vc != VersionConstraint::Equal || &v != version)
258        {
259            found.push(i);
260        }
261    }
262    if found.is_empty() {
263        changed = true;
264        let relation = Relation::new(package, Some((VersionConstraint::Equal, version.clone())));
265        if let Some(position) = position {
266            relations.insert(position, relation.into());
267        } else {
268            relations.add_dependency(relation.into(), None);
269        }
270    } else {
271        for i in found.into_iter().rev() {
272            relations.replace(
273                i,
274                Relation::new(package, Some((VersionConstraint::Equal, version.clone()))).into(),
275            );
276            changed = true;
277        }
278    }
279    changed
280}
281
282/// Ensure that a relation exists for a particular package.
283///
284/// # Arguments
285/// * `relations` - Relations to update
286/// * `package` - Package name
287///
288/// # Examples
289/// ```rust
290/// use debian_control::lossless::relations::Relations;
291/// use debian_analyzer::relations::ensure_some_version;
292///
293/// let mut rels: Relations = "".parse().unwrap();
294/// ensure_some_version(&mut rels, "foo");
295/// assert_eq!("foo", rels.to_string());
296///
297/// let mut rels: Relations = "foo".parse().unwrap();
298/// ensure_some_version(&mut rels, "foo");
299/// assert_eq!("foo", rels.to_string());
300///
301/// let mut rels: Relations = "foo (>= 1), bar".parse().unwrap();
302/// ensure_some_version(&mut rels, "foo");
303/// assert_eq!("foo (>= 1), bar", rels.to_string());
304/// ```
305pub fn ensure_some_version(relations: &mut Relations, package: &str) -> bool {
306    for entry in relations.entries() {
307        let names = entry
308            .relations()
309            .map(|r| r.name().to_string())
310            .collect::<Vec<_>>();
311        if names == [package] {
312            return false;
313        }
314    }
315    relations.add_dependency(Relation::simple(package).into(), None);
316    true
317}
318
319#[cfg(test)]
320mod tests {
321    use super::*;
322    use debian_control::lossless::relations::{Relation, Relations};
323
324    mod is_dep_implied {
325        use super::*;
326        fn parse(s: &str) -> Relation {
327            let rs: Relations = s.parse().unwrap();
328            let mut entries = rs.entries();
329            let entry = entries.next().unwrap();
330            assert_eq!(entries.next(), None);
331            let mut relations = entry.relations();
332            let relation = relations.next().unwrap();
333            assert_eq!(relations.next(), None);
334            relation
335        }
336
337        fn is_dep_implied(inner: &str, outer: &str) -> bool {
338            super::is_dep_implied(&parse(inner), &parse(outer))
339        }
340
341        #[test]
342        fn test_no_version() {
343            assert!(is_dep_implied("bzr", "bzr"));
344            assert!(is_dep_implied("bzr", "bzr (>= 3)"));
345            assert!(is_dep_implied("bzr", "bzr (<< 3)"));
346        }
347
348        #[test]
349        fn test_wrong_package() {
350            assert!(!is_dep_implied("bzr", "foo (<< 3)"));
351        }
352
353        #[test]
354        fn test_version() {
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 (>> 3)"));
359            assert!(!is_dep_implied("bzr (= 3)", "bzr (= 4)"));
360            assert!(!is_dep_implied("bzr (>= 3)", "bzr (>= 2)"));
361            assert!(is_dep_implied("bzr (>= 3)", "bzr (>= 3)"));
362            assert!(is_dep_implied("bzr", "bzr (<< 3)"));
363            assert!(is_dep_implied("bzr (<< 3)", "bzr (<< 3)"));
364            assert!(is_dep_implied("bzr (<= 3)", "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 (<= 2)", "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 (>= 3)"));
371            assert!(is_dep_implied("bzr (>> 5)", "bzr (>> 6)"));
372            assert!(is_dep_implied("bzr (>> 5)", "bzr (>> 5)"));
373            assert!(!is_dep_implied("bzr (>> 5)", "bzr (>> 4)"));
374            assert!(is_dep_implied("bzr (>> 5)", "bzr (= 6)"));
375            assert!(!is_dep_implied("bzr (>> 5)", "bzr (= 5)"));
376            assert!(is_dep_implied("bzr:any (>> 5)", "bzr:any (= 6)"));
377        }
378    }
379
380    mod is_relation_implied {
381        use debian_control::lossless::relations::Relations;
382
383        fn parse(s: &str) -> super::Entry {
384            let r: Relations = s.parse().unwrap();
385            let mut entries = r.entries();
386            let entry = entries.next().unwrap();
387            assert_eq!(entries.next(), None);
388            entry
389        }
390
391        fn is_relation_implied(inner: &str, outer: &str) -> bool {
392            super::is_relation_implied(&parse(inner), &parse(outer))
393        }
394
395        #[test]
396        fn test_unrelated() {
397            assert!(!is_relation_implied("bzr", "bar"));
398            assert!(!is_relation_implied("bzr (= 3)", "bar"));
399            assert!(!is_relation_implied("bzr (= 3) | foo", "bar"));
400        }
401
402        #[test]
403        fn test_too_old() {
404            assert!(!is_relation_implied("bzr (= 3)", "bzr"));
405            assert!(!is_relation_implied("bzr (= 3)", "bzr (= 2)"));
406            assert!(!is_relation_implied("bzr (= 3)", "bzr (>= 2)"));
407        }
408
409        #[test]
410        fn test_ors() {
411            assert!(!is_relation_implied("bzr (= 3)", "bzr | foo"));
412            assert!(is_relation_implied("bzr", "bzr | foo"));
413            assert!(is_relation_implied("bzr | foo", "bzr | foo"));
414        }
415
416        #[test]
417        fn test_implied() {
418            assert!(is_relation_implied("bzr (= 3)", "bzr (= 3)"));
419            assert!(is_relation_implied("bzr (>= 3)", "bzr (>= 4)"));
420            assert!(is_relation_implied("bzr (>= 4)", "bzr (>= 4)"));
421            assert!(is_relation_implied("bzr", "bzr"));
422            assert!(is_relation_implied("bzr | foo", "bzr"));
423            assert!(!is_relation_implied("bzr (= 3)", "bzr (>= 3)"));
424            assert!(is_relation_implied(
425                "python3:any | dh-sequence-python3",
426                "python3:any"
427            ));
428            assert!(is_relation_implied(
429                "python3:any | python3-dev:any | dh-sequence-python3",
430                "python3:any | python3-dev:any"
431            ));
432        }
433    }
434
435    #[test]
436    fn test_ensure_relation() {
437        let mut rels: Relations = "".parse().unwrap();
438        let rel = Relation::simple("foo");
439        ensure_relation(&mut rels, rel.into());
440        assert_eq!("foo", rels.to_string());
441    }
442
443    #[test]
444    fn test_ensure_relation_upgrade() {
445        let mut rels = "foo".parse().unwrap();
446        let newrel: Entry = Relation::new(
447            "foo",
448            Some((VersionConstraint::Equal, "1.0".parse().unwrap())),
449        )
450        .into();
451        ensure_relation(&mut rels, newrel);
452        assert_eq!("foo (= 1.0)", rels.to_string());
453    }
454
455    #[test]
456    fn test_ensure_relation_new() {
457        let mut rels = "bar (= 1.0)".parse().unwrap();
458        let newrel: Entry = Relation::new(
459            "foo",
460            Some((VersionConstraint::Equal, "2.0".parse().unwrap())),
461        )
462        .into();
463        ensure_relation(&mut rels, newrel);
464        assert_eq!("bar (= 1.0), foo (= 2.0)", rels.to_string());
465    }
466
467    #[test]
468    fn test_drops_obsolete() {
469        let mut rels = "bar (= 1.0), foo (>= 2.0), foo (>= 1.0)".parse().unwrap();
470        let newrel: Entry = Relation::new(
471            "foo",
472            Some((VersionConstraint::GreaterThanEqual, "3.0".parse().unwrap())),
473        )
474        .into();
475        ensure_relation(&mut rels, newrel);
476        assert_eq!("bar (= 1.0), foo (>= 3.0)", rels.to_string());
477    }
478
479    #[test]
480    fn test_ensure_relation_with_error() {
481        let mut rels = Relations::parse_relaxed("@cdbs@, debhelper (>= 9)", false).0;
482        let newrel: Entry = Relation::new("foo", None).into();
483
484        ensure_relation(&mut rels, newrel);
485        assert_eq!("@cdbs@, debhelper (>= 9), foo", rels.to_string());
486    }
487
488    #[test]
489    fn test_ensure_minimum_version() {
490        let mut rels = "".parse().unwrap();
491        ensure_minimum_version(&mut rels, "foo", &"1.0".parse().unwrap());
492        assert_eq!("foo (>= 1.0)", rels.to_string());
493    }
494
495    #[test]
496    fn test_ensure_minimum_version_upgrade() {
497        let mut rels = "foo (>= 1.0)".parse().unwrap();
498        ensure_minimum_version(&mut rels, "foo", &"2.0".parse().unwrap());
499        assert_eq!("foo (>= 2.0)", rels.to_string());
500    }
501
502    #[test]
503    fn test_ensure_minimum_version_upgrade_with_or() {
504        let mut rels = "foo (>= 1.0) | bar".parse().unwrap();
505        ensure_minimum_version(&mut rels, "foo", &"2.0".parse().unwrap());
506        assert_eq!("foo (>= 2.0)", rels.to_string());
507    }
508
509    #[test]
510    fn test_ensure_exact_version() {
511        let mut rels = "".parse().unwrap();
512        ensure_exact_version(&mut rels, "foo", &"1.0".parse().unwrap(), None);
513        assert_eq!("foo (= 1.0)", rels.to_string());
514    }
515
516    #[test]
517    fn test_ensure_exact_version_upgrade() {
518        let mut rels = "foo (= 1.0)".parse().unwrap();
519        ensure_exact_version(&mut rels, "foo", &"2.0".parse().unwrap(), None);
520        assert_eq!("foo (= 2.0)", rels.to_string());
521    }
522
523    #[test]
524    fn test_ensure_exact_version_upgrade_with_position() {
525        let mut rels = "foo (= 1.0)".parse().unwrap();
526        ensure_exact_version(&mut rels, "foo", &"2.0".parse().unwrap(), Some(0));
527        assert_eq!("foo (= 2.0)", rels.to_string());
528    }
529}