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