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