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