Skip to main content

check_updates/
lib.rs

1use std::collections::HashSet;
2use std::path::PathBuf;
3use std::rc::Rc;
4
5use curl::multi::Multi;
6use semver::VersionReq;
7
8use crate::registry::*;
9
10mod package;
11mod registry;
12
13pub use package::{DepKind, Package, PackageVersion, Packages, Unit, Usage};
14
15type Purl = purl::GenericPurl<String>;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
18pub enum RegistryCachePolicy {
19    PreferLocal,
20    #[default]
21    Refresh,
22    NoCache,
23}
24
25#[derive(Debug, Clone, Copy, Default)]
26pub struct Options {
27    pub registry_cache_policy: RegistryCachePolicy,
28}
29
30pub struct State {
31    multi: Multi,
32    root: Option<PathBuf>,
33    registry_cache_policy: RegistryCachePolicy,
34}
35
36impl State {
37    pub fn new(root: Option<PathBuf>, options: Options) -> Self {
38        let mut multi = Multi::new();
39        multi.pipelining(false, true).ok();
40        Self {
41            multi,
42            root,
43            registry_cache_policy: options.registry_cache_policy,
44        }
45    }
46
47    pub fn multi(&self) -> &Multi {
48        &self.multi
49    }
50
51    pub fn root(&self) -> Option<&PathBuf> {
52        self.root.as_ref()
53    }
54
55    pub fn registry_cache_policy(&self) -> RegistryCachePolicy {
56        self.registry_cache_policy
57    }
58}
59
60#[derive(Debug, thiserror::Error)]
61pub enum Error {
62    #[error("registry error: {0}")]
63    Registry(#[from] registry::RegistryError),
64}
65
66pub struct CheckUpdates {
67    cargo: Registry,
68}
69
70impl CheckUpdates {
71    pub fn new(root: Option<PathBuf>) -> Self {
72        Self::with_options(root, Options::default())
73    }
74
75    pub fn with_options(root: Option<PathBuf>, options: Options) -> Self {
76        let state = Rc::new(State::new(root, options));
77        let cargo = CargoRegistry::new(state.clone());
78
79        Self {
80            cargo: cargo.into(),
81        }
82    }
83
84    pub fn packages(&self) -> Result<Packages, Error> {
85        let mut res: Packages = Default::default();
86        // Track (unit, package_name, req, kind) to deduplicate while still
87        // preserving distinct dep sections in output.
88        let mut seen: HashSet<(Unit, String, String, DepKind)> = HashSet::new();
89        for package in self.cargo.packages()? {
90            for usage in &package.usages {
91                // Wildcard requirements have nothing to update.
92                if usage.req == VersionReq::STAR {
93                    continue;
94                }
95                let key = (
96                    usage.unit.clone(),
97                    package.purl.name().to_string(),
98                    usage.req.to_string(),
99                    usage.kind,
100                );
101                if !seen.insert(key) {
102                    continue;
103                }
104                res.entry(usage.unit.clone()).or_default().push((
105                    usage.req.clone(),
106                    usage.kind,
107                    package.clone(),
108                ));
109            }
110        }
111        Ok(res)
112    }
113
114    /// Update the locally installed versions of the given packages to the one specified
115    pub fn update_versions<'a>(
116        &self,
117        packages: impl IntoIterator<Item = (&'a Usage, &'a Package, VersionReq)>,
118    ) -> Result<(), Error> {
119        self.cargo.update_versions(packages)?;
120        Ok(())
121    }
122}