]> git.proxmox.com Git - cargo.git/blob - src/cargo/util/toml/mod.rs
Auto merge of #9792 - weihanglo:issue-9782, r=ehuss
[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_backend: Option<InternedString>,
437 pub codegen_units: Option<u32>,
438 pub debug: Option<U32OrBool>,
439 pub split_debuginfo: Option<String>,
440 pub debug_assertions: Option<bool>,
441 pub rpath: Option<bool>,
442 pub panic: Option<String>,
443 pub overflow_checks: Option<bool>,
444 pub incremental: Option<bool>,
445 pub dir_name: Option<InternedString>,
446 pub inherits: Option<InternedString>,
447 pub strip: Option<StringOrBool>,
448 // These two fields must be last because they are sub-tables, and TOML
449 // requires all non-tables to be listed first.
450 pub package: Option<BTreeMap<ProfilePackageSpec, TomlProfile>>,
451 pub build_override: Option<Box<TomlProfile>>,
452 }
453
454 #[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
455 pub enum ProfilePackageSpec {
456 Spec(PackageIdSpec),
457 All,
458 }
459
460 impl ser::Serialize for ProfilePackageSpec {
461 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
462 where
463 S: ser::Serializer,
464 {
465 match *self {
466 ProfilePackageSpec::Spec(ref spec) => spec.serialize(s),
467 ProfilePackageSpec::All => "*".serialize(s),
468 }
469 }
470 }
471
472 impl<'de> de::Deserialize<'de> for ProfilePackageSpec {
473 fn deserialize<D>(d: D) -> Result<ProfilePackageSpec, D::Error>
474 where
475 D: de::Deserializer<'de>,
476 {
477 let string = String::deserialize(d)?;
478 if string == "*" {
479 Ok(ProfilePackageSpec::All)
480 } else {
481 PackageIdSpec::parse(&string)
482 .map_err(de::Error::custom)
483 .map(ProfilePackageSpec::Spec)
484 }
485 }
486 }
487
488 impl TomlProfile {
489 pub fn validate(
490 &self,
491 name: &str,
492 features: &Features,
493 warnings: &mut Vec<String>,
494 ) -> CargoResult<()> {
495 if let Some(ref profile) = self.build_override {
496 features.require(Feature::profile_overrides())?;
497 profile.validate_override("build-override", features)?;
498 }
499 if let Some(ref packages) = self.package {
500 features.require(Feature::profile_overrides())?;
501 for profile in packages.values() {
502 profile.validate_override("package", features)?;
503 }
504 }
505
506 // Feature gate definition of named profiles
507 match name {
508 "dev" | "release" | "bench" | "test" | "doc" => {}
509 _ => {
510 features.require(Feature::named_profiles())?;
511 }
512 }
513
514 // Profile name validation
515 Self::validate_name(name)?;
516
517 // Feature gate on uses of keys related to named profiles
518 if self.inherits.is_some() {
519 features.require(Feature::named_profiles())?;
520 }
521
522 if let Some(dir_name) = self.dir_name {
523 // This is disabled for now, as we would like to stabilize named
524 // profiles without this, and then decide in the future if it is
525 // needed. This helps simplify the UI a little.
526 bail!(
527 "dir-name=\"{}\" in profile `{}` is not currently allowed, \
528 directory names are tied to the profile name for custom profiles",
529 dir_name,
530 name
531 );
532 }
533
534 // `inherits` validation
535 if matches!(self.inherits.map(|s| s.as_str()), Some("debug")) {
536 bail!(
537 "profile.{}.inherits=\"debug\" should be profile.{}.inherits=\"dev\"",
538 name,
539 name
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
569 if let Some(codegen_backend) = &self.codegen_backend {
570 features.require(Feature::codegen_backend())?;
571 if codegen_backend.contains(|c: char| !c.is_ascii_alphanumeric() && c != '_') {
572 bail!(
573 "`profile.{}.codegen-backend` setting of `{}` is not a valid backend name.",
574 name,
575 codegen_backend,
576 );
577 }
578 }
579
580 Ok(())
581 }
582
583 /// Validate dir-names and profile names according to RFC 2678.
584 pub fn validate_name(name: &str) -> CargoResult<()> {
585 if let Some(ch) = name
586 .chars()
587 .find(|ch| !ch.is_alphanumeric() && *ch != '_' && *ch != '-')
588 {
589 bail!(
590 "invalid character `{}` in profile name `{}`\n\
591 Allowed characters are letters, numbers, underscore, and hyphen.",
592 ch,
593 name
594 );
595 }
596
597 const SEE_DOCS: &str = "See https://doc.rust-lang.org/cargo/reference/profiles.html \
598 for more on configuring profiles.";
599
600 let lower_name = name.to_lowercase();
601 if lower_name == "debug" {
602 bail!(
603 "profile name `{}` is reserved\n\
604 To configure the default development profile, use the name `dev` \
605 as in [profile.dev]\n\
606 {}",
607 name,
608 SEE_DOCS
609 );
610 }
611 if lower_name == "build-override" {
612 bail!(
613 "profile name `{}` is reserved\n\
614 To configure build dependency settings, use [profile.dev.build-override] \
615 and [profile.release.build-override]\n\
616 {}",
617 name,
618 SEE_DOCS
619 );
620 }
621
622 // These are some arbitrary reservations. We have no plans to use
623 // these, but it seems safer to reserve a few just in case we want to
624 // add more built-in profiles in the future. We can also uses special
625 // syntax like cargo:foo if needed. But it is unlikely these will ever
626 // be used.
627 if matches!(
628 lower_name.as_str(),
629 "build"
630 | "check"
631 | "clean"
632 | "config"
633 | "fetch"
634 | "fix"
635 | "install"
636 | "metadata"
637 | "package"
638 | "publish"
639 | "report"
640 | "root"
641 | "run"
642 | "rust"
643 | "rustc"
644 | "rustdoc"
645 | "tmp"
646 | "uninstall"
647 ) || lower_name.starts_with("cargo")
648 {
649 bail!(
650 "profile name `{}` is reserved\n\
651 Please choose a different name.\n\
652 {}",
653 name,
654 SEE_DOCS
655 );
656 }
657
658 Ok(())
659 }
660
661 fn validate_override(&self, which: &str, features: &Features) -> CargoResult<()> {
662 if self.package.is_some() {
663 bail!("package-specific profiles cannot be nested");
664 }
665 if self.build_override.is_some() {
666 bail!("build-override profiles cannot be nested");
667 }
668 if self.panic.is_some() {
669 bail!("`panic` may not be specified in a `{}` profile", which)
670 }
671 if self.lto.is_some() {
672 bail!("`lto` may not be specified in a `{}` profile", which)
673 }
674 if self.rpath.is_some() {
675 bail!("`rpath` may not be specified in a `{}` profile", which)
676 }
677 if self.codegen_backend.is_some() {
678 features.require(Feature::codegen_backend())?;
679 }
680 Ok(())
681 }
682
683 /// Overwrite self's values with the given profile.
684 pub fn merge(&mut self, profile: &TomlProfile) {
685 if let Some(v) = &profile.opt_level {
686 self.opt_level = Some(v.clone());
687 }
688
689 if let Some(v) = &profile.lto {
690 self.lto = Some(v.clone());
691 }
692
693 if let Some(v) = profile.codegen_backend {
694 self.codegen_backend = Some(v);
695 }
696
697 if let Some(v) = profile.codegen_units {
698 self.codegen_units = Some(v);
699 }
700
701 if let Some(v) = &profile.debug {
702 self.debug = Some(v.clone());
703 }
704
705 if let Some(v) = profile.debug_assertions {
706 self.debug_assertions = Some(v);
707 }
708
709 if let Some(v) = &profile.split_debuginfo {
710 self.split_debuginfo = Some(v.clone());
711 }
712
713 if let Some(v) = profile.rpath {
714 self.rpath = Some(v);
715 }
716
717 if let Some(v) = &profile.panic {
718 self.panic = Some(v.clone());
719 }
720
721 if let Some(v) = profile.overflow_checks {
722 self.overflow_checks = Some(v);
723 }
724
725 if let Some(v) = profile.incremental {
726 self.incremental = Some(v);
727 }
728
729 if let Some(other_package) = &profile.package {
730 match &mut self.package {
731 Some(self_package) => {
732 for (spec, other_pkg_profile) in other_package {
733 match self_package.get_mut(spec) {
734 Some(p) => p.merge(other_pkg_profile),
735 None => {
736 self_package.insert(spec.clone(), other_pkg_profile.clone());
737 }
738 }
739 }
740 }
741 None => self.package = Some(other_package.clone()),
742 }
743 }
744
745 if let Some(other_bo) = &profile.build_override {
746 match &mut self.build_override {
747 Some(self_bo) => self_bo.merge(other_bo),
748 None => self.build_override = Some(other_bo.clone()),
749 }
750 }
751
752 if let Some(v) = &profile.inherits {
753 self.inherits = Some(*v);
754 }
755
756 if let Some(v) = &profile.dir_name {
757 self.dir_name = Some(*v);
758 }
759
760 if let Some(v) = &profile.strip {
761 self.strip = Some(v.clone());
762 }
763 }
764 }
765
766 /// A StringOrVec can be parsed from either a TOML string or array,
767 /// but is always stored as a vector.
768 #[derive(Clone, Debug, Serialize, Eq, PartialEq, PartialOrd, Ord)]
769 pub struct StringOrVec(Vec<String>);
770
771 impl<'de> de::Deserialize<'de> for StringOrVec {
772 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
773 where
774 D: de::Deserializer<'de>,
775 {
776 struct Visitor;
777
778 impl<'de> de::Visitor<'de> for Visitor {
779 type Value = StringOrVec;
780
781 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
782 formatter.write_str("string or list of strings")
783 }
784
785 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
786 where
787 E: de::Error,
788 {
789 Ok(StringOrVec(vec![s.to_string()]))
790 }
791
792 fn visit_seq<V>(self, v: V) -> Result<Self::Value, V::Error>
793 where
794 V: de::SeqAccess<'de>,
795 {
796 let seq = de::value::SeqAccessDeserializer::new(v);
797 Vec::deserialize(seq).map(StringOrVec)
798 }
799 }
800
801 deserializer.deserialize_any(Visitor)
802 }
803 }
804
805 impl StringOrVec {
806 pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, String> {
807 self.0.iter()
808 }
809 }
810
811 #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
812 #[serde(untagged, expecting = "expected a boolean or a string")]
813 pub enum StringOrBool {
814 String(String),
815 Bool(bool),
816 }
817
818 #[derive(PartialEq, Clone, Debug, Serialize)]
819 #[serde(untagged)]
820 pub enum VecStringOrBool {
821 VecString(Vec<String>),
822 Bool(bool),
823 }
824
825 impl<'de> de::Deserialize<'de> for VecStringOrBool {
826 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
827 where
828 D: de::Deserializer<'de>,
829 {
830 struct Visitor;
831
832 impl<'de> de::Visitor<'de> for Visitor {
833 type Value = VecStringOrBool;
834
835 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
836 formatter.write_str("a boolean or vector of strings")
837 }
838
839 fn visit_seq<V>(self, v: V) -> Result<Self::Value, V::Error>
840 where
841 V: de::SeqAccess<'de>,
842 {
843 let seq = de::value::SeqAccessDeserializer::new(v);
844 Vec::deserialize(seq).map(VecStringOrBool::VecString)
845 }
846
847 fn visit_bool<E>(self, b: bool) -> Result<Self::Value, E>
848 where
849 E: de::Error,
850 {
851 Ok(VecStringOrBool::Bool(b))
852 }
853 }
854
855 deserializer.deserialize_any(Visitor)
856 }
857 }
858
859 fn version_trim_whitespace<'de, D>(deserializer: D) -> Result<semver::Version, D::Error>
860 where
861 D: de::Deserializer<'de>,
862 {
863 struct Visitor;
864
865 impl<'de> de::Visitor<'de> for Visitor {
866 type Value = semver::Version;
867
868 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
869 formatter.write_str("SemVer version")
870 }
871
872 fn visit_str<E>(self, string: &str) -> Result<Self::Value, E>
873 where
874 E: de::Error,
875 {
876 string.trim().parse().map_err(de::Error::custom)
877 }
878 }
879
880 deserializer.deserialize_str(Visitor)
881 }
882
883 /// Represents the `package`/`project` sections of a `Cargo.toml`.
884 ///
885 /// Note that the order of the fields matters, since this is the order they
886 /// are serialized to a TOML file. For example, you cannot have values after
887 /// the field `metadata`, since it is a table and values cannot appear after
888 /// tables.
889 #[derive(Deserialize, Serialize, Clone, Debug)]
890 #[serde(rename_all = "kebab-case")]
891 pub struct TomlProject {
892 edition: Option<String>,
893 rust_version: Option<String>,
894 name: InternedString,
895 #[serde(deserialize_with = "version_trim_whitespace")]
896 version: semver::Version,
897 authors: Option<Vec<String>>,
898 build: Option<StringOrBool>,
899 metabuild: Option<StringOrVec>,
900 #[serde(rename = "default-target")]
901 default_target: Option<String>,
902 #[serde(rename = "forced-target")]
903 forced_target: Option<String>,
904 links: Option<String>,
905 exclude: Option<Vec<String>>,
906 include: Option<Vec<String>>,
907 publish: Option<VecStringOrBool>,
908 workspace: Option<String>,
909 im_a_teapot: Option<bool>,
910 autobins: Option<bool>,
911 autoexamples: Option<bool>,
912 autotests: Option<bool>,
913 autobenches: Option<bool>,
914 default_run: Option<String>,
915
916 // Package metadata.
917 description: Option<String>,
918 homepage: Option<String>,
919 documentation: Option<String>,
920 readme: Option<StringOrBool>,
921 keywords: Option<Vec<String>>,
922 categories: Option<Vec<String>>,
923 license: Option<String>,
924 license_file: Option<String>,
925 repository: Option<String>,
926 resolver: Option<String>,
927
928 // Note that this field must come last due to the way toml serialization
929 // works which requires tables to be emitted after all values.
930 metadata: Option<toml::Value>,
931 }
932
933 #[derive(Debug, Deserialize, Serialize)]
934 pub struct TomlWorkspace {
935 members: Option<Vec<String>>,
936 #[serde(rename = "default-members")]
937 default_members: Option<Vec<String>>,
938 exclude: Option<Vec<String>>,
939 resolver: Option<String>,
940
941 // Note that this field must come last due to the way toml serialization
942 // works which requires tables to be emitted after all values.
943 metadata: Option<toml::Value>,
944 }
945
946 impl TomlProject {
947 pub fn to_package_id(&self, source_id: SourceId) -> CargoResult<PackageId> {
948 PackageId::new(self.name, self.version.clone(), source_id)
949 }
950 }
951
952 struct Context<'a, 'b> {
953 deps: &'a mut Vec<Dependency>,
954 source_id: SourceId,
955 nested_paths: &'a mut Vec<PathBuf>,
956 config: &'b Config,
957 warnings: &'a mut Vec<String>,
958 platform: Option<Platform>,
959 root: &'a Path,
960 features: &'a Features,
961 }
962
963 impl TomlManifest {
964 /// Prepares the manifest for publishing.
965 // - Path and git components of dependency specifications are removed.
966 // - License path is updated to point within the package.
967 pub fn prepare_for_publish(
968 &self,
969 ws: &Workspace<'_>,
970 package_root: &Path,
971 ) -> CargoResult<TomlManifest> {
972 let config = ws.config();
973 let mut package = self
974 .package
975 .as_ref()
976 .or_else(|| self.project.as_ref())
977 .unwrap()
978 .clone();
979 package.workspace = None;
980 package.resolver = ws.resolve_behavior().to_manifest();
981 if let Some(license_file) = &package.license_file {
982 let license_path = Path::new(&license_file);
983 let abs_license_path = paths::normalize_path(&package_root.join(license_path));
984 if abs_license_path.strip_prefix(package_root).is_err() {
985 // This path points outside of the package root. `cargo package`
986 // will copy it into the root, so adjust the path to this location.
987 package.license_file = Some(
988 license_path
989 .file_name()
990 .unwrap()
991 .to_str()
992 .unwrap()
993 .to_string(),
994 );
995 }
996 }
997 let all = |_d: &TomlDependency| true;
998 return Ok(TomlManifest {
999 package: Some(package),
1000 project: None,
1001 profile: self.profile.clone(),
1002 lib: self.lib.clone(),
1003 bin: self.bin.clone(),
1004 example: self.example.clone(),
1005 test: self.test.clone(),
1006 bench: self.bench.clone(),
1007 dependencies: map_deps(config, self.dependencies.as_ref(), all)?,
1008 dev_dependencies: map_deps(
1009 config,
1010 self.dev_dependencies
1011 .as_ref()
1012 .or_else(|| self.dev_dependencies2.as_ref()),
1013 TomlDependency::is_version_specified,
1014 )?,
1015 dev_dependencies2: None,
1016 build_dependencies: map_deps(
1017 config,
1018 self.build_dependencies
1019 .as_ref()
1020 .or_else(|| self.build_dependencies2.as_ref()),
1021 all,
1022 )?,
1023 build_dependencies2: None,
1024 features: self.features.clone(),
1025 target: match self.target.as_ref().map(|target_map| {
1026 target_map
1027 .iter()
1028 .map(|(k, v)| {
1029 Ok((
1030 k.clone(),
1031 TomlPlatform {
1032 dependencies: map_deps(config, v.dependencies.as_ref(), all)?,
1033 dev_dependencies: map_deps(
1034 config,
1035 v.dev_dependencies
1036 .as_ref()
1037 .or_else(|| v.dev_dependencies2.as_ref()),
1038 TomlDependency::is_version_specified,
1039 )?,
1040 dev_dependencies2: None,
1041 build_dependencies: map_deps(
1042 config,
1043 v.build_dependencies
1044 .as_ref()
1045 .or_else(|| v.build_dependencies2.as_ref()),
1046 all,
1047 )?,
1048 build_dependencies2: None,
1049 },
1050 ))
1051 })
1052 .collect()
1053 }) {
1054 Some(Ok(v)) => Some(v),
1055 Some(Err(e)) => return Err(e),
1056 None => None,
1057 },
1058 replace: None,
1059 patch: None,
1060 workspace: None,
1061 badges: self.badges.clone(),
1062 cargo_features: self.cargo_features.clone(),
1063 });
1064
1065 fn map_deps(
1066 config: &Config,
1067 deps: Option<&BTreeMap<String, TomlDependency>>,
1068 filter: impl Fn(&TomlDependency) -> bool,
1069 ) -> CargoResult<Option<BTreeMap<String, TomlDependency>>> {
1070 let deps = match deps {
1071 Some(deps) => deps,
1072 None => return Ok(None),
1073 };
1074 let deps = deps
1075 .iter()
1076 .filter(|(_k, v)| filter(v))
1077 .map(|(k, v)| Ok((k.clone(), map_dependency(config, v)?)))
1078 .collect::<CargoResult<BTreeMap<_, _>>>()?;
1079 Ok(Some(deps))
1080 }
1081
1082 fn map_dependency(config: &Config, dep: &TomlDependency) -> CargoResult<TomlDependency> {
1083 match dep {
1084 TomlDependency::Detailed(d) => {
1085 let mut d = d.clone();
1086 // Path dependencies become crates.io deps.
1087 d.path.take();
1088 // Same with git dependencies.
1089 d.git.take();
1090 d.branch.take();
1091 d.tag.take();
1092 d.rev.take();
1093 // registry specifications are elaborated to the index URL
1094 if let Some(registry) = d.registry.take() {
1095 let src = SourceId::alt_registry(config, &registry)?;
1096 d.registry_index = Some(src.url().to_string());
1097 }
1098 Ok(TomlDependency::Detailed(d))
1099 }
1100 TomlDependency::Simple(s) => Ok(TomlDependency::Detailed(DetailedTomlDependency {
1101 version: Some(s.clone()),
1102 ..Default::default()
1103 })),
1104 }
1105 }
1106 }
1107
1108 pub fn to_real_manifest(
1109 me: &Rc<TomlManifest>,
1110 source_id: SourceId,
1111 package_root: &Path,
1112 config: &Config,
1113 ) -> CargoResult<(Manifest, Vec<PathBuf>)> {
1114 let mut nested_paths = vec![];
1115 let mut warnings = vec![];
1116 let mut errors = vec![];
1117
1118 // Parse features first so they will be available when parsing other parts of the TOML.
1119 let empty = Vec::new();
1120 let cargo_features = me.cargo_features.as_ref().unwrap_or(&empty);
1121 let features = Features::new(cargo_features, config, &mut warnings, source_id.is_path())?;
1122
1123 let project = me.project.as_ref().or_else(|| me.package.as_ref());
1124 let project = project.ok_or_else(|| anyhow!("no `package` section found"))?;
1125
1126 let package_name = project.name.trim();
1127 if package_name.is_empty() {
1128 bail!("package name cannot be an empty string")
1129 }
1130
1131 validate_package_name(package_name, "package name", "")?;
1132
1133 let pkgid = project.to_package_id(source_id)?;
1134
1135 let edition = if let Some(ref edition) = project.edition {
1136 features
1137 .require(Feature::edition())
1138 .with_context(|| "editions are unstable")?;
1139 edition
1140 .parse()
1141 .with_context(|| "failed to parse the `edition` key")?
1142 } else {
1143 Edition::Edition2015
1144 };
1145 if edition == Edition::Edition2021 {
1146 features.require(Feature::edition2021())?;
1147 } else if !edition.is_stable() {
1148 // Guard in case someone forgets to add .require()
1149 return Err(util::errors::internal(format!(
1150 "edition {} should be gated",
1151 edition
1152 )));
1153 }
1154
1155 let rust_version = if let Some(rust_version) = &project.rust_version {
1156 let req = match semver::VersionReq::parse(rust_version) {
1157 // Exclude semver operators like `^` and pre-release identifiers
1158 Ok(req) if rust_version.chars().all(|c| c.is_ascii_digit() || c == '.') => req,
1159 _ => bail!("`rust-version` must be a value like \"1.32\""),
1160 };
1161 if let Some(first_version) = edition.first_version() {
1162 let unsupported =
1163 semver::Version::new(first_version.major, first_version.minor - 1, 9999);
1164 if req.matches(&unsupported) {
1165 bail!(
1166 "rust-version {} is older than first version ({}) required by \
1167 the specified edition ({})",
1168 rust_version,
1169 first_version,
1170 edition,
1171 )
1172 }
1173 }
1174 Some(rust_version.clone())
1175 } else {
1176 None
1177 };
1178
1179 if project.metabuild.is_some() {
1180 features.require(Feature::metabuild())?;
1181 }
1182
1183 if project.resolver.is_some()
1184 || me
1185 .workspace
1186 .as_ref()
1187 .map_or(false, |ws| ws.resolver.is_some())
1188 {
1189 features.require(Feature::resolver())?;
1190 }
1191 let resolve_behavior = match (
1192 project.resolver.as_ref(),
1193 me.workspace.as_ref().and_then(|ws| ws.resolver.as_ref()),
1194 ) {
1195 (None, None) => None,
1196 (Some(s), None) | (None, Some(s)) => Some(ResolveBehavior::from_manifest(s)?),
1197 (Some(_), Some(_)) => {
1198 bail!("cannot specify `resolver` field in both `[workspace]` and `[package]`")
1199 }
1200 };
1201
1202 // If we have no lib at all, use the inferred lib, if available.
1203 // If we have a lib with a path, we're done.
1204 // If we have a lib with no path, use the inferred lib or else the package name.
1205 let targets = targets(
1206 &features,
1207 me,
1208 package_name,
1209 package_root,
1210 edition,
1211 &project.build,
1212 &project.metabuild,
1213 &mut warnings,
1214 &mut errors,
1215 )?;
1216
1217 if targets.is_empty() {
1218 debug!("manifest has no build targets");
1219 }
1220
1221 if let Err(e) = unique_build_targets(&targets, package_root) {
1222 warnings.push(format!(
1223 "file found to be present in multiple \
1224 build targets: {}",
1225 e
1226 ));
1227 }
1228
1229 if let Some(links) = &project.links {
1230 if !targets.iter().any(|t| t.is_custom_build()) {
1231 bail!(
1232 "package `{}` specifies that it links to `{}` but does not \
1233 have a custom build script",
1234 pkgid,
1235 links
1236 )
1237 }
1238 }
1239
1240 let mut deps = Vec::new();
1241 let replace;
1242 let patch;
1243
1244 {
1245 let mut cx = Context {
1246 deps: &mut deps,
1247 source_id,
1248 nested_paths: &mut nested_paths,
1249 config,
1250 warnings: &mut warnings,
1251 features: &features,
1252 platform: None,
1253 root: package_root,
1254 };
1255
1256 fn process_dependencies(
1257 cx: &mut Context<'_, '_>,
1258 new_deps: Option<&BTreeMap<String, TomlDependency>>,
1259 kind: Option<DepKind>,
1260 ) -> CargoResult<()> {
1261 let dependencies = match new_deps {
1262 Some(dependencies) => dependencies,
1263 None => return Ok(()),
1264 };
1265 for (n, v) in dependencies.iter() {
1266 let dep = v.to_dependency(n, cx, kind)?;
1267 validate_package_name(dep.name_in_toml().as_str(), "dependency name", "")?;
1268 cx.deps.push(dep);
1269 }
1270
1271 Ok(())
1272 }
1273
1274 // Collect the dependencies.
1275 process_dependencies(&mut cx, me.dependencies.as_ref(), None)?;
1276 let dev_deps = me
1277 .dev_dependencies
1278 .as_ref()
1279 .or_else(|| me.dev_dependencies2.as_ref());
1280 process_dependencies(&mut cx, dev_deps, Some(DepKind::Development))?;
1281 let build_deps = me
1282 .build_dependencies
1283 .as_ref()
1284 .or_else(|| me.build_dependencies2.as_ref());
1285 process_dependencies(&mut cx, build_deps, Some(DepKind::Build))?;
1286
1287 for (name, platform) in me.target.iter().flatten() {
1288 cx.platform = {
1289 let platform: Platform = name.parse()?;
1290 platform.check_cfg_attributes(&mut cx.warnings);
1291 Some(platform)
1292 };
1293 process_dependencies(&mut cx, platform.dependencies.as_ref(), None)?;
1294 let build_deps = platform
1295 .build_dependencies
1296 .as_ref()
1297 .or_else(|| platform.build_dependencies2.as_ref());
1298 process_dependencies(&mut cx, build_deps, Some(DepKind::Build))?;
1299 let dev_deps = platform
1300 .dev_dependencies
1301 .as_ref()
1302 .or_else(|| platform.dev_dependencies2.as_ref());
1303 process_dependencies(&mut cx, dev_deps, Some(DepKind::Development))?;
1304 }
1305
1306 replace = me.replace(&mut cx)?;
1307 patch = me.patch(&mut cx)?;
1308 }
1309
1310 {
1311 let mut names_sources = BTreeMap::new();
1312 for dep in &deps {
1313 let name = dep.name_in_toml();
1314 let prev = names_sources.insert(name.to_string(), dep.source_id());
1315 if prev.is_some() && prev != Some(dep.source_id()) {
1316 bail!(
1317 "Dependency '{}' has different source paths depending on the build \
1318 target. Each dependency must have a single canonical source path \
1319 irrespective of build target.",
1320 name
1321 );
1322 }
1323 }
1324 }
1325
1326 let exclude = project.exclude.clone().unwrap_or_default();
1327 let include = project.include.clone().unwrap_or_default();
1328 let empty_features = BTreeMap::new();
1329
1330 let summary = Summary::new(
1331 config,
1332 pkgid,
1333 deps,
1334 me.features.as_ref().unwrap_or(&empty_features),
1335 project.links.as_deref(),
1336 )?;
1337 let unstable = config.cli_unstable();
1338 summary.unstable_gate(unstable.namespaced_features, unstable.weak_dep_features)?;
1339
1340 let metadata = ManifestMetadata {
1341 description: project.description.clone(),
1342 homepage: project.homepage.clone(),
1343 documentation: project.documentation.clone(),
1344 readme: readme_for_project(package_root, project),
1345 authors: project.authors.clone().unwrap_or_default(),
1346 license: project.license.clone(),
1347 license_file: project.license_file.clone(),
1348 repository: project.repository.clone(),
1349 keywords: project.keywords.clone().unwrap_or_default(),
1350 categories: project.categories.clone().unwrap_or_default(),
1351 badges: me.badges.clone().unwrap_or_default(),
1352 links: project.links.clone(),
1353 };
1354
1355 let workspace_config = match (me.workspace.as_ref(), project.workspace.as_ref()) {
1356 (Some(config), None) => WorkspaceConfig::Root(WorkspaceRootConfig::new(
1357 package_root,
1358 &config.members,
1359 &config.default_members,
1360 &config.exclude,
1361 &config.metadata,
1362 )),
1363 (None, root) => WorkspaceConfig::Member {
1364 root: root.cloned(),
1365 },
1366 (Some(..), Some(..)) => bail!(
1367 "cannot configure both `package.workspace` and \
1368 `[workspace]`, only one can be specified"
1369 ),
1370 };
1371 let profiles = me.profile.clone();
1372 if let Some(profiles) = &profiles {
1373 profiles.validate(&features, &mut warnings)?;
1374 }
1375 let publish = match project.publish {
1376 Some(VecStringOrBool::VecString(ref vecstring)) => Some(vecstring.clone()),
1377 Some(VecStringOrBool::Bool(false)) => Some(vec![]),
1378 None | Some(VecStringOrBool::Bool(true)) => None,
1379 };
1380
1381 if summary.features().contains_key("default-features") {
1382 warnings.push(
1383 "`default-features = [\"..\"]` was found in [features]. \
1384 Did you mean to use `default = [\"..\"]`?"
1385 .to_string(),
1386 )
1387 }
1388
1389 if let Some(run) = &project.default_run {
1390 if !targets
1391 .iter()
1392 .filter(|t| t.is_bin())
1393 .any(|t| t.name() == run)
1394 {
1395 let suggestion =
1396 util::closest_msg(run, targets.iter().filter(|t| t.is_bin()), |t| t.name());
1397 bail!("default-run target `{}` not found{}", run, suggestion);
1398 }
1399 }
1400
1401 let default_kind = project
1402 .default_target
1403 .as_ref()
1404 .map(|t| CompileTarget::new(&*t))
1405 .transpose()?
1406 .map(CompileKind::Target);
1407 let forced_kind = project
1408 .forced_target
1409 .as_ref()
1410 .map(|t| CompileTarget::new(&*t))
1411 .transpose()?
1412 .map(CompileKind::Target);
1413
1414 let custom_metadata = project.metadata.clone();
1415 let mut manifest = Manifest::new(
1416 summary,
1417 default_kind,
1418 forced_kind,
1419 targets,
1420 exclude,
1421 include,
1422 project.links.clone(),
1423 metadata,
1424 custom_metadata,
1425 profiles,
1426 publish,
1427 replace,
1428 patch,
1429 workspace_config,
1430 features,
1431 edition,
1432 rust_version,
1433 project.im_a_teapot,
1434 project.default_run.clone(),
1435 Rc::clone(me),
1436 project.metabuild.clone().map(|sov| sov.0),
1437 resolve_behavior,
1438 );
1439 if project.license_file.is_some() && project.license.is_some() {
1440 manifest.warnings_mut().add_warning(
1441 "only one of `license` or \
1442 `license-file` is necessary"
1443 .to_string(),
1444 );
1445 }
1446 for warning in warnings {
1447 manifest.warnings_mut().add_warning(warning);
1448 }
1449 for error in errors {
1450 manifest.warnings_mut().add_critical_warning(error);
1451 }
1452
1453 manifest.feature_gate()?;
1454
1455 Ok((manifest, nested_paths))
1456 }
1457
1458 fn to_virtual_manifest(
1459 me: &Rc<TomlManifest>,
1460 source_id: SourceId,
1461 root: &Path,
1462 config: &Config,
1463 ) -> CargoResult<(VirtualManifest, Vec<PathBuf>)> {
1464 if me.project.is_some() {
1465 bail!("this virtual manifest specifies a [project] section, which is not allowed");
1466 }
1467 if me.package.is_some() {
1468 bail!("this virtual manifest specifies a [package] section, which is not allowed");
1469 }
1470 if me.lib.is_some() {
1471 bail!("this virtual manifest specifies a [lib] section, which is not allowed");
1472 }
1473 if me.bin.is_some() {
1474 bail!("this virtual manifest specifies a [[bin]] section, which is not allowed");
1475 }
1476 if me.example.is_some() {
1477 bail!("this virtual manifest specifies a [[example]] section, which is not allowed");
1478 }
1479 if me.test.is_some() {
1480 bail!("this virtual manifest specifies a [[test]] section, which is not allowed");
1481 }
1482 if me.bench.is_some() {
1483 bail!("this virtual manifest specifies a [[bench]] section, which is not allowed");
1484 }
1485 if me.dependencies.is_some() {
1486 bail!("this virtual manifest specifies a [dependencies] section, which is not allowed");
1487 }
1488 if me.dev_dependencies.is_some() || me.dev_dependencies2.is_some() {
1489 bail!("this virtual manifest specifies a [dev-dependencies] section, which is not allowed");
1490 }
1491 if me.build_dependencies.is_some() || me.build_dependencies2.is_some() {
1492 bail!("this virtual manifest specifies a [build-dependencies] section, which is not allowed");
1493 }
1494 if me.features.is_some() {
1495 bail!("this virtual manifest specifies a [features] section, which is not allowed");
1496 }
1497 if me.target.is_some() {
1498 bail!("this virtual manifest specifies a [target] section, which is not allowed");
1499 }
1500 if me.badges.is_some() {
1501 bail!("this virtual manifest specifies a [badges] section, which is not allowed");
1502 }
1503
1504 let mut nested_paths = Vec::new();
1505 let mut warnings = Vec::new();
1506 let mut deps = Vec::new();
1507 let empty = Vec::new();
1508 let cargo_features = me.cargo_features.as_ref().unwrap_or(&empty);
1509 let features = Features::new(cargo_features, config, &mut warnings, source_id.is_path())?;
1510
1511 let (replace, patch) = {
1512 let mut cx = Context {
1513 deps: &mut deps,
1514 source_id,
1515 nested_paths: &mut nested_paths,
1516 config,
1517 warnings: &mut warnings,
1518 platform: None,
1519 features: &features,
1520 root,
1521 };
1522 (me.replace(&mut cx)?, me.patch(&mut cx)?)
1523 };
1524 let profiles = me.profile.clone();
1525 if let Some(profiles) = &profiles {
1526 profiles.validate(&features, &mut warnings)?;
1527 }
1528 if me
1529 .workspace
1530 .as_ref()
1531 .map_or(false, |ws| ws.resolver.is_some())
1532 {
1533 features.require(Feature::resolver())?;
1534 }
1535 let resolve_behavior = me
1536 .workspace
1537 .as_ref()
1538 .and_then(|ws| ws.resolver.as_deref())
1539 .map(|r| ResolveBehavior::from_manifest(r))
1540 .transpose()?;
1541 let workspace_config = match me.workspace {
1542 Some(ref config) => WorkspaceConfig::Root(WorkspaceRootConfig::new(
1543 root,
1544 &config.members,
1545 &config.default_members,
1546 &config.exclude,
1547 &config.metadata,
1548 )),
1549 None => {
1550 bail!("virtual manifests must be configured with [workspace]");
1551 }
1552 };
1553 Ok((
1554 VirtualManifest::new(
1555 replace,
1556 patch,
1557 workspace_config,
1558 profiles,
1559 features,
1560 resolve_behavior,
1561 ),
1562 nested_paths,
1563 ))
1564 }
1565
1566 fn replace(&self, cx: &mut Context<'_, '_>) -> CargoResult<Vec<(PackageIdSpec, Dependency)>> {
1567 if self.patch.is_some() && self.replace.is_some() {
1568 bail!("cannot specify both [replace] and [patch]");
1569 }
1570 let mut replace = Vec::new();
1571 for (spec, replacement) in self.replace.iter().flatten() {
1572 let mut spec = PackageIdSpec::parse(spec).with_context(|| {
1573 format!(
1574 "replacements must specify a valid semver \
1575 version to replace, but `{}` does not",
1576 spec
1577 )
1578 })?;
1579 if spec.url().is_none() {
1580 spec.set_url(CRATES_IO_INDEX.parse().unwrap());
1581 }
1582
1583 if replacement.is_version_specified() {
1584 bail!(
1585 "replacements cannot specify a version \
1586 requirement, but found one for `{}`",
1587 spec
1588 );
1589 }
1590
1591 let mut dep = replacement.to_dependency(spec.name().as_str(), cx, None)?;
1592 {
1593 let version = spec.version().ok_or_else(|| {
1594 anyhow!(
1595 "replacements must specify a version \
1596 to replace, but `{}` does not",
1597 spec
1598 )
1599 })?;
1600 dep.set_version_req(VersionReq::exact(version));
1601 }
1602 replace.push((spec, dep));
1603 }
1604 Ok(replace)
1605 }
1606
1607 fn patch(&self, cx: &mut Context<'_, '_>) -> CargoResult<HashMap<Url, Vec<Dependency>>> {
1608 let mut patch = HashMap::new();
1609 for (url, deps) in self.patch.iter().flatten() {
1610 let url = match &url[..] {
1611 CRATES_IO_REGISTRY => CRATES_IO_INDEX.parse().unwrap(),
1612 _ => cx
1613 .config
1614 .get_registry_index(url)
1615 .or_else(|_| url.into_url())
1616 .with_context(|| {
1617 format!("[patch] entry `{}` should be a URL or registry name", url)
1618 })?,
1619 };
1620 patch.insert(
1621 url,
1622 deps.iter()
1623 .map(|(name, dep)| dep.to_dependency(name, cx, None))
1624 .collect::<CargoResult<Vec<_>>>()?,
1625 );
1626 }
1627 Ok(patch)
1628 }
1629
1630 /// Returns the path to the build script if one exists for this crate.
1631 fn maybe_custom_build(
1632 &self,
1633 build: &Option<StringOrBool>,
1634 package_root: &Path,
1635 ) -> Option<PathBuf> {
1636 let build_rs = package_root.join("build.rs");
1637 match *build {
1638 // Explicitly no build script.
1639 Some(StringOrBool::Bool(false)) => None,
1640 Some(StringOrBool::Bool(true)) => Some(build_rs),
1641 Some(StringOrBool::String(ref s)) => Some(PathBuf::from(s)),
1642 None => {
1643 // If there is a `build.rs` file next to the `Cargo.toml`, assume it is
1644 // a build script.
1645 if build_rs.is_file() {
1646 Some(build_rs)
1647 } else {
1648 None
1649 }
1650 }
1651 }
1652 }
1653
1654 pub fn has_profiles(&self) -> bool {
1655 self.profile.is_some()
1656 }
1657
1658 pub fn features(&self) -> Option<&BTreeMap<InternedString, Vec<InternedString>>> {
1659 self.features.as_ref()
1660 }
1661 }
1662
1663 /// Returns the name of the README file for a `TomlProject`.
1664 fn readme_for_project(package_root: &Path, project: &TomlProject) -> Option<String> {
1665 match &project.readme {
1666 None => default_readme_from_package_root(package_root),
1667 Some(value) => match value {
1668 StringOrBool::Bool(false) => None,
1669 StringOrBool::Bool(true) => Some("README.md".to_string()),
1670 StringOrBool::String(v) => Some(v.clone()),
1671 },
1672 }
1673 }
1674
1675 const DEFAULT_README_FILES: [&str; 3] = ["README.md", "README.txt", "README"];
1676
1677 /// Checks if a file with any of the default README file names exists in the package root.
1678 /// If so, returns a `String` representing that name.
1679 fn default_readme_from_package_root(package_root: &Path) -> Option<String> {
1680 for &readme_filename in DEFAULT_README_FILES.iter() {
1681 if package_root.join(readme_filename).is_file() {
1682 return Some(readme_filename.to_string());
1683 }
1684 }
1685
1686 None
1687 }
1688
1689 /// Checks a list of build targets, and ensures the target names are unique within a vector.
1690 /// If not, the name of the offending build target is returned.
1691 fn unique_build_targets(targets: &[Target], package_root: &Path) -> Result<(), String> {
1692 let mut seen = HashSet::new();
1693 for target in targets {
1694 if let TargetSourcePath::Path(path) = target.src_path() {
1695 let full = package_root.join(path);
1696 if !seen.insert(full.clone()) {
1697 return Err(full.display().to_string());
1698 }
1699 }
1700 }
1701 Ok(())
1702 }
1703
1704 impl<P: ResolveToPath> TomlDependency<P> {
1705 pub(crate) fn to_dependency_split(
1706 &self,
1707 name: &str,
1708 source_id: SourceId,
1709 nested_paths: &mut Vec<PathBuf>,
1710 config: &Config,
1711 warnings: &mut Vec<String>,
1712 platform: Option<Platform>,
1713 root: &Path,
1714 features: &Features,
1715 kind: Option<DepKind>,
1716 ) -> CargoResult<Dependency> {
1717 self.to_dependency(
1718 name,
1719 &mut Context {
1720 deps: &mut Vec::new(),
1721 source_id,
1722 nested_paths,
1723 config,
1724 warnings,
1725 platform,
1726 root,
1727 features,
1728 },
1729 kind,
1730 )
1731 }
1732
1733 fn to_dependency(
1734 &self,
1735 name: &str,
1736 cx: &mut Context<'_, '_>,
1737 kind: Option<DepKind>,
1738 ) -> CargoResult<Dependency> {
1739 match *self {
1740 TomlDependency::Simple(ref version) => DetailedTomlDependency::<P> {
1741 version: Some(version.clone()),
1742 ..Default::default()
1743 }
1744 .to_dependency(name, cx, kind),
1745 TomlDependency::Detailed(ref details) => details.to_dependency(name, cx, kind),
1746 }
1747 }
1748
1749 fn is_version_specified(&self) -> bool {
1750 match self {
1751 TomlDependency::Detailed(d) => d.version.is_some(),
1752 TomlDependency::Simple(..) => true,
1753 }
1754 }
1755 }
1756
1757 impl<P: ResolveToPath> DetailedTomlDependency<P> {
1758 fn to_dependency(
1759 &self,
1760 name_in_toml: &str,
1761 cx: &mut Context<'_, '_>,
1762 kind: Option<DepKind>,
1763 ) -> CargoResult<Dependency> {
1764 if self.version.is_none() && self.path.is_none() && self.git.is_none() {
1765 bail!(
1766 "dependency ({}) specified without \
1767 providing a local path, Git repository, or version to use.",
1768 name_in_toml
1769 );
1770 }
1771
1772 if let Some(version) = &self.version {
1773 if version.contains('+') {
1774 cx.warnings.push(format!(
1775 "version requirement `{}` for dependency `{}` \
1776 includes semver metadata which will be ignored, removing the \
1777 metadata is recommended to avoid confusion",
1778 version, name_in_toml
1779 ));
1780 }
1781 }
1782
1783 if self.git.is_none() {
1784 let git_only_keys = [
1785 (&self.branch, "branch"),
1786 (&self.tag, "tag"),
1787 (&self.rev, "rev"),
1788 ];
1789
1790 for &(key, key_name) in &git_only_keys {
1791 if key.is_some() {
1792 bail!(
1793 "key `{}` is ignored for dependency ({}).",
1794 key_name,
1795 name_in_toml
1796 );
1797 }
1798 }
1799 }
1800
1801 // Early detection of potentially misused feature syntax
1802 // instead of generating a "feature not found" error.
1803 if let Some(features) = &self.features {
1804 for feature in features {
1805 if feature.contains('/') {
1806 bail!(
1807 "feature `{}` in dependency `{}` is not allowed to contain slashes\n\
1808 If you want to enable features of a transitive dependency, \
1809 the direct dependency needs to re-export those features from \
1810 the `[features]` table.",
1811 feature,
1812 name_in_toml
1813 );
1814 }
1815 if feature.starts_with("dep:") {
1816 bail!(
1817 "feature `{}` in dependency `{}` is not allowed to use explicit \
1818 `dep:` syntax\n\
1819 If you want to enable an optional dependency, specify the name \
1820 of the optional dependency without the `dep:` prefix, or specify \
1821 a feature from the dependency's `[features]` table that enables \
1822 the optional dependency.",
1823 feature,
1824 name_in_toml
1825 );
1826 }
1827 }
1828 }
1829
1830 let new_source_id = match (
1831 self.git.as_ref(),
1832 self.path.as_ref(),
1833 self.registry.as_ref(),
1834 self.registry_index.as_ref(),
1835 ) {
1836 (Some(_), _, Some(_), _) | (Some(_), _, _, Some(_)) => bail!(
1837 "dependency ({}) specification is ambiguous. \
1838 Only one of `git` or `registry` is allowed.",
1839 name_in_toml
1840 ),
1841 (_, _, Some(_), Some(_)) => bail!(
1842 "dependency ({}) specification is ambiguous. \
1843 Only one of `registry` or `registry-index` is allowed.",
1844 name_in_toml
1845 ),
1846 (Some(git), maybe_path, _, _) => {
1847 if maybe_path.is_some() {
1848 bail!(
1849 "dependency ({}) specification is ambiguous. \
1850 Only one of `git` or `path` is allowed.",
1851 name_in_toml
1852 );
1853 }
1854
1855 let n_details = [&self.branch, &self.tag, &self.rev]
1856 .iter()
1857 .filter(|d| d.is_some())
1858 .count();
1859
1860 if n_details > 1 {
1861 bail!(
1862 "dependency ({}) specification is ambiguous. \
1863 Only one of `branch`, `tag` or `rev` is allowed.",
1864 name_in_toml
1865 );
1866 }
1867
1868 let reference = self
1869 .branch
1870 .clone()
1871 .map(GitReference::Branch)
1872 .or_else(|| self.tag.clone().map(GitReference::Tag))
1873 .or_else(|| self.rev.clone().map(GitReference::Rev))
1874 .unwrap_or(GitReference::DefaultBranch);
1875 let loc = git.into_url()?;
1876
1877 if let Some(fragment) = loc.fragment() {
1878 let msg = format!(
1879 "URL fragment `#{}` in git URL is ignored for dependency ({}). \
1880 If you were trying to specify a specific git revision, \
1881 use `rev = \"{}\"` in the dependency declaration.",
1882 fragment, name_in_toml, fragment
1883 );
1884 cx.warnings.push(msg)
1885 }
1886
1887 SourceId::for_git(&loc, reference)?
1888 }
1889 (None, Some(path), _, _) => {
1890 let path = path.resolve(cx.config);
1891 cx.nested_paths.push(path.clone());
1892 // If the source ID for the package we're parsing is a path
1893 // source, then we normalize the path here to get rid of
1894 // components like `..`.
1895 //
1896 // The purpose of this is to get a canonical ID for the package
1897 // that we're depending on to ensure that builds of this package
1898 // always end up hashing to the same value no matter where it's
1899 // built from.
1900 if cx.source_id.is_path() {
1901 let path = cx.root.join(path);
1902 let path = paths::normalize_path(&path);
1903 SourceId::for_path(&path)?
1904 } else {
1905 cx.source_id
1906 }
1907 }
1908 (None, None, Some(registry), None) => SourceId::alt_registry(cx.config, registry)?,
1909 (None, None, None, Some(registry_index)) => {
1910 let url = registry_index.into_url()?;
1911 SourceId::for_registry(&url)?
1912 }
1913 (None, None, None, None) => SourceId::crates_io(cx.config)?,
1914 };
1915
1916 let (pkg_name, explicit_name_in_toml) = match self.package {
1917 Some(ref s) => (&s[..], Some(name_in_toml)),
1918 None => (name_in_toml, None),
1919 };
1920
1921 let version = self.version.as_deref();
1922 let mut dep = Dependency::parse(pkg_name, version, new_source_id)?;
1923 dep.set_features(self.features.iter().flatten())
1924 .set_default_features(
1925 self.default_features
1926 .or(self.default_features2)
1927 .unwrap_or(true),
1928 )
1929 .set_optional(self.optional.unwrap_or(false))
1930 .set_platform(cx.platform.clone());
1931 if let Some(registry) = &self.registry {
1932 let registry_id = SourceId::alt_registry(cx.config, registry)?;
1933 dep.set_registry_id(registry_id);
1934 }
1935 if let Some(registry_index) = &self.registry_index {
1936 let url = registry_index.into_url()?;
1937 let registry_id = SourceId::for_registry(&url)?;
1938 dep.set_registry_id(registry_id);
1939 }
1940
1941 if let Some(kind) = kind {
1942 dep.set_kind(kind);
1943 }
1944 if let Some(name_in_toml) = explicit_name_in_toml {
1945 cx.features.require(Feature::rename_dependency())?;
1946 dep.set_explicit_name_in_toml(name_in_toml);
1947 }
1948
1949 if let Some(p) = self.public {
1950 cx.features.require(Feature::public_dependency())?;
1951
1952 if dep.kind() != DepKind::Normal {
1953 bail!("'public' specifier can only be used on regular dependencies, not {:?} dependencies", dep.kind());
1954 }
1955
1956 dep.set_public(p);
1957 }
1958 Ok(dep)
1959 }
1960 }
1961
1962 #[derive(Default, Serialize, Deserialize, Debug, Clone)]
1963 struct TomlTarget {
1964 name: Option<String>,
1965
1966 // The intention was to only accept `crate-type` here but historical
1967 // versions of Cargo also accepted `crate_type`, so look for both.
1968 #[serde(rename = "crate-type")]
1969 crate_type: Option<Vec<String>>,
1970 #[serde(rename = "crate_type")]
1971 crate_type2: Option<Vec<String>>,
1972
1973 path: Option<PathValue>,
1974 // Note that `filename` is used for the cargo-feature `different_binary_name`
1975 filename: Option<String>,
1976 test: Option<bool>,
1977 doctest: Option<bool>,
1978 bench: Option<bool>,
1979 doc: Option<bool>,
1980 plugin: Option<bool>,
1981 #[serde(rename = "proc-macro")]
1982 proc_macro_raw: Option<bool>,
1983 #[serde(rename = "proc_macro")]
1984 proc_macro_raw2: Option<bool>,
1985 harness: Option<bool>,
1986 #[serde(rename = "required-features")]
1987 required_features: Option<Vec<String>>,
1988 edition: Option<String>,
1989 }
1990
1991 #[derive(Clone)]
1992 struct PathValue(PathBuf);
1993
1994 impl<'de> de::Deserialize<'de> for PathValue {
1995 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1996 where
1997 D: de::Deserializer<'de>,
1998 {
1999 Ok(PathValue(String::deserialize(deserializer)?.into()))
2000 }
2001 }
2002
2003 impl ser::Serialize for PathValue {
2004 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2005 where
2006 S: ser::Serializer,
2007 {
2008 self.0.serialize(serializer)
2009 }
2010 }
2011
2012 /// Corresponds to a `target` entry, but `TomlTarget` is already used.
2013 #[derive(Serialize, Deserialize, Debug)]
2014 struct TomlPlatform {
2015 dependencies: Option<BTreeMap<String, TomlDependency>>,
2016 #[serde(rename = "build-dependencies")]
2017 build_dependencies: Option<BTreeMap<String, TomlDependency>>,
2018 #[serde(rename = "build_dependencies")]
2019 build_dependencies2: Option<BTreeMap<String, TomlDependency>>,
2020 #[serde(rename = "dev-dependencies")]
2021 dev_dependencies: Option<BTreeMap<String, TomlDependency>>,
2022 #[serde(rename = "dev_dependencies")]
2023 dev_dependencies2: Option<BTreeMap<String, TomlDependency>>,
2024 }
2025
2026 impl TomlTarget {
2027 fn new() -> TomlTarget {
2028 TomlTarget::default()
2029 }
2030
2031 fn name(&self) -> String {
2032 match self.name {
2033 Some(ref name) => name.clone(),
2034 None => panic!("target name is required"),
2035 }
2036 }
2037
2038 fn proc_macro(&self) -> Option<bool> {
2039 self.proc_macro_raw.or(self.proc_macro_raw2).or_else(|| {
2040 if let Some(types) = self.crate_types() {
2041 if types.contains(&"proc-macro".to_string()) {
2042 return Some(true);
2043 }
2044 }
2045 None
2046 })
2047 }
2048
2049 fn crate_types(&self) -> Option<&Vec<String>> {
2050 self.crate_type
2051 .as_ref()
2052 .or_else(|| self.crate_type2.as_ref())
2053 }
2054 }
2055
2056 impl fmt::Debug for PathValue {
2057 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2058 self.0.fmt(f)
2059 }
2060 }