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