]> git.proxmox.com Git - cargo.git/blame - src/cargo/core/summary.rs
Document lib before bin.
[cargo.git] / src / cargo / core / summary.rs
CommitLineData
04ddd4d0 1use crate::core::{Dependency, PackageId, SourceId};
7f73a6c7 2use crate::util::interning::InternedString;
b731190d 3use crate::util::{CargoResult, Config};
bcfdf9fb
EH
4use anyhow::bail;
5use semver::Version;
6use std::collections::{BTreeMap, HashMap, HashSet};
7use std::fmt;
8use std::hash::{Hash, Hasher};
9use std::mem;
10use 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 17pub struct Summary {
0d17d6c4
AC
18 inner: Rc<Inner>,
19}
20
21#[derive(Debug, Clone)]
22struct 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
32impl 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
168impl 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
174impl Eq for Summary {}
175
176impl 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).
188fn 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 380pub 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
396impl 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
431impl 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 449pub type FeatureMap = BTreeMap<InternedString, Vec<FeatureValue>>;
b731190d
EH
450
451fn 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}