]> git.proxmox.com Git - cargo.git/blob - src/cargo/util/toml/mod.rs
Fix typo
[cargo.git] / src / cargo / util / toml / mod.rs
1 use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
2 use std::fmt;
3 use std::marker::PhantomData;
4 use std::path::{Path, PathBuf};
5 use std::rc::Rc;
6 use std::str;
7
8 use anyhow::{anyhow, bail, Context as _};
9 use cargo_platform::Platform;
10 use cargo_util::paths;
11 use log::{debug, trace};
12 use semver::{self, VersionReq};
13 use serde::de;
14 use serde::ser;
15 use serde::{Deserialize, Serialize};
16 use url::Url;
17
18 use crate::core::compiler::{CompileKind, CompileTarget};
19 use crate::core::dependency::DepKind;
20 use crate::core::manifest::{ManifestMetadata, TargetSourcePath, Warnings};
21 use crate::core::resolver::ResolveBehavior;
22 use crate::core::{Dependency, Manifest, PackageId, Summary, Target};
23 use crate::core::{Edition, EitherManifest, Feature, Features, VirtualManifest, Workspace};
24 use crate::core::{GitReference, PackageIdSpec, SourceId, WorkspaceConfig, WorkspaceRootConfig};
25 use crate::sources::{CRATES_IO_INDEX, CRATES_IO_REGISTRY};
26 use crate::util::errors::{CargoResult, ManifestError};
27 use crate::util::interning::InternedString;
28 use crate::util::{
29 self, config::ConfigRelativePath, validate_package_name, Config, IntoUrl, VersionReqExt,
30 };
31
32 mod targets;
33 use self::targets::targets;
34
35 /// Loads a `Cargo.toml` from a file on disk.
36 ///
37 /// This could result in a real or virtual manifest being returned.
38 ///
39 /// A list of nested paths is also returned, one for each path dependency
40 /// within the manifest. For virtual manifests, these paths can only
41 /// come from patched or replaced dependencies. These paths are not
42 /// canonicalized.
43 pub fn read_manifest(
44 path: &Path,
45 source_id: SourceId,
46 config: &Config,
47 ) -> Result<(EitherManifest, Vec<PathBuf>), ManifestError> {
48 trace!(
49 "read_manifest; path={}; source-id={}",
50 path.display(),
51 source_id
52 );
53 let contents = paths::read(path).map_err(|err| ManifestError::new(err, path.into()))?;
54
55 do_read_manifest(&contents, path, source_id, config)
56 .with_context(|| format!("failed to parse manifest at `{}`", path.display()))
57 .map_err(|err| ManifestError::new(err, path.into()))
58 }
59
60 fn do_read_manifest(
61 contents: &str,
62 manifest_file: &Path,
63 source_id: SourceId,
64 config: &Config,
65 ) -> CargoResult<(EitherManifest, Vec<PathBuf>)> {
66 let package_root = manifest_file.parent().unwrap();
67
68 let toml = {
69 let pretty_filename = manifest_file
70 .strip_prefix(config.cwd())
71 .unwrap_or(manifest_file);
72 parse(contents, pretty_filename, config)?
73 };
74
75 // Provide a helpful error message for a common user error.
76 if let Some(package) = toml.get("package").or_else(|| toml.get("project")) {
77 if let Some(feats) = package.get("cargo-features") {
78 bail!(
79 "cargo-features = {} was found in the wrong location, it \
80 should be set at the top of Cargo.toml before any tables",
81 toml::to_string(feats).unwrap()
82 );
83 }
84 }
85
86 let mut unused = BTreeSet::new();
87 let manifest: TomlManifest = serde_ignored::deserialize(toml, |path| {
88 let mut key = String::new();
89 stringify(&mut key, &path);
90 unused.insert(key);
91 })?;
92 let add_unused = |warnings: &mut Warnings| {
93 for key in unused {
94 warnings.add_warning(format!("unused manifest key: {}", key));
95 if key == "profiles.debug" {
96 warnings.add_warning("use `[profile.dev]` to configure debug builds".to_string());
97 }
98 }
99 };
100
101 let manifest = Rc::new(manifest);
102 return if manifest.project.is_some() || manifest.package.is_some() {
103 let (mut manifest, paths) =
104 TomlManifest::to_real_manifest(&manifest, source_id, package_root, config)?;
105 add_unused(manifest.warnings_mut());
106 if manifest.targets().iter().all(|t| t.is_custom_build()) {
107 bail!(
108 "no targets specified in the manifest\n\
109 either src/lib.rs, src/main.rs, a [lib] section, or \
110 [[bin]] section must be present"
111 )
112 }
113 Ok((EitherManifest::Real(manifest), paths))
114 } else {
115 let (mut m, paths) =
116 TomlManifest::to_virtual_manifest(&manifest, source_id, package_root, config)?;
117 add_unused(m.warnings_mut());
118 Ok((EitherManifest::Virtual(m), paths))
119 };
120
121 fn stringify(dst: &mut String, path: &serde_ignored::Path<'_>) {
122 use serde_ignored::Path;
123
124 match *path {
125 Path::Root => {}
126 Path::Seq { parent, index } => {
127 stringify(dst, parent);
128 if !dst.is_empty() {
129 dst.push('.');
130 }
131 dst.push_str(&index.to_string());
132 }
133 Path::Map { parent, ref key } => {
134 stringify(dst, parent);
135 if !dst.is_empty() {
136 dst.push('.');
137 }
138 dst.push_str(key);
139 }
140 Path::Some { parent }
141 | Path::NewtypeVariant { parent }
142 | Path::NewtypeStruct { parent } => stringify(dst, parent),
143 }
144 }
145 }
146
147 /// Attempts to parse a string into a [`toml::Value`]. This is not specific to any
148 /// particular kind of TOML file.
149 ///
150 /// The purpose of this wrapper is to detect invalid TOML which was previously
151 /// accepted and display a warning to the user in that case. The `file` and `config`
152 /// parameters are only used by this fallback path.
153 pub fn parse(toml: &str, file: &Path, config: &Config) -> CargoResult<toml::Value> {
154 let first_error = match toml.parse() {
155 Ok(ret) => return Ok(ret),
156 Err(e) => e,
157 };
158
159 let mut second_parser = toml::de::Deserializer::new(toml);
160 second_parser.set_require_newline_after_table(false);
161 if let Ok(ret) = toml::Value::deserialize(&mut second_parser) {
162 let msg = format!(
163 "\
164 TOML file found which contains invalid syntax and will soon not parse
165 at `{}`.
166
167 The TOML spec requires newlines after table definitions (e.g., `[a] b = 1` is
168 invalid), but this file has a table header which does not have a newline after
169 it. A newline needs to be added and this warning will soon become a hard error
170 in the future.",
171 file.display()
172 );
173 config.shell().warn(&msg)?;
174 return Ok(ret);
175 }
176
177 let mut third_parser = toml::de::Deserializer::new(toml);
178 third_parser.set_allow_duplicate_after_longer_table(true);
179 if let Ok(ret) = toml::Value::deserialize(&mut third_parser) {
180 let msg = format!(
181 "\
182 TOML file found which contains invalid syntax and will soon not parse
183 at `{}`.
184
185 The TOML spec requires that each table header is defined at most once, but
186 historical versions of Cargo have erroneously accepted this file. The table
187 definitions will need to be merged together with one table header to proceed,
188 and this will become a hard error in the future.",
189 file.display()
190 );
191 config.shell().warn(&msg)?;
192 return Ok(ret);
193 }
194
195 let first_error = anyhow::Error::from(first_error);
196 Err(first_error.context("could not parse input as TOML"))
197 }
198
199 type TomlLibTarget = TomlTarget;
200 type TomlBinTarget = TomlTarget;
201 type TomlExampleTarget = TomlTarget;
202 type TomlTestTarget = TomlTarget;
203 type TomlBenchTarget = TomlTarget;
204
205 #[derive(Clone, Debug, Serialize)]
206 #[serde(untagged)]
207 pub enum TomlDependency<P = String> {
208 /// In the simple format, only a version is specified, eg.
209 /// `package = "<version>"`
210 Simple(String),
211 /// The simple format is equivalent to a detailed dependency
212 /// specifying only a version, eg.
213 /// `package = { version = "<version>" }`
214 Detailed(DetailedTomlDependency<P>),
215 }
216
217 impl<'de, P: Deserialize<'de>> de::Deserialize<'de> for TomlDependency<P> {
218 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
219 where
220 D: de::Deserializer<'de>,
221 {
222 struct TomlDependencyVisitor<P>(PhantomData<P>);
223
224 impl<'de, P: Deserialize<'de>> de::Visitor<'de> for TomlDependencyVisitor<P> {
225 type Value = TomlDependency<P>;
226
227 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
228 formatter.write_str(
229 "a version string like \"0.9.8\" or a \
230 detailed dependency like { version = \"0.9.8\" }",
231 )
232 }
233
234 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
235 where
236 E: de::Error,
237 {
238 Ok(TomlDependency::Simple(s.to_owned()))
239 }
240
241 fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error>
242 where
243 V: de::MapAccess<'de>,
244 {
245 let mvd = de::value::MapAccessDeserializer::new(map);
246 DetailedTomlDependency::deserialize(mvd).map(TomlDependency::Detailed)
247 }
248 }
249
250 deserializer.deserialize_any(TomlDependencyVisitor(PhantomData))
251 }
252 }
253
254 pub trait ResolveToPath {
255 fn resolve(&self, config: &Config) -> PathBuf;
256 }
257
258 impl ResolveToPath for String {
259 fn resolve(&self, _: &Config) -> PathBuf {
260 self.into()
261 }
262 }
263
264 impl ResolveToPath for ConfigRelativePath {
265 fn resolve(&self, c: &Config) -> PathBuf {
266 self.resolve_path(c)
267 }
268 }
269
270 #[derive(Deserialize, Serialize, Clone, Debug)]
271 #[serde(rename_all = "kebab-case")]
272 pub struct DetailedTomlDependency<P = String> {
273 version: Option<String>,
274 registry: Option<String>,
275 /// The URL of the `registry` field.
276 /// This is an internal implementation detail. When Cargo creates a
277 /// package, it replaces `registry` with `registry-index` so that the
278 /// manifest contains the correct URL. All users won't have the same
279 /// registry names configured, so Cargo can't rely on just the name for
280 /// crates published by other users.
281 registry_index: Option<String>,
282 // `path` is relative to the file it appears in. If that's a `Cargo.toml`, it'll be relative to
283 // that TOML file, and if it's a `.cargo/config` file, it'll be relative to that file.
284 path: Option<P>,
285 git: Option<String>,
286 branch: Option<String>,
287 tag: Option<String>,
288 rev: Option<String>,
289 features: Option<Vec<String>>,
290 optional: Option<bool>,
291 default_features: Option<bool>,
292 #[serde(rename = "default_features")]
293 default_features2: Option<bool>,
294 package: Option<String>,
295 public: Option<bool>,
296 }
297
298 // Explicit implementation so we avoid pulling in P: Default
299 impl<P> Default for DetailedTomlDependency<P> {
300 fn default() -> Self {
301 Self {
302 version: Default::default(),
303 registry: Default::default(),
304 registry_index: Default::default(),
305 path: Default::default(),
306 git: Default::default(),
307 branch: Default::default(),
308 tag: Default::default(),
309 rev: Default::default(),
310 features: Default::default(),
311 optional: Default::default(),
312 default_features: Default::default(),
313 default_features2: Default::default(),
314 package: Default::default(),
315 public: Default::default(),
316 }
317 }
318 }
319
320 /// This type is used to deserialize `Cargo.toml` files.
321 #[derive(Debug, Deserialize, Serialize)]
322 #[serde(rename_all = "kebab-case")]
323 pub struct TomlManifest {
324 cargo_features: Option<Vec<String>>,
325 package: Option<Box<TomlProject>>,
326 project: Option<Box<TomlProject>>,
327 profile: Option<TomlProfiles>,
328 lib: Option<TomlLibTarget>,
329 bin: Option<Vec<TomlBinTarget>>,
330 example: Option<Vec<TomlExampleTarget>>,
331 test: Option<Vec<TomlTestTarget>>,
332 bench: Option<Vec<TomlTestTarget>>,
333 dependencies: Option<BTreeMap<String, TomlDependency>>,
334 dev_dependencies: Option<BTreeMap<String, TomlDependency>>,
335 #[serde(rename = "dev_dependencies")]
336 dev_dependencies2: Option<BTreeMap<String, TomlDependency>>,
337 build_dependencies: Option<BTreeMap<String, TomlDependency>>,
338 #[serde(rename = "build_dependencies")]
339 build_dependencies2: Option<BTreeMap<String, TomlDependency>>,
340 features: Option<BTreeMap<InternedString, Vec<InternedString>>>,
341 target: Option<BTreeMap<String, TomlPlatform>>,
342 replace: Option<BTreeMap<String, TomlDependency>>,
343 patch: Option<BTreeMap<String, BTreeMap<String, TomlDependency>>>,
344 workspace: Option<TomlWorkspace>,
345 badges: Option<BTreeMap<String, BTreeMap<String, String>>>,
346 }
347
348 #[derive(Deserialize, Serialize, Clone, Debug, Default)]
349 pub struct TomlProfiles(BTreeMap<InternedString, TomlProfile>);
350
351 impl TomlProfiles {
352 pub fn get_all(&self) -> &BTreeMap<InternedString, TomlProfile> {
353 &self.0
354 }
355
356 pub fn get(&self, name: &str) -> Option<&TomlProfile> {
357 self.0.get(name)
358 }
359
360 pub fn validate(&self, features: &Features, warnings: &mut Vec<String>) -> CargoResult<()> {
361 for (name, profile) in &self.0 {
362 profile.validate(name, features, warnings)?;
363 }
364 Ok(())
365 }
366 }
367
368 #[derive(Clone, Debug, Eq, PartialEq)]
369 pub struct TomlOptLevel(pub String);
370
371 impl<'de> de::Deserialize<'de> for TomlOptLevel {
372 fn deserialize<D>(d: D) -> Result<TomlOptLevel, D::Error>
373 where
374 D: de::Deserializer<'de>,
375 {
376 struct Visitor;
377
378 impl<'de> de::Visitor<'de> for Visitor {
379 type Value = TomlOptLevel;
380
381 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
382 formatter.write_str("an optimization level")
383 }
384
385 fn visit_i64<E>(self, value: i64) -> Result<TomlOptLevel, E>
386 where
387 E: de::Error,
388 {
389 Ok(TomlOptLevel(value.to_string()))
390 }
391
392 fn visit_str<E>(self, value: &str) -> Result<TomlOptLevel, E>
393 where
394 E: de::Error,
395 {
396 if value == "s" || value == "z" {
397 Ok(TomlOptLevel(value.to_string()))
398 } else {
399 Err(E::custom(format!(
400 "must be `0`, `1`, `2`, `3`, `s` or `z`, \
401 but found the string: \"{}\"",
402 value
403 )))
404 }
405 }
406 }
407
408 d.deserialize_any(Visitor)
409 }
410 }
411
412 impl ser::Serialize for TomlOptLevel {
413 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
414 where
415 S: ser::Serializer,
416 {
417 match self.0.parse::<u32>() {
418 Ok(n) => n.serialize(serializer),
419 Err(_) => self.0.serialize(serializer),
420 }
421 }
422 }
423
424 #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
425 #[serde(untagged, expecting = "expected a boolean or an integer")]
426 pub enum U32OrBool {
427 U32(u32),
428 Bool(bool),
429 }
430
431 #[derive(Deserialize, Serialize, Clone, Debug, Default, Eq, PartialEq)]
432 #[serde(default, rename_all = "kebab-case")]
433 pub struct TomlProfile {
434 pub opt_level: Option<TomlOptLevel>,
435 pub lto: Option<StringOrBool>,
436 pub codegen_units: Option<u32>,
437 pub debug: Option<U32OrBool>,
438 pub split_debuginfo: Option<String>,
439 pub debug_assertions: Option<bool>,
440 pub rpath: Option<bool>,
441 pub panic: Option<String>,
442 pub overflow_checks: Option<bool>,
443 pub incremental: Option<bool>,
444 pub package: Option<BTreeMap<ProfilePackageSpec, TomlProfile>>,
445 pub build_override: Option<Box<TomlProfile>>,
446 pub dir_name: Option<InternedString>,
447 pub inherits: Option<InternedString>,
448 pub strip: Option<StringOrBool>,
449 }
450
451 #[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
452 pub enum ProfilePackageSpec {
453 Spec(PackageIdSpec),
454 All,
455 }
456
457 impl ser::Serialize for ProfilePackageSpec {
458 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
459 where
460 S: ser::Serializer,
461 {
462 match *self {
463 ProfilePackageSpec::Spec(ref spec) => spec.serialize(s),
464 ProfilePackageSpec::All => "*".serialize(s),
465 }
466 }
467 }
468
469 impl<'de> de::Deserialize<'de> for ProfilePackageSpec {
470 fn deserialize<D>(d: D) -> Result<ProfilePackageSpec, D::Error>
471 where
472 D: de::Deserializer<'de>,
473 {
474 let string = String::deserialize(d)?;
475 if string == "*" {
476 Ok(ProfilePackageSpec::All)
477 } else {
478 PackageIdSpec::parse(&string)
479 .map_err(de::Error::custom)
480 .map(ProfilePackageSpec::Spec)
481 }
482 }
483 }
484
485 impl TomlProfile {
486 pub fn validate(
487 &self,
488 name: &str,
489 features: &Features,
490 warnings: &mut Vec<String>,
491 ) -> CargoResult<()> {
492 if name == "debug" {
493 warnings.push("use `[profile.dev]` to configure debug builds".to_string());
494 }
495
496 if let Some(ref profile) = self.build_override {
497 features.require(Feature::profile_overrides())?;
498 profile.validate_override("build-override")?;
499 }
500 if let Some(ref packages) = self.package {
501 features.require(Feature::profile_overrides())?;
502 for profile in packages.values() {
503 profile.validate_override("package")?;
504 }
505 }
506
507 // Feature gate definition of named profiles
508 match name {
509 "dev" | "release" | "bench" | "test" | "doc" => {}
510 _ => {
511 features.require(Feature::named_profiles())?;
512 }
513 }
514
515 // Profile name validation
516 Self::validate_name(name, "profile name")?;
517
518 // Feature gate on uses of keys related to named profiles
519 if self.inherits.is_some() {
520 features.require(Feature::named_profiles())?;
521 }
522
523 if self.dir_name.is_some() {
524 features.require(Feature::named_profiles())?;
525 }
526
527 // `dir-name` validation
528 match &self.dir_name {
529 None => {}
530 Some(dir_name) => {
531 Self::validate_name(dir_name, "dir-name")?;
532 }
533 }
534
535 // `inherits` validation
536 match &self.inherits {
537 None => {}
538 Some(inherits) => {
539 Self::validate_name(inherits, "inherits")?;
540 }
541 }
542
543 match name {
544 "doc" => {
545 warnings.push("profile `doc` is deprecated and has no effect".to_string());
546 }
547 "test" | "bench" => {
548 if self.panic.is_some() {
549 warnings.push(format!("`panic` setting is ignored for `{}` profile", name))
550 }
551 }
552 _ => {}
553 }
554
555 if let Some(panic) = &self.panic {
556 if panic != "unwind" && panic != "abort" {
557 bail!(
558 "`panic` setting of `{}` is not a valid setting, \
559 must be `unwind` or `abort`",
560 panic
561 );
562 }
563 }
564
565 if self.strip.is_some() {
566 features.require(Feature::strip())?;
567 }
568 Ok(())
569 }
570
571 /// Validate dir-names and profile names according to RFC 2678.
572 pub fn validate_name(name: &str, what: &str) -> CargoResult<()> {
573 if let Some(ch) = name
574 .chars()
575 .find(|ch| !ch.is_alphanumeric() && *ch != '_' && *ch != '-')
576 {
577 bail!("Invalid character `{}` in {}: `{}`", ch, what, name);
578 }
579
580 match name {
581 "package" | "build" => {
582 bail!("Invalid {}: `{}`", what, name);
583 }
584 "debug" if what == "profile" => {
585 if what == "profile name" {
586 // Allowed, but will emit warnings
587 } else {
588 bail!("Invalid {}: `{}`", what, name);
589 }
590 }
591 "doc" if what == "dir-name" => {
592 bail!("Invalid {}: `{}`", what, name);
593 }
594 _ => {}
595 }
596
597 Ok(())
598 }
599
600 fn validate_override(&self, which: &str) -> CargoResult<()> {
601 if self.package.is_some() {
602 bail!("package-specific profiles cannot be nested");
603 }
604 if self.build_override.is_some() {
605 bail!("build-override profiles cannot be nested");
606 }
607 if self.panic.is_some() {
608 bail!("`panic` may not be specified in a `{}` profile", which)
609 }
610 if self.lto.is_some() {
611 bail!("`lto` may not be specified in a `{}` profile", which)
612 }
613 if self.rpath.is_some() {
614 bail!("`rpath` may not be specified in a `{}` profile", which)
615 }
616 Ok(())
617 }
618
619 /// Overwrite self's values with the given profile.
620 pub fn merge(&mut self, profile: &TomlProfile) {
621 if let Some(v) = &profile.opt_level {
622 self.opt_level = Some(v.clone());
623 }
624
625 if let Some(v) = &profile.lto {
626 self.lto = Some(v.clone());
627 }
628
629 if let Some(v) = profile.codegen_units {
630 self.codegen_units = Some(v);
631 }
632
633 if let Some(v) = &profile.debug {
634 self.debug = Some(v.clone());
635 }
636
637 if let Some(v) = profile.debug_assertions {
638 self.debug_assertions = Some(v);
639 }
640
641 if let Some(v) = &profile.split_debuginfo {
642 self.split_debuginfo = Some(v.clone());
643 }
644
645 if let Some(v) = profile.rpath {
646 self.rpath = Some(v);
647 }
648
649 if let Some(v) = &profile.panic {
650 self.panic = Some(v.clone());
651 }
652
653 if let Some(v) = profile.overflow_checks {
654 self.overflow_checks = Some(v);
655 }
656
657 if let Some(v) = profile.incremental {
658 self.incremental = Some(v);
659 }
660
661 if let Some(other_package) = &profile.package {
662 match &mut self.package {
663 Some(self_package) => {
664 for (spec, other_pkg_profile) in other_package {
665 match self_package.get_mut(spec) {
666 Some(p) => p.merge(other_pkg_profile),
667 None => {
668 self_package.insert(spec.clone(), other_pkg_profile.clone());
669 }
670 }
671 }
672 }
673 None => self.package = Some(other_package.clone()),
674 }
675 }
676
677 if let Some(other_bo) = &profile.build_override {
678 match &mut self.build_override {
679 Some(self_bo) => self_bo.merge(other_bo),
680 None => self.build_override = Some(other_bo.clone()),
681 }
682 }
683
684 if let Some(v) = &profile.inherits {
685 self.inherits = Some(*v);
686 }
687
688 if let Some(v) = &profile.dir_name {
689 self.dir_name = Some(*v);
690 }
691
692 if let Some(v) = &profile.strip {
693 self.strip = Some(v.clone());
694 }
695 }
696 }
697
698 #[derive(Clone, Debug, Serialize, Eq, PartialEq)]
699 pub struct StringOrVec(Vec<String>);
700
701 impl<'de> de::Deserialize<'de> for StringOrVec {
702 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
703 where
704 D: de::Deserializer<'de>,
705 {
706 struct Visitor;
707
708 impl<'de> de::Visitor<'de> for Visitor {
709 type Value = StringOrVec;
710
711 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
712 formatter.write_str("string or list of strings")
713 }
714
715 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
716 where
717 E: de::Error,
718 {
719 Ok(StringOrVec(vec![s.to_string()]))
720 }
721
722 fn visit_seq<V>(self, v: V) -> Result<Self::Value, V::Error>
723 where
724 V: de::SeqAccess<'de>,
725 {
726 let seq = de::value::SeqAccessDeserializer::new(v);
727 Vec::deserialize(seq).map(StringOrVec)
728 }
729 }
730
731 deserializer.deserialize_any(Visitor)
732 }
733 }
734
735 #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
736 #[serde(untagged, expecting = "expected a boolean or a string")]
737 pub enum StringOrBool {
738 String(String),
739 Bool(bool),
740 }
741
742 #[derive(PartialEq, Clone, Debug, Serialize)]
743 #[serde(untagged)]
744 pub enum VecStringOrBool {
745 VecString(Vec<String>),
746 Bool(bool),
747 }
748
749 impl<'de> de::Deserialize<'de> for VecStringOrBool {
750 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
751 where
752 D: de::Deserializer<'de>,
753 {
754 struct Visitor;
755
756 impl<'de> de::Visitor<'de> for Visitor {
757 type Value = VecStringOrBool;
758
759 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
760 formatter.write_str("a boolean or vector of strings")
761 }
762
763 fn visit_seq<V>(self, v: V) -> Result<Self::Value, V::Error>
764 where
765 V: de::SeqAccess<'de>,
766 {
767 let seq = de::value::SeqAccessDeserializer::new(v);
768 Vec::deserialize(seq).map(VecStringOrBool::VecString)
769 }
770
771 fn visit_bool<E>(self, b: bool) -> Result<Self::Value, E>
772 where
773 E: de::Error,
774 {
775 Ok(VecStringOrBool::Bool(b))
776 }
777 }
778
779 deserializer.deserialize_any(Visitor)
780 }
781 }
782
783 fn version_trim_whitespace<'de, D>(deserializer: D) -> Result<semver::Version, D::Error>
784 where
785 D: de::Deserializer<'de>,
786 {
787 struct Visitor;
788
789 impl<'de> de::Visitor<'de> for Visitor {
790 type Value = semver::Version;
791
792 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
793 formatter.write_str("SemVer version")
794 }
795
796 fn visit_str<E>(self, string: &str) -> Result<Self::Value, E>
797 where
798 E: de::Error,
799 {
800 string.trim().parse().map_err(de::Error::custom)
801 }
802 }
803
804 deserializer.deserialize_str(Visitor)
805 }
806
807 /// Represents the `package`/`project` sections of a `Cargo.toml`.
808 ///
809 /// Note that the order of the fields matters, since this is the order they
810 /// are serialized to a TOML file. For example, you cannot have values after
811 /// the field `metadata`, since it is a table and values cannot appear after
812 /// tables.
813 #[derive(Deserialize, Serialize, Clone, Debug)]
814 #[serde(rename_all = "kebab-case")]
815 pub struct TomlProject {
816 edition: Option<String>,
817 rust_version: Option<String>,
818 name: InternedString,
819 #[serde(deserialize_with = "version_trim_whitespace")]
820 version: semver::Version,
821 authors: Option<Vec<String>>,
822 build: Option<StringOrBool>,
823 metabuild: Option<StringOrVec>,
824 #[serde(rename = "default-target")]
825 default_target: Option<String>,
826 #[serde(rename = "forced-target")]
827 forced_target: Option<String>,
828 links: Option<String>,
829 exclude: Option<Vec<String>>,
830 include: Option<Vec<String>>,
831 publish: Option<VecStringOrBool>,
832 workspace: Option<String>,
833 im_a_teapot: Option<bool>,
834 autobins: Option<bool>,
835 autoexamples: Option<bool>,
836 autotests: Option<bool>,
837 autobenches: Option<bool>,
838 default_run: Option<String>,
839
840 // Package metadata.
841 description: Option<String>,
842 homepage: Option<String>,
843 documentation: Option<String>,
844 readme: Option<StringOrBool>,
845 keywords: Option<Vec<String>>,
846 categories: Option<Vec<String>>,
847 license: Option<String>,
848 license_file: Option<String>,
849 repository: Option<String>,
850 resolver: Option<String>,
851
852 // Note that this field must come last due to the way toml serialization
853 // works which requires tables to be emitted after all values.
854 metadata: Option<toml::Value>,
855 }
856
857 #[derive(Debug, Deserialize, Serialize)]
858 pub struct TomlWorkspace {
859 members: Option<Vec<String>>,
860 #[serde(rename = "default-members")]
861 default_members: Option<Vec<String>>,
862 exclude: Option<Vec<String>>,
863 resolver: Option<String>,
864
865 // Note that this field must come last due to the way toml serialization
866 // works which requires tables to be emitted after all values.
867 metadata: Option<toml::Value>,
868 }
869
870 impl TomlProject {
871 pub fn to_package_id(&self, source_id: SourceId) -> CargoResult<PackageId> {
872 PackageId::new(self.name, self.version.clone(), source_id)
873 }
874 }
875
876 struct Context<'a, 'b> {
877 deps: &'a mut Vec<Dependency>,
878 source_id: SourceId,
879 nested_paths: &'a mut Vec<PathBuf>,
880 config: &'b Config,
881 warnings: &'a mut Vec<String>,
882 platform: Option<Platform>,
883 root: &'a Path,
884 features: &'a Features,
885 }
886
887 impl TomlManifest {
888 /// Prepares the manifest for publishing.
889 // - Path and git components of dependency specifications are removed.
890 // - License path is updated to point within the package.
891 pub fn prepare_for_publish(
892 &self,
893 ws: &Workspace<'_>,
894 package_root: &Path,
895 ) -> CargoResult<TomlManifest> {
896 let config = ws.config();
897 let mut package = self
898 .package
899 .as_ref()
900 .or_else(|| self.project.as_ref())
901 .unwrap()
902 .clone();
903 package.workspace = None;
904 package.resolver = ws.resolve_behavior().to_manifest();
905 if let Some(license_file) = &package.license_file {
906 let license_path = Path::new(&license_file);
907 let abs_license_path = paths::normalize_path(&package_root.join(license_path));
908 if abs_license_path.strip_prefix(package_root).is_err() {
909 // This path points outside of the package root. `cargo package`
910 // will copy it into the root, so adjust the path to this location.
911 package.license_file = Some(
912 license_path
913 .file_name()
914 .unwrap()
915 .to_str()
916 .unwrap()
917 .to_string(),
918 );
919 }
920 }
921 let all = |_d: &TomlDependency| true;
922 return Ok(TomlManifest {
923 package: Some(package),
924 project: None,
925 profile: self.profile.clone(),
926 lib: self.lib.clone(),
927 bin: self.bin.clone(),
928 example: self.example.clone(),
929 test: self.test.clone(),
930 bench: self.bench.clone(),
931 dependencies: map_deps(config, self.dependencies.as_ref(), all)?,
932 dev_dependencies: map_deps(
933 config,
934 self.dev_dependencies
935 .as_ref()
936 .or_else(|| self.dev_dependencies2.as_ref()),
937 TomlDependency::is_version_specified,
938 )?,
939 dev_dependencies2: None,
940 build_dependencies: map_deps(
941 config,
942 self.build_dependencies
943 .as_ref()
944 .or_else(|| self.build_dependencies2.as_ref()),
945 all,
946 )?,
947 build_dependencies2: None,
948 features: self.features.clone(),
949 target: match self.target.as_ref().map(|target_map| {
950 target_map
951 .iter()
952 .map(|(k, v)| {
953 Ok((
954 k.clone(),
955 TomlPlatform {
956 dependencies: map_deps(config, v.dependencies.as_ref(), all)?,
957 dev_dependencies: map_deps(
958 config,
959 v.dev_dependencies
960 .as_ref()
961 .or_else(|| v.dev_dependencies2.as_ref()),
962 TomlDependency::is_version_specified,
963 )?,
964 dev_dependencies2: None,
965 build_dependencies: map_deps(
966 config,
967 v.build_dependencies
968 .as_ref()
969 .or_else(|| v.build_dependencies2.as_ref()),
970 all,
971 )?,
972 build_dependencies2: None,
973 },
974 ))
975 })
976 .collect()
977 }) {
978 Some(Ok(v)) => Some(v),
979 Some(Err(e)) => return Err(e),
980 None => None,
981 },
982 replace: None,
983 patch: None,
984 workspace: None,
985 badges: self.badges.clone(),
986 cargo_features: self.cargo_features.clone(),
987 });
988
989 fn map_deps(
990 config: &Config,
991 deps: Option<&BTreeMap<String, TomlDependency>>,
992 filter: impl Fn(&TomlDependency) -> bool,
993 ) -> CargoResult<Option<BTreeMap<String, TomlDependency>>> {
994 let deps = match deps {
995 Some(deps) => deps,
996 None => return Ok(None),
997 };
998 let deps = deps
999 .iter()
1000 .filter(|(_k, v)| filter(v))
1001 .map(|(k, v)| Ok((k.clone(), map_dependency(config, v)?)))
1002 .collect::<CargoResult<BTreeMap<_, _>>>()?;
1003 Ok(Some(deps))
1004 }
1005
1006 fn map_dependency(config: &Config, dep: &TomlDependency) -> CargoResult<TomlDependency> {
1007 match dep {
1008 TomlDependency::Detailed(d) => {
1009 let mut d = d.clone();
1010 // Path dependencies become crates.io deps.
1011 d.path.take();
1012 // Same with git dependencies.
1013 d.git.take();
1014 d.branch.take();
1015 d.tag.take();
1016 d.rev.take();
1017 // registry specifications are elaborated to the index URL
1018 if let Some(registry) = d.registry.take() {
1019 let src = SourceId::alt_registry(config, &registry)?;
1020 d.registry_index = Some(src.url().to_string());
1021 }
1022 Ok(TomlDependency::Detailed(d))
1023 }
1024 TomlDependency::Simple(s) => Ok(TomlDependency::Detailed(DetailedTomlDependency {
1025 version: Some(s.clone()),
1026 ..Default::default()
1027 })),
1028 }
1029 }
1030 }
1031
1032 pub fn to_real_manifest(
1033 me: &Rc<TomlManifest>,
1034 source_id: SourceId,
1035 package_root: &Path,
1036 config: &Config,
1037 ) -> CargoResult<(Manifest, Vec<PathBuf>)> {
1038 let mut nested_paths = vec![];
1039 let mut warnings = vec![];
1040 let mut errors = vec![];
1041
1042 // Parse features first so they will be available when parsing other parts of the TOML.
1043 let empty = Vec::new();
1044 let cargo_features = me.cargo_features.as_ref().unwrap_or(&empty);
1045 let features = Features::new(cargo_features, config, &mut warnings)?;
1046
1047 let project = me.project.as_ref().or_else(|| me.package.as_ref());
1048 let project = project.ok_or_else(|| anyhow!("no `package` section found"))?;
1049
1050 let package_name = project.name.trim();
1051 if package_name.is_empty() {
1052 bail!("package name cannot be an empty string")
1053 }
1054
1055 validate_package_name(package_name, "package name", "")?;
1056
1057 let pkgid = project.to_package_id(source_id)?;
1058
1059 let edition = if let Some(ref edition) = project.edition {
1060 features
1061 .require(Feature::edition())
1062 .with_context(|| "editions are unstable")?;
1063 edition
1064 .parse()
1065 .with_context(|| "failed to parse the `edition` key")?
1066 } else {
1067 Edition::Edition2015
1068 };
1069 if edition == Edition::Edition2021 {
1070 features.require(Feature::edition2021())?;
1071 } else if !edition.is_stable() {
1072 // Guard in case someone forgets to add .require()
1073 return Err(util::errors::internal(format!(
1074 "edition {} should be gated",
1075 edition
1076 )));
1077 }
1078
1079 let rust_version = if let Some(rust_version) = &project.rust_version {
1080 if features.require(Feature::rust_version()).is_err() {
1081 let mut msg =
1082 "`rust-version` is not supported on this version of Cargo and will be ignored"
1083 .to_string();
1084 if config.nightly_features_allowed {
1085 msg.push_str(
1086 "\n\n\
1087 consider adding `cargo-features = [\"rust-version\"]` to the manifest",
1088 );
1089 } else {
1090 msg.push_str(
1091 "\n\n\
1092 this Cargo does not support nightly features, but if you\n\
1093 switch to nightly channel you can add\n\
1094 `cargo-features = [\"rust-version\"]` to enable this feature",
1095 );
1096 }
1097 warnings.push(msg);
1098 None
1099 } else {
1100 let req = match semver::VersionReq::parse(rust_version) {
1101 // Exclude semver operators like `^` and pre-release identifiers
1102 Ok(req) if rust_version.chars().all(|c| c.is_ascii_digit() || c == '.') => req,
1103 _ => bail!("`rust-version` must be a value like \"1.32\""),
1104 };
1105 if let Some(first_version) = edition.first_version() {
1106 let unsupported =
1107 semver::Version::new(first_version.major, first_version.minor - 1, 9999);
1108 if req.matches(&unsupported) {
1109 bail!(
1110 "rust-version {} is older than first version ({}) required by \
1111 the specified edition ({})",
1112 rust_version,
1113 first_version,
1114 edition,
1115 )
1116 }
1117 }
1118 Some(rust_version.clone())
1119 }
1120 } else {
1121 None
1122 };
1123
1124 if project.metabuild.is_some() {
1125 features.require(Feature::metabuild())?;
1126 }
1127
1128 if project.resolver.is_some()
1129 || me
1130 .workspace
1131 .as_ref()
1132 .map_or(false, |ws| ws.resolver.is_some())
1133 {
1134 features.require(Feature::resolver())?;
1135 }
1136 let resolve_behavior = match (
1137 project.resolver.as_ref(),
1138 me.workspace.as_ref().and_then(|ws| ws.resolver.as_ref()),
1139 ) {
1140 (None, None) => None,
1141 (Some(s), None) | (None, Some(s)) => Some(ResolveBehavior::from_manifest(s)?),
1142 (Some(_), Some(_)) => {
1143 bail!("cannot specify `resolver` field in both `[workspace]` and `[package]`")
1144 }
1145 };
1146
1147 // If we have no lib at all, use the inferred lib, if available.
1148 // If we have a lib with a path, we're done.
1149 // If we have a lib with no path, use the inferred lib or else the package name.
1150 let targets = targets(
1151 &features,
1152 me,
1153 package_name,
1154 package_root,
1155 edition,
1156 &project.build,
1157 &project.metabuild,
1158 &mut warnings,
1159 &mut errors,
1160 )?;
1161
1162 if targets.is_empty() {
1163 debug!("manifest has no build targets");
1164 }
1165
1166 if let Err(e) = unique_build_targets(&targets, package_root) {
1167 warnings.push(format!(
1168 "file found to be present in multiple \
1169 build targets: {}",
1170 e
1171 ));
1172 }
1173
1174 if let Some(links) = &project.links {
1175 if !targets.iter().any(|t| t.is_custom_build()) {
1176 bail!(
1177 "package `{}` specifies that it links to `{}` but does not \
1178 have a custom build script",
1179 pkgid,
1180 links
1181 )
1182 }
1183 }
1184
1185 let mut deps = Vec::new();
1186 let replace;
1187 let patch;
1188
1189 {
1190 let mut cx = Context {
1191 deps: &mut deps,
1192 source_id,
1193 nested_paths: &mut nested_paths,
1194 config,
1195 warnings: &mut warnings,
1196 features: &features,
1197 platform: None,
1198 root: package_root,
1199 };
1200
1201 fn process_dependencies(
1202 cx: &mut Context<'_, '_>,
1203 new_deps: Option<&BTreeMap<String, TomlDependency>>,
1204 kind: Option<DepKind>,
1205 ) -> CargoResult<()> {
1206 let dependencies = match new_deps {
1207 Some(dependencies) => dependencies,
1208 None => return Ok(()),
1209 };
1210 for (n, v) in dependencies.iter() {
1211 let dep = v.to_dependency(n, cx, kind)?;
1212 validate_package_name(dep.name_in_toml().as_str(), "dependency name", "")?;
1213 cx.deps.push(dep);
1214 }
1215
1216 Ok(())
1217 }
1218
1219 // Collect the dependencies.
1220 process_dependencies(&mut cx, me.dependencies.as_ref(), None)?;
1221 let dev_deps = me
1222 .dev_dependencies
1223 .as_ref()
1224 .or_else(|| me.dev_dependencies2.as_ref());
1225 process_dependencies(&mut cx, dev_deps, Some(DepKind::Development))?;
1226 let build_deps = me
1227 .build_dependencies
1228 .as_ref()
1229 .or_else(|| me.build_dependencies2.as_ref());
1230 process_dependencies(&mut cx, build_deps, Some(DepKind::Build))?;
1231
1232 for (name, platform) in me.target.iter().flatten() {
1233 cx.platform = {
1234 let platform: Platform = name.parse()?;
1235 platform.check_cfg_attributes(&mut cx.warnings);
1236 Some(platform)
1237 };
1238 process_dependencies(&mut cx, platform.dependencies.as_ref(), None)?;
1239 let build_deps = platform
1240 .build_dependencies
1241 .as_ref()
1242 .or_else(|| platform.build_dependencies2.as_ref());
1243 process_dependencies(&mut cx, build_deps, Some(DepKind::Build))?;
1244 let dev_deps = platform
1245 .dev_dependencies
1246 .as_ref()
1247 .or_else(|| platform.dev_dependencies2.as_ref());
1248 process_dependencies(&mut cx, dev_deps, Some(DepKind::Development))?;
1249 }
1250
1251 replace = me.replace(&mut cx)?;
1252 patch = me.patch(&mut cx)?;
1253 }
1254
1255 {
1256 let mut names_sources = BTreeMap::new();
1257 for dep in &deps {
1258 let name = dep.name_in_toml();
1259 let prev = names_sources.insert(name.to_string(), dep.source_id());
1260 if prev.is_some() && prev != Some(dep.source_id()) {
1261 bail!(
1262 "Dependency '{}' has different source paths depending on the build \
1263 target. Each dependency must have a single canonical source path \
1264 irrespective of build target.",
1265 name
1266 );
1267 }
1268 }
1269 }
1270
1271 let exclude = project.exclude.clone().unwrap_or_default();
1272 let include = project.include.clone().unwrap_or_default();
1273 let empty_features = BTreeMap::new();
1274
1275 let summary = Summary::new(
1276 config,
1277 pkgid,
1278 deps,
1279 me.features.as_ref().unwrap_or(&empty_features),
1280 project.links.as_deref(),
1281 )?;
1282 let unstable = config.cli_unstable();
1283 summary.unstable_gate(unstable.namespaced_features, unstable.weak_dep_features)?;
1284
1285 let metadata = ManifestMetadata {
1286 description: project.description.clone(),
1287 homepage: project.homepage.clone(),
1288 documentation: project.documentation.clone(),
1289 readme: readme_for_project(package_root, project),
1290 authors: project.authors.clone().unwrap_or_default(),
1291 license: project.license.clone(),
1292 license_file: project.license_file.clone(),
1293 repository: project.repository.clone(),
1294 keywords: project.keywords.clone().unwrap_or_default(),
1295 categories: project.categories.clone().unwrap_or_default(),
1296 badges: me.badges.clone().unwrap_or_default(),
1297 links: project.links.clone(),
1298 };
1299
1300 let workspace_config = match (me.workspace.as_ref(), project.workspace.as_ref()) {
1301 (Some(config), None) => WorkspaceConfig::Root(WorkspaceRootConfig::new(
1302 package_root,
1303 &config.members,
1304 &config.default_members,
1305 &config.exclude,
1306 &config.metadata,
1307 )),
1308 (None, root) => WorkspaceConfig::Member {
1309 root: root.cloned(),
1310 },
1311 (Some(..), Some(..)) => bail!(
1312 "cannot configure both `package.workspace` and \
1313 `[workspace]`, only one can be specified"
1314 ),
1315 };
1316 let profiles = me.profile.clone();
1317 if let Some(profiles) = &profiles {
1318 profiles.validate(&features, &mut warnings)?;
1319 }
1320 let publish = match project.publish {
1321 Some(VecStringOrBool::VecString(ref vecstring)) => Some(vecstring.clone()),
1322 Some(VecStringOrBool::Bool(false)) => Some(vec![]),
1323 None | Some(VecStringOrBool::Bool(true)) => None,
1324 };
1325
1326 if summary.features().contains_key("default-features") {
1327 warnings.push(
1328 "`default-features = [\"..\"]` was found in [features]. \
1329 Did you mean to use `default = [\"..\"]`?"
1330 .to_string(),
1331 )
1332 }
1333
1334 if let Some(run) = &project.default_run {
1335 if !targets
1336 .iter()
1337 .filter(|t| t.is_bin())
1338 .any(|t| t.name() == run)
1339 {
1340 let suggestion =
1341 util::closest_msg(run, targets.iter().filter(|t| t.is_bin()), |t| t.name());
1342 bail!("default-run target `{}` not found{}", run, suggestion);
1343 }
1344 }
1345
1346 let default_kind = project
1347 .default_target
1348 .as_ref()
1349 .map(|t| CompileTarget::new(&*t))
1350 .transpose()?
1351 .map(CompileKind::Target);
1352 let forced_kind = project
1353 .forced_target
1354 .as_ref()
1355 .map(|t| CompileTarget::new(&*t))
1356 .transpose()?
1357 .map(CompileKind::Target);
1358
1359 let custom_metadata = project.metadata.clone();
1360 let mut manifest = Manifest::new(
1361 summary,
1362 default_kind,
1363 forced_kind,
1364 targets,
1365 exclude,
1366 include,
1367 project.links.clone(),
1368 metadata,
1369 custom_metadata,
1370 profiles,
1371 publish,
1372 replace,
1373 patch,
1374 workspace_config,
1375 features,
1376 edition,
1377 rust_version,
1378 project.im_a_teapot,
1379 project.default_run.clone(),
1380 Rc::clone(me),
1381 project.metabuild.clone().map(|sov| sov.0),
1382 resolve_behavior,
1383 );
1384 if project.license_file.is_some() && project.license.is_some() {
1385 manifest.warnings_mut().add_warning(
1386 "only one of `license` or \
1387 `license-file` is necessary"
1388 .to_string(),
1389 );
1390 }
1391 for warning in warnings {
1392 manifest.warnings_mut().add_warning(warning);
1393 }
1394 for error in errors {
1395 manifest.warnings_mut().add_critical_warning(error);
1396 }
1397
1398 manifest.feature_gate()?;
1399
1400 Ok((manifest, nested_paths))
1401 }
1402
1403 fn to_virtual_manifest(
1404 me: &Rc<TomlManifest>,
1405 source_id: SourceId,
1406 root: &Path,
1407 config: &Config,
1408 ) -> CargoResult<(VirtualManifest, Vec<PathBuf>)> {
1409 if me.project.is_some() {
1410 bail!("this virtual manifest specifies a [project] section, which is not allowed");
1411 }
1412 if me.package.is_some() {
1413 bail!("this virtual manifest specifies a [package] section, which is not allowed");
1414 }
1415 if me.lib.is_some() {
1416 bail!("this virtual manifest specifies a [lib] section, which is not allowed");
1417 }
1418 if me.bin.is_some() {
1419 bail!("this virtual manifest specifies a [[bin]] section, which is not allowed");
1420 }
1421 if me.example.is_some() {
1422 bail!("this virtual manifest specifies a [[example]] section, which is not allowed");
1423 }
1424 if me.test.is_some() {
1425 bail!("this virtual manifest specifies a [[test]] section, which is not allowed");
1426 }
1427 if me.bench.is_some() {
1428 bail!("this virtual manifest specifies a [[bench]] section, which is not allowed");
1429 }
1430 if me.dependencies.is_some() {
1431 bail!("this virtual manifest specifies a [dependencies] section, which is not allowed");
1432 }
1433 if me.dev_dependencies.is_some() || me.dev_dependencies2.is_some() {
1434 bail!("this virtual manifest specifies a [dev-dependencies] section, which is not allowed");
1435 }
1436 if me.build_dependencies.is_some() || me.build_dependencies2.is_some() {
1437 bail!("this virtual manifest specifies a [build-dependencies] section, which is not allowed");
1438 }
1439 if me.features.is_some() {
1440 bail!("this virtual manifest specifies a [features] section, which is not allowed");
1441 }
1442 if me.target.is_some() {
1443 bail!("this virtual manifest specifies a [target] section, which is not allowed");
1444 }
1445 if me.badges.is_some() {
1446 bail!("this virtual manifest specifies a [badges] section, which is not allowed");
1447 }
1448
1449 let mut nested_paths = Vec::new();
1450 let mut warnings = Vec::new();
1451 let mut deps = Vec::new();
1452 let empty = Vec::new();
1453 let cargo_features = me.cargo_features.as_ref().unwrap_or(&empty);
1454 let features = Features::new(cargo_features, config, &mut warnings)?;
1455
1456 let (replace, patch) = {
1457 let mut cx = Context {
1458 deps: &mut deps,
1459 source_id,
1460 nested_paths: &mut nested_paths,
1461 config,
1462 warnings: &mut warnings,
1463 platform: None,
1464 features: &features,
1465 root,
1466 };
1467 (me.replace(&mut cx)?, me.patch(&mut cx)?)
1468 };
1469 let profiles = me.profile.clone();
1470 if let Some(profiles) = &profiles {
1471 profiles.validate(&features, &mut warnings)?;
1472 }
1473 if me
1474 .workspace
1475 .as_ref()
1476 .map_or(false, |ws| ws.resolver.is_some())
1477 {
1478 features.require(Feature::resolver())?;
1479 }
1480 let resolve_behavior = me
1481 .workspace
1482 .as_ref()
1483 .and_then(|ws| ws.resolver.as_deref())
1484 .map(|r| ResolveBehavior::from_manifest(r))
1485 .transpose()?;
1486 let workspace_config = match me.workspace {
1487 Some(ref config) => WorkspaceConfig::Root(WorkspaceRootConfig::new(
1488 root,
1489 &config.members,
1490 &config.default_members,
1491 &config.exclude,
1492 &config.metadata,
1493 )),
1494 None => {
1495 bail!("virtual manifests must be configured with [workspace]");
1496 }
1497 };
1498 Ok((
1499 VirtualManifest::new(
1500 replace,
1501 patch,
1502 workspace_config,
1503 profiles,
1504 features,
1505 resolve_behavior,
1506 ),
1507 nested_paths,
1508 ))
1509 }
1510
1511 fn replace(&self, cx: &mut Context<'_, '_>) -> CargoResult<Vec<(PackageIdSpec, Dependency)>> {
1512 if self.patch.is_some() && self.replace.is_some() {
1513 bail!("cannot specify both [replace] and [patch]");
1514 }
1515 let mut replace = Vec::new();
1516 for (spec, replacement) in self.replace.iter().flatten() {
1517 let mut spec = PackageIdSpec::parse(spec).with_context(|| {
1518 format!(
1519 "replacements must specify a valid semver \
1520 version to replace, but `{}` does not",
1521 spec
1522 )
1523 })?;
1524 if spec.url().is_none() {
1525 spec.set_url(CRATES_IO_INDEX.parse().unwrap());
1526 }
1527
1528 if replacement.is_version_specified() {
1529 bail!(
1530 "replacements cannot specify a version \
1531 requirement, but found one for `{}`",
1532 spec
1533 );
1534 }
1535
1536 let mut dep = replacement.to_dependency(spec.name().as_str(), cx, None)?;
1537 {
1538 let version = spec.version().ok_or_else(|| {
1539 anyhow!(
1540 "replacements must specify a version \
1541 to replace, but `{}` does not",
1542 spec
1543 )
1544 })?;
1545 dep.set_version_req(VersionReq::exact(version));
1546 }
1547 replace.push((spec, dep));
1548 }
1549 Ok(replace)
1550 }
1551
1552 fn patch(&self, cx: &mut Context<'_, '_>) -> CargoResult<HashMap<Url, Vec<Dependency>>> {
1553 let mut patch = HashMap::new();
1554 for (url, deps) in self.patch.iter().flatten() {
1555 let url = match &url[..] {
1556 CRATES_IO_REGISTRY => CRATES_IO_INDEX.parse().unwrap(),
1557 _ => cx
1558 .config
1559 .get_registry_index(url)
1560 .or_else(|_| url.into_url())
1561 .with_context(|| {
1562 format!("[patch] entry `{}` should be a URL or registry name", url)
1563 })?,
1564 };
1565 patch.insert(
1566 url,
1567 deps.iter()
1568 .map(|(name, dep)| dep.to_dependency(name, cx, None))
1569 .collect::<CargoResult<Vec<_>>>()?,
1570 );
1571 }
1572 Ok(patch)
1573 }
1574
1575 /// Returns the path to the build script if one exists for this crate.
1576 fn maybe_custom_build(
1577 &self,
1578 build: &Option<StringOrBool>,
1579 package_root: &Path,
1580 ) -> Option<PathBuf> {
1581 let build_rs = package_root.join("build.rs");
1582 match *build {
1583 // Explicitly no build script.
1584 Some(StringOrBool::Bool(false)) => None,
1585 Some(StringOrBool::Bool(true)) => Some(build_rs),
1586 Some(StringOrBool::String(ref s)) => Some(PathBuf::from(s)),
1587 None => {
1588 // If there is a `build.rs` file next to the `Cargo.toml`, assume it is
1589 // a build script.
1590 if build_rs.is_file() {
1591 Some(build_rs)
1592 } else {
1593 None
1594 }
1595 }
1596 }
1597 }
1598
1599 pub fn has_profiles(&self) -> bool {
1600 self.profile.is_some()
1601 }
1602
1603 pub fn features(&self) -> Option<&BTreeMap<InternedString, Vec<InternedString>>> {
1604 self.features.as_ref()
1605 }
1606 }
1607
1608 /// Returns the name of the README file for a `TomlProject`.
1609 fn readme_for_project(package_root: &Path, project: &TomlProject) -> Option<String> {
1610 match &project.readme {
1611 None => default_readme_from_package_root(package_root),
1612 Some(value) => match value {
1613 StringOrBool::Bool(false) => None,
1614 StringOrBool::Bool(true) => Some("README.md".to_string()),
1615 StringOrBool::String(v) => Some(v.clone()),
1616 },
1617 }
1618 }
1619
1620 const DEFAULT_README_FILES: [&str; 3] = ["README.md", "README.txt", "README"];
1621
1622 /// Checks if a file with any of the default README file names exists in the package root.
1623 /// If so, returns a `String` representing that name.
1624 fn default_readme_from_package_root(package_root: &Path) -> Option<String> {
1625 for &readme_filename in DEFAULT_README_FILES.iter() {
1626 if package_root.join(readme_filename).is_file() {
1627 return Some(readme_filename.to_string());
1628 }
1629 }
1630
1631 None
1632 }
1633
1634 /// Checks a list of build targets, and ensures the target names are unique within a vector.
1635 /// If not, the name of the offending build target is returned.
1636 fn unique_build_targets(targets: &[Target], package_root: &Path) -> Result<(), String> {
1637 let mut seen = HashSet::new();
1638 for target in targets {
1639 if let TargetSourcePath::Path(path) = target.src_path() {
1640 let full = package_root.join(path);
1641 if !seen.insert(full.clone()) {
1642 return Err(full.display().to_string());
1643 }
1644 }
1645 }
1646 Ok(())
1647 }
1648
1649 impl<P: ResolveToPath> TomlDependency<P> {
1650 pub(crate) fn to_dependency_split(
1651 &self,
1652 name: &str,
1653 source_id: SourceId,
1654 nested_paths: &mut Vec<PathBuf>,
1655 config: &Config,
1656 warnings: &mut Vec<String>,
1657 platform: Option<Platform>,
1658 root: &Path,
1659 features: &Features,
1660 kind: Option<DepKind>,
1661 ) -> CargoResult<Dependency> {
1662 self.to_dependency(
1663 name,
1664 &mut Context {
1665 deps: &mut Vec::new(),
1666 source_id,
1667 nested_paths,
1668 config,
1669 warnings,
1670 platform,
1671 root,
1672 features,
1673 },
1674 kind,
1675 )
1676 }
1677
1678 fn to_dependency(
1679 &self,
1680 name: &str,
1681 cx: &mut Context<'_, '_>,
1682 kind: Option<DepKind>,
1683 ) -> CargoResult<Dependency> {
1684 match *self {
1685 TomlDependency::Simple(ref version) => DetailedTomlDependency::<P> {
1686 version: Some(version.clone()),
1687 ..Default::default()
1688 }
1689 .to_dependency(name, cx, kind),
1690 TomlDependency::Detailed(ref details) => details.to_dependency(name, cx, kind),
1691 }
1692 }
1693
1694 fn is_version_specified(&self) -> bool {
1695 match self {
1696 TomlDependency::Detailed(d) => d.version.is_some(),
1697 TomlDependency::Simple(..) => true,
1698 }
1699 }
1700 }
1701
1702 impl<P: ResolveToPath> DetailedTomlDependency<P> {
1703 fn to_dependency(
1704 &self,
1705 name_in_toml: &str,
1706 cx: &mut Context<'_, '_>,
1707 kind: Option<DepKind>,
1708 ) -> CargoResult<Dependency> {
1709 if self.version.is_none() && self.path.is_none() && self.git.is_none() {
1710 let msg = format!(
1711 "dependency ({}) specified without \
1712 providing a local path, Git repository, or \
1713 version to use. This will be considered an \
1714 error in future versions",
1715 name_in_toml
1716 );
1717 cx.warnings.push(msg);
1718 }
1719
1720 if let Some(version) = &self.version {
1721 if version.contains('+') {
1722 cx.warnings.push(format!(
1723 "version requirement `{}` for dependency `{}` \
1724 includes semver metadata which will be ignored, removing the \
1725 metadata is recommended to avoid confusion",
1726 version, name_in_toml
1727 ));
1728 }
1729 }
1730
1731 if self.git.is_none() {
1732 let git_only_keys = [
1733 (&self.branch, "branch"),
1734 (&self.tag, "tag"),
1735 (&self.rev, "rev"),
1736 ];
1737
1738 for &(key, key_name) in &git_only_keys {
1739 if key.is_some() {
1740 let msg = format!(
1741 "key `{}` is ignored for dependency ({}). \
1742 This will be considered an error in future versions",
1743 key_name, name_in_toml
1744 );
1745 cx.warnings.push(msg)
1746 }
1747 }
1748 }
1749
1750 // Early detection of potentially misused feature syntax
1751 // instead of generating a "feature not found" error.
1752 if let Some(features) = &self.features {
1753 for feature in features {
1754 if feature.contains('/') {
1755 bail!(
1756 "feature `{}` in dependency `{}` is not allowed to contain slashes\n\
1757 If you want to enable features of a transitive dependency, \
1758 the direct dependency needs to re-export those features from \
1759 the `[features]` table.",
1760 feature,
1761 name_in_toml
1762 );
1763 }
1764 if feature.starts_with("dep:") {
1765 bail!(
1766 "feature `{}` in dependency `{}` is not allowed to use explicit \
1767 `dep:` syntax\n\
1768 If you want to enable an optional dependency, specify the name \
1769 of the optional dependency without the `dep:` prefix, or specify \
1770 a feature from the dependency's `[features]` table that enables \
1771 the optional dependency.",
1772 feature,
1773 name_in_toml
1774 );
1775 }
1776 }
1777 }
1778
1779 let new_source_id = match (
1780 self.git.as_ref(),
1781 self.path.as_ref(),
1782 self.registry.as_ref(),
1783 self.registry_index.as_ref(),
1784 ) {
1785 (Some(_), _, Some(_), _) | (Some(_), _, _, Some(_)) => bail!(
1786 "dependency ({}) specification is ambiguous. \
1787 Only one of `git` or `registry` is allowed.",
1788 name_in_toml
1789 ),
1790 (_, _, Some(_), Some(_)) => bail!(
1791 "dependency ({}) specification is ambiguous. \
1792 Only one of `registry` or `registry-index` is allowed.",
1793 name_in_toml
1794 ),
1795 (Some(git), maybe_path, _, _) => {
1796 if maybe_path.is_some() {
1797 let msg = format!(
1798 "dependency ({}) specification is ambiguous. \
1799 Only one of `git` or `path` is allowed. \
1800 This will be considered an error in future versions",
1801 name_in_toml
1802 );
1803 cx.warnings.push(msg)
1804 }
1805
1806 let n_details = [&self.branch, &self.tag, &self.rev]
1807 .iter()
1808 .filter(|d| d.is_some())
1809 .count();
1810
1811 if n_details > 1 {
1812 bail!(
1813 "dependency ({}) specification is ambiguous. \
1814 Only one of `branch`, `tag` or `rev` is allowed.",
1815 name_in_toml
1816 );
1817 }
1818
1819 let reference = self
1820 .branch
1821 .clone()
1822 .map(GitReference::Branch)
1823 .or_else(|| self.tag.clone().map(GitReference::Tag))
1824 .or_else(|| self.rev.clone().map(GitReference::Rev))
1825 .unwrap_or(GitReference::DefaultBranch);
1826 let loc = git.into_url()?;
1827
1828 if let Some(fragment) = loc.fragment() {
1829 let msg = format!(
1830 "URL fragment `#{}` in git URL is ignored for dependency ({}). \
1831 If you were trying to specify a specific git revision, \
1832 use `rev = \"{}\"` in the dependency declaration.",
1833 fragment, name_in_toml, fragment
1834 );
1835 cx.warnings.push(msg)
1836 }
1837
1838 SourceId::for_git(&loc, reference)?
1839 }
1840 (None, Some(path), _, _) => {
1841 let path = path.resolve(cx.config);
1842 cx.nested_paths.push(path.clone());
1843 // If the source ID for the package we're parsing is a path
1844 // source, then we normalize the path here to get rid of
1845 // components like `..`.
1846 //
1847 // The purpose of this is to get a canonical ID for the package
1848 // that we're depending on to ensure that builds of this package
1849 // always end up hashing to the same value no matter where it's
1850 // built from.
1851 if cx.source_id.is_path() {
1852 let path = cx.root.join(path);
1853 let path = paths::normalize_path(&path);
1854 SourceId::for_path(&path)?
1855 } else {
1856 cx.source_id
1857 }
1858 }
1859 (None, None, Some(registry), None) => SourceId::alt_registry(cx.config, registry)?,
1860 (None, None, None, Some(registry_index)) => {
1861 let url = registry_index.into_url()?;
1862 SourceId::for_registry(&url)?
1863 }
1864 (None, None, None, None) => SourceId::crates_io(cx.config)?,
1865 };
1866
1867 let (pkg_name, explicit_name_in_toml) = match self.package {
1868 Some(ref s) => (&s[..], Some(name_in_toml)),
1869 None => (name_in_toml, None),
1870 };
1871
1872 let version = self.version.as_deref();
1873 let mut dep = Dependency::parse(pkg_name, version, new_source_id)?;
1874 dep.set_features(self.features.iter().flatten())
1875 .set_default_features(
1876 self.default_features
1877 .or(self.default_features2)
1878 .unwrap_or(true),
1879 )
1880 .set_optional(self.optional.unwrap_or(false))
1881 .set_platform(cx.platform.clone());
1882 if let Some(registry) = &self.registry {
1883 let registry_id = SourceId::alt_registry(cx.config, registry)?;
1884 dep.set_registry_id(registry_id);
1885 }
1886 if let Some(registry_index) = &self.registry_index {
1887 let url = registry_index.into_url()?;
1888 let registry_id = SourceId::for_registry(&url)?;
1889 dep.set_registry_id(registry_id);
1890 }
1891
1892 if let Some(kind) = kind {
1893 dep.set_kind(kind);
1894 }
1895 if let Some(name_in_toml) = explicit_name_in_toml {
1896 cx.features.require(Feature::rename_dependency())?;
1897 dep.set_explicit_name_in_toml(name_in_toml);
1898 }
1899
1900 if let Some(p) = self.public {
1901 cx.features.require(Feature::public_dependency())?;
1902
1903 if dep.kind() != DepKind::Normal {
1904 bail!("'public' specifier can only be used on regular dependencies, not {:?} dependencies", dep.kind());
1905 }
1906
1907 dep.set_public(p);
1908 }
1909 Ok(dep)
1910 }
1911 }
1912
1913 #[derive(Default, Serialize, Deserialize, Debug, Clone)]
1914 struct TomlTarget {
1915 name: Option<String>,
1916
1917 // The intention was to only accept `crate-type` here but historical
1918 // versions of Cargo also accepted `crate_type`, so look for both.
1919 #[serde(rename = "crate-type")]
1920 crate_type: Option<Vec<String>>,
1921 #[serde(rename = "crate_type")]
1922 crate_type2: Option<Vec<String>>,
1923
1924 path: Option<PathValue>,
1925 test: Option<bool>,
1926 doctest: Option<bool>,
1927 bench: Option<bool>,
1928 doc: Option<bool>,
1929 plugin: Option<bool>,
1930 #[serde(rename = "proc-macro")]
1931 proc_macro_raw: Option<bool>,
1932 #[serde(rename = "proc_macro")]
1933 proc_macro_raw2: Option<bool>,
1934 harness: Option<bool>,
1935 #[serde(rename = "required-features")]
1936 required_features: Option<Vec<String>>,
1937 edition: Option<String>,
1938 }
1939
1940 #[derive(Clone)]
1941 struct PathValue(PathBuf);
1942
1943 impl<'de> de::Deserialize<'de> for PathValue {
1944 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1945 where
1946 D: de::Deserializer<'de>,
1947 {
1948 Ok(PathValue(String::deserialize(deserializer)?.into()))
1949 }
1950 }
1951
1952 impl ser::Serialize for PathValue {
1953 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1954 where
1955 S: ser::Serializer,
1956 {
1957 self.0.serialize(serializer)
1958 }
1959 }
1960
1961 /// Corresponds to a `target` entry, but `TomlTarget` is already used.
1962 #[derive(Serialize, Deserialize, Debug)]
1963 struct TomlPlatform {
1964 dependencies: Option<BTreeMap<String, TomlDependency>>,
1965 #[serde(rename = "build-dependencies")]
1966 build_dependencies: Option<BTreeMap<String, TomlDependency>>,
1967 #[serde(rename = "build_dependencies")]
1968 build_dependencies2: Option<BTreeMap<String, TomlDependency>>,
1969 #[serde(rename = "dev-dependencies")]
1970 dev_dependencies: Option<BTreeMap<String, TomlDependency>>,
1971 #[serde(rename = "dev_dependencies")]
1972 dev_dependencies2: Option<BTreeMap<String, TomlDependency>>,
1973 }
1974
1975 impl TomlTarget {
1976 fn new() -> TomlTarget {
1977 TomlTarget::default()
1978 }
1979
1980 fn name(&self) -> String {
1981 match self.name {
1982 Some(ref name) => name.clone(),
1983 None => panic!("target name is required"),
1984 }
1985 }
1986
1987 fn proc_macro(&self) -> Option<bool> {
1988 self.proc_macro_raw.or(self.proc_macro_raw2).or_else(|| {
1989 if let Some(types) = self.crate_types() {
1990 if types.contains(&"proc-macro".to_string()) {
1991 return Some(true);
1992 }
1993 }
1994 None
1995 })
1996 }
1997
1998 fn crate_types(&self) -> Option<&Vec<String>> {
1999 self.crate_type
2000 .as_ref()
2001 .or_else(|| self.crate_type2.as_ref())
2002 }
2003 }
2004
2005 impl fmt::Debug for PathValue {
2006 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2007 self.0.fmt(f)
2008 }
2009 }