use std::borrow::Borrow;
use std::collections::{BTreeMap, HashMap};
use std::fmt::Display;
use std::hash::{Hash, Hasher};
use std::mem;
use std::rc::Rc;
use serde::{Serialize, Serializer};
use crate::core::interning::InternedString;
use crate::core::{Dependency, PackageId, SourceId};
use semver::Version;
use crate::util::CargoResult;
#[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>,
namespaced_features: bool,
}
impl Summary {
pub fn new<K>(
pkg_id: PackageId,
dependencies: Vec<Dependency>,
features: &BTreeMap<K, Vec<impl AsRef<str>>>,
links: Option<impl Into<InternedString>>,
namespaced_features: bool,
) -> CargoResult<Summary>
where
K: Borrow<str> + Ord + Display,
{
for dep in dependencies.iter() {
let feature = dep.name_in_toml();
if !namespaced_features && features.get(&*feature).is_some() {
failure::bail!(
"Features and dependencies cannot have the \
same name: `{}`",
feature
)
}
if dep.is_optional() && !dep.is_transitive() {
failure::bail!(
"Dev-dependencies are not allowed to be optional: `{}`",
feature
)
}
}
let feature_map = build_feature_map(features, &dependencies, namespaced_features)?;
Ok(Summary {
inner: Rc::new(Inner {
package_id: pkg_id,
dependencies,
features: Rc::new(feature_map),
checksum: None,
links: links.map(|l| l.into()),
namespaced_features,
}),
})
}
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_ref().map(|s| &s[..])
}
pub fn links(&self) -> Option<InternedString> {
self.inner.links
}
pub fn namespaced_features(&self) -> bool {
self.inner.namespaced_features
}
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;
let deps = mem::replace(slot, Vec::new());
*slot = deps.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);
}
}
fn build_feature_map<K>(
features: &BTreeMap<K, Vec<impl AsRef<str>>>,
dependencies: &[Dependency],
namespaced: bool,
) -> CargoResult<FeatureMap>
where
K: Borrow<str> + Ord + Display,
{
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 = BTreeMap::new();
for (feature, list) in features.iter() {
let mut dependency_found = if namespaced {
match dep_map.get(feature.borrow()) {
Some(dep_data) => {
if !dep_data.iter().any(|d| d.is_optional()) {
failure::bail!(
"Feature `{}` includes the dependency of the same name, but this is \
left implicit in the features included by this feature.\n\
Additionally, the dependency must be marked as optional to be \
included in the feature definition.\n\
Consider adding `crate:{}` to this feature's requirements \
and marking the dependency as `optional = true`",
feature,
feature
)
} else {
false
}
}
None => true,
}
} else {
true
};
let mut values = vec![];
for dep in list {
let val = FeatureValue::build(
InternedString::new(dep.as_ref()),
|fs| features.contains_key(fs.as_str()),
namespaced,
);
let dep_data = {
match val {
Feature(ref dep_name) | Crate(ref dep_name) | CrateFeature(ref dep_name, _) => {
dep_map.get(dep_name.as_str())
}
}
};
let is_optional_dep = dep_data
.iter()
.flat_map(|d| d.iter())
.any(|d| d.is_optional());
if let FeatureValue::Crate(ref dep_name) = val {
if !dependency_found && feature.borrow() == dep_name.as_str() {
dependency_found = true;
}
}
match (&val, dep_data.is_some(), is_optional_dep) {
(&Feature(feat), _, true) => {
if namespaced && !features.contains_key(&*feat) {
map.insert(feat, vec![FeatureValue::Crate(feat)]);
}
}
(&Feature(feat), dep_exists, false) => {
if namespaced && !features.contains_key(&*feat) {
if dep_exists {
failure::bail!(
"Feature `{}` includes `{}` which is not defined as a feature.\n\
A non-optional dependency of the same name is defined; consider \
adding `optional = true` to its definition",
feature,
feat
)
} else {
failure::bail!(
"Feature `{}` includes `{}` which is not defined as a feature",
feature,
feat
)
}
}
}
(&Crate(ref dep), true, false) => {
if namespaced {
failure::bail!(
"Feature `{}` includes `crate:{}` which is not an \
optional dependency.\nConsider adding \
`optional = true` to the dependency",
feature,
dep
)
} else {
failure::bail!(
"Feature `{}` depends on `{}` which is not an \
optional dependency.\nConsider adding \
`optional = true` to the dependency",
feature,
dep
)
}
}
(&Crate(ref dep), false, _) => {
if namespaced {
failure::bail!(
"Feature `{}` includes `crate:{}` which is not a known \
dependency",
feature,
dep
)
} else {
failure::bail!(
"Feature `{}` includes `{}` which is neither a dependency nor \
another feature",
feature,
dep
)
}
}
(&Crate(_), true, true) => {}
(&CrateFeature(ref dep, _), false, _) => failure::bail!(
"Feature `{}` requires a feature of `{}` which is not a \
dependency",
feature,
dep
),
(&CrateFeature(_, _), true, _) => {}
}
values.push(val);
}
if !dependency_found {
failure::bail!(
"Feature `{}` includes the optional dependency of the \
same name, but this is left implicit in the features \
included by this feature.\nConsider adding \
`crate:{}` to this feature's requirements.",
feature,
feature
)
}
map.insert(InternedString::new(feature.borrow()), values);
}
Ok(map)
}
#[derive(Clone, Debug)]
pub enum FeatureValue {
Feature(InternedString),
Crate(InternedString),
CrateFeature(InternedString, InternedString),
}
impl FeatureValue {
fn build<T>(feature: InternedString, is_feature: T, namespaced: bool) -> FeatureValue
where
T: Fn(InternedString) -> bool,
{
match (feature.find('/'), namespaced) {
(Some(pos), _) => {
let (dep, dep_feat) = feature.split_at(pos);
let dep_feat = &dep_feat[1..];
FeatureValue::CrateFeature(InternedString::new(dep), InternedString::new(dep_feat))
}
(None, true) if feature.starts_with("crate:") => {
FeatureValue::Crate(InternedString::new(&feature[6..]))
}
(None, true) => FeatureValue::Feature(feature),
(None, false) if is_feature(feature) => FeatureValue::Feature(feature),
(None, false) => FeatureValue::Crate(feature),
}
}
pub fn new(feature: InternedString, s: &Summary) -> FeatureValue {
Self::build(
feature,
|fs| s.features().contains_key(&fs),
s.namespaced_features(),
)
}
pub fn to_string(&self, s: &Summary) -> String {
use self::FeatureValue::*;
match *self {
Feature(ref f) => f.to_string(),
Crate(ref c) => {
if s.namespaced_features() {
format!("crate:{}", &c)
} else {
c.to_string()
}
}
CrateFeature(ref c, ref f) => [c.as_ref(), f.as_ref()].join("/"),
}
}
}
impl Serialize for FeatureValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
use self::FeatureValue::*;
match *self {
Feature(ref f) => serializer.serialize_str(f),
Crate(ref c) => serializer.serialize_str(c),
CrateFeature(ref c, ref f) => {
serializer.serialize_str(&[c.as_ref(), f.as_ref()].join("/"))
}
}
}
}
pub type FeatureMap = BTreeMap<InternedString, Vec<FeatureValue>>;