]> git.proxmox.com Git - cargo.git/blob - src/cargo/core/summary.rs
Stabilize namespaced and weak dependency features.
[cargo.git] / src / cargo / core / summary.rs
1 use crate::core::{Dependency, PackageId, SourceId};
2 use crate::util::interning::InternedString;
3 use crate::util::{CargoResult, Config};
4 use anyhow::bail;
5 use semver::Version;
6 use std::collections::{BTreeMap, HashMap, HashSet};
7 use std::fmt;
8 use std::hash::{Hash, Hasher};
9 use std::mem;
10 use std::rc::Rc;
11
12 /// Subset of a `Manifest`. Contains only the most important information about
13 /// a package.
14 ///
15 /// Summaries are cloned, and should not be mutated after creation
16 #[derive(Debug, Clone)]
17 pub struct Summary {
18 inner: Rc<Inner>,
19 }
20
21 #[derive(Debug, Clone)]
22 struct Inner {
23 package_id: PackageId,
24 dependencies: Vec<Dependency>,
25 features: Rc<FeatureMap>,
26 checksum: Option<String>,
27 links: Option<InternedString>,
28 }
29
30 impl Summary {
31 pub fn new(
32 config: &Config,
33 pkg_id: PackageId,
34 dependencies: Vec<Dependency>,
35 features: &BTreeMap<InternedString, Vec<InternedString>>,
36 links: Option<impl Into<InternedString>>,
37 ) -> CargoResult<Summary> {
38 // ****CAUTION**** If you change anything here that may raise a new
39 // error, be sure to coordinate that change with either the index
40 // schema field or the SummariesCache version.
41 for dep in dependencies.iter() {
42 let dep_name = dep.name_in_toml();
43 if dep.is_optional() && !dep.is_transitive() {
44 bail!(
45 "dev-dependencies are not allowed to be optional: `{}`",
46 dep_name
47 )
48 }
49 }
50 let feature_map = build_feature_map(config, pkg_id, features, &dependencies)?;
51 Ok(Summary {
52 inner: Rc::new(Inner {
53 package_id: pkg_id,
54 dependencies,
55 features: Rc::new(feature_map),
56 checksum: None,
57 links: links.map(|l| l.into()),
58 }),
59 })
60 }
61
62 pub fn package_id(&self) -> PackageId {
63 self.inner.package_id
64 }
65 pub fn name(&self) -> InternedString {
66 self.package_id().name()
67 }
68 pub fn version(&self) -> &Version {
69 self.package_id().version()
70 }
71 pub fn source_id(&self) -> SourceId {
72 self.package_id().source_id()
73 }
74 pub fn dependencies(&self) -> &[Dependency] {
75 &self.inner.dependencies
76 }
77 pub fn features(&self) -> &FeatureMap {
78 &self.inner.features
79 }
80
81 pub fn checksum(&self) -> Option<&str> {
82 self.inner.checksum.as_deref()
83 }
84 pub fn links(&self) -> Option<InternedString> {
85 self.inner.links
86 }
87
88 pub fn override_id(mut self, id: PackageId) -> Summary {
89 Rc::make_mut(&mut self.inner).package_id = id;
90 self
91 }
92
93 pub fn set_checksum(&mut self, cksum: String) {
94 Rc::make_mut(&mut self.inner).checksum = Some(cksum);
95 }
96
97 pub fn map_dependencies<F>(mut self, f: F) -> Summary
98 where
99 F: FnMut(Dependency) -> Dependency,
100 {
101 {
102 let slot = &mut Rc::make_mut(&mut self.inner).dependencies;
103 *slot = mem::take(slot).into_iter().map(f).collect();
104 }
105 self
106 }
107
108 pub fn map_source(self, to_replace: SourceId, replace_with: SourceId) -> Summary {
109 let me = if self.package_id().source_id() == to_replace {
110 let new_id = self.package_id().with_source_id(replace_with);
111 self.override_id(new_id)
112 } else {
113 self
114 };
115 me.map_dependencies(|dep| dep.map_source(to_replace, replace_with))
116 }
117 }
118
119 impl PartialEq for Summary {
120 fn eq(&self, other: &Summary) -> bool {
121 self.inner.package_id == other.inner.package_id
122 }
123 }
124
125 impl Eq for Summary {}
126
127 impl Hash for Summary {
128 fn hash<H: Hasher>(&self, state: &mut H) {
129 self.inner.package_id.hash(state);
130 }
131 }
132
133 /// Checks features for errors, bailing out a CargoResult:Err if invalid,
134 /// and creates FeatureValues for each feature.
135 fn build_feature_map(
136 config: &Config,
137 pkg_id: PackageId,
138 features: &BTreeMap<InternedString, Vec<InternedString>>,
139 dependencies: &[Dependency],
140 ) -> CargoResult<FeatureMap> {
141 use self::FeatureValue::*;
142 let mut dep_map = HashMap::new();
143 for dep in dependencies.iter() {
144 dep_map
145 .entry(dep.name_in_toml())
146 .or_insert_with(Vec::new)
147 .push(dep);
148 }
149
150 let mut map: FeatureMap = features
151 .iter()
152 .map(|(feature, list)| {
153 let fvs: Vec<_> = list
154 .iter()
155 .map(|feat_value| FeatureValue::new(*feat_value))
156 .collect();
157 (*feature, fvs)
158 })
159 .collect();
160
161 // Add implicit features for optional dependencies if they weren't
162 // explicitly listed anywhere.
163 let explicitly_listed: HashSet<_> = map
164 .values()
165 .flatten()
166 .filter_map(|fv| match fv {
167 Dep { dep_name } => Some(*dep_name),
168 _ => None,
169 })
170 .collect();
171 for dep in dependencies {
172 if !dep.is_optional() {
173 continue;
174 }
175 let dep_name_in_toml = dep.name_in_toml();
176 if features.contains_key(&dep_name_in_toml) || explicitly_listed.contains(&dep_name_in_toml)
177 {
178 continue;
179 }
180 let fv = Dep {
181 dep_name: dep_name_in_toml,
182 };
183 map.insert(dep_name_in_toml, vec![fv]);
184 }
185
186 // Validate features are listed properly.
187 for (feature, fvs) in &map {
188 if feature.starts_with("dep:") {
189 bail!(
190 "feature named `{}` is not allowed to start with `dep:`",
191 feature
192 );
193 }
194 if feature.contains('/') {
195 bail!(
196 "feature named `{}` is not allowed to contain slashes",
197 feature
198 );
199 }
200 validate_feature_name(config, pkg_id, feature)?;
201 for fv in fvs {
202 // Find data for the referenced dependency...
203 let dep_data = {
204 match fv {
205 Feature(dep_name) | Dep { dep_name, .. } | DepFeature { dep_name, .. } => {
206 dep_map.get(dep_name)
207 }
208 }
209 };
210 let is_optional_dep = dep_data
211 .iter()
212 .flat_map(|d| d.iter())
213 .any(|d| d.is_optional());
214 let is_any_dep = dep_data.is_some();
215 match fv {
216 Feature(f) => {
217 if !features.contains_key(f) {
218 if !is_any_dep {
219 bail!(
220 "feature `{}` includes `{}` which is neither a dependency \
221 nor another feature",
222 feature,
223 fv
224 );
225 }
226 if is_optional_dep {
227 if !map.contains_key(f) {
228 bail!(
229 "feature `{}` includes `{}`, but `{}` is an \
230 optional dependency without an implicit feature\n\
231 Use `dep:{}` to enable the dependency.",
232 feature,
233 fv,
234 f,
235 f
236 );
237 }
238 } else {
239 bail!("feature `{}` includes `{}`, but `{}` is not an optional dependency\n\
240 A non-optional dependency of the same name is defined; \
241 consider adding `optional = true` to its definition.",
242 feature, fv, f);
243 }
244 }
245 }
246 Dep { dep_name } => {
247 if !is_any_dep {
248 bail!(
249 "feature `{}` includes `{}`, but `{}` is not listed as a dependency",
250 feature,
251 fv,
252 dep_name
253 );
254 }
255 if !is_optional_dep {
256 bail!(
257 "feature `{}` includes `{}`, but `{}` is not an optional dependency\n\
258 A non-optional dependency of the same name is defined; \
259 consider adding `optional = true` to its definition.",
260 feature,
261 fv,
262 dep_name
263 );
264 }
265 }
266 DepFeature {
267 dep_name,
268 dep_feature,
269 weak,
270 ..
271 } => {
272 // Early check for some unlikely syntax.
273 if dep_feature.contains('/') {
274 bail!(
275 "multiple slashes in feature `{}` (included by feature `{}`) are not allowed",
276 fv,
277 feature
278 );
279 }
280 // Validation of the feature name will be performed in the resolver.
281 if !is_any_dep {
282 bail!(
283 "feature `{}` includes `{}`, but `{}` is not a dependency",
284 feature,
285 fv,
286 dep_name
287 );
288 }
289 if *weak && !is_optional_dep {
290 bail!("feature `{}` includes `{}` with a `?`, but `{}` is not an optional dependency\n\
291 A non-optional dependency of the same name is defined; \
292 consider removing the `?` or changing the dependency to be optional",
293 feature, fv, dep_name);
294 }
295 }
296 }
297 }
298 }
299
300 // Make sure every optional dep is mentioned at least once.
301 let used: HashSet<_> = map
302 .values()
303 .flatten()
304 .filter_map(|fv| match fv {
305 Dep { dep_name } | DepFeature { dep_name, .. } => Some(dep_name),
306 _ => None,
307 })
308 .collect();
309 if let Some(dep) = dependencies
310 .iter()
311 .find(|dep| dep.is_optional() && !used.contains(&dep.name_in_toml()))
312 {
313 bail!(
314 "optional dependency `{}` is not included in any feature\n\
315 Make sure that `dep:{}` is included in one of features in the [features] table.",
316 dep.name_in_toml(),
317 dep.name_in_toml(),
318 );
319 }
320
321 Ok(map)
322 }
323
324 /// FeatureValue represents the types of dependencies a feature can have.
325 #[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
326 pub enum FeatureValue {
327 /// A feature enabling another feature.
328 Feature(InternedString),
329 /// A feature enabling a dependency with `dep:dep_name` syntax.
330 Dep { dep_name: InternedString },
331 /// A feature enabling a feature on a dependency with `crate_name/feat_name` syntax.
332 DepFeature {
333 dep_name: InternedString,
334 dep_feature: InternedString,
335 /// If `true`, indicates the `?` syntax is used, which means this will
336 /// not automatically enable the dependency unless the dependency is
337 /// activated through some other means.
338 weak: bool,
339 },
340 }
341
342 impl FeatureValue {
343 pub fn new(feature: InternedString) -> FeatureValue {
344 match feature.find('/') {
345 Some(pos) => {
346 let (dep, dep_feat) = feature.split_at(pos);
347 let dep_feat = &dep_feat[1..];
348 let (dep, weak) = if let Some(dep) = dep.strip_suffix('?') {
349 (dep, true)
350 } else {
351 (dep, false)
352 };
353 FeatureValue::DepFeature {
354 dep_name: InternedString::new(dep),
355 dep_feature: InternedString::new(dep_feat),
356 weak,
357 }
358 }
359 None => {
360 if let Some(dep_name) = feature.strip_prefix("dep:") {
361 FeatureValue::Dep {
362 dep_name: InternedString::new(dep_name),
363 }
364 } else {
365 FeatureValue::Feature(feature)
366 }
367 }
368 }
369 }
370
371 /// Returns `true` if this feature explicitly used `dep:` syntax.
372 pub fn has_dep_prefix(&self) -> bool {
373 matches!(self, FeatureValue::Dep { .. })
374 }
375 }
376
377 impl fmt::Display for FeatureValue {
378 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
379 use self::FeatureValue::*;
380 match self {
381 Feature(feat) => write!(f, "{}", feat),
382 Dep { dep_name } => write!(f, "dep:{}", dep_name),
383 DepFeature {
384 dep_name,
385 dep_feature,
386 weak,
387 } => {
388 let weak = if *weak { "?" } else { "" };
389 write!(f, "{}{}/{}", dep_name, weak, dep_feature)
390 }
391 }
392 }
393 }
394
395 pub type FeatureMap = BTreeMap<InternedString, Vec<FeatureValue>>;
396
397 fn validate_feature_name(config: &Config, pkg_id: PackageId, name: &str) -> CargoResult<()> {
398 let mut chars = name.chars();
399 const FUTURE: &str = "This was previously accepted but is being phased out; \
400 it will become a hard error in a future release.\n\
401 For more information, see issue #8813 <https://github.com/rust-lang/cargo/issues/8813>, \
402 and please leave a comment if this will be a problem for your project.";
403 if let Some(ch) = chars.next() {
404 if !(unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_' || ch.is_digit(10)) {
405 config.shell().warn(&format!(
406 "invalid character `{}` in feature `{}` in package {}, \
407 the first character must be a Unicode XID start character or digit \
408 (most letters or `_` or `0` to `9`)\n\
409 {}",
410 ch, name, pkg_id, FUTURE
411 ))?;
412 }
413 }
414 for ch in chars {
415 if !(unicode_xid::UnicodeXID::is_xid_continue(ch) || ch == '-' || ch == '+' || ch == '.') {
416 config.shell().warn(&format!(
417 "invalid character `{}` in feature `{}` in package {}, \
418 characters must be Unicode XID characters, `+`, or `.` \
419 (numbers, `+`, `-`, `_`, `.`, or most letters)\n\
420 {}",
421 ch, name, pkg_id, FUTURE
422 ))?;
423 }
424 }
425 Ok(())
426 }