rustup_available_packages/
availability.rs

1//! Availability evaluation tools.
2
3use crate::manifest::Manifest;
4use chrono::NaiveDate;
5use std::{
6    borrow::Borrow,
7    collections::{HashMap, HashSet},
8};
9
10type PackageName = String;
11type TargetTriple = String;
12type DatesSet = HashSet<NaiveDate>;
13type PackagesAvailability = HashMap<PackageName, DatesSet>;
14
15/// Data about packages availability in rust builds.
16#[derive(Debug, Default)]
17pub struct AvailabilityData {
18    data: HashMap<TargetTriple, PackagesAvailability>,
19}
20
21/// A single row in an availability table.
22#[derive(Debug, serde::Serialize)]
23pub struct AvailabilityRow<'a> {
24    /// Name of the package.
25    pub package_name: &'a str,
26    /// List of "availabilities".
27    pub availability_list: Vec<bool>,
28    /// Date when the component has been available for the last time.
29    pub last_available: Option<NaiveDate>,
30    /// A hidden field to improve compatibility.
31    _hidden: (),
32}
33
34impl AvailabilityData {
35    /// Adds an availability data from a given [`Manifest`].
36    pub fn add_manifest(&mut self, manifest: Manifest) {
37        let reverse_renames: HashMap<_, _> = manifest
38            .renames
39            .iter()
40            .map(|(key, value)| (&value.to, key))
41            .collect();
42        for (package_name, info) in manifest.packages {
43            let package_name = reverse_renames
44                .get(&package_name)
45                .map(|name| String::clone(name))
46                .unwrap_or(package_name);
47            for (target_triple, target_info) in info.targets {
48                if target_info.available {
49                    self.data
50                        .entry(target_triple.clone())
51                        .or_default()
52                        .entry(package_name.clone())
53                        .or_default()
54                        .insert(manifest.date);
55                }
56            }
57        }
58    }
59
60    /// Adds multiple [`Manifest`]s at once.
61    pub fn add_manifests(&mut self, manifests: impl IntoIterator<Item = Manifest>) {
62        manifests
63            .into_iter()
64            .for_each(|manifest| self.add_manifest(manifest));
65    }
66
67    /// Gets a list of targets that have been extracted from manifest files except for the '*'
68    /// target.
69    pub fn get_available_targets(&self) -> HashSet<&'_ str> {
70        self.data
71            .keys()
72            .filter(|target| target != &"*")
73            .map(AsRef::as_ref)
74            .collect()
75    }
76
77    /// Returns all available packages throughout all the targets and all the times.
78    pub fn get_available_packages<'a>(&'a self) -> HashSet<&'a str> {
79        self.data
80            .iter()
81            .flat_map(|(_, per_target)| per_target.keys())
82            .map(AsRef::as_ref)
83            .collect()
84    }
85
86    /// Makes an iterator that maps given dates to `true` or `false`, depending on whether or not the
87    /// given package is available on a given moment.
88    ///
89    /// Availability is checked against the specified target and against the `*` target.
90    pub fn get_availability_row<'a, I>(
91        &self,
92        target: &str,
93        pkg: &'a str,
94        dates: I,
95    ) -> Option<AvailabilityRow<'a>>
96    where
97        I: IntoIterator,
98        I::Item: Borrow<NaiveDate>,
99    {
100        if self.data.get(target).and_then(|t| t.get(pkg)).is_none() {
101            return None;
102        }
103        let available_dates = self.available_dates(target, pkg);
104        let availability_list = dates
105            .into_iter()
106            .map(|date| available_dates.contains(date.borrow()))
107            .collect();
108        Some(AvailabilityRow {
109            package_name: pkg,
110            availability_list,
111            last_available: available_dates.into_iter().max(),
112            _hidden: (),
113        })
114    }
115
116    /// Retrieves a set of all the dates when a given package was available on a given target.
117    fn available_dates(&self, target: &str, pkg: &str) -> HashSet<NaiveDate> {
118        let available_on_target = self.data.get(target).and_then(|packages| packages.get(pkg));
119        let available_on_wildcard = self.data.get("*").and_then(|packages| packages.get(pkg));
120        // FIXME cloned -> copied when 1.36 releases.
121        match (available_on_target, available_on_wildcard) {
122            (Some(x), Some(y)) => x.union(y).cloned().collect(),
123            (Some(x), None) | (None, Some(x)) => x.iter().cloned().collect(),
124            (None, None) => HashSet::new(),
125        }
126    }
127
128    /// Finds when a given package was last available on a given target.
129    pub fn last_available(&self, target: &str, pkg: &str) -> Option<NaiveDate> {
130        self.available_dates(target, pkg).into_iter().max()
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137    use crate::manifest::Manifest;
138
139    #[test]
140    fn check() {
141        let data = r#"date = "2018-09-03"
142[pkg.rust-src.target."*"]
143available = true
144[pkg.ahaha.target.lol]
145available = true
146"#;
147        let parsed_manifest: Manifest = toml::from_str(data).unwrap();
148        let mut availability: AvailabilityData = Default::default();
149        availability.add_manifest(parsed_manifest);
150        let all_packages = availability.get_available_packages();
151        assert_eq!(2, all_packages.len());
152        assert!(all_packages.contains("rust-src"));
153        assert!(all_packages.contains("ahaha"));
154
155        let all_targets = availability.get_available_targets();
156        // The *wildcard* target is ignored here.
157        assert_eq!(1, all_targets.len());
158        assert!(all_targets.contains("lol"));
159
160        let package_exists = availability
161            .get_availability_row("*", "rust-src", vec![NaiveDate::from_ymd(2018, 9, 3)])
162            .unwrap();
163        assert_eq!("rust-src", package_exists.package_name);
164        assert_eq!(vec!(true), package_exists.availability_list);
165        let package_exists = availability.get_availability_row(
166            "lol",
167            "rust-src",
168            vec![NaiveDate::from_ymd(2018, 9, 3)],
169        );
170        // rust-src is not present in lol target
171        assert!(package_exists.is_none());
172        let package_exists = availability
173            .get_availability_row("lol", "ahaha", vec![NaiveDate::from_ymd(2018, 9, 3)])
174            .unwrap();
175        assert_eq!("ahaha", package_exists.package_name);
176        assert_eq!(vec!(true), package_exists.availability_list);
177    }
178
179    #[test]
180    fn check_rename() {
181        let data = r#"date = "2018-09-03"
182[pkg.ahaha.target.lol]
183available = true
184[renames.kek]
185to = "ahaha"
186"#;
187        let parsed_manifest: Manifest = toml::from_str(data).unwrap();
188        let mut availability: AvailabilityData = Default::default();
189        availability.add_manifest(parsed_manifest);
190        let all_packages = availability.get_available_packages();
191        assert_eq!(1, all_packages.len());
192        assert!(all_packages.contains("kek"));
193    }
194}