summaryrefslogtreecommitdiffstats
path: root/src/cargo/core/summary.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/cargo/core/summary.rs')
-rw-r--r--src/cargo/core/summary.rs456
1 files changed, 456 insertions, 0 deletions
diff --git a/src/cargo/core/summary.rs b/src/cargo/core/summary.rs
new file mode 100644
index 0000000..8a7238e
--- /dev/null
+++ b/src/cargo/core/summary.rs
@@ -0,0 +1,456 @@
+use crate::core::{Dependency, PackageId, SourceId};
+use crate::util::interning::InternedString;
+use crate::util::{CargoResult, Config};
+use anyhow::bail;
+use semver::Version;
+use std::collections::{BTreeMap, HashMap, HashSet};
+use std::fmt;
+use std::hash::{Hash, Hasher};
+use std::mem;
+use std::rc::Rc;
+
+/// 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: Rc<Inner>,
+}
+
+#[derive(Debug, Clone)]
+struct Inner {
+ package_id: PackageId,
+ dependencies: Vec<Dependency>,
+ features: Rc<FeatureMap>,
+ checksum: Option<String>,
+ links: Option<InternedString>,
+}
+
+impl Summary {
+ pub fn new(
+ config: &Config,
+ pkg_id: PackageId,
+ dependencies: Vec<Dependency>,
+ features: &BTreeMap<InternedString, Vec<InternedString>>,
+ links: Option<impl Into<InternedString>>,
+ ) -> CargoResult<Summary> {
+ // ****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(config, pkg_id, features, &dependencies)?;
+ Ok(Summary {
+ inner: Rc::new(Inner {
+ package_id: pkg_id,
+ dependencies,
+ features: Rc::new(feature_map),
+ checksum: None,
+ links: links.map(|l| l.into()),
+ }),
+ })
+ }
+
+ 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<InternedString> {
+ self.inner.links
+ }
+
+ pub fn override_id(mut self, id: PackageId) -> Summary {
+ Rc::make_mut(&mut self.inner).package_id = id;
+ self
+ }
+
+ pub fn set_checksum(&mut self, cksum: String) {
+ Rc::make_mut(&mut self.inner).checksum = Some(cksum);
+ }
+
+ pub fn map_dependencies<F>(mut self, f: F) -> Summary
+ where
+ F: FnMut(Dependency) -> Dependency,
+ {
+ {
+ let slot = &mut Rc::make_mut(&mut self.inner).dependencies;
+ *slot = mem::take(slot).into_iter().map(f).collect();
+ }
+ 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<H: Hasher>(&self, state: &mut H) {
+ self.inner.package_id.hash(state);
+ }
+}
+
+/// Checks features for errors, bailing out a CargoResult:Err if invalid,
+/// and creates FeatureValues for each feature.
+fn build_feature_map(
+ config: &Config,
+ pkg_id: PackageId,
+ features: &BTreeMap<InternedString, Vec<InternedString>>,
+ dependencies: &[Dependency],
+) -> CargoResult<FeatureMap> {
+ use self::FeatureValue::*;
+ let mut dep_map = HashMap::new();
+ for dep in dependencies.iter() {
+ dep_map
+ .entry(dep.name_in_toml())
+ .or_insert_with(Vec::new)
+ .push(dep);
+ }
+
+ 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| match fv {
+ Dep { dep_name } => Some(*dep_name),
+ _ => None,
+ })
+ .collect();
+ for dep in dependencies {
+ if !dep.is_optional() {
+ continue;
+ }
+ let dep_name_in_toml = dep.name_in_toml();
+ if features.contains_key(&dep_name_in_toml) || explicitly_listed.contains(&dep_name_in_toml)
+ {
+ continue;
+ }
+ let fv = Dep {
+ dep_name: dep_name_in_toml,
+ };
+ map.insert(dep_name_in_toml, vec![fv]);
+ }
+
+ // Validate features are listed properly.
+ for (feature, fvs) in &map {
+ if feature.starts_with("dep:") {
+ bail!(
+ "feature named `{}` is not allowed to start with `dep:`",
+ feature
+ );
+ }
+ if feature.contains('/') {
+ bail!(
+ "feature named `{}` is not allowed to contain slashes",
+ feature
+ );
+ }
+ validate_feature_name(config, pkg_id, feature)?;
+ for fv in fvs {
+ // Find data for the referenced dependency...
+ let dep_data = {
+ match fv {
+ Feature(dep_name) | Dep { dep_name, .. } | DepFeature { dep_name, .. } => {
+ dep_map.get(dep_name)
+ }
+ }
+ };
+ let is_optional_dep = dep_data
+ .iter()
+ .flat_map(|d| d.iter())
+ .any(|d| d.is_optional());
+ let is_any_dep = dep_data.is_some();
+ match fv {
+ Feature(f) => {
+ if !features.contains_key(f) {
+ if !is_any_dep {
+ bail!(
+ "feature `{}` includes `{}` which is neither a dependency \
+ nor another feature",
+ feature,
+ fv
+ );
+ }
+ if is_optional_dep {
+ if !map.contains_key(f) {
+ bail!(
+ "feature `{}` includes `{}`, but `{}` is an \
+ optional dependency without an implicit feature\n\
+ Use `dep:{}` to enable the dependency.",
+ feature,
+ fv,
+ f,
+ f
+ );
+ }
+ } else {
+ bail!("feature `{}` includes `{}`, but `{}` is not an optional dependency\n\
+ A non-optional dependency of the same name is defined; \
+ consider adding `optional = true` to its definition.",
+ feature, fv, f);
+ }
+ }
+ }
+ Dep { dep_name } => {
+ if !is_any_dep {
+ bail!(
+ "feature `{}` includes `{}`, but `{}` is not listed as a dependency",
+ feature,
+ fv,
+ dep_name
+ );
+ }
+ if !is_optional_dep {
+ bail!(
+ "feature `{}` includes `{}`, but `{}` is not an optional dependency\n\
+ A non-optional dependency of the same name is defined; \
+ consider adding `optional = true` to its definition.",
+ feature,
+ fv,
+ dep_name
+ );
+ }
+ }
+ DepFeature {
+ dep_name,
+ dep_feature,
+ weak,
+ ..
+ } => {
+ // Early check for some unlikely syntax.
+ if dep_feature.contains('/') {
+ bail!(
+ "multiple slashes in feature `{}` (included by feature `{}`) are not allowed",
+ fv,
+ feature
+ );
+ }
+
+ // 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)
+ .iter()
+ .flat_map(|d| d.iter())
+ .any(|d| d.is_optional());
+ 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!(
+ "feature `{}` includes `{}`, but `{}` is not a dependency",
+ feature,
+ fv,
+ dep_name
+ );
+ }
+ if *weak && !is_optional_dep {
+ bail!("feature `{}` includes `{}` with a `?`, but `{}` 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",
+ feature, fv, dep_name);
+ }
+ }
+ }
+ }
+ }
+
+ // 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) = dependencies
+ .iter()
+ .find(|dep| dep.is_optional() && !used.contains(&dep.name_in_toml()))
+ {
+ bail!(
+ "optional dependency `{}` is not included in any feature\n\
+ Make sure that `dep:{}` is included in one of features in the [features] table.",
+ dep.name_in_toml(),
+ dep.name_in_toml(),
+ );
+ }
+
+ 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.find('/') {
+ Some(pos) => {
+ let (dep, dep_feat) = feature.split_at(pos);
+ let dep_feat = &dep_feat[1..];
+ let (dep, weak) = if let Some(dep) = dep.strip_suffix('?') {
+ (dep, true)
+ } else {
+ (dep, false)
+ };
+ FeatureValue::DepFeature {
+ dep_name: InternedString::new(dep),
+ dep_feature: InternedString::new(dep_feat),
+ weak,
+ }
+ }
+ None => {
+ if let Some(dep_name) = feature.strip_prefix("dep:") {
+ FeatureValue::Dep {
+ dep_name: InternedString::new(dep_name),
+ }
+ } else {
+ FeatureValue::Feature(feature)
+ }
+ }
+ }
+ }
+
+ /// Returns `true` if this feature explicitly used `dep:` syntax.
+ pub fn has_dep_prefix(&self) -> bool {
+ matches!(self, FeatureValue::Dep { .. })
+ }
+}
+
+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<InternedString, Vec<FeatureValue>>;
+
+fn validate_feature_name(config: &Config, pkg_id: PackageId, name: &str) -> CargoResult<()> {
+ let mut chars = name.chars();
+ const FUTURE: &str = "This was previously accepted but is being phased out; \
+ it will become a hard error in a future release.\n\
+ For more information, see issue #8813 <https://github.com/rust-lang/cargo/issues/8813>, \
+ and please leave a comment if this will be a problem for your project.";
+ if let Some(ch) = chars.next() {
+ if !(unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_' || ch.is_digit(10)) {
+ config.shell().warn(&format!(
+ "invalid character `{}` in feature `{}` in package {}, \
+ the first character must be a Unicode XID start character or digit \
+ (most letters or `_` or `0` to `9`)\n\
+ {}",
+ ch, name, pkg_id, FUTURE
+ ))?;
+ }
+ }
+ for ch in chars {
+ if !(unicode_xid::UnicodeXID::is_xid_continue(ch) || ch == '-' || ch == '+' || ch == '.') {
+ config.shell().warn(&format!(
+ "invalid character `{}` in feature `{}` in package {}, \
+ characters must be Unicode XID characters, `+`, or `.` \
+ (numbers, `+`, `-`, `_`, `.`, or most letters)\n\
+ {}",
+ ch, name, pkg_id, FUTURE
+ ))?;
+ }
+ }
+ Ok(())
+}