use crate::util::CargoResult; use crate::util::closest_msg; use crate::util::data_structures::{HashMap, HashSet}; use crate::util::interning::InternedString; use crate::workspace::{Dependency, PackageId, SourceId}; use anyhow::bail; use cargo_util_schemas::manifest::FeatureName; use cargo_util_schemas::manifest::RustVersion; use semver::Version; use std::collections::BTreeMap; use std::fmt; use std::hash::{Hash, Hasher}; use std::mem; use std::sync::Arc; /// Subset of a `Manifest`. Contains only the most important information about /// a package. /// /// Summaries are cloned, and should not be mutated after creation #[derive(Debug, Clone)] pub struct Summary { inner: Arc, } #[derive(Debug, Clone)] struct Inner { package_id: PackageId, dependencies: Vec, features: Arc, checksum: Option, links: Option, rust_version: Option, pubtime: Option, } /// Indicates the dependency inferred from the `dep` syntax that should exist, /// but missing on the resolved dependencies tables. #[derive(Debug)] pub struct MissingDependencyError { pub dep_name: InternedString, pub feature: InternedString, pub feature_value: FeatureValue, /// Indicates the dependency inferred from the `dep?` syntax that is weak optional pub weak_optional: bool, } impl std::error::Error for MissingDependencyError {} impl fmt::Display for MissingDependencyError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { dep_name, feature, feature_value: fv, .. } = self; write!( f, "feature `{feature}` includes `{fv}`, but `{dep_name}` is not a dependency", ) } } impl Summary { #[tracing::instrument(skip_all)] pub fn new( pkg_id: PackageId, dependencies: Vec, features: &BTreeMap>, links: Option>, rust_version: Option, ) -> CargoResult { // ****CAUTION**** If you change anything here that may raise a new // error, be sure to coordinate that change with either the index // schema field or the SummariesCache version. for dep in dependencies.iter() { let dep_name = dep.name_in_toml(); if dep.is_optional() && !dep.is_transitive() { bail!( "dev-dependencies are not allowed to be optional: `{}`", dep_name ) } } let feature_map = build_feature_map(features, &dependencies)?; Ok(Summary { inner: Arc::new(Inner { package_id: pkg_id, dependencies, features: Arc::new(feature_map), checksum: None, links: links.map(|l| l.into()), rust_version, pubtime: None, }), }) } pub fn package_id(&self) -> PackageId { self.inner.package_id } pub fn name(&self) -> InternedString { self.package_id().name() } pub fn version(&self) -> &Version { self.package_id().version() } pub fn source_id(&self) -> SourceId { self.package_id().source_id() } pub fn dependencies(&self) -> &[Dependency] { &self.inner.dependencies } pub fn features(&self) -> &FeatureMap { &self.inner.features } pub fn checksum(&self) -> Option<&str> { self.inner.checksum.as_deref() } pub fn links(&self) -> Option { self.inner.links } pub fn rust_version(&self) -> Option<&RustVersion> { self.inner.rust_version.as_ref() } pub fn pubtime(&self) -> Option { self.inner.pubtime } pub fn override_id(mut self, id: PackageId) -> Summary { Arc::make_mut(&mut self.inner).package_id = id; self } pub fn set_checksum(&mut self, cksum: String) { Arc::make_mut(&mut self.inner).checksum = Some(cksum); } pub fn set_pubtime(&mut self, pubtime: jiff::Timestamp) { Arc::make_mut(&mut self.inner).pubtime = Some(pubtime); } pub fn map_dependencies(self, mut f: F) -> Summary where F: FnMut(Dependency) -> Dependency, { self.try_map_dependencies(|dep| Ok(f(dep))).unwrap() } pub fn try_map_dependencies(mut self, f: F) -> CargoResult where F: FnMut(Dependency) -> CargoResult, { { let slot = &mut Arc::make_mut(&mut self.inner).dependencies; *slot = mem::take(slot) .into_iter() .map(f) .collect::>()?; } Ok(self) } pub fn map_source(self, to_replace: SourceId, replace_with: SourceId) -> Summary { let me = if self.package_id().source_id() == to_replace { let new_id = self.package_id().with_source_id(replace_with); self.override_id(new_id) } else { self }; me.map_dependencies(|dep| dep.map_source(to_replace, replace_with)) } } impl PartialEq for Summary { fn eq(&self, other: &Summary) -> bool { self.inner.package_id == other.inner.package_id } } impl Eq for Summary {} impl Hash for Summary { fn hash(&self, state: &mut H) { self.inner.package_id.hash(state); } } // A check that only compiles if Summary is Sync const _: fn() = || { fn is_sync() {} is_sync::(); }; /// Checks features for errors, bailing out a CargoResult:Err if invalid, /// and creates `FeatureValues` for each feature. fn build_feature_map( features: &BTreeMap>, dependencies: &[Dependency], ) -> CargoResult { use self::FeatureValue::*; // A map of dependency names to whether there are any that are optional. let mut dep_map: HashMap = HashMap::default(); for dep in dependencies.iter() { *dep_map.entry(dep.name_in_toml()).or_insert(false) |= dep.is_optional(); } let dep_map = dep_map; // We are done mutating this variable let mut map: FeatureMap = features .iter() .map(|(feature, list)| { let fvs: Vec<_> = list .iter() .map(|feat_value| FeatureValue::new(*feat_value)) .collect(); (*feature, fvs) }) .collect(); // Add implicit features for optional dependencies if they weren't // explicitly listed anywhere. let explicitly_listed: HashSet<_> = map .values() .flatten() .filter_map(|fv| fv.explicit_dep_name()) .collect(); for dep in dependencies { if !dep.is_optional() { continue; } let dep_name = dep.name_in_toml(); if features.contains_key(&dep_name) || explicitly_listed.contains(&dep_name) { continue; } map.insert(dep_name, vec![Dep { dep_name }]); } let map = map; // We are done mutating this variable // Validate features are listed properly. for (feature, fvs) in &map { FeatureName::new(feature)?; for fv in fvs { // Find data for the referenced dependency... let dep_data = dep_map.get(&fv.feature_or_dep_name()); let is_any_dep = dep_data.is_some(); let is_optional_dep = dep_data.is_some_and(|&o| o); match fv { Feature(f) => { if !features.contains_key(f) { if !is_any_dep { let closest = closest_msg(f, features.keys(), |k| k, "feature"); bail!( "feature `{feature}` includes `{fv}` which is neither a dependency \ nor another feature{closest}" ); } if is_optional_dep { if !map.contains_key(f) { bail!( "feature `{feature}` includes `{fv}`, but `{f}` is an \ optional dependency without an implicit feature\n\ Use `dep:{f}` to enable the dependency." ); } } else { bail!( "feature `{feature}` includes `{fv}`, but `{f}` is not an optional dependency\n\ A non-optional dependency of the same name is defined; \ consider adding `optional = true` to its definition." ); } } } Dep { dep_name } => { if !is_any_dep { bail!( "feature `{feature}` includes `{fv}`, but `{dep_name}` is not listed as a dependency" ); } if !is_optional_dep { bail!( "feature `{feature}` includes `{fv}`, but `{dep_name}` is not an optional dependency\n\ A non-optional dependency of the same name is defined; \ consider adding `optional = true` to its definition." ); } } DepFeature { dep_name, dep_feature, weak, } => { // Early check for some unlikely syntax. if dep_feature.contains('/') { bail!( "multiple slashes in feature `{fv}` (included by feature `{feature}`) are not allowed" ); } // dep: cannot be combined with / if let Some(stripped_dep) = dep_name.strip_prefix("dep:") { let has_other_dep = explicitly_listed.contains(stripped_dep); let is_optional = dep_map.get(stripped_dep).is_some_and(|&o| o); let extra_help = if *weak || has_other_dep || !is_optional { // In this case, the user should just remove dep:. // Note that "hiding" an optional dependency // wouldn't work with just a single `dep:foo?/bar` // because there would not be any way to enable // `foo`. String::new() } else { format!( "\nIf the intent is to avoid creating an implicit feature \ `{stripped_dep}` for an optional dependency, \ then consider replacing this with two values:\n \ \"dep:{stripped_dep}\", \"{stripped_dep}/{dep_feature}\"" ) }; bail!( "feature `{feature}` includes `{fv}` with both `dep:` and `/`\n\ To fix this, remove the `dep:` prefix.{extra_help}" ) } // Validation of the feature name will be performed in the resolver. if !is_any_dep { bail!(MissingDependencyError { feature: *feature, feature_value: (*fv).clone(), dep_name: *dep_name, weak_optional: *weak, }) } if *weak && !is_optional_dep { bail!( "feature `{feature}` includes `{fv}` with a `?`, but `{dep_name}` is not an optional dependency\n\ A non-optional dependency of the same name is defined; \ consider removing the `?` or changing the dependency to be optional" ); } } } } } // Make sure every optional dep is mentioned at least once. let used: HashSet<_> = map .values() .flatten() .filter_map(|fv| match fv { Dep { dep_name } | DepFeature { dep_name, .. } => Some(dep_name), _ => None, }) .collect(); if let Some((dep, _)) = dep_map .iter() .find(|&(dep, &is_optional)| is_optional && !used.contains(dep)) { bail!( "optional dependency `{dep}` is not included in any feature\n\ Make sure that `dep:{dep}` is included in one of features in the [features] table." ); } Ok(map) } /// `FeatureValue` represents the types of dependencies a feature can have. #[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] pub enum FeatureValue { /// A feature enabling another feature. Feature(InternedString), /// A feature enabling a dependency with `dep:dep_name` syntax. Dep { dep_name: InternedString }, /// A feature enabling a feature on a dependency with `crate_name/feat_name` syntax. DepFeature { dep_name: InternedString, dep_feature: InternedString, /// If `true`, indicates the `?` syntax is used, which means this will /// not automatically enable the dependency unless the dependency is /// activated through some other means. weak: bool, }, } impl FeatureValue { pub fn new(feature: InternedString) -> FeatureValue { match feature.split_once('/') { Some((dep, dep_feat)) => { let dep_name = dep.strip_suffix('?'); FeatureValue::DepFeature { dep_name: dep_name.unwrap_or(dep).into(), dep_feature: dep_feat.into(), weak: dep_name.is_some(), } } None => { if let Some(dep_name) = feature.strip_prefix("dep:") { FeatureValue::Dep { dep_name: dep_name.into(), } } else { FeatureValue::Feature(feature) } } } } /// Returns the name of the dependency if and only if it was explicitly named with the `dep:` syntax. fn explicit_dep_name(&self) -> Option { match self { FeatureValue::Dep { dep_name, .. } => Some(*dep_name), _ => None, } } fn feature_or_dep_name(&self) -> InternedString { match self { FeatureValue::Feature(dep_name) | FeatureValue::Dep { dep_name, .. } | FeatureValue::DepFeature { dep_name, .. } => *dep_name, } } } impl fmt::Display for FeatureValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use self::FeatureValue::*; match self { Feature(feat) => write!(f, "{feat}"), Dep { dep_name } => write!(f, "dep:{dep_name}"), DepFeature { dep_name, dep_feature, weak, } => { let weak = if *weak { "?" } else { "" }; write!(f, "{dep_name}{weak}/{dep_feature}") } } } } pub type FeatureMap = BTreeMap>;