#
cargo/core/
summary.rs

    1use crate::core::{Dependency, PackageId, SourceId};
    2use crate::util::CargoResult;
    3use crate::util::closest_msg;
    4use crate::util::interning::InternedString;
    5use anyhow::bail;
    6use cargo_util_schemas::manifest::FeatureName;
    7use cargo_util_schemas::manifest::RustVersion;
    8use semver::Version;
    9use std::collections::{BTreeMap, HashMap, HashSet};
    10use std::fmt;
    11use std::hash::{Hash, Hasher};
    12use std::mem;
    13use std::sync::Arc;
    14
    15/// Subset of a `Manifest`. Contains only the most important information about
    16/// a package.
    17///
    18/// Summaries are cloned, and should not be mutated after creation
    19#[derive(Debug, Clone)]
    20pub struct Summary {
    21    inner: Arc<Inner>,
    22}
    23
    24#[derive(Debug, Clone)]
    25struct Inner {
    26    package_id: PackageId,
    27    dependencies: Vec<Dependency>,
    28    features: Arc<FeatureMap>,
    29    checksum: Option<String>,
    30    links: Option<InternedString>,
    31    rust_version: Option<RustVersion>,
    32    pubtime: Option<jiff::Timestamp>,
    33}
    34
    35/// Indicates the dependency inferred from the `dep` syntax that should exist,
    36/// but missing on the resolved dependencies tables.
    37#[derive(Debug)]
    38pub struct MissingDependencyError {
    39    pub dep_name: InternedString,
    40    pub feature: InternedString,
    41    pub feature_value: FeatureValue,
    42    /// Indicates the dependency inferred from the `dep?` syntax that is weak optional
    43    pub weak_optional: bool,
    44}
    45
    46impl std::error::Error for MissingDependencyError {}
    47
    48impl fmt::Display for MissingDependencyError {
    49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    50        let Self {
    51            dep_name,
    52            feature,
    53            feature_value: fv,
    54            ..
    55        } = self;
    56
    57        write!(
    58            f,
    59            "feature `{feature}` includes `{fv}`, but `{dep_name}` is not a dependency",
    60        )
    61    }
    62}
    63
    64impl Summary {
    65    #[tracing::instrument(skip_all)]
    66    pub fn new(
    67        pkg_id: PackageId,
    68        dependencies: Vec<Dependency>,
    69        features: &BTreeMap<InternedString, Vec<InternedString>>,
    70        links: Option<impl Into<InternedString>>,
    71        rust_version: Option<RustVersion>,
    72    ) -> CargoResult<Summary> {
    73        // ****CAUTION**** If you change anything here that may raise a new
    74        // error, be sure to coordinate that change with either the index
    75        // schema field or the SummariesCache version.
    76        for dep in dependencies.iter() {
    77            let dep_name = dep.name_in_toml();
    78            if dep.is_optional() && !dep.is_transitive() {
    79                bail!(
    80                    "dev-dependencies are not allowed to be optional: `{}`",
    81                    dep_name
    82                )
    83            }
    84        }
    85        let feature_map = build_feature_map(features, &dependencies)?;
    86        Ok(Summary {
    87            inner: Arc::new(Inner {
    88                package_id: pkg_id,
    89                dependencies,
    90                features: Arc::new(feature_map),
    91                checksum: None,
    92                links: links.map(|l| l.into()),
    93                rust_version,
    94                pubtime: None,
    95            }),
    96        })
    97    }
    98
    99    pub fn package_id(&self) -> PackageId {
    100        self.inner.package_id
    101    }
    102    pub fn name(&self) -> InternedString {
    103        self.package_id().name()
    104    }
    105    pub fn version(&self) -> &Version {
    106        self.package_id().version()
    107    }
    108    pub fn source_id(&self) -> SourceId {
    109        self.package_id().source_id()
    110    }
    111    pub fn dependencies(&self) -> &[Dependency] {
    112        &self.inner.dependencies
    113    }
    114    pub fn features(&self) -> &FeatureMap {
    115        &self.inner.features
    116    }
    117
    118    pub fn checksum(&self) -> Option<&str> {
    119        self.inner.checksum.as_deref()
    120    }
    121    pub fn links(&self) -> Option<InternedString> {
    122        self.inner.links
    123    }
    124
    125    pub fn rust_version(&self) -> Option<&RustVersion> {
    126        self.inner.rust_version.as_ref()
    127    }
    128
    129    pub fn pubtime(&self) -> Option<jiff::Timestamp> {
    130        self.inner.pubtime
    131    }
    132
    133    pub fn override_id(mut self, id: PackageId) -> Summary {
    134        Arc::make_mut(&mut self.inner).package_id = id;
    135        self
    136    }
    137
    138    pub fn set_checksum(&mut self, cksum: String) {
    139        Arc::make_mut(&mut self.inner).checksum = Some(cksum);
    140    }
    141
    142    pub fn set_pubtime(&mut self, pubtime: jiff::Timestamp) {
    143        Arc::make_mut(&mut self.inner).pubtime = Some(pubtime);
    144    }
    145
    146    pub fn map_dependencies<F>(self, mut f: F) -> Summary
    147    where
    148        F: FnMut(Dependency) -> Dependency,
    149    {
    150        self.try_map_dependencies(|dep| Ok(f(dep))).unwrap()
    151    }
    152
    153    pub fn try_map_dependencies<F>(mut self, f: F) -> CargoResult<Summary>
    154    where
    155        F: FnMut(Dependency) -> CargoResult<Dependency>,
    156    {
    157        {
    158            let slot = &mut Arc::make_mut(&mut self.inner).dependencies;
    159            *slot = mem::take(slot)
    160                .into_iter()
    161                .map(f)
    162                .collect::<CargoResult<_>>()?;
    163        }
    164        Ok(self)
    165    }
    166
    167    pub fn map_source(self, to_replace: SourceId, replace_with: SourceId) -> Summary {
    168        let me = if self.package_id().source_id() == to_replace {
    169            let new_id = self.package_id().with_source_id(replace_with);
    170            self.override_id(new_id)
    171        } else {
    172            self
    173        };
    174        me.map_dependencies(|dep| dep.map_source(to_replace, replace_with))
    175    }
    176}
    177
    178impl PartialEq for Summary {
    179    fn eq(&self, other: &Summary) -> bool {
    180        self.inner.package_id == other.inner.package_id
    181    }
    182}
    183
    184impl Eq for Summary {}
    185
    186impl Hash for Summary {
    187    fn hash<H: Hasher>(&self, state: &mut H) {
    188        self.inner.package_id.hash(state);
    189    }
    190}
    191
    192// A check that only compiles if Summary is Sync
    193const _: fn() = || {
    194    fn is_sync<T: Sync>() {}
    195    is_sync::<Summary>();
    196};
    197
    198/// Checks features for errors, bailing out a CargoResult:Err if invalid,
    199/// and creates `FeatureValues` for each feature.
    200fn build_feature_map(
    201    features: &BTreeMap<InternedString, Vec<InternedString>>,
    202    dependencies: &[Dependency],
    203) -> CargoResult<FeatureMap> {
    204    use self::FeatureValue::*;
    205    // A map of dependency names to whether there are any that are optional.
    206    let mut dep_map: HashMap<InternedString, bool> = HashMap::new();
    207    for dep in dependencies.iter() {
    208        *dep_map.entry(dep.name_in_toml()).or_insert(false) |= dep.is_optional();
    209    }
    210    let dep_map = dep_map; // We are done mutating this variable
    211
    212    let mut map: FeatureMap = features
    213        .iter()
    214        .map(|(feature, list)| {
    215            let fvs: Vec<_> = list
    216                .iter()
    217                .map(|feat_value| FeatureValue::new(*feat_value))
    218                .collect();
    219            (*feature, fvs)
    220        })
    221        .collect();
    222
    223    // Add implicit features for optional dependencies if they weren't
    224    // explicitly listed anywhere.
    225    let explicitly_listed: HashSet<_> = map
    226        .values()
    227        .flatten()
    228        .filter_map(|fv| fv.explicit_dep_name())
    229        .collect();
    230
    231    for dep in dependencies {
    232        if !dep.is_optional() {
    233            continue;
    234        }
    235        let dep_name = dep.name_in_toml();
    236        if features.contains_key(&dep_name) || explicitly_listed.contains(&dep_name) {
    237            continue;
    238        }
    239        map.insert(dep_name, vec![Dep { dep_name }]);
    240    }
    241    let map = map; // We are done mutating this variable
    242
    243    // Validate features are listed properly.
    244    for (feature, fvs) in &map {
    245        FeatureName::new(feature)?;
    246        for fv in fvs {
    247            // Find data for the referenced dependency...
    248            let dep_data = dep_map.get(&fv.feature_or_dep_name());
    249            let is_any_dep = dep_data.is_some();
    250            let is_optional_dep = dep_data.is_some_and(|&o| o);
    251            match fv {
    252                Feature(f) => {
    253                    if !features.contains_key(f) {
    254                        if !is_any_dep {
    255                            let closest = closest_msg(f, features.keys(), |k| k, "feature");
    256                            bail!(
    257                                "feature `{feature}` includes `{fv}` which is neither a dependency \
    258                                 nor another feature{closest}"
    259                            );
    260                        }
    261                        if is_optional_dep {
    262                            if !map.contains_key(f) {
    263                                bail!(
    264                                    "feature `{feature}` includes `{fv}`, but `{f}` is an \
    265                                     optional dependency without an implicit feature\n\
    266                                     Use `dep:{f}` to enable the dependency."
    267                                );
    268                            }
    269                        } else {
    270                            bail!(
    271                                "feature `{feature}` includes `{fv}`, but `{f}` is not an optional dependency\n\
    272                                A non-optional dependency of the same name is defined; \
    273                                consider adding `optional = true` to its definition."
    274                            );
    275                        }
    276                    }
    277                }
    278                Dep { dep_name } => {
    279                    if !is_any_dep {
    280                        bail!(
    281                            "feature `{feature}` includes `{fv}`, but `{dep_name}` is not listed as a dependency"
    282                        );
    283                    }
    284                    if !is_optional_dep {
    285                        bail!(
    286                            "feature `{feature}` includes `{fv}`, but `{dep_name}` is not an optional dependency\n\
    287                             A non-optional dependency of the same name is defined; \
    288                             consider adding `optional = true` to its definition."
    289                        );
    290                    }
    291                }
    292                DepFeature {
    293                    dep_name,
    294                    dep_feature,
    295                    weak,
    296                } => {
    297                    // Early check for some unlikely syntax.
    298                    if dep_feature.contains('/') {
    299                        bail!(
    300                            "multiple slashes in feature `{fv}` (included by feature `{feature}`) are not allowed"
    301                        );
    302                    }
    303
    304                    // dep: cannot be combined with /
    305                    if let Some(stripped_dep) = dep_name.strip_prefix("dep:") {
    306                        let has_other_dep = explicitly_listed.contains(stripped_dep);
    307                        let is_optional = dep_map.get(stripped_dep).is_some_and(|&o| o);
    308                        let extra_help = if *weak || has_other_dep || !is_optional {
    309                            // In this case, the user should just remove dep:.
    310                            // Note that "hiding" an optional dependency
    311                            // wouldn't work with just a single `dep:foo?/bar`
    312                            // because there would not be any way to enable
    313                            // `foo`.
    314                            String::new()
    315                        } else {
    316                            format!(
    317                                "\nIf the intent is to avoid creating an implicit feature \
    318                                 `{stripped_dep}` for an optional dependency, \
    319                                 then consider replacing this with two values:\n    \
    320                                 \"dep:{stripped_dep}\", \"{stripped_dep}/{dep_feature}\""
    321                            )
    322                        };
    323                        bail!(
    324                            "feature `{feature}` includes `{fv}` with both `dep:` and `/`\n\
    325                            To fix this, remove the `dep:` prefix.{extra_help}"
    326                        )
    327                    }
    328
    329                    // Validation of the feature name will be performed in the resolver.
    330                    if !is_any_dep {
    331                        bail!(MissingDependencyError {
    332                            feature: *feature,
    333                            feature_value: (*fv).clone(),
    334                            dep_name: *dep_name,
    335                            weak_optional: *weak,
    336                        })
    337                    }
    338                    if *weak && !is_optional_dep {
    339                        bail!(
    340                            "feature `{feature}` includes `{fv}` with a `?`, but `{dep_name}` is not an optional dependency\n\
    341                            A non-optional dependency of the same name is defined; \
    342                            consider removing the `?` or changing the dependency to be optional"
    343                        );
    344                    }
    345                }
    346            }
    347        }
    348    }
    349
    350    // Make sure every optional dep is mentioned at least once.
    351    let used: HashSet<_> = map
    352        .values()
    353        .flatten()
    354        .filter_map(|fv| match fv {
    355            Dep { dep_name } | DepFeature { dep_name, .. } => Some(dep_name),
    356            _ => None,
    357        })
    358        .collect();
    359    if let Some((dep, _)) = dep_map
    360        .iter()
    361        .find(|&(dep, &is_optional)| is_optional && !used.contains(dep))
    362    {
    363        bail!(
    364            "optional dependency `{dep}` is not included in any feature\n\
    365            Make sure that `dep:{dep}` is included in one of features in the [features] table."
    366        );
    367    }
    368
    369    Ok(map)
    370}
    371
    372/// `FeatureValue` represents the types of dependencies a feature can have.
    373#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
    374pub enum FeatureValue {
    375    /// A feature enabling another feature.
    376    Feature(InternedString),
    377    /// A feature enabling a dependency with `dep:dep_name` syntax.
    378    Dep { dep_name: InternedString },
    379    /// A feature enabling a feature on a dependency with `crate_name/feat_name` syntax.
    380    DepFeature {
    381        dep_name: InternedString,
    382        dep_feature: InternedString,
    383        /// If `true`, indicates the `?` syntax is used, which means this will
    384        /// not automatically enable the dependency unless the dependency is
    385        /// activated through some other means.
    386        weak: bool,
    387    },
    388}
    389
    390impl FeatureValue {
    391    pub fn new(feature: InternedString) -> FeatureValue {
    392        match feature.split_once('/') {
    393            Some((dep, dep_feat)) => {
    394                let dep_name = dep.strip_suffix('?');
    395                FeatureValue::DepFeature {
    396                    dep_name: dep_name.unwrap_or(dep).into(),
    397                    dep_feature: dep_feat.into(),
    398                    weak: dep_name.is_some(),
    399                }
    400            }
    401            None => {
    402                if let Some(dep_name) = feature.strip_prefix("dep:") {
    403                    FeatureValue::Dep {
    404                        dep_name: dep_name.into(),
    405                    }
    406                } else {
    407                    FeatureValue::Feature(feature)
    408                }
    409            }
    410        }
    411    }
    412
    413    /// Returns the name of the dependency if and only if it was explicitly named with the `dep:` syntax.
    414    fn explicit_dep_name(&self) -> Option<InternedString> {
    415        match self {
    416            FeatureValue::Dep { dep_name, .. } => Some(*dep_name),
    417            _ => None,
    418        }
    419    }
    420
    421    fn feature_or_dep_name(&self) -> InternedString {
    422        match self {
    423            FeatureValue::Feature(dep_name)
    424            | FeatureValue::Dep { dep_name, .. }
    425            | FeatureValue::DepFeature { dep_name, .. } => *dep_name,
    426        }
    427    }
    428}
    429
    430impl fmt::Display for FeatureValue {
    431    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    432        use self::FeatureValue::*;
    433        match self {
    434            Feature(feat) => write!(f, "{feat}"),
    435            Dep { dep_name } => write!(f, "dep:{dep_name}"),
    436            DepFeature {
    437                dep_name,
    438                dep_feature,
    439                weak,
    440            } => {
    441                let weak = if *weak { "?" } else { "" };
    442                write!(f, "{dep_name}{weak}/{dep_feature}")
    443            }
    444        }
    445    }
    446}
    447
    448pub type FeatureMap = BTreeMap<InternedString, Vec<FeatureValue>>;