1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
use std::collections::{BTreeMap, BTreeSet};

pub type BuildIndexCache =
    Box<dyn FnOnce(BTreeSet<String>) -> BTreeMap<String, Option<IndexKrate>>>;

pub type FeaturesMap = BTreeMap<String, Vec<String>>;

#[derive(Clone)]
pub struct IndexKrateVersion {
    pub version: semver::Version,
    pub features: FeaturesMap,
}

#[derive(Clone)]
pub struct IndexKrate {
    pub versions: Vec<IndexKrateVersion>,
}

pub struct CachingIndex {
    cache: BTreeMap<String, Option<IndexKrate>>,
}

impl CachingIndex {
    /// Creates a caching index for information provided by the user
    #[inline]
    pub fn new(build: BuildIndexCache, krates: BTreeSet<String>) -> Self {
        Self {
            cache: build(krates),
        }
    }

    fn index_krate_features(&self, name: &str, version: &semver::Version) -> Option<&FeaturesMap> {
        self.cache.get(name).and_then(|ik| {
            ik.as_ref().and_then(|ik| {
                ik.versions
                    .iter()
                    .find_map(|ikv| (&ikv.version == version).then_some(&ikv.features))
            })
        })
    }
}

/// Due to <https://github.com/rust-lang/cargo/issues/11319>, we can't actually
/// trust cargo to give us the correct package metadata, so we instead use the
/// (presumably) correct data from the the index
pub(super) fn fix_features(index: &CachingIndex, krate: &mut cargo_metadata::Package) {
    if krate
        .source
        .as_ref()
        .map_or(true, |src| !src.is_crates_io())
    {
        return;
    }

    let Some(features) = index.index_krate_features(&krate.name, &krate.version) else {
        return;
    };

    for (ikey, ivalue) in features {
        if !krate.features.contains_key(ikey) {
            krate.features.insert(ikey.clone(), ivalue.clone());
        }
    }

    // The index entry features might not have the `dep:<crate>`
    // used with weak features if the crate version was
    // published with cargo <1.60.0 version, so we need to
    // manually fix that up since we depend on that format
    let missing_deps: Vec<_> = krate
        .features
        .iter()
        .flat_map(|(_, sf)| sf.iter())
        .filter_map(|sf| {
            let pf = crate::ParsedFeature::from(sf.as_str());

            if let super::features::Feature::Simple(simple) = pf.feat() {
                if krate.features.contains_key(simple) {
                    None
                } else {
                    Some(simple.to_owned())
                }
            } else {
                None
            }
        })
        .collect();

    for missing in missing_deps {
        let dep_feature = format!("dep:{missing}");
        krate.features.insert(missing, vec![dep_feature]);
    }
}