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