use crate::core::{Dependency, PackageId, SourceId};
use crate::util::interning::InternedString;
use crate::util::CargoResult;
use anyhow::bail;
use cargo_util_schemas::manifest::FeatureName;
use cargo_util_schemas::manifest::RustVersion;
use semver::Version;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt;
use std::hash::{Hash, Hasher};
use std::mem;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct Summary {
inner: Arc<Inner>,
}
#[derive(Debug, Clone)]
struct Inner {
package_id: PackageId,
dependencies: Vec<Dependency>,
features: Arc<FeatureMap>,
checksum: Option<String>,
links: Option<InternedString>,
rust_version: Option<RustVersion>,
}
#[derive(Debug)]
pub struct MissingDependencyError {
pub dep_name: InternedString,
pub feature: InternedString,
pub feature_value: FeatureValue,
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<Dependency>,
features: &BTreeMap<InternedString, Vec<InternedString>>,
links: Option<impl Into<InternedString>>,
rust_version: Option<RustVersion>,
) -> CargoResult<Summary> {
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,
}),
})
}
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 rust_version(&self) -> Option<&RustVersion> {
self.inner.rust_version.as_ref()
}
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 map_dependencies<F>(self, mut f: F) -> Summary
where
F: FnMut(Dependency) -> Dependency,
{
self.try_map_dependencies(|dep| Ok(f(dep))).unwrap()
}
pub fn try_map_dependencies<F>(mut self, f: F) -> CargoResult<Summary>
where
F: FnMut(Dependency) -> CargoResult<Dependency>,
{
{
let slot = &mut Arc::make_mut(&mut self.inner).dependencies;
*slot = mem::take(slot)
.into_iter()
.map(f)
.collect::<CargoResult<_>>()?;
}
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<H: Hasher>(&self, state: &mut H) {
self.inner.package_id.hash(state);
}
}
const _: fn() = || {
fn is_sync<T: Sync>() {}
is_sync::<Summary>();
};
fn build_feature_map(
features: &BTreeMap<InternedString, Vec<InternedString>>,
dependencies: &[Dependency],
) -> CargoResult<FeatureMap> {
use self::FeatureValue::*;
let mut dep_map: HashMap<InternedString, bool> = HashMap::new();
for dep in dependencies.iter() {
*dep_map.entry(dep.name_in_toml()).or_insert(false) |= dep.is_optional();
}
let dep_map = dep_map; 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();
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; for (feature, fvs) in &map {
FeatureName::new(feature)?;
for fv in fvs {
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 {
bail!(
"feature `{feature}` includes `{fv}` which is neither a dependency \
nor another feature"
);
}
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,
} => {
if dep_feature.contains('/') {
bail!("multiple slashes in feature `{fv}` (included by feature `{feature}`) are not allowed");
}
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 {
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}"
)
}
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"
);
}
}
}
}
}
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)
}
#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub enum FeatureValue {
Feature(InternedString),
Dep { dep_name: InternedString },
DepFeature {
dep_name: InternedString,
dep_feature: InternedString,
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: InternedString::new(dep_name.unwrap_or(dep)),
dep_feature: InternedString::new(dep_feat),
weak: dep_name.is_some(),
}
}
None => {
if let Some(dep_name) = feature.strip_prefix("dep:") {
FeatureValue::Dep {
dep_name: InternedString::new(dep_name),
}
} else {
FeatureValue::Feature(feature)
}
}
}
}
fn explicit_dep_name(&self) -> Option<InternedString> {
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<InternedString, Vec<FeatureValue>>;