Skip to main content

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