debian_packaging/
dependency_resolution.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/*! Package dependency resolution. */
6
7use {
8    crate::{
9        binary_package_control::BinaryPackageControlFile,
10        dependency::{
11            BinaryDependency, DependencyVersionConstraint, PackageDependencyFields,
12            SingleDependency,
13        },
14        error::Result,
15        package_version::PackageVersion,
16    },
17    std::collections::{HashMap, HashSet, VecDeque},
18};
19
20/// Holds [BinaryPackageControlFile] references satisfying a single dependency expression.
21#[derive(Clone, Debug)]
22pub struct BinaryPackageSingleDependencyResolution<'file, 'data: 'file> {
23    pub expression: SingleDependency,
24    pub candidates: Vec<&'file BinaryPackageControlFile<'data>>,
25}
26
27impl<'file, 'data: 'file> BinaryPackageSingleDependencyResolution<'file, 'data> {
28    /// Whether the set of packages satisfying the constraint is empty.
29    pub fn is_empty(&self) -> bool {
30        self.candidates.is_empty()
31    }
32
33    /// Iterate over packages satisfying this dependency expression.
34    pub fn packages(&self) -> impl Iterator<Item = &'file BinaryPackageControlFile<'data>> + '_ {
35        self.candidates.iter().copied()
36    }
37
38    /// Iterate over packages while also emitting the expression being satisfied.
39    pub fn packages_with_expression(
40        &self,
41    ) -> impl Iterator<Item = (&'_ SingleDependency, &'file BinaryPackageControlFile<'data>)> + '_
42    {
43        self.candidates.iter().map(|p| (&self.expression, *p))
44    }
45
46    /// Obtain all candidates in this data structure, indexed by package name.
47    pub fn group_by_package_name(
48        &self,
49    ) -> HashMap<&'file str, Vec<&'file BinaryPackageControlFile<'data>>> {
50        let mut h: HashMap<&str, Vec<&BinaryPackageControlFile>> = HashMap::new();
51
52        for cf in self.candidates.iter() {
53            let entry = h
54                .entry(cf.package().expect(
55                    "Package field should have been validated during dependency resolution",
56                ))
57                .or_default();
58
59            entry.push(cf);
60        }
61
62        h
63    }
64}
65
66/// A collection of [BinaryPackageSingleDependencyResolution] satisfying a set of alternative expressions.
67#[derive(Clone, Debug, Default)]
68pub struct BinaryPackageAlternativesResolution<'file, 'data: 'file> {
69    pub alternatives: Vec<BinaryPackageSingleDependencyResolution<'file, 'data>>,
70}
71
72impl<'file, 'data: 'file> BinaryPackageAlternativesResolution<'file, 'data> {
73    /// Whether no packages satisfy constraints from this list of dependency expressions.
74    ///
75    /// Returns true if the set of dependency expressions is empty or if all expressions have
76    /// empty packages lists.
77    pub fn is_empty(&self) -> bool {
78        self.alternatives.is_empty() || self.alternatives.iter().any(|x| x.is_empty())
79    }
80
81    /// Obtain alternative dependency constraints for this set.
82    pub fn alternative_constraints(&self) -> impl Iterator<Item = &'_ SingleDependency> {
83        self.alternatives.iter().map(|alt| &alt.expression)
84    }
85
86    /// Iterate over all packages in this set of alternatives.
87    ///
88    /// There may be duplicates in the output stream.
89    pub fn packages(&self) -> impl Iterator<Item = &'file BinaryPackageControlFile<'data>> + '_ {
90        self.alternatives.iter().flat_map(|alt| alt.packages())
91    }
92
93    /// Iterate over packages while also emitting the expression being satisfied.
94    pub fn packages_with_expression(
95        &self,
96    ) -> impl Iterator<Item = (&'_ SingleDependency, &'file BinaryPackageControlFile<'data>)> + '_
97    {
98        self.alternatives
99            .iter()
100            .flat_map(|alt| alt.packages_with_expression())
101    }
102
103    /// Prune empty alternatives from this data structure.
104    ///
105    /// Dependency expressions not having any satisfying packages will be removed.
106    pub fn prune_empty(&mut self) {
107        self.alternatives = self
108            .alternatives
109            .drain(..)
110            .filter(|alt| !alt.is_empty())
111            .collect::<Vec<_>>();
112    }
113}
114
115/// A collection of [BinaryPackageAlternativesResolution] satisfying a list of independent constraints.
116#[derive(Clone, Debug, Default)]
117pub struct BinaryPackageDependenciesResolution<'file, 'data: 'file> {
118    pub parts: Vec<BinaryPackageAlternativesResolution<'file, 'data>>,
119}
120
121impl<'file, 'data: 'file> BinaryPackageDependenciesResolution<'file, 'data> {
122    /// Iterate over all packages referenced by this instance.
123    ///
124    /// This returns all packages satisfying all alternatives in the list of expressions.
125    ///
126    /// There may be duplicates in the output stream.
127    pub fn packages(&self) -> impl Iterator<Item = &'file BinaryPackageControlFile<'data>> + '_ {
128        self.parts.iter().flat_map(|req| req.packages())
129    }
130
131    /// Iterate over packages while also emitting the expression being satisfied.
132    pub fn packages_with_expression(
133        &self,
134    ) -> impl Iterator<Item = (&'_ SingleDependency, &'file BinaryPackageControlFile<'data>)> + '_
135    {
136        self.parts
137            .iter()
138            .flat_map(|req| req.packages_with_expression())
139    }
140
141    /// Iterate over dependency alternates that have no satisfying packages.
142    pub fn empty_requirements(
143        &self,
144    ) -> impl Iterator<Item = &'_ BinaryPackageAlternativesResolution<'file, 'data>> {
145        self.parts.iter().filter(|alts| alts.is_empty())
146    }
147
148    /// Whether there are unsatisfied dependency constraints in this result.
149    ///
150    /// Returns true if any of the dependency requirements sets are empty.
151    pub fn has_unsatisfied(&self) -> bool {
152        self.empty_requirements().next().is_none()
153    }
154}
155
156/// Describes the source of a dependency between binary packages.
157#[derive(Clone, Debug)]
158pub struct BinaryPackageDependencySource<'file, 'data> {
159    /// The package the dependency came from.
160    pub package: &'file BinaryPackageControlFile<'data>,
161    /// The control file field the dependency constraint came from.
162    pub field: BinaryDependency,
163    /// The dependency constraint expression being satisfied.
164    pub constraint: SingleDependency,
165}
166
167#[derive(Clone, Debug, Default)]
168pub struct BinaryPackageTransitiveDependenciesResolution<'file, 'data: 'file> {
169    evaluation_order: Vec<&'file BinaryPackageControlFile<'data>>,
170    reverse_dependencies: HashMap<
171        &'file BinaryPackageControlFile<'data>,
172        Vec<BinaryPackageDependencySource<'file, 'data>>,
173    >,
174}
175
176impl<'file, 'data: 'file> BinaryPackageTransitiveDependenciesResolution<'file, 'data> {
177    /// Obtain all packages in this collection.
178    ///
179    /// Packages are guaranteed to be emitted at most once. However, the uniqueness of each
180    /// package is defined by the composition of the control paragraph. So packages with the same
181    /// name and version may occur multiple times if their control paragraphs aren't identical.
182    pub fn packages(&self) -> impl Iterator<Item = &'file BinaryPackageControlFile<'data>> + '_ {
183        self.evaluation_order.iter().rev().copied()
184    }
185
186    /// Obtain all packages in this collection along with annotations of its reverse dependencies.
187    ///
188    /// Packages are emitted in the same order as [Self::packages()]. Associated with each entry
189    /// is a list of direct dependency sources that caused this package to be present.
190    pub fn packages_with_sources(
191        &self,
192    ) -> impl Iterator<
193        Item = (
194            &'file BinaryPackageControlFile<'data>,
195            &'_ Vec<BinaryPackageDependencySource<'file, 'data>>,
196        ),
197    > + '_ {
198        self.evaluation_order.iter().rev().map(|key| {
199            (
200                *key,
201                self.reverse_dependencies
202                    .get(key)
203                    .expect("reverse dependencies should have key for all packages"),
204            )
205        })
206    }
207}
208
209#[derive(Clone, Debug)]
210struct BinaryPackageEntry<'file, 'data: 'file> {
211    file: &'file BinaryPackageControlFile<'data>,
212    name: String,
213    version: PackageVersion,
214    arch: String,
215    deps: PackageDependencyFields,
216}
217
218#[derive(Clone, Debug)]
219struct VirtualBinaryPackageEntry<'file, 'data: 'file> {
220    file: &'file BinaryPackageControlFile<'data>,
221
222    /// The version of the virtual package being provided.
223    provided_version: Option<DependencyVersionConstraint>,
224
225    /// The package providing it.
226    #[allow(unused)]
227    name: String,
228
229    /// The version of the package providing it.
230    #[allow(unused)]
231    version: PackageVersion,
232}
233
234/// An entity for resolving dependencies between packages.
235#[derive(Clone, Debug, Default)]
236pub struct DependencyResolver<'file, 'data: 'file> {
237    /// Map of package name to entries for each package
238    binary_packages: HashMap<String, Vec<BinaryPackageEntry<'file, 'data>>>,
239
240    /// Map of provided package name to packages that provide.
241    virtual_binary_packages: HashMap<String, Vec<VirtualBinaryPackageEntry<'file, 'data>>>,
242}
243
244impl<'file, 'data: 'file> DependencyResolver<'file, 'data> {
245    /// Load an iterable of binary packages into the resolver.
246    ///
247    /// This effectively indexes the given binary package definitions and enables them to
248    /// be discovered during subsequent dependency resolution.
249    pub fn load_binary_packages(
250        &mut self,
251        files: impl Iterator<Item = &'file BinaryPackageControlFile<'data>>,
252    ) -> Result<()> {
253        for cf in files {
254            let package = cf.package()?;
255
256            let entry = BinaryPackageEntry {
257                file: cf,
258                name: package.to_string(),
259                version: cf.version()?,
260                arch: cf.architecture()?.to_string(),
261                deps: cf.package_dependency_fields()?,
262            };
263
264            if let Some(provides) = &entry.deps.provides {
265                for variants in provides.requirements() {
266                    for dep in variants.iter() {
267                        let virtual_entry = VirtualBinaryPackageEntry {
268                            file: cf,
269                            provided_version: dep.version_constraint.clone(),
270                            name: entry.name.clone(),
271                            version: entry.version.clone(),
272                        };
273
274                        self.virtual_binary_packages
275                            .entry(dep.package.clone())
276                            .or_default()
277                            .push(virtual_entry);
278                    }
279                }
280            }
281
282            self.binary_packages
283                .entry(package.to_string())
284                .or_default()
285                .push(entry);
286        }
287
288        Ok(())
289    }
290
291    /// Find direct dependencies given a binary control file and a dependency field.
292    ///
293    /// This will resolve the specified [BinaryDependency] field to a list of constraints
294    /// and then find candidate [BinaryPackageControlFile] satisfying all requirements within.
295    pub fn find_direct_binary_package_dependencies(
296        &self,
297        cf: &BinaryPackageControlFile,
298        dep: BinaryDependency,
299    ) -> Result<BinaryPackageDependenciesResolution<'file, 'data>> {
300        let fields = cf.package_dependency_fields()?;
301
302        let mut res = BinaryPackageDependenciesResolution::default();
303
304        if let Some(deps) = fields.binary_dependency(dep) {
305            for req in deps.requirements() {
306                let mut variants_res = BinaryPackageAlternativesResolution::default();
307
308                for alt in req.iter() {
309                    let mut deps_res = BinaryPackageSingleDependencyResolution {
310                        expression: alt.clone(),
311                        candidates: vec![],
312                    };
313
314                    // Look for concrete packages with this name satisfying the constraints.
315                    if let Some(entries) = self.binary_packages.get(&alt.package) {
316                        for entry in entries {
317                            if alt.package_satisfies(&entry.name, &entry.version, &entry.arch) {
318                                deps_res.candidates.push(entry.file);
319                            }
320                        }
321                    }
322
323                    // Look for virtual packages with this name satisfying the constraints.
324                    if let Some(entries) = self.virtual_binary_packages.get(&alt.package) {
325                        for entry in entries {
326                            if alt.package_satisfies_virtual(
327                                &alt.package,
328                                entry.provided_version.as_ref(),
329                            ) {
330                                deps_res.candidates.push(entry.file);
331                            }
332                        }
333                    }
334
335                    variants_res.alternatives.push(deps_res);
336                }
337
338                res.parts.push(variants_res);
339            }
340        }
341
342        Ok(res)
343    }
344
345    /// Resolve binary package dependencies transitively.
346    ///
347    /// Given a binary package control file and an iterable of dependency fields
348    /// to follow, this function will resolve the complete dependency graph for the
349    /// given package.
350    ///
351    /// It works by resolving direct dependencies. Then for each direct dependency,
352    /// it resolves its direct dependencies. And this cycle continues until no new
353    /// packages are discovered.
354    ///
355    /// Only the dependency fields specified by `fields` are searched. This allows
356    /// callers to e.g. not include `Recommends` or `Suggests` dependencies in the
357    /// returned set. Callers are strongly encouraged to include
358    /// [BinaryDependency::Depends] and [BinaryDependency::PreDepends] in this
359    /// iterable because the dependency graph will be logically incomplete with them.
360    pub fn find_transitive_binary_package_dependencies(
361        &self,
362        cf: &'file BinaryPackageControlFile<'data>,
363        fields: impl Iterator<Item = BinaryDependency>,
364    ) -> Result<BinaryPackageTransitiveDependenciesResolution<'file, 'data>> {
365        let fields = fields.collect::<Vec<_>>();
366
367        // Dependency evaluation queue. Consume from front. Push discovered items to end.
368        let mut remaining = VecDeque::new();
369        remaining.push_back(cf);
370
371        // Order the dependencies were evaluated in. Packages earlier in this list
372        // are dependent on packages later in this list.
373        let mut evaluation_order = vec![];
374
375        let mut seen = HashSet::new();
376
377        let mut reverse_dependencies: HashMap<_, Vec<_>> = HashMap::new();
378        reverse_dependencies.insert(cf, vec![]);
379
380        // Evaluate direct dependencies for all unexamined packages.
381        while let Some(cf) = remaining.pop_front() {
382            // We may have already seen this package. Skip if so.
383            if seen.contains(cf) {
384                continue;
385            }
386
387            for field in &fields {
388                let deps = self.find_direct_binary_package_dependencies(cf, *field)?;
389
390                // Here is where we could add logic to prune the candidates set, error if
391                // we're not satisfying a constraint, etc.
392
393                // Record reverse dependencies to facilitate fast querying and inspection later.
394                for (expression, package) in deps.packages_with_expression() {
395                    reverse_dependencies.entry(package).or_default().push(
396                        BinaryPackageDependencySource {
397                            package: cf,
398                            field: *field,
399                            constraint: expression.clone(),
400                        },
401                    );
402                }
403
404                remaining.extend(deps.packages());
405            }
406
407            evaluation_order.push(cf);
408            seen.insert(cf);
409        }
410
411        Ok(BinaryPackageTransitiveDependenciesResolution {
412            evaluation_order,
413            reverse_dependencies,
414        })
415    }
416}