1use cargo_util::collections::HashMap;
2use cargo_util_terminal::report::{AnnotationKind, Group, Level, Snippet};
3use std::borrow::Cow;
4use std::cell::OnceCell;
5use std::collections::{BTreeMap, BTreeSet};
6use std::ffi::OsStr;
7use std::path::{Path, PathBuf};
8use std::rc::Rc;
9use std::str::{self, FromStr};
10use std::sync::Arc;
11
12use crate::AlreadyPrintedError;
13use crate::workspace::summary::MissingDependencyError;
14use anyhow::{Context as _, anyhow, bail};
15use cargo_platform::Platform;
16use cargo_util::paths;
17use cargo_util_schemas::manifest::{
18 self, FeatureDefinition, FeatureMetadata, FeatureName, PackageName, PathBaseName,
19 TomlDependency, TomlDetailedDependency, TomlManifest, TomlPackageBuild, TomlWorkspace,
20};
21use cargo_util_schemas::manifest::{RustVersion, StringOrBool};
22use itertools::Itertools;
23use pathdiff::diff_paths;
24use url::Url;
25
26use crate::compiler::{CompileKind, CompileTarget};
27use crate::context::{ConfigRelativePath, ConfigRelativePathResolveExt, TOP_LEVEL_CONFIG_KEYS};
28use crate::diagnostics::cwd_rel_path;
29use crate::diagnostics::get_key_value_span;
30use crate::resolver::ResolveBehavior;
31use crate::sources::{CRATES_IO_INDEX, CRATES_IO_REGISTRY};
32use crate::util::errors::{CargoResult, ManifestError};
33use crate::util::interning::InternedString;
34use crate::util::{self, GlobalContext, IntoUrl, OnceExt, OptVersionReq};
35use crate::workspace::dependency::{Artifact, ArtifactTarget, DepKind};
36use crate::workspace::manifest::{ManifestMetadata, TargetSourcePath};
37use crate::workspace::{
38 CliUnstable, FeatureValue, Patch, PatchLocation, find_workspace_root, resolve_relative_path,
39};
40use crate::workspace::{Dependency, Manifest, Package, PackageId, Summary, Target};
41use crate::workspace::{Edition, EitherManifest, Feature, Features, VirtualManifest, Workspace};
42use crate::workspace::{
43 GitReference, PackageIdSpec, SourceId, WorkspaceConfig, WorkspaceRootConfig,
44};
45
46mod embedded;
47mod targets;
48
49use self::targets::to_targets;
50
51pub fn is_embedded(path: &Path) -> bool {
53 let ext = path.extension();
54 ext == Some(OsStr::new("rs")) || ext.is_none()
55}
56
57#[tracing::instrument(skip(gctx))]
66pub fn read_manifest(
67 path: &Path,
68 source_id: SourceId,
69 gctx: &GlobalContext,
70) -> CargoResult<EitherManifest> {
71 let mut warnings = Default::default();
72 let mut errors = Default::default();
73
74 let is_embedded = is_embedded(path);
75 let contents = read_toml_string(path, is_embedded, gctx)?;
76 let document = parse_document(&contents)
77 .map_err(|e| emit_toml_diagnostic(e.into(), &contents, path, gctx))?;
78 let original_toml = deserialize_toml(&document)
79 .map_err(|e| emit_toml_diagnostic(e.into(), &contents, path, gctx))?;
80
81 let document = if source_id.is_path() {
82 Some(make_document_owned(document))
83 } else {
84 None
85 };
86
87 let mut manifest = (|| {
88 let empty = Vec::new();
89 let cargo_features = original_toml.cargo_features.as_ref().unwrap_or(&empty);
90 let features = Features::new(cargo_features, gctx, &mut warnings, source_id.is_path())?;
91 let workspace_config =
92 to_workspace_config(&original_toml, path, is_embedded, &mut warnings, &features)?;
93 if let WorkspaceConfig::Root(ws_root_config) = &workspace_config {
94 let package_root = path.parent().unwrap();
95 gctx.ws_roots()
96 .insert(package_root.to_owned(), ws_root_config.clone());
97 }
98 let normalized_toml = normalize_toml(
99 &original_toml,
100 &features,
101 &workspace_config,
102 path,
103 is_embedded,
104 gctx,
105 &mut warnings,
106 &mut errors,
107 )?;
108
109 if normalized_toml.package().is_some() {
110 to_real_manifest(
111 Some(contents),
112 document,
113 original_toml,
114 normalized_toml,
115 features,
116 workspace_config,
117 source_id,
118 path,
119 is_embedded,
120 gctx,
121 &mut warnings,
122 &mut errors,
123 )
124 .map(EitherManifest::Real)
125 } else if normalized_toml.workspace.is_some() {
126 assert!(!is_embedded);
127 to_virtual_manifest(
128 Some(contents),
129 document,
130 original_toml,
131 normalized_toml,
132 features,
133 workspace_config,
134 source_id,
135 path,
136 gctx,
137 &mut warnings,
138 &mut errors,
139 )
140 .map(EitherManifest::Virtual)
141 } else {
142 anyhow::bail!("manifest is missing either a `[package]` or a `[workspace]`")
143 }
144 })()
145 .map_err(|err| {
146 ManifestError::new(
147 err.context(format!("failed to parse manifest at `{}`", path.display())),
148 path.into(),
149 )
150 })?;
151
152 for warning in warnings {
153 manifest.warnings_mut().add_warning(warning);
154 }
155 for error in errors {
156 manifest.warnings_mut().add_critical_warning(error);
157 }
158
159 Ok(manifest)
160}
161
162fn make_document_owned(
165 mut document: toml::Spanned<toml::de::DeTable<'_>>,
166) -> toml::Spanned<toml::de::DeTable<'static>> {
167 document.get_mut().make_owned();
168 unsafe {
171 std::mem::transmute::<
172 toml::Spanned<toml::de::DeTable<'_>>,
173 toml::Spanned<toml::de::DeTable<'static>>,
174 >(document)
175 }
176}
177
178#[tracing::instrument(skip_all)]
179fn read_toml_string(path: &Path, is_embedded: bool, gctx: &GlobalContext) -> CargoResult<String> {
180 let mut contents = paths::read(path).map_err(|err| ManifestError::new(err, path.into()))?;
181 if is_embedded {
182 if !gctx.cli_unstable().script {
183 anyhow::bail!("parsing `{}` requires `-Zscript`", path.display());
184 }
185 contents = embedded::expand_manifest(&contents)
186 .map_err(|e| emit_frontmatter_diagnostic(e, &contents, path, gctx))?;
187 }
188 Ok(contents)
189}
190
191#[tracing::instrument(skip_all)]
192fn parse_document(contents: &str) -> Result<toml::Spanned<toml::de::DeTable<'_>>, toml::de::Error> {
193 toml::de::DeTable::parse(&contents)
194}
195
196#[tracing::instrument(skip_all)]
197fn deserialize_toml(
198 document: &toml::Spanned<toml::de::DeTable<'_>>,
199) -> Result<manifest::TomlManifest, toml::de::Error> {
200 let mut unused = BTreeSet::new();
201 let deserializer = toml::de::Deserializer::from(document.clone());
202 let mut document: manifest::TomlManifest = serde_ignored::deserialize(deserializer, |path| {
203 let mut key = String::new();
204 stringify(&mut key, &path);
205 unused.insert(key);
206 })?;
207 document._unused_keys = unused;
208 Ok(document)
209}
210
211fn stringify(dst: &mut String, path: &serde_ignored::Path<'_>) {
212 use serde_ignored::Path;
213
214 match *path {
215 Path::Root => {}
216 Path::Seq { parent, index } => {
217 stringify(dst, parent);
218 if !dst.is_empty() {
219 dst.push('.');
220 }
221 dst.push_str(&index.to_string());
222 }
223 Path::Map { parent, ref key } => {
224 stringify(dst, parent);
225 if !dst.is_empty() {
226 dst.push('.');
227 }
228 dst.push_str(key);
229 }
230 Path::Some { parent }
231 | Path::NewtypeVariant { parent }
232 | Path::NewtypeStruct { parent } => stringify(dst, parent),
233 }
234}
235
236fn to_workspace_config(
237 original_toml: &manifest::TomlManifest,
238 manifest_file: &Path,
239 is_embedded: bool,
240 warnings: &mut Vec<String>,
241 features: &Features,
242) -> CargoResult<WorkspaceConfig> {
243 if is_embedded {
244 let ws_root_config = to_workspace_root_config(&TomlWorkspace::default(), manifest_file);
245 return Ok(WorkspaceConfig::Root(ws_root_config));
246 }
247 let workspace_config = match (
248 original_toml.workspace.as_ref(),
249 original_toml.package().and_then(|p| p.workspace.as_ref()),
250 ) {
251 (Some(toml_config), None) => {
252 verify_lints(toml_config.lints.as_ref(), warnings)?;
253 if let Some(ws_deps) = &toml_config.dependencies {
254 for (name, dep) in ws_deps {
255 if let TomlDependency::Detailed(d) = dep {
256 if d.builtin {
257 features
258 .require(Feature::builtin_dependencies())
259 .with_context(|| {
260 format!("resolving workspace dependency `{name}`")
261 })?;
262 }
263 }
264 if dep.is_optional() {
265 bail!("{name} is optional, but workspace dependencies cannot be optional",);
266 }
267 if dep.is_public() {
268 bail!("{name} is public, but workspace dependencies cannot be public",);
269 }
270 }
271
272 for (name, dep) in ws_deps {
273 unused_dep_keys(name, "workspace.dependencies", dep.unused_keys(), warnings);
274 }
275 }
276 let ws_root_config = to_workspace_root_config(toml_config, manifest_file);
277 WorkspaceConfig::Root(ws_root_config)
278 }
279 (None, root) => WorkspaceConfig::Member {
280 root: root.cloned(),
281 },
282 (Some(..), Some(..)) => bail!(
283 "cannot configure both `package.workspace` and \
284 `[workspace]`, only one can be specified"
285 ),
286 };
287 Ok(workspace_config)
288}
289
290fn to_workspace_root_config(
291 normalized_toml: &manifest::TomlWorkspace,
292 manifest_file: &Path,
293) -> WorkspaceRootConfig {
294 let package_root = manifest_file.parent().unwrap();
295 let inheritable = InheritableFields {
296 package: normalized_toml.package.clone(),
297 dependencies: normalized_toml.dependencies.clone(),
298 lints: normalized_toml.lints.clone(),
299 _ws_root: package_root.to_owned(),
300 };
301 let ws_root_config = WorkspaceRootConfig::new(
302 package_root,
303 &normalized_toml.members,
304 &normalized_toml.default_members,
305 &normalized_toml.exclude,
306 &Some(inheritable),
307 &normalized_toml.metadata,
308 );
309 ws_root_config
310}
311
312#[tracing::instrument(skip_all)]
314fn normalize_toml(
315 original_toml: &manifest::TomlManifest,
316 features: &Features,
317 workspace_config: &WorkspaceConfig,
318 manifest_file: &Path,
319 is_embedded: bool,
320 gctx: &GlobalContext,
321 warnings: &mut Vec<String>,
322 errors: &mut Vec<String>,
323) -> CargoResult<manifest::TomlManifest> {
324 let package_root = manifest_file.parent().unwrap();
325
326 let inherit_cell: OnceCell<InheritableFields> = OnceCell::new();
327 let inherit = || {
328 inherit_cell
329 .try_borrow_with(|| load_inheritable_fields(gctx, manifest_file, &workspace_config))
330 };
331 let workspace_root = || inherit().map(|fields| fields.ws_root().as_path());
332
333 let mut normalized_toml = manifest::TomlManifest {
334 cargo_features: original_toml.cargo_features.clone(),
335 package: None,
336 project: None,
337 badges: None,
338 features: None,
339 lib: None,
340 bin: None,
341 example: None,
342 test: None,
343 bench: None,
344 dependencies: None,
345 dev_dependencies: None,
346 dev_dependencies2: None,
347 build_dependencies: None,
348 build_dependencies2: None,
349 target: None,
350 lints: None,
351 hints: None,
352 workspace: original_toml.workspace.clone().or_else(|| {
353 is_embedded.then(manifest::TomlWorkspace::default)
355 }),
356 profile: original_toml.profile.clone(),
357 patch: normalize_patch(
358 gctx,
359 original_toml.patch.as_ref(),
360 &workspace_root,
361 features,
362 )?,
363 replace: original_toml.replace.clone(),
364 _unused_keys: Default::default(),
365 };
366
367 if let Some(original_package) = original_toml.package().map(Cow::Borrowed).or_else(|| {
368 if is_embedded {
369 Some(Cow::Owned(Box::new(manifest::TomlPackage::default())))
370 } else {
371 None
372 }
373 }) {
374 let normalized_package = normalize_package_toml(
375 &original_package,
376 manifest_file,
377 is_embedded,
378 gctx,
379 &inherit,
380 features,
381 )?;
382 let package_name = &normalized_package
383 .normalized_name()
384 .expect("previously normalized")
385 .clone();
386 let edition = normalized_package
387 .normalized_edition()
388 .expect("previously normalized")
389 .map_or(Edition::default(), |e| {
390 Edition::from_str(&e).unwrap_or_default()
391 });
392 normalized_toml.package = Some(normalized_package);
393
394 normalized_toml.features = normalize_features(original_toml.features.as_ref())?;
395
396 let auto_embedded = is_embedded.then_some(false);
397 normalized_toml.lib = targets::normalize_lib(
398 original_toml.lib.as_ref(),
399 package_root,
400 package_name,
401 edition,
402 original_package.autolib.or(auto_embedded),
403 warnings,
404 )?;
405 let original_toml_bin = if is_embedded {
406 let name = package_name.as_ref().to_owned();
407 let manifest_file_name = manifest_file
408 .file_name()
409 .expect("file name enforced previously");
410 let path = PathBuf::from(manifest_file_name);
411 Cow::Owned(Some(vec![manifest::TomlBinTarget {
412 name: Some(name),
413 crate_type: None,
414 crate_type2: None,
415 path: Some(manifest::PathValue(path)),
416 filename: None,
417 test: None,
418 doctest: None,
419 bench: None,
420 doc: None,
421 doc_scrape_examples: None,
422 proc_macro: None,
423 proc_macro2: None,
424 harness: None,
425 required_features: None,
426 edition: None,
427 }]))
428 } else {
429 Cow::Borrowed(&original_toml.bin)
430 };
431 normalized_toml.bin = Some(targets::normalize_bins(
432 original_toml_bin.as_ref().as_ref(),
433 package_root,
434 package_name,
435 edition,
436 original_package.autobins.or(auto_embedded),
437 warnings,
438 errors,
439 normalized_toml.lib.is_some(),
440 )?);
441 normalized_toml.example = Some(targets::normalize_examples(
442 original_toml.example.as_ref(),
443 package_root,
444 edition,
445 original_package.autoexamples.or(auto_embedded),
446 warnings,
447 errors,
448 )?);
449 normalized_toml.test = Some(targets::normalize_tests(
450 original_toml.test.as_ref(),
451 package_root,
452 edition,
453 original_package.autotests.or(auto_embedded),
454 warnings,
455 errors,
456 )?);
457 normalized_toml.bench = Some(targets::normalize_benches(
458 original_toml.bench.as_ref(),
459 package_root,
460 edition,
461 original_package.autobenches.or(auto_embedded),
462 warnings,
463 errors,
464 )?);
465
466 normalized_toml.dependencies = normalize_dependencies(
467 gctx,
468 edition,
469 &features,
470 original_toml.dependencies.as_ref(),
471 DepKind::Normal,
472 &inherit,
473 &workspace_root,
474 package_root,
475 warnings,
476 )?;
477 deprecated_underscore(
478 &original_toml.dev_dependencies2,
479 &original_toml.dev_dependencies,
480 "dev-dependencies",
481 package_name,
482 "package",
483 edition,
484 warnings,
485 )?;
486 normalized_toml.dev_dependencies = normalize_dependencies(
487 gctx,
488 edition,
489 &features,
490 original_toml.dev_dependencies(),
491 DepKind::Development,
492 &inherit,
493 &workspace_root,
494 package_root,
495 warnings,
496 )?;
497 deprecated_underscore(
498 &original_toml.build_dependencies2,
499 &original_toml.build_dependencies,
500 "build-dependencies",
501 package_name,
502 "package",
503 edition,
504 warnings,
505 )?;
506 normalized_toml.build_dependencies = normalize_dependencies(
507 gctx,
508 edition,
509 &features,
510 original_toml.build_dependencies(),
511 DepKind::Build,
512 &inherit,
513 &workspace_root,
514 package_root,
515 warnings,
516 )?;
517 let mut normalized_target = BTreeMap::new();
518 for (name, platform) in original_toml.target.iter().flatten() {
519 let normalized_dependencies = normalize_dependencies(
520 gctx,
521 edition,
522 &features,
523 platform.dependencies.as_ref(),
524 DepKind::Normal,
525 &inherit,
526 &workspace_root,
527 package_root,
528 warnings,
529 )?;
530 deprecated_underscore(
531 &platform.dev_dependencies2,
532 &platform.dev_dependencies,
533 "dev-dependencies",
534 name,
535 "platform target",
536 edition,
537 warnings,
538 )?;
539 let normalized_dev_dependencies = normalize_dependencies(
540 gctx,
541 edition,
542 &features,
543 platform.dev_dependencies(),
544 DepKind::Development,
545 &inherit,
546 &workspace_root,
547 package_root,
548 warnings,
549 )?;
550 deprecated_underscore(
551 &platform.build_dependencies2,
552 &platform.build_dependencies,
553 "build-dependencies",
554 name,
555 "platform target",
556 edition,
557 warnings,
558 )?;
559 let normalized_build_dependencies = normalize_dependencies(
560 gctx,
561 edition,
562 &features,
563 platform.build_dependencies(),
564 DepKind::Build,
565 &inherit,
566 &workspace_root,
567 package_root,
568 warnings,
569 )?;
570 normalized_target.insert(
571 name.clone(),
572 manifest::TomlPlatform {
573 dependencies: normalized_dependencies,
574 build_dependencies: normalized_build_dependencies,
575 build_dependencies2: None,
576 dev_dependencies: normalized_dev_dependencies,
577 dev_dependencies2: None,
578 },
579 );
580 }
581 normalized_toml.target = (!normalized_target.is_empty()).then_some(normalized_target);
582
583 let normalized_lints = original_toml
584 .lints
585 .clone()
586 .map(|value| lints_inherit_with(value, || inherit()?.lints()))
587 .transpose()?;
588 normalized_toml.lints = normalized_lints.map(|lints| manifest::InheritableLints {
589 workspace: false,
590 lints,
591 });
592
593 normalized_toml.hints = original_toml.hints.clone();
594
595 normalized_toml.badges = original_toml.badges.clone();
596 } else {
597 if let Some(field) = original_toml.requires_package().next() {
598 let suggestion = if field == "lints" {
599 "\nhelp: a similar field exists: `[workspace.lints]`"
600 } else {
601 ""
602 };
603 bail!(
604 "this virtual manifest specifies a `{field}` section, which is not allowed{suggestion}"
605 );
606 }
607 }
608
609 Ok(normalized_toml)
610}
611
612fn normalize_patch<'a>(
613 gctx: &GlobalContext,
614 original_patch: Option<&BTreeMap<String, BTreeMap<PackageName, TomlDependency>>>,
615 workspace_root: &dyn Fn() -> CargoResult<&'a Path>,
616 features: &Features,
617) -> CargoResult<Option<BTreeMap<String, BTreeMap<PackageName, TomlDependency>>>> {
618 if let Some(patch) = original_patch {
619 let mut normalized_patch = BTreeMap::new();
620 for (name, packages) in patch {
621 let mut normalized_packages = BTreeMap::new();
622 for (pkg, dep) in packages {
623 let dep = if let TomlDependency::Detailed(dep) = dep {
624 let mut dep = dep.clone();
625 normalize_path_dependency(gctx, &mut dep, workspace_root, features)
626 .with_context(|| {
627 format!("resolving path for patch of ({pkg}) for source ({name})")
628 })?;
629 TomlDependency::Detailed(dep)
630 } else {
631 dep.clone()
632 };
633 normalized_packages.insert(pkg.clone(), dep);
634 }
635 normalized_patch.insert(name.clone(), normalized_packages);
636 }
637 Ok(Some(normalized_patch))
638 } else {
639 Ok(None)
640 }
641}
642
643#[tracing::instrument(skip_all)]
644fn normalize_package_toml<'a>(
645 original_package: &manifest::TomlPackage,
646 manifest_file: &Path,
647 is_embedded: bool,
648 gctx: &GlobalContext,
649 inherit: &dyn Fn() -> CargoResult<&'a InheritableFields>,
650 features: &Features,
651) -> CargoResult<Box<manifest::TomlPackage>> {
652 let package_root = manifest_file.parent().unwrap();
653
654 let edition = original_package
655 .edition
656 .clone()
657 .map(|value| field_inherit_with(value, "edition", || inherit()?.edition()))
658 .transpose()?
659 .map(manifest::InheritableField::Value)
660 .or_else(|| {
661 if is_embedded {
662 const DEFAULT_EDITION: crate::workspace::features::Edition =
663 crate::workspace::features::Edition::LATEST_STABLE;
664 let mut report = vec![Group::with_title(Level::WARNING.secondary_title(format!(
665 "`package.edition` is unspecified, defaulting to the latest edition (currently `{DEFAULT_EDITION}`)"
666 )))];
667 if !matches!(gctx.shell().verbosity(), cargo_util_terminal::Verbosity::Quiet) {
668 report.push(Group::with_title(Level::HELP.secondary_title(format!(
669 "to pin the edition, run `cargo fix --manifest-path {}`", manifest_file.display()
670 ))));
671 }
672 let _ = gctx.shell().print_report(&report, true);
673 Some(manifest::InheritableField::Value(
674 DEFAULT_EDITION.to_string(),
675 ))
676 } else {
677 None
678 }
679 });
680 let rust_version = original_package
681 .rust_version
682 .clone()
683 .map(|value| field_inherit_with(value, "rust-version", || inherit()?.rust_version()))
684 .transpose()?
685 .map(manifest::InheritableField::Value);
686 let name = Some(
687 original_package
688 .name
689 .clone()
690 .or_else(|| {
691 if is_embedded {
692 let file_stem = manifest_file
693 .file_stem()
694 .expect("file name enforced previously")
695 .to_string_lossy();
696 let name = embedded::sanitize_name(file_stem.as_ref());
697 let name =
698 manifest::PackageName::new(name).expect("sanitize made the name valid");
699 Some(name)
700 } else {
701 None
702 }
703 })
704 .ok_or_else(|| anyhow::format_err!("missing field `package.name`"))?,
705 );
706 let version = original_package
707 .version
708 .clone()
709 .map(|value| field_inherit_with(value, "version", || inherit()?.version()))
710 .transpose()?
711 .map(manifest::InheritableField::Value);
712 let authors = original_package
713 .authors
714 .clone()
715 .map(|value| field_inherit_with(value, "authors", || inherit()?.authors()))
716 .transpose()?
717 .map(manifest::InheritableField::Value);
718 let build = if is_embedded {
719 Some(TomlPackageBuild::Auto(false))
720 } else {
721 if let Some(TomlPackageBuild::MultipleScript(_)) = original_package.build {
722 features.require(Feature::multiple_build_scripts())?;
723 }
724 targets::normalize_build(original_package.build.as_ref(), package_root)?
725 };
726 let metabuild = original_package.metabuild.clone();
727 let default_target = original_package.default_target.clone();
728 let forced_target = original_package.forced_target.clone();
729 let links = original_package.links.clone();
730 let exclude = original_package
731 .exclude
732 .clone()
733 .map(|value| field_inherit_with(value, "exclude", || inherit()?.exclude()))
734 .transpose()?
735 .map(manifest::InheritableField::Value);
736 let include = original_package
737 .include
738 .clone()
739 .map(|value| field_inherit_with(value, "include", || inherit()?.include()))
740 .transpose()?
741 .map(manifest::InheritableField::Value);
742 let publish = original_package
743 .publish
744 .clone()
745 .map(|value| field_inherit_with(value, "publish", || inherit()?.publish()))
746 .transpose()?
747 .map(manifest::InheritableField::Value);
748 let workspace = original_package.workspace.clone();
749 let im_a_teapot = original_package.im_a_teapot.clone();
750 let autolib = Some(false);
751 let autobins = Some(false);
752 let autoexamples = Some(false);
753 let autotests = Some(false);
754 let autobenches = Some(false);
755 let default_run = original_package.default_run.clone();
756 let description = original_package
757 .description
758 .clone()
759 .map(|value| field_inherit_with(value, "description", || inherit()?.description()))
760 .transpose()?
761 .map(manifest::InheritableField::Value);
762 let homepage = original_package
763 .homepage
764 .clone()
765 .map(|value| field_inherit_with(value, "homepage", || inherit()?.homepage()))
766 .transpose()?
767 .map(manifest::InheritableField::Value);
768 let documentation = original_package
769 .documentation
770 .clone()
771 .map(|value| field_inherit_with(value, "documentation", || inherit()?.documentation()))
772 .transpose()?
773 .map(manifest::InheritableField::Value);
774 let readme = normalize_package_readme(
775 package_root,
776 original_package
777 .readme
778 .clone()
779 .map(|value| field_inherit_with(value, "readme", || inherit()?.readme(package_root)))
780 .transpose()?
781 .as_ref(),
782 )
783 .map(|s| manifest::InheritableField::Value(StringOrBool::String(s)))
784 .or(Some(manifest::InheritableField::Value(StringOrBool::Bool(
785 false,
786 ))));
787 let keywords = original_package
788 .keywords
789 .clone()
790 .map(|value| field_inherit_with(value, "keywords", || inherit()?.keywords()))
791 .transpose()?
792 .map(manifest::InheritableField::Value);
793 let categories = original_package
794 .categories
795 .clone()
796 .map(|value| field_inherit_with(value, "categories", || inherit()?.categories()))
797 .transpose()?
798 .map(manifest::InheritableField::Value);
799 let license = original_package
800 .license
801 .clone()
802 .map(|value| field_inherit_with(value, "license", || inherit()?.license()))
803 .transpose()?
804 .map(manifest::InheritableField::Value);
805 let license_file = original_package
806 .license_file
807 .clone()
808 .map(|value| {
809 field_inherit_with(value, "license-file", || {
810 inherit()?.license_file(package_root)
811 })
812 })
813 .transpose()?
814 .map(manifest::InheritableField::Value);
815 let repository = original_package
816 .repository
817 .clone()
818 .map(|value| field_inherit_with(value, "repository", || inherit()?.repository()))
819 .transpose()?
820 .map(manifest::InheritableField::Value);
821 let resolver = original_package.resolver.clone();
822 let metadata = original_package.metadata.clone();
823
824 let normalized_package = manifest::TomlPackage {
825 edition,
826 rust_version,
827 name,
828 version,
829 authors,
830 build,
831 metabuild,
832 default_target,
833 forced_target,
834 links,
835 exclude,
836 include,
837 publish,
838 workspace,
839 im_a_teapot,
840 autolib,
841 autobins,
842 autoexamples,
843 autotests,
844 autobenches,
845 default_run,
846 description,
847 homepage,
848 documentation,
849 readme,
850 keywords,
851 categories,
852 license,
853 license_file,
854 repository,
855 resolver,
856 metadata,
857 _invalid_cargo_features: Default::default(),
858 };
859
860 Ok(Box::new(normalized_package))
861}
862
863fn normalize_package_readme(
865 package_root: &Path,
866 readme: Option<&manifest::StringOrBool>,
867) -> Option<String> {
868 match &readme {
869 None => default_readme_from_package_root(package_root),
870 Some(value) => match value {
871 manifest::StringOrBool::Bool(false) => None,
872 manifest::StringOrBool::Bool(true) => Some("README.md".to_string()),
873 manifest::StringOrBool::String(v) => Some(v.clone()),
874 },
875 }
876}
877
878pub const DEFAULT_README_FILES: [&str; 3] = ["README.md", "README.txt", "README"];
879
880pub(crate) fn default_readme_from_package_root(package_root: &Path) -> Option<String> {
883 for &readme_filename in DEFAULT_README_FILES.iter() {
884 if package_root.join(readme_filename).is_file() {
885 return Some(readme_filename.to_string());
886 }
887 }
888
889 None
890}
891
892#[tracing::instrument(skip_all)]
893fn normalize_features(
894 original_features: Option<&BTreeMap<manifest::FeatureName, FeatureDefinition>>,
895) -> CargoResult<Option<BTreeMap<manifest::FeatureName, FeatureDefinition>>> {
896 let Some(normalized_features) = original_features.cloned() else {
897 return Ok(None);
898 };
899
900 Ok(Some(normalized_features))
901}
902
903#[tracing::instrument(skip_all)]
904fn normalize_dependencies<'a>(
905 gctx: &GlobalContext,
906 edition: Edition,
907 features: &Features,
908 orig_deps: Option<&BTreeMap<manifest::PackageName, manifest::InheritableDependency>>,
909 kind: DepKind,
910 inherit: &dyn Fn() -> CargoResult<&'a InheritableFields>,
911 workspace_root: &dyn Fn() -> CargoResult<&'a Path>,
912 package_root: &Path,
913 warnings: &mut Vec<String>,
914) -> CargoResult<Option<BTreeMap<manifest::PackageName, manifest::InheritableDependency>>> {
915 let Some(dependencies) = orig_deps else {
916 return Ok(None);
917 };
918
919 let mut deps = BTreeMap::new();
920 for (name_in_toml, v) in dependencies.iter() {
921 let mut resolved = dependency_inherit_with(
922 v.clone(),
923 name_in_toml,
924 inherit,
925 package_root,
926 edition,
927 warnings,
928 )?;
929 if let manifest::TomlDependency::Detailed(ref mut d) = resolved {
930 deprecated_underscore(
931 &d.default_features2,
932 &d.default_features,
933 "default-features",
934 name_in_toml,
935 "dependency",
936 edition,
937 warnings,
938 )?;
939 if d.public.is_some() {
940 let with_public_feature = features.require(Feature::public_dependency()).is_ok();
941 let with_z_public = gctx.cli_unstable().public_dependency;
942 match kind {
943 DepKind::Normal => {
944 if !with_public_feature && !with_z_public {
945 d.public = None;
946 warnings.push(format!(
947 "ignoring `public` on dependency {name_in_toml}, pass `-Zpublic-dependency` to enable support for it"
948 ));
949 }
950 }
951 DepKind::Development | DepKind::Build => {
952 let kind_name = kind.kind_table();
953 let hint = format!(
954 "'public' specifier can only be used on regular dependencies, not {kind_name}",
955 );
956 if with_public_feature || with_z_public {
957 bail!(hint)
958 } else {
959 warnings.push(hint);
961 d.public = None;
962 }
963 }
964 }
965 }
966 if d.builtin {
967 features
968 .require(Feature::builtin_dependencies())
969 .with_context(|| format!("resolving builtin dependency {name_in_toml}"))?;
970 }
971 normalize_path_dependency(gctx, d, workspace_root, features)
972 .with_context(|| format!("resolving path dependency {name_in_toml}"))?;
973 }
974
975 deps.insert(
976 name_in_toml.clone(),
977 manifest::InheritableDependency::Value(resolved.clone()),
978 );
979 }
980 Ok(Some(deps))
981}
982
983fn normalize_path_dependency<'a>(
984 gctx: &GlobalContext,
985 detailed_dep: &mut TomlDetailedDependency,
986 workspace_root: &dyn Fn() -> CargoResult<&'a Path>,
987 features: &Features,
988) -> CargoResult<()> {
989 if let Some(base) = detailed_dep.base.take() {
990 if let Some(path) = detailed_dep.path.as_mut() {
991 let new_path = lookup_path_base(&base, gctx, workspace_root, features)?.join(&path);
992 *path = new_path.to_str().unwrap().to_string();
993 } else {
994 bail!("`base` can only be used with path dependencies");
995 }
996 }
997 Ok(())
998}
999
1000fn load_inheritable_fields(
1001 gctx: &GlobalContext,
1002 normalized_path: &Path,
1003 workspace_config: &WorkspaceConfig,
1004) -> CargoResult<InheritableFields> {
1005 match workspace_config {
1006 WorkspaceConfig::Root(root) => Ok(root.inheritable().clone()),
1007 WorkspaceConfig::Member {
1008 root: Some(path_to_root),
1009 } => {
1010 let path = normalized_path
1011 .parent()
1012 .unwrap()
1013 .join(path_to_root)
1014 .join("Cargo.toml");
1015 let root_path = paths::normalize_path(&path);
1016 inheritable_from_path(gctx, root_path)
1017 }
1018 WorkspaceConfig::Member { root: None } => {
1019 match find_workspace_root(&normalized_path, gctx)? {
1020 Some(path_to_root) => inheritable_from_path(gctx, path_to_root),
1021 None => Err(anyhow!("failed to find a workspace root")),
1022 }
1023 }
1024 }
1025}
1026
1027fn inheritable_from_path(
1028 gctx: &GlobalContext,
1029 workspace_path: PathBuf,
1030) -> CargoResult<InheritableFields> {
1031 let workspace_path_root = workspace_path.parent().unwrap();
1033
1034 if let Some(ws_root) = gctx.ws_roots().get(workspace_path_root) {
1037 return Ok(ws_root.inheritable().clone());
1038 };
1039
1040 let source_id = SourceId::for_manifest_path(&workspace_path)?;
1041 let man = read_manifest(&workspace_path, source_id, gctx)?;
1042 match man.workspace_config() {
1043 WorkspaceConfig::Root(root) => {
1044 gctx.ws_roots().insert(workspace_path, root.clone());
1045 Ok(root.inheritable().clone())
1046 }
1047 _ => bail!(
1048 "root of a workspace inferred but wasn't a root: {}",
1049 workspace_path.display()
1050 ),
1051 }
1052}
1053
1054macro_rules! package_field_getter {
1056 ( $(($key:literal, $field:ident -> $ret:ty),)* ) => (
1057 $(
1058 #[doc = concat!("Gets the field `workspace.package.", $key, "`.")]
1059 fn $field(&self) -> CargoResult<$ret> {
1060 let Some(val) = self.package.as_ref().and_then(|p| p.$field.as_ref()) else {
1061 bail!("`workspace.package.{}` was not defined", $key);
1062 };
1063 Ok(val.clone())
1064 }
1065 )*
1066 )
1067}
1068
1069#[derive(Clone, Debug, Default)]
1071pub struct InheritableFields {
1072 package: Option<manifest::InheritablePackage>,
1073 dependencies: Option<BTreeMap<manifest::PackageName, manifest::TomlDependency>>,
1074 lints: Option<manifest::TomlLints>,
1075
1076 _ws_root: PathBuf,
1078}
1079
1080impl InheritableFields {
1081 package_field_getter! {
1082 ("authors", authors -> Vec<String>),
1084 ("categories", categories -> Vec<String>),
1085 ("description", description -> String),
1086 ("documentation", documentation -> String),
1087 ("edition", edition -> String),
1088 ("exclude", exclude -> Vec<String>),
1089 ("homepage", homepage -> String),
1090 ("include", include -> Vec<String>),
1091 ("keywords", keywords -> Vec<String>),
1092 ("license", license -> String),
1093 ("publish", publish -> manifest::VecStringOrBool),
1094 ("repository", repository -> String),
1095 ("rust-version", rust_version -> RustVersion),
1096 ("version", version -> semver::Version),
1097 }
1098
1099 fn get_dependency(
1101 &self,
1102 name: &str,
1103 package_root: &Path,
1104 ) -> CargoResult<manifest::TomlDependency> {
1105 let Some(deps) = &self.dependencies else {
1106 bail!("`workspace.dependencies` was not defined");
1107 };
1108 let Some(dep) = deps.get(name) else {
1109 bail!("`dependency.{name}` was not found in `workspace.dependencies`");
1110 };
1111 let mut dep = dep.clone();
1112 if let manifest::TomlDependency::Detailed(detailed) = &mut dep {
1113 if detailed.base.is_none() {
1114 if let Some(rel_path) = &detailed.path {
1117 detailed.path = Some(resolve_relative_path(
1118 name,
1119 self.ws_root(),
1120 package_root,
1121 rel_path,
1122 )?);
1123 }
1124 }
1125 }
1126 Ok(dep)
1127 }
1128
1129 pub fn lints(&self) -> CargoResult<manifest::TomlLints> {
1131 let Some(val) = &self.lints else {
1132 bail!("`workspace.lints` was not defined");
1133 };
1134 Ok(val.clone())
1135 }
1136
1137 fn license_file(&self, package_root: &Path) -> CargoResult<String> {
1139 let Some(license_file) = self.package.as_ref().and_then(|p| p.license_file.as_ref()) else {
1140 bail!("`workspace.package.license-file` was not defined");
1141 };
1142 resolve_relative_path("license-file", &self._ws_root, package_root, license_file)
1143 }
1144
1145 fn readme(&self, package_root: &Path) -> CargoResult<manifest::StringOrBool> {
1147 let Some(readme) = normalize_package_readme(
1148 self._ws_root.as_path(),
1149 self.package.as_ref().and_then(|p| p.readme.as_ref()),
1150 ) else {
1151 bail!("`workspace.package.readme` was not defined");
1152 };
1153 resolve_relative_path("readme", &self._ws_root, package_root, &readme)
1154 .map(manifest::StringOrBool::String)
1155 }
1156
1157 fn ws_root(&self) -> &PathBuf {
1158 &self._ws_root
1159 }
1160}
1161
1162fn field_inherit_with<'a, T>(
1163 field: manifest::InheritableField<T>,
1164 label: &str,
1165 get_ws_inheritable: impl FnOnce() -> CargoResult<T>,
1166) -> CargoResult<T> {
1167 match field {
1168 manifest::InheritableField::Value(value) => Ok(value),
1169 manifest::InheritableField::Inherit(_) => get_ws_inheritable().with_context(|| {
1170 format!(
1171 "error inheriting `{label}` from workspace root manifest's `workspace.package.{label}`",
1172 )
1173 }),
1174 }
1175}
1176
1177fn lints_inherit_with(
1178 lints: manifest::InheritableLints,
1179 get_ws_inheritable: impl FnOnce() -> CargoResult<manifest::TomlLints>,
1180) -> CargoResult<manifest::TomlLints> {
1181 if lints.workspace {
1182 if !lints.lints.is_empty() {
1183 anyhow::bail!(
1184 "cannot override `workspace.lints` in `lints`, either remove the overrides or `lints.workspace = true` and manually specify the lints"
1185 );
1186 }
1187 get_ws_inheritable().with_context(
1188 || "error inheriting `lints` from workspace root manifest's `workspace.lints`",
1189 )
1190 } else {
1191 Ok(lints.lints)
1192 }
1193}
1194
1195fn dependency_inherit_with<'a>(
1196 dependency: manifest::InheritableDependency,
1197 name: &str,
1198 inherit: &dyn Fn() -> CargoResult<&'a InheritableFields>,
1199 package_root: &Path,
1200 edition: Edition,
1201 warnings: &mut Vec<String>,
1202) -> CargoResult<manifest::TomlDependency> {
1203 match dependency {
1204 manifest::InheritableDependency::Value(value) => Ok(value),
1205 manifest::InheritableDependency::Inherit(w) => {
1206 inner_dependency_inherit_with(w, name, inherit, package_root, edition, warnings).with_context(|| {
1207 format!(
1208 "error inheriting `{name}` from workspace root manifest's `workspace.dependencies.{name}`",
1209 )
1210 })
1211 }
1212 }
1213}
1214
1215fn inner_dependency_inherit_with<'a>(
1216 pkg_dep: manifest::TomlInheritedDependency,
1217 name: &str,
1218 inherit: &dyn Fn() -> CargoResult<&'a InheritableFields>,
1219 package_root: &Path,
1220 edition: Edition,
1221 warnings: &mut Vec<String>,
1222) -> CargoResult<manifest::TomlDependency> {
1223 let ws_dep = inherit()?.get_dependency(name, package_root)?;
1224 let mut merged_dep = match ws_dep {
1225 manifest::TomlDependency::Simple(ws_version) => manifest::TomlDetailedDependency {
1226 version: Some(ws_version),
1227 ..Default::default()
1228 },
1229 manifest::TomlDependency::Detailed(ws_dep) => ws_dep.clone(),
1230 };
1231 let manifest::TomlInheritedDependency {
1232 workspace: _,
1233
1234 features,
1235 optional,
1236 default_features,
1237 default_features2,
1238 public,
1239
1240 _unused_keys: _,
1241 } = &pkg_dep;
1242 let default_features = default_features.or(*default_features2);
1243
1244 if edition >= Edition::Edition2024 {
1247 merged_dep.default_features = default_features.or(merged_dep.default_features);
1248 } else {
1249 match (default_features, merged_dep.default_features()) {
1250 (Some(true), Some(false)) => {
1254 merged_dep.default_features = Some(true);
1255 }
1256 (Some(false), Some(true)) => {
1260 deprecated_ws_default_features(name, Some(true), warnings);
1261 }
1262 (Some(false), None) => {
1265 deprecated_ws_default_features(name, None, warnings);
1266 }
1267 _ => {}
1268 }
1269 }
1270 merged_dep.features = match (merged_dep.features.clone(), features.clone()) {
1271 (Some(dep_feat), Some(inherit_feat)) => Some(
1272 dep_feat
1273 .into_iter()
1274 .chain(inherit_feat)
1275 .collect::<Vec<String>>(),
1276 ),
1277 (Some(dep_fet), None) => Some(dep_fet),
1278 (None, Some(inherit_feat)) => Some(inherit_feat),
1279 (None, None) => None,
1280 };
1281 merged_dep.optional = *optional;
1282 merged_dep.public = *public;
1283 Ok(manifest::TomlDependency::Detailed(merged_dep))
1284}
1285
1286fn deprecated_ws_default_features(
1287 label: &str,
1288 ws_def_feat: Option<bool>,
1289 warnings: &mut Vec<String>,
1290) {
1291 let ws_def_feat = match ws_def_feat {
1292 Some(true) => "true",
1293 Some(false) => "false",
1294 None => "not specified",
1295 };
1296 warnings.push(format!(
1297 "`default-features` is ignored for {label}, since `default-features` was \
1298 {ws_def_feat} for `workspace.dependencies.{label}`; \
1299 overriding workspace `default-features` to false requires Rust 1.99+ \
1300 and the 2024 edition"
1301 ));
1302}
1303
1304#[tracing::instrument(skip_all)]
1305pub fn to_real_manifest(
1306 contents: Option<String>,
1307 document: Option<toml::Spanned<toml::de::DeTable<'static>>>,
1308 original_toml: manifest::TomlManifest,
1309 normalized_toml: manifest::TomlManifest,
1310 features: Features,
1311 workspace_config: WorkspaceConfig,
1312 source_id: SourceId,
1313 manifest_file: &Path,
1314 is_embedded: bool,
1315 gctx: &GlobalContext,
1316 warnings: &mut Vec<String>,
1317 _errors: &mut Vec<String>,
1318) -> CargoResult<Manifest> {
1319 let package_root = manifest_file.parent().unwrap();
1320 if !package_root.is_dir() {
1321 bail!(
1322 "package root '{}' is not a directory",
1323 package_root.display()
1324 );
1325 };
1326
1327 let normalized_package = normalized_toml
1328 .package()
1329 .expect("previously verified to have a `[package]`");
1330 let package_name = normalized_package
1331 .normalized_name()
1332 .expect("previously normalized");
1333 if package_name.contains(':') {
1334 features.require(Feature::open_namespaces())?;
1335 }
1336 let rust_version = normalized_package
1337 .normalized_rust_version()
1338 .expect("previously normalized")
1339 .cloned();
1340
1341 let edition = if let Some(edition) = normalized_package
1342 .normalized_edition()
1343 .expect("previously normalized")
1344 {
1345 let edition: Edition = edition
1346 .parse()
1347 .context("failed to parse the `edition` key")?;
1348 if let Some(pkg_msrv) = &rust_version {
1349 if let Some(edition_msrv) = edition.first_version() {
1350 let edition_msrv = RustVersion::try_from(edition_msrv).unwrap();
1351 if !edition_msrv.is_compatible_with(&pkg_msrv.to_partial()) {
1352 bail!(
1353 "rust-version {} is incompatible with the version ({}) required by \
1354 the specified edition ({})",
1355 pkg_msrv,
1356 edition_msrv,
1357 edition,
1358 )
1359 }
1360 }
1361 }
1362 edition
1363 } else {
1364 let msrv_edition = if let Some(pkg_msrv) = &rust_version {
1365 Edition::ALL
1366 .iter()
1367 .filter(|e| {
1368 e.first_version()
1369 .map(|e| {
1370 let e = RustVersion::try_from(e).unwrap();
1371 e.is_compatible_with(&pkg_msrv.to_partial())
1372 })
1373 .unwrap_or_default()
1374 })
1375 .max()
1376 .copied()
1377 } else {
1378 None
1379 }
1380 .unwrap_or_default();
1381 let default_edition = Edition::default();
1382 let latest_edition = Edition::LATEST_STABLE;
1383
1384 if msrv_edition != default_edition || rust_version.is_none() {
1389 let tip = if msrv_edition == latest_edition || rust_version.is_none() {
1390 format!(" while the latest is `{latest_edition}`")
1391 } else {
1392 format!(" while {msrv_edition} is compatible with `rust-version`")
1393 };
1394 warnings.push(format!(
1395 "`package.edition` is unspecified, defaulting to `{default_edition}`{tip}"
1396 ));
1397 }
1398 default_edition
1399 };
1400 if !edition.is_stable() {
1401 let version = normalized_package
1402 .normalized_version()
1403 .expect("previously normalized")
1404 .map(|v| format!("@{v}"))
1405 .unwrap_or_default();
1406 let hint = rust_version
1407 .as_ref()
1408 .map(|rv| format!("help: {package_name}{version} requires rust {rv}"));
1409 features.require_with_hint(Feature::unstable_editions(), hint.as_deref())?;
1410 }
1411
1412 if original_toml.project.is_some() {
1413 if Edition::Edition2024 <= edition {
1414 anyhow::bail!(
1415 "`[project]` is not supported as of the 2024 Edition, please use `[package]`"
1416 );
1417 } else {
1418 warnings.push(format!("`[project]` is deprecated in favor of `[package]`"));
1419 }
1420 }
1421
1422 if normalized_package.metabuild.is_some() {
1423 features.require(Feature::metabuild())?;
1424 }
1425
1426 if is_embedded {
1427 let manifest::TomlManifest {
1428 cargo_features: _,
1429 package: _,
1430 project: _,
1431 badges: _,
1432 features: _,
1433 lib,
1434 bin,
1435 example,
1436 test,
1437 bench,
1438 dependencies: _,
1439 dev_dependencies: _,
1440 dev_dependencies2: _,
1441 build_dependencies,
1442 build_dependencies2,
1443 target: _,
1444 lints: _,
1445 hints: _,
1446 workspace,
1447 profile: _,
1448 patch: _,
1449 replace: _,
1450 _unused_keys: _,
1451 } = &original_toml;
1452 let mut invalid_fields = vec![
1453 ("`workspace`", workspace.is_some()),
1454 ("`lib`", lib.is_some()),
1455 ("`bin`", bin.is_some()),
1456 ("`example`", example.is_some()),
1457 ("`test`", test.is_some()),
1458 ("`bench`", bench.is_some()),
1459 ("`build-dependencies`", build_dependencies.is_some()),
1460 ("`build_dependencies`", build_dependencies2.is_some()),
1461 ];
1462 if let Some(package) = original_toml.package() {
1463 let manifest::TomlPackage {
1464 edition: _,
1465 rust_version: _,
1466 name: _,
1467 version: _,
1468 authors: _,
1469 build,
1470 metabuild,
1471 default_target: _,
1472 forced_target: _,
1473 links,
1474 exclude: _,
1475 include: _,
1476 publish: _,
1477 workspace,
1478 im_a_teapot: _,
1479 autolib,
1480 autobins,
1481 autoexamples,
1482 autotests,
1483 autobenches,
1484 default_run,
1485 description: _,
1486 homepage: _,
1487 documentation: _,
1488 readme: _,
1489 keywords: _,
1490 categories: _,
1491 license: _,
1492 license_file: _,
1493 repository: _,
1494 resolver: _,
1495 metadata: _,
1496 _invalid_cargo_features: _,
1497 } = package.as_ref();
1498 invalid_fields.extend([
1499 ("`package.workspace`", workspace.is_some()),
1500 ("`package.build`", build.is_some()),
1501 ("`package.metabuild`", metabuild.is_some()),
1502 ("`package.links`", links.is_some()),
1503 ("`package.autolib`", autolib.is_some()),
1504 ("`package.autobins`", autobins.is_some()),
1505 ("`package.autoexamples`", autoexamples.is_some()),
1506 ("`package.autotests`", autotests.is_some()),
1507 ("`package.autobenches`", autobenches.is_some()),
1508 ("`package.default-run`", default_run.is_some()),
1509 ]);
1510 }
1511 let invalid_fields = invalid_fields
1512 .into_iter()
1513 .filter_map(|(name, invalid)| invalid.then_some(name))
1514 .collect::<Vec<_>>();
1515 if !invalid_fields.is_empty() {
1516 let fields = invalid_fields.join(", ");
1517 let are = if invalid_fields.len() == 1 {
1518 "is"
1519 } else {
1520 "are"
1521 };
1522 anyhow::bail!("{fields} {are} not allowed in embedded manifests")
1523 }
1524 }
1525
1526 let resolve_behavior = match (
1527 normalized_package.resolver.as_ref(),
1528 normalized_toml
1529 .workspace
1530 .as_ref()
1531 .and_then(|ws| ws.resolver.as_ref()),
1532 ) {
1533 (None, None) => None,
1534 (Some(s), None) | (None, Some(s)) => Some(ResolveBehavior::from_manifest(s)?),
1535 (Some(_), Some(_)) => {
1536 bail!("cannot specify `resolver` field in both `[workspace]` and `[package]`")
1537 }
1538 };
1539
1540 let targets = to_targets(
1544 &features,
1545 &original_toml,
1546 &normalized_toml,
1547 package_root,
1548 edition,
1549 &normalized_package.metabuild,
1550 warnings,
1551 )?;
1552
1553 if targets.iter().all(|t| t.is_custom_build()) {
1554 bail!(
1555 "no targets specified in the manifest\n\
1556 either src/lib.rs, src/main.rs, a [lib] section, or \
1557 [[bin]] section must be present"
1558 )
1559 }
1560
1561 if let Err(conflict_targets) = unique_build_targets(&targets, package_root) {
1562 conflict_targets
1563 .iter()
1564 .for_each(|(target_path, conflicts)| {
1565 warnings.push(format!(
1566 "file `{}` found to be present in multiple \
1567 build targets:\n{}",
1568 target_path.display(),
1569 conflicts
1570 .iter()
1571 .map(|t| format!(" * `{}` target `{}`", t.kind().description(), t.name(),))
1572 .join("\n")
1573 ));
1574 })
1575 }
1576
1577 if let Some(links) = &normalized_package.links {
1578 if !targets.iter().any(|t| t.is_custom_build()) {
1579 bail!(
1580 "package specifies that it links to `{links}` but does not have a custom build script"
1581 )
1582 }
1583 }
1584
1585 validate_feature_definitions(&features, original_toml.features.as_ref(), warnings)?;
1586
1587 validate_dependencies(original_toml.dependencies.as_ref(), None, None, warnings)?;
1588 validate_dependencies(
1589 original_toml.dev_dependencies(),
1590 None,
1591 Some(DepKind::Development),
1592 warnings,
1593 )?;
1594 validate_dependencies(
1595 original_toml.build_dependencies(),
1596 None,
1597 Some(DepKind::Build),
1598 warnings,
1599 )?;
1600 for (name, platform) in original_toml.target.iter().flatten() {
1601 let platform_kind: Platform = name.parse()?;
1602 platform_kind.check_cfg_attributes(warnings);
1603 platform_kind.check_cfg_keywords(warnings, manifest_file);
1604 let platform_kind = Some(platform_kind);
1605 validate_dependencies(
1606 platform.dependencies.as_ref(),
1607 platform_kind.as_ref(),
1608 None,
1609 warnings,
1610 )?;
1611 validate_dependencies(
1612 platform.build_dependencies(),
1613 platform_kind.as_ref(),
1614 Some(DepKind::Build),
1615 warnings,
1616 )?;
1617 validate_dependencies(
1618 platform.dev_dependencies(),
1619 platform_kind.as_ref(),
1620 Some(DepKind::Development),
1621 warnings,
1622 )?;
1623 }
1624
1625 let mut deps = Vec::new();
1627 let mut manifest_ctx = ManifestContext {
1628 deps: &mut deps,
1629 source_id,
1630 gctx,
1631 warnings,
1632 platform: None,
1633 file: manifest_file,
1634 };
1635 gather_dependencies(
1636 &mut manifest_ctx,
1637 normalized_toml.dependencies.as_ref(),
1638 None,
1639 )?;
1640 gather_dependencies(
1641 &mut manifest_ctx,
1642 normalized_toml.dev_dependencies(),
1643 Some(DepKind::Development),
1644 )?;
1645 gather_dependencies(
1646 &mut manifest_ctx,
1647 normalized_toml.build_dependencies(),
1648 Some(DepKind::Build),
1649 )?;
1650 for (name, platform) in normalized_toml.target.iter().flatten() {
1651 manifest_ctx.platform = Some(name.parse()?);
1652 gather_dependencies(&mut manifest_ctx, platform.dependencies.as_ref(), None)?;
1653 gather_dependencies(
1654 &mut manifest_ctx,
1655 platform.build_dependencies(),
1656 Some(DepKind::Build),
1657 )?;
1658 gather_dependencies(
1659 &mut manifest_ctx,
1660 platform.dev_dependencies(),
1661 Some(DepKind::Development),
1662 )?;
1663 }
1664 let replace = replace(&normalized_toml, &mut manifest_ctx, &features)?;
1665 let patch = patch(&normalized_toml, &mut manifest_ctx, &features)?;
1666
1667 {
1668 let mut names_sources = BTreeMap::new();
1669 for dep in &deps {
1670 let name = dep.name_in_toml();
1671 let prev = names_sources.insert(name, dep.source_id());
1672 if prev.is_some() && prev != Some(dep.source_id()) {
1673 bail!(
1674 "Dependency '{}' has different source paths depending on the build \
1675 target. Each dependency must have a single canonical source path \
1676 irrespective of build target.",
1677 name
1678 );
1679 }
1680 }
1681 }
1682
1683 verify_lints(
1684 normalized_toml
1685 .normalized_lints()
1686 .expect("previously normalized"),
1687 warnings,
1688 )?;
1689 let default = manifest::TomlLints::default();
1690 let rustflags = lints_to_rustflags(
1691 normalized_toml
1692 .normalized_lints()
1693 .expect("previously normalized")
1694 .unwrap_or(&default),
1695 )?;
1696
1697 let hints = normalized_toml.hints.clone();
1698
1699 let metadata = ManifestMetadata {
1700 description: normalized_package
1701 .normalized_description()
1702 .expect("previously normalized")
1703 .cloned(),
1704 homepage: normalized_package
1705 .normalized_homepage()
1706 .expect("previously normalized")
1707 .cloned(),
1708 documentation: normalized_package
1709 .normalized_documentation()
1710 .expect("previously normalized")
1711 .cloned(),
1712 readme: normalized_package
1713 .normalized_readme()
1714 .expect("previously normalized")
1715 .cloned(),
1716 authors: normalized_package
1717 .normalized_authors()
1718 .expect("previously normalized")
1719 .cloned()
1720 .unwrap_or_default(),
1721 license: normalized_package
1722 .normalized_license()
1723 .expect("previously normalized")
1724 .cloned(),
1725 license_file: normalized_package
1726 .normalized_license_file()
1727 .expect("previously normalized")
1728 .cloned(),
1729 repository: normalized_package
1730 .normalized_repository()
1731 .expect("previously normalized")
1732 .cloned(),
1733 keywords: normalized_package
1734 .normalized_keywords()
1735 .expect("previously normalized")
1736 .cloned()
1737 .unwrap_or_default(),
1738 categories: normalized_package
1739 .normalized_categories()
1740 .expect("previously normalized")
1741 .cloned()
1742 .unwrap_or_default(),
1743 badges: normalized_toml.badges.clone().unwrap_or_default(),
1744 links: normalized_package.links.clone(),
1745 rust_version: rust_version.clone(),
1746 };
1747
1748 if let Some(profiles) = &normalized_toml.profile {
1749 let cli_unstable = gctx.cli_unstable();
1750 validate_profiles(profiles, cli_unstable, &features, warnings)?;
1751 }
1752
1753 let version = normalized_package
1754 .normalized_version()
1755 .expect("previously normalized");
1756 let publish = match normalized_package
1757 .normalized_publish()
1758 .expect("previously normalized")
1759 {
1760 Some(manifest::VecStringOrBool::VecString(vecstring)) => Some(vecstring.clone()),
1761 Some(manifest::VecStringOrBool::Bool(false)) => Some(vec![]),
1762 Some(manifest::VecStringOrBool::Bool(true)) => None,
1763 None => version.is_none().then_some(vec![]),
1764 };
1765
1766 if version.is_none() && publish != Some(vec![]) {
1767 bail!("`package.publish` requires `package.version` be specified");
1768 }
1769
1770 let pkgid = PackageId::new(
1771 package_name.as_str().into(),
1772 version
1773 .cloned()
1774 .unwrap_or_else(|| semver::Version::new(0, 0, 0)),
1775 source_id,
1776 );
1777 let summary = {
1778 let summary = Summary::new(
1779 pkgid,
1780 deps,
1781 &normalized_toml
1782 .features
1783 .as_ref()
1784 .unwrap_or(&Default::default())
1785 .iter()
1786 .map(|(k, v)| {
1787 (
1788 k.to_string().into(),
1789 v.enables().iter().map(InternedString::from).collect(),
1790 )
1791 })
1792 .collect(),
1793 normalized_package.links.as_deref(),
1794 rust_version.clone(),
1795 );
1796 if let Err(ref err) = summary {
1799 if let Some(missing_dep) = err.downcast_ref::<MissingDependencyError>() {
1800 missing_dep_diagnostic(
1801 missing_dep,
1802 &original_toml,
1803 document.as_ref(),
1804 contents.as_deref(),
1805 manifest_file,
1806 gctx,
1807 )?;
1808 }
1809 }
1810 summary?
1811 };
1812
1813 if summary.features().contains_key("default-features") {
1814 warnings.push(
1815 "`[features]` defines a feature named `default-features`
1816note: only a feature named `default` will be enabled by default"
1817 .to_string(),
1818 )
1819 }
1820
1821 if let Some(run) = &normalized_package.default_run {
1822 if !targets
1823 .iter()
1824 .filter(|t| t.is_bin())
1825 .any(|t| t.name() == run)
1826 {
1827 let suggestion = util::closest_msg(
1828 run,
1829 targets.iter().filter(|t| t.is_bin()),
1830 |t| t.name(),
1831 "target",
1832 );
1833 bail!("default-run target `{}` not found{}", run, suggestion);
1834 }
1835 }
1836
1837 let default_kind = normalized_package
1838 .default_target
1839 .as_ref()
1840 .map(|t| CompileTarget::new(&*t, gctx.cli_unstable().json_target_spec))
1841 .transpose()?
1842 .map(CompileKind::Target);
1843 let forced_kind = normalized_package
1844 .forced_target
1845 .as_ref()
1846 .map(|t| CompileTarget::new(&*t, gctx.cli_unstable().json_target_spec))
1847 .transpose()?
1848 .map(CompileKind::Target);
1849 let include = normalized_package
1850 .normalized_include()
1851 .expect("previously normalized")
1852 .cloned()
1853 .unwrap_or_default();
1854 let exclude = normalized_package
1855 .normalized_exclude()
1856 .expect("previously normalized")
1857 .cloned()
1858 .unwrap_or_default();
1859 let links = normalized_package.links.clone();
1860 let custom_metadata = normalized_package.metadata.clone();
1861 let im_a_teapot = normalized_package.im_a_teapot;
1862 let default_run = normalized_package.default_run.clone();
1863 let metabuild = normalized_package.metabuild.clone().map(|sov| sov.0);
1864 let manifest = Manifest::new(
1865 contents.map(Rc::new),
1866 document.map(Arc::new),
1867 Some(Rc::new(original_toml)),
1868 Rc::new(normalized_toml),
1869 summary,
1870 default_kind,
1871 forced_kind,
1872 targets,
1873 exclude,
1874 include,
1875 links,
1876 metadata,
1877 custom_metadata,
1878 publish,
1879 replace,
1880 patch,
1881 workspace_config,
1882 features,
1883 edition,
1884 rust_version,
1885 im_a_teapot,
1886 default_run,
1887 metabuild,
1888 resolve_behavior,
1889 rustflags,
1890 hints,
1891 is_embedded,
1892 );
1893 if manifest
1894 .normalized_toml()
1895 .package()
1896 .unwrap()
1897 .license_file
1898 .is_some()
1899 && manifest
1900 .normalized_toml()
1901 .package()
1902 .unwrap()
1903 .license
1904 .is_some()
1905 {
1906 warnings.push(
1907 "only one of `license` or `license-file` is necessary\n\
1908 `license` should be used if the package license can be expressed \
1909 with a standard SPDX expression.\n\
1910 `license-file` should be used if the package uses a non-standard license.\n\
1911 See https://doc.rust-lang.org/cargo/reference/manifest.html#the-license-and-license-file-fields \
1912 for more information."
1913 .to_owned(),
1914 );
1915 }
1916 if let Some(original_toml) = manifest.original_toml() {
1917 warn_on_unused(&original_toml._unused_keys, warnings);
1918 }
1919
1920 manifest.feature_gate()?;
1921
1922 Ok(manifest)
1923}
1924
1925fn missing_dep_diagnostic(
1926 missing_dep: &MissingDependencyError,
1927 orig_toml: &TomlManifest,
1928 document: Option<&toml::Spanned<toml::de::DeTable<'static>>>,
1929 contents: Option<&str>,
1930 manifest_file: &Path,
1931 gctx: &GlobalContext,
1932) -> CargoResult<()> {
1933 let dep_name = missing_dep.dep_name;
1934 let manifest_path = cwd_rel_path(manifest_file, gctx);
1935
1936 let title = format!(
1937 "feature `{}` includes `{}`, but `{}` is not a dependency",
1938 missing_dep.feature, missing_dep.feature_value, &dep_name
1939 );
1940 let help = format!("enable the dependency with `dep:{dep_name}`");
1941 let info_label = format!(
1942 "`{}` is an unused optional dependency since no feature enables it",
1943 &dep_name
1944 );
1945 let group = Group::with_title(Level::ERROR.primary_title(&title));
1946 let group =
1947 if let Some(contents) = contents
1948 && let Some(document) = document
1949 {
1950 let feature_span =
1951 get_key_value_span(&document, &["features", missing_dep.feature.as_str()]).unwrap();
1952
1953 let snippet = Snippet::source(contents)
1954 .path(manifest_path)
1955 .annotation(AnnotationKind::Primary.span(feature_span.value));
1956
1957 if missing_dep.weak_optional {
1958 let mut orig_deps = vec![
1959 (
1960 orig_toml.dependencies.as_ref(),
1961 vec![DepKind::Normal.kind_table()],
1962 ),
1963 (
1964 orig_toml.build_dependencies.as_ref(),
1965 vec![DepKind::Build.kind_table()],
1966 ),
1967 ];
1968 for (name, platform) in orig_toml.target.iter().flatten() {
1969 orig_deps.push((
1970 platform.dependencies.as_ref(),
1971 vec!["target", name, DepKind::Normal.kind_table()],
1972 ));
1973 orig_deps.push((
1974 platform.build_dependencies.as_ref(),
1975 vec!["target", name, DepKind::Normal.kind_table()],
1976 ));
1977 }
1978
1979 if let Some((_, toml_path)) = orig_deps.iter().find(|(deps, _)| {
1980 if let Some(deps) = deps {
1981 deps.keys().any(|p| *p.as_str() == *dep_name)
1982 } else {
1983 false
1984 }
1985 }) {
1986 let toml_path = toml_path
1987 .iter()
1988 .map(|s| *s)
1989 .chain(std::iter::once(dep_name.as_str()))
1990 .collect::<Vec<_>>();
1991 let dep_span = get_key_value_span(&document, &toml_path).unwrap();
1992
1993 group
1994 .element(snippet.annotation(
1995 AnnotationKind::Context.span(dep_span.key).label(info_label),
1996 ))
1997 .element(Level::HELP.message(help))
1998 } else {
1999 group.element(snippet)
2000 }
2001 } else {
2002 group.element(snippet)
2003 }
2004 } else {
2005 group
2006 };
2007
2008 if let Err(err) = gctx.shell().print_report(&[group], true) {
2009 return Err(err.into());
2010 }
2011 Err(AlreadyPrintedError::new(anyhow!("").into()).into())
2012}
2013
2014fn to_virtual_manifest(
2015 contents: Option<String>,
2016 document: Option<toml::Spanned<toml::de::DeTable<'static>>>,
2017 original_toml: manifest::TomlManifest,
2018 normalized_toml: manifest::TomlManifest,
2019 features: Features,
2020 workspace_config: WorkspaceConfig,
2021 source_id: SourceId,
2022 manifest_file: &Path,
2023 gctx: &GlobalContext,
2024 warnings: &mut Vec<String>,
2025 _errors: &mut Vec<String>,
2026) -> CargoResult<VirtualManifest> {
2027 let mut deps = Vec::new();
2028 let (replace, patch) = {
2029 let mut manifest_ctx = ManifestContext {
2030 deps: &mut deps,
2031 source_id,
2032 gctx,
2033 warnings,
2034 platform: None,
2035 file: manifest_file,
2036 };
2037 (
2038 replace(&normalized_toml, &mut manifest_ctx, &features)?,
2039 patch(&normalized_toml, &mut manifest_ctx, &features)?,
2040 )
2041 };
2042 if let Some(profiles) = &normalized_toml.profile {
2043 validate_profiles(profiles, gctx.cli_unstable(), &features, warnings)?;
2044 }
2045 let resolve_behavior = normalized_toml
2046 .workspace
2047 .as_ref()
2048 .and_then(|ws| ws.resolver.as_deref())
2049 .map(|r| ResolveBehavior::from_manifest(r))
2050 .transpose()?;
2051 if let WorkspaceConfig::Member { .. } = &workspace_config {
2052 bail!("virtual manifests must be configured with [workspace]");
2053 }
2054 let manifest = VirtualManifest::new(
2055 contents.map(Rc::new),
2056 document.map(Rc::new),
2057 Some(Rc::new(original_toml)),
2058 Rc::new(normalized_toml),
2059 replace,
2060 patch,
2061 workspace_config,
2062 features,
2063 resolve_behavior,
2064 );
2065
2066 if let Some(original_toml) = manifest.original_toml() {
2067 warn_on_unused(&original_toml._unused_keys, warnings);
2068 }
2069
2070 Ok(manifest)
2071}
2072
2073fn validate_feature_definitions(
2074 cargo_features: &Features,
2075 features: Option<&BTreeMap<FeatureName, FeatureDefinition>>,
2076 warnings: &mut Vec<String>,
2077) -> CargoResult<()> {
2078 let Some(features) = features else {
2079 return Ok(());
2080 };
2081 for (feature, feature_definition) in features {
2082 match feature_definition {
2083 FeatureDefinition::Array(..) => {}
2084 FeatureDefinition::Metadata(FeatureMetadata { _unused_keys, .. }) => {
2085 cargo_features.require(Feature::feature_metadata())?;
2086 warnings.extend(
2087 _unused_keys
2088 .keys()
2089 .map(|k| format!("unused manifest key: `features.{feature}.{k}`")),
2090 );
2091 }
2092 }
2093 }
2094 Ok(())
2095}
2096
2097#[tracing::instrument(skip_all)]
2098fn validate_dependencies(
2099 original_deps: Option<&BTreeMap<manifest::PackageName, manifest::InheritableDependency>>,
2100 platform: Option<&Platform>,
2101 kind: Option<DepKind>,
2102 warnings: &mut Vec<String>,
2103) -> CargoResult<()> {
2104 let Some(dependencies) = original_deps else {
2105 return Ok(());
2106 };
2107
2108 for (name_in_toml, v) in dependencies.iter() {
2109 let kind_name = match kind {
2110 Some(k) => k.kind_table(),
2111 None => "dependencies",
2112 };
2113 let table_in_toml = if let Some(platform) = platform {
2114 format!("target.{platform}.{kind_name}")
2115 } else {
2116 kind_name.to_string()
2117 };
2118 unused_dep_keys(name_in_toml, &table_in_toml, v.unused_keys(), warnings);
2119 }
2120 Ok(())
2121}
2122
2123struct ManifestContext<'a, 'b> {
2124 deps: &'a mut Vec<Dependency>,
2125 source_id: SourceId,
2126 gctx: &'b GlobalContext,
2127 warnings: &'a mut Vec<String>,
2128 platform: Option<Platform>,
2129 file: &'a Path,
2130}
2131
2132#[tracing::instrument(skip_all)]
2133fn gather_dependencies(
2134 manifest_ctx: &mut ManifestContext<'_, '_>,
2135 normalized_deps: Option<&BTreeMap<manifest::PackageName, manifest::InheritableDependency>>,
2136 kind: Option<DepKind>,
2137) -> CargoResult<()> {
2138 let Some(dependencies) = normalized_deps else {
2139 return Ok(());
2140 };
2141
2142 for (n, v) in dependencies.iter() {
2143 let resolved = v.normalized().expect("previously normalized");
2144 let dep = dep_to_dependency(&resolved, n, manifest_ctx, kind)?;
2145 manifest_ctx.deps.push(dep);
2146 }
2147 Ok(())
2148}
2149
2150fn replace(
2151 me: &manifest::TomlManifest,
2152 manifest_ctx: &mut ManifestContext<'_, '_>,
2153 features: &Features,
2154) -> CargoResult<Vec<(PackageIdSpec, Dependency)>> {
2155 if me.patch.is_some() && me.replace.is_some() {
2156 bail!("cannot specify both [replace] and [patch]");
2157 }
2158 let mut replace = Vec::new();
2159 for (spec, replacement) in me.replace.iter().flatten() {
2160 if let TomlDependency::Detailed(d) = replacement {
2161 if d.builtin {
2162 features
2163 .require(Feature::builtin_dependencies())
2164 .with_context(|| format!("invalid replacement for `{spec}`"))?;
2165 }
2166 }
2167 let mut spec = PackageIdSpec::parse(spec).with_context(|| {
2168 format!(
2169 "replacements must specify a valid semver \
2170 version to replace, but `{}` does not",
2171 spec
2172 )
2173 })?;
2174 if spec.url().is_none() {
2175 spec.set_url(CRATES_IO_INDEX.parse().unwrap());
2176 }
2177
2178 if replacement.is_version_specified() {
2179 bail!(
2180 "replacements cannot specify a version \
2181 requirement, but found one for `{}`",
2182 spec
2183 );
2184 }
2185
2186 let mut dep = dep_to_dependency(replacement, spec.name(), manifest_ctx, None)?;
2187 let version = spec.version().ok_or_else(|| {
2188 anyhow!(
2189 "replacements must specify a version \
2190 to replace, but `{}` does not",
2191 spec
2192 )
2193 })?;
2194 unused_dep_keys(
2195 dep.name_in_toml().as_str(),
2196 "replace",
2197 replacement.unused_keys(),
2198 &mut manifest_ctx.warnings,
2199 );
2200 dep.set_version_req(OptVersionReq::exact(&version));
2201 replace.push((spec, dep));
2202 }
2203 Ok(replace)
2204}
2205
2206fn patch(
2207 me: &TomlManifest,
2208 manifest_ctx: &mut ManifestContext<'_, '_>,
2209 features: &Features,
2210) -> CargoResult<HashMap<Url, Vec<Patch>>> {
2211 let mut patch = HashMap::default();
2212 for (toml_url, deps) in me.patch.iter().flatten() {
2213 let url = match &toml_url[..] {
2214 CRATES_IO_REGISTRY => CRATES_IO_INDEX.parse().unwrap(),
2215 _ => manifest_ctx
2216 .gctx
2217 .get_registry_index(toml_url)
2218 .or_else(|_| toml_url.into_url())
2219 .with_context(|| {
2220 format!(
2221 "[patch] entry `{}` should be a URL or registry name{}",
2222 toml_url,
2223 if toml_url == "crates" {
2224 "\nFor crates.io, use [patch.crates-io] (with a dash)"
2225 } else {
2226 ""
2227 }
2228 )
2229 })?,
2230 };
2231 patch.insert(
2232 url,
2233 deps.iter()
2234 .map(|(name, dep)| {
2235 if let manifest::TomlDependency::Detailed(d) = dep {
2236 if d.builtin {
2237 features
2238 .require(Feature::builtin_dependencies())
2239 .with_context(|| format!("resolving patch for `{name}`"))?;
2240 }
2241 }
2242
2243 unused_dep_keys(
2244 name,
2245 &format!("patch.{toml_url}",),
2246 dep.unused_keys(),
2247 &mut manifest_ctx.warnings,
2248 );
2249
2250 let dep = dep_to_dependency(dep, name, manifest_ctx, None)?;
2251 let loc = PatchLocation::Manifest(manifest_ctx.file.to_path_buf());
2252 Ok(Patch { dep, loc })
2253 })
2254 .collect::<CargoResult<Vec<_>>>()?,
2255 );
2256 }
2257 Ok(patch)
2258}
2259
2260pub(crate) fn config_patch_to_dependency<P: ResolveToPath + Clone>(
2262 config_patch: &manifest::TomlDependency<P>,
2263 name: &str,
2264 source_id: SourceId,
2265 gctx: &GlobalContext,
2266 warnings: &mut Vec<String>,
2267) -> CargoResult<Dependency> {
2268 let manifest_ctx = &mut ManifestContext {
2269 deps: &mut Vec::new(),
2270 source_id,
2271 gctx,
2272 warnings,
2273 platform: None,
2274 file: Path::new("unused"),
2276 };
2277 dep_to_dependency(config_patch, name, manifest_ctx, None)
2278}
2279
2280fn dep_to_dependency<P: ResolveToPath + Clone>(
2281 orig: &manifest::TomlDependency<P>,
2282 name_in_toml: &str,
2283 manifest_ctx: &mut ManifestContext<'_, '_>,
2284 kind: Option<DepKind>,
2285) -> CargoResult<Dependency> {
2286 let orig = match orig {
2287 manifest::TomlDependency::Simple(version) => &manifest::TomlDetailedDependency::<P> {
2288 version: Some(version.clone()),
2289 ..Default::default()
2290 },
2291 manifest::TomlDependency::Detailed(details) => details,
2292 };
2293
2294 if let Some(version) = &orig.version {
2295 if version.contains('+') {
2296 manifest_ctx.warnings.push(format!(
2297 "version requirement `{}` for dependency `{}` \
2298 includes semver metadata which will be ignored, removing the \
2299 metadata is recommended to avoid confusion",
2300 version, name_in_toml
2301 ));
2302 }
2303 }
2304
2305 if let Some(features) = &orig.features {
2308 for feature in features {
2309 if feature.contains('/') {
2310 bail!(
2311 "feature `{}` in dependency `{}` is not allowed to contain slashes\n\
2312 If you want to enable features of a transitive dependency, \
2313 the direct dependency needs to re-export those features from \
2314 the `[features]` table.",
2315 feature,
2316 name_in_toml
2317 );
2318 }
2319 if feature.starts_with("dep:") {
2320 bail!(
2321 "feature `{}` in dependency `{}` is not allowed to use explicit \
2322 `dep:` syntax\n\
2323 If you want to enable an optional dependency, specify the name \
2324 of the optional dependency without the `dep:` prefix, or specify \
2325 a feature from the dependency's `[features]` table that enables \
2326 the optional dependency.",
2327 feature,
2328 name_in_toml
2329 );
2330 }
2331 }
2332 }
2333
2334 let new_source_id = to_dependency_source_id(orig, name_in_toml, manifest_ctx, kind)?;
2335
2336 let (pkg_name, explicit_name_in_toml) = match orig.package {
2337 Some(ref s) => (&s[..], Some(name_in_toml)),
2338 None => (name_in_toml, None),
2339 };
2340
2341 let version = orig.version.as_deref();
2342 let mut dep = Dependency::parse(pkg_name, version, new_source_id)?;
2343 dep.set_features(orig.features.iter().flatten())
2344 .set_default_features(orig.default_features().unwrap_or(true))
2345 .set_optional(orig.optional.unwrap_or(false))
2346 .set_platform(manifest_ctx.platform.clone());
2347 if let Some(registry) = &orig.registry {
2348 let registry_id = SourceId::alt_registry(manifest_ctx.gctx, registry)?;
2349 dep.set_registry_id(registry_id);
2350 }
2351 if let Some(registry_index) = &orig.registry_index {
2352 let url = registry_index.into_url()?;
2353 let registry_id = SourceId::for_registry(&url)?;
2354 dep.set_registry_id(registry_id);
2355 }
2356
2357 if let Some(kind) = kind {
2358 dep.set_kind(kind);
2359 }
2360 if let Some(name_in_toml) = explicit_name_in_toml {
2361 dep.set_explicit_name_in_toml(name_in_toml);
2362 }
2363
2364 if let Some(p) = orig.public {
2365 dep.set_public(p);
2366 }
2367
2368 if let (Some(artifact), is_lib, target) = (
2369 orig.artifact.as_ref(),
2370 orig.lib.unwrap_or(false),
2371 orig.target.as_deref(),
2372 ) {
2373 if manifest_ctx.gctx.cli_unstable().bindeps {
2374 let artifact = Artifact::parse(
2375 &artifact.0,
2376 is_lib,
2377 target,
2378 manifest_ctx.gctx.cli_unstable().json_target_spec,
2379 )?;
2380 if dep.kind() != DepKind::Build
2381 && artifact.target() == Some(ArtifactTarget::BuildDependencyAssumeTarget)
2382 {
2383 bail!(
2384 r#"`target = "target"` in normal- or dev-dependencies has no effect ({})"#,
2385 name_in_toml
2386 );
2387 }
2388 dep.set_artifact(artifact)
2389 } else {
2390 bail!("`artifact = …` requires `-Z bindeps` ({})", name_in_toml);
2391 }
2392 } else if orig.lib.is_some() || orig.target.is_some() {
2393 for (is_set, specifier) in [
2394 (orig.lib.is_some(), "lib"),
2395 (orig.target.is_some(), "target"),
2396 ] {
2397 if !is_set {
2398 continue;
2399 }
2400 bail!(
2401 "'{}' specifier cannot be used without an 'artifact = …' value ({})",
2402 specifier,
2403 name_in_toml
2404 )
2405 }
2406 }
2407 Ok(dep)
2408}
2409
2410fn to_dependency_source_id<P: ResolveToPath + Clone>(
2411 orig: &manifest::TomlDetailedDependency<P>,
2412 name_in_toml: &str,
2413 manifest_ctx: &mut ManifestContext<'_, '_>,
2414 kind: Option<DepKind>,
2415) -> CargoResult<SourceId> {
2416 let manifest::TomlDetailedDependency {
2417 version,
2418 registry,
2419 registry_index,
2420 path,
2421 base: _,
2422 git,
2423 branch,
2424 tag,
2425 rev,
2426 builtin,
2427 features: _,
2428 optional: _,
2429 default_features: _,
2430 default_features2: _,
2431 package: _,
2432 public: _,
2433 artifact: _,
2434 lib: _,
2435 target: _,
2436 _unused_keys: _,
2437 } = orig;
2438
2439 let builtin_kind = if *builtin { Some(()) } else { None };
2440
2441 let git_kind = if let Some(git) = git {
2442 Some(git)
2443 } else {
2444 let git_only_keys = [
2445 (&orig.branch, "branch"),
2446 (&orig.tag, "tag"),
2447 (&orig.rev, "rev"),
2448 ];
2449 for &(key, key_name) in &git_only_keys {
2450 if key.is_some() {
2451 bail!(
2452 "key `{}` is ignored for dependency ({}).",
2453 key_name,
2454 name_in_toml
2455 );
2456 }
2457 }
2458
2459 None
2460 };
2461
2462 let path_kind = if let Some(path) = path {
2463 Some(path)
2464 } else {
2465 None
2466 };
2467
2468 let registry_kind = if version.is_some() {
2469 Some(())
2470 } else {
2471 if orig.registry.is_some() {
2472 manifest_ctx.warnings.push(format!(
2473 "key `registry` is ignored for dependency `{name_in_toml}`",
2474 ));
2475 }
2476 if orig.registry_index.is_some() {
2477 manifest_ctx.warnings.push(format!(
2478 "key `registry-index` is ignored for dependency `{name_in_toml}`",
2479 ));
2480 }
2481
2482 None
2483 };
2484
2485 let source = match (builtin_kind, git_kind, path_kind, registry_kind) {
2486 (Some(()), None, None, None) => {
2487 if kind == Some(DepKind::Build) {
2488 bail!("builtin dependency `{name_in_toml}` cannot be used as a build dependency")
2489 }
2490 SourceId::for_builtin()?
2491 }
2492 (None, Some(git), None, _) => {
2493 let n_details = [&branch, &tag, &rev].iter().filter(|d| d.is_some()).count();
2494 if n_details > 1 {
2495 bail!(
2496 "dependency ({name_in_toml}) specification is ambiguous. \
2497 Only one of `branch`, `tag` or `rev` is allowed.",
2498 );
2499 }
2500
2501 let reference = orig
2502 .branch
2503 .clone()
2504 .map(GitReference::Branch)
2505 .or_else(|| orig.tag.clone().map(GitReference::Tag))
2506 .or_else(|| orig.rev.clone().map(GitReference::Rev))
2507 .unwrap_or(GitReference::DefaultBranch);
2508
2509 let loc = git.into_url()?;
2510 if let Some(fragment) = loc.fragment() {
2511 let msg = format!(
2512 "URL fragment `#{fragment}` in git URL is ignored for dependency ({name_in_toml}). \
2513 If you were trying to specify a specific git revision, \
2514 use `rev = \"{fragment}\"` in the dependency declaration.",
2515 );
2516 manifest_ctx.warnings.push(msg);
2517 }
2518
2519 SourceId::for_git(&loc, reference)?
2520 }
2521 (None, None, Some(path), _) => {
2522 let path = path.resolve(manifest_ctx.gctx);
2523 if manifest_ctx.source_id.is_path() {
2532 let path = manifest_ctx.file.parent().unwrap().join(path);
2533 let path = paths::normalize_path(&path);
2534 SourceId::for_path(&path)?
2535 } else {
2536 manifest_ctx.source_id
2537 }
2538 }
2539 (None, None, None, Some(())) => match (registry, registry_index) {
2540 (Some(_), Some(_)) => {
2541 bail!(
2542 "dependency ({name_in_toml}) specification is ambiguous. \
2543 Only one of `registry` or `registry-index` is allowed.",
2544 )
2545 }
2546 (Some(registry), None) => SourceId::alt_registry(manifest_ctx.gctx, registry)?,
2547 (None, Some(registry_index)) => {
2548 let url = registry_index.into_url()?;
2549 SourceId::for_registry(&url)?
2550 }
2551 (None, None) => SourceId::crates_io(manifest_ctx.gctx)?,
2552 },
2553 (None, None, None, None) => {
2554 bail!(
2555 "dependency ({name_in_toml}) specified without \
2556 providing a local path, Git repository, version, or \
2557 workspace dependency to use"
2558 )
2559 }
2560 (builtin_kind, git_kind, path_kind, registry_kind) => {
2561 if builtin_kind.is_some() {
2562 if registry_kind.is_some() {
2563 bail!(
2564 "builtin dependency `{name_in_toml}` cannot be combined with a version requirement\n\
2565 Builtin dependencies are unversioned."
2566 )
2567 }
2568 bail!(
2569 "dependency ({name_in_toml}) specification is ambiguous. \
2570 `builtin = true` cannot be combined with any other dependency source"
2571 )
2572 }
2573
2574 if git_kind.is_some() && path_kind.is_some() {
2575 bail!(
2576 "dependency ({name_in_toml}) specification is ambiguous. \
2577 Only one of `git` or `path` is allowed.",
2578 )
2579 }
2580
2581 bail!("dependency ({name_in_toml}) specification is ambiguous")
2582 }
2583 };
2584 Ok(source)
2585}
2586
2587pub(crate) fn lookup_path_base<'a>(
2588 base: &PathBaseName,
2589 gctx: &GlobalContext,
2590 workspace_root: &dyn Fn() -> CargoResult<&'a Path>,
2591 features: &Features,
2592) -> CargoResult<PathBuf> {
2593 features.require(Feature::path_bases())?;
2594
2595 if let Some(path_bases) =
2597 gctx.get::<Option<ConfigRelativePath>>(["path-bases", base.as_str()])?
2598 {
2599 Ok(path_bases.resolve_path(gctx))
2600 } else {
2601 match base.as_str() {
2603 "workspace" => Ok(workspace_root()?.to_path_buf()),
2604 _ => bail!(
2605 "path base `{base}` is undefined. \
2606 You must add an entry for `{base}` in the Cargo configuration [path-bases] table."
2607 ),
2608 }
2609 }
2610}
2611
2612pub trait ResolveToPath {
2613 fn resolve(&self, gctx: &GlobalContext) -> PathBuf;
2614}
2615
2616impl ResolveToPath for String {
2617 fn resolve(&self, _: &GlobalContext) -> PathBuf {
2618 self.into()
2619 }
2620}
2621
2622impl ResolveToPath for ConfigRelativePath {
2623 fn resolve(&self, gctx: &GlobalContext) -> PathBuf {
2624 self.resolve_path(gctx)
2625 }
2626}
2627
2628#[tracing::instrument(skip_all)]
2631fn unique_build_targets(
2632 targets: &[Target],
2633 package_root: &Path,
2634) -> Result<(), HashMap<PathBuf, Vec<Target>>> {
2635 let mut source_targets = HashMap::<_, Vec<_>>::default();
2636 for target in targets {
2637 if let TargetSourcePath::Path(path) = target.src_path() {
2638 let full = package_root.join(path);
2639 source_targets.entry(full).or_default().push(target.clone());
2640 }
2641 }
2642
2643 let conflict_targets = source_targets
2644 .into_iter()
2645 .filter(|(_, targets)| targets.len() > 1)
2646 .collect::<HashMap<_, _>>();
2647
2648 if !conflict_targets.is_empty() {
2649 return Err(conflict_targets);
2650 }
2651
2652 Ok(())
2653}
2654
2655fn validate_profiles(
2660 profiles: &manifest::TomlProfiles,
2661 cli_unstable: &CliUnstable,
2662 features: &Features,
2663 warnings: &mut Vec<String>,
2664) -> CargoResult<()> {
2665 for (name, profile) in &profiles.0 {
2666 validate_profile(profile, name, cli_unstable, features, warnings)?;
2667 }
2668 Ok(())
2669}
2670
2671pub fn validate_profile(
2673 root: &manifest::TomlProfile,
2674 name: &str,
2675 cli_unstable: &CliUnstable,
2676 features: &Features,
2677 warnings: &mut Vec<String>,
2678) -> CargoResult<()> {
2679 validate_profile_layer(root, cli_unstable, features)?;
2680 if let Some(ref profile) = root.build_override {
2681 validate_profile_override(profile, "build-override")?;
2682 validate_profile_layer(profile, cli_unstable, features)?;
2683 }
2684 if let Some(ref packages) = root.package {
2685 for profile in packages.values() {
2686 validate_profile_override(profile, "package")?;
2687 validate_profile_layer(profile, cli_unstable, features)?;
2688 }
2689 }
2690
2691 if let Some(dir_name) = &root.dir_name {
2692 bail!(
2696 "dir-name=\"{}\" in profile `{}` is not currently allowed, \
2697 directory names are tied to the profile name for custom profiles",
2698 dir_name,
2699 name
2700 );
2701 }
2702
2703 match name {
2704 "doc" => {
2705 warnings.push("profile `doc` is deprecated and has no effect".to_string());
2706 }
2707 "test" | "bench" => {
2708 if root.panic.is_some() {
2709 warnings.push(format!("`panic` setting is ignored for `{}` profile", name))
2710 }
2711 }
2712 _ => {}
2713 }
2714
2715 if let Some(panic) = &root.panic {
2716 if panic != "unwind" && panic != "abort" && panic != "immediate-abort" {
2717 bail!(
2718 "`panic` setting of `{}` is not a valid setting, \
2719 must be `unwind`, `abort`, or `immediate-abort`.",
2720 panic
2721 );
2722 }
2723 }
2724
2725 if let Some(manifest::StringOrBool::String(arg)) = &root.lto {
2726 if arg == "true" || arg == "false" {
2727 bail!(
2728 "`lto` setting of string `\"{arg}\"` for `{name}` profile is not \
2729 a valid setting, must be a boolean (`true`/`false`) or a string \
2730 (`\"thin\"`/`\"fat\"`/`\"off\"`) or omitted.",
2731 );
2732 }
2733 }
2734
2735 Ok(())
2736}
2737
2738fn validate_profile_layer(
2742 profile: &manifest::TomlProfile,
2743 cli_unstable: &CliUnstable,
2744 features: &Features,
2745) -> CargoResult<()> {
2746 if profile.codegen_backend.is_some() {
2747 match (
2748 features.require(Feature::codegen_backend()),
2749 cli_unstable.codegen_backend,
2750 ) {
2751 (Err(e), false) => return Err(e),
2752 _ => {}
2753 }
2754 }
2755 if profile.rustflags.is_some() {
2756 match (
2757 features.require(Feature::profile_rustflags()),
2758 cli_unstable.profile_rustflags,
2759 ) {
2760 (Err(e), false) => return Err(e),
2761 _ => {}
2762 }
2763 }
2764 if profile.panic.as_deref() == Some("immediate-abort") {
2765 match (
2766 features.require(Feature::panic_immediate_abort()),
2767 cli_unstable.panic_immediate_abort,
2768 ) {
2769 (Err(e), false) => return Err(e),
2770 _ => {}
2771 }
2772 }
2773 Ok(())
2774}
2775
2776fn validate_profile_override(profile: &manifest::TomlProfile, which: &str) -> CargoResult<()> {
2778 if profile.package.is_some() {
2779 bail!("package-specific profiles cannot be nested");
2780 }
2781 if profile.build_override.is_some() {
2782 bail!("build-override profiles cannot be nested");
2783 }
2784 if profile.panic.is_some() {
2785 bail!("`panic` may not be specified in a `{}` profile", which)
2786 }
2787 if profile.lto.is_some() {
2788 bail!("`lto` may not be specified in a `{}` profile", which)
2789 }
2790 if profile.rpath.is_some() {
2791 bail!("`rpath` may not be specified in a `{}` profile", which)
2792 }
2793 Ok(())
2794}
2795
2796fn verify_lints(
2797 lints: Option<&manifest::TomlLints>,
2798 warnings: &mut Vec<String>,
2799) -> CargoResult<()> {
2800 let Some(lints) = lints else {
2801 return Ok(());
2802 };
2803
2804 for (tool, lints) in lints {
2805 let supported = ["cargo", "clippy", "rust", "rustdoc"];
2806 if !supported.contains(&tool.as_str()) {
2807 let message = format!(
2808 "unrecognized lint tool `lints.{tool}`, specifying unrecognized tools may break in the future.
2809supported tools: {}",
2810 supported.join(", "),
2811 );
2812 warnings.push(message);
2813 continue;
2814 }
2815 let mut seen_normalized: HashMap<String, String> = HashMap::default();
2816 for (name, config) in lints {
2817 let normalized = name.replace('-', "_");
2818 if name.contains('-') {
2819 warnings.push(format!(
2820 "`lints.{tool}.{name}` is deprecated in favor of \
2821 `lints.{tool}.{normalized}` and will not work in a \
2822 future edition"
2823 ));
2824 }
2825 if let Some(existing) = seen_normalized.get(&normalized) {
2826 warnings.push(format!(
2827 "duplicate lint `{existing}` in `[lints.{tool}]`, \
2828 conflicts with `{name}` and will not work in a future edition"
2829 ));
2830 }
2831 seen_normalized.insert(normalized.clone(), name.to_string());
2832 if let Some((prefix, suffix)) = name.split_once("::") {
2833 if tool == prefix {
2834 anyhow::bail!(
2835 "`lints.{tool}.{name}` is not valid lint name; try `lints.{prefix}.{suffix}`"
2836 )
2837 } else if tool == "rust" && supported.contains(&prefix) {
2838 anyhow::bail!(
2839 "`lints.{tool}.{name}` is not valid lint name; try `lints.{prefix}.{suffix}`"
2840 )
2841 } else {
2842 anyhow::bail!("`lints.{tool}.{name}` is not a valid lint name")
2843 }
2844 } else if let Some(config) = config.config() {
2845 for config_name in config.keys() {
2846 let expected = EXPECTED_LINT_CONFIG.contains(&(tool, name, config_name));
2849 if !expected {
2850 let message =
2851 format!("unused manifest key: `lints.{tool}.{name}.{config_name}`");
2852 warnings.push(message);
2853 }
2854 }
2855 }
2856 }
2857 }
2858
2859 Ok(())
2860}
2861
2862static EXPECTED_LINT_CONFIG: &[(&str, &str, &str)] = &[
2863 ("rust", "unexpected_cfgs", "check-cfg"),
2865];
2866
2867fn lints_to_rustflags(lints: &manifest::TomlLints) -> CargoResult<Vec<String>> {
2868 let mut rustflags = lints
2869 .iter()
2870 .filter(|(tool, _)| tool != &"cargo")
2872 .flat_map(|(tool, lints)| {
2873 lints.iter().map(move |(name, config)| {
2874 let flag = match config.level() {
2875 manifest::TomlLintLevel::Forbid => "--forbid",
2876 manifest::TomlLintLevel::Deny => "--deny",
2877 manifest::TomlLintLevel::Warn => "--warn",
2878 manifest::TomlLintLevel::Allow => "--allow",
2879 };
2880
2881 let option = if tool == "rust" {
2882 format!("{flag}={name}")
2883 } else {
2884 format!("{flag}={tool}::{name}")
2885 };
2886 (
2887 config.priority(),
2888 std::cmp::Reverse(name),
2891 option,
2892 )
2893 })
2894 })
2895 .collect::<Vec<_>>();
2896 rustflags.sort();
2897
2898 let mut rustflags: Vec<_> = rustflags.into_iter().map(|(_, _, option)| option).collect();
2899
2900 if let Some(rust_lints) = lints.get("rust") {
2902 if let Some(unexpected_cfgs) = rust_lints.get("unexpected_cfgs") {
2903 if let Some(config) = unexpected_cfgs.config() {
2904 if let Some(check_cfg) = config.get("check-cfg") {
2905 if let Ok(check_cfgs) = toml::Value::try_into::<Vec<String>>(check_cfg.clone())
2906 {
2907 for check_cfg in check_cfgs {
2908 rustflags.push("--check-cfg".to_string());
2909 rustflags.push(check_cfg);
2910 }
2911 } else {
2913 bail!("`lints.rust.unexpected_cfgs.check-cfg` must be a list of string");
2914 }
2915 }
2916 }
2917 }
2918 }
2919
2920 Ok(rustflags)
2921}
2922
2923fn emit_frontmatter_diagnostic(
2924 e: crate::util::frontmatter::FrontmatterError,
2925 contents: &str,
2926 manifest_file: &Path,
2927 gctx: &GlobalContext,
2928) -> anyhow::Error {
2929 let primary_span = e.primary_span();
2930
2931 let manifest_path = diff_paths(manifest_file, gctx.cwd())
2933 .unwrap_or_else(|| manifest_file.to_path_buf())
2934 .display()
2935 .to_string();
2936 let group = Group::with_title(Level::ERROR.primary_title(e.message())).element(
2937 Snippet::source(contents)
2938 .path(manifest_path)
2939 .annotation(AnnotationKind::Primary.span(primary_span))
2940 .annotations(
2941 e.visible_spans()
2942 .iter()
2943 .map(|s| AnnotationKind::Visible.span(s.clone())),
2944 ),
2945 );
2946
2947 if let Err(err) = gctx.shell().print_report(&[group], true) {
2948 return err.into();
2949 }
2950 return AlreadyPrintedError::new(e.into()).into();
2951}
2952
2953fn emit_toml_diagnostic(
2954 e: toml::de::Error,
2955 contents: &str,
2956 manifest_file: &Path,
2957 gctx: &GlobalContext,
2958) -> anyhow::Error {
2959 let Some(span) = e.span() else {
2960 return e.into();
2961 };
2962
2963 let manifest_path = diff_paths(manifest_file, gctx.cwd())
2965 .unwrap_or_else(|| manifest_file.to_path_buf())
2966 .display()
2967 .to_string();
2968 let group = Group::with_title(Level::ERROR.primary_title(e.message())).element(
2969 Snippet::source(contents)
2970 .path(manifest_path)
2971 .annotation(AnnotationKind::Primary.span(span)),
2972 );
2973
2974 if let Err(err) = gctx.shell().print_report(&[group], true) {
2975 return err.into();
2976 }
2977 return AlreadyPrintedError::new(e.into()).into();
2978}
2979
2980fn deprecated_underscore<T>(
2982 old: &Option<T>,
2983 new: &Option<T>,
2984 new_path: &str,
2985 name: &str,
2986 kind: &str,
2987 edition: Edition,
2988 warnings: &mut Vec<String>,
2989) -> CargoResult<()> {
2990 let old_path = new_path.replace("-", "_");
2991 if old.is_some() && Edition::Edition2024 <= edition {
2992 anyhow::bail!(
2993 "`{old_path}` is unsupported as of the 2024 edition; instead use `{new_path}`\n(in the `{name}` {kind})"
2994 );
2995 } else if old.is_some() && new.is_some() {
2996 warnings.push(format!(
2997 "`{old_path}` is redundant with `{new_path}`, preferring `{new_path}` in the `{name}` {kind}"
2998 ))
2999 } else if old.is_some() {
3000 warnings.push(format!(
3001 "`{old_path}` is deprecated in favor of `{new_path}` and will not work in the 2024 edition\n(in the `{name}` {kind})"
3002 ))
3003 }
3004 Ok(())
3005}
3006
3007fn warn_on_unused(unused: &BTreeSet<String>, warnings: &mut Vec<String>) {
3008 use std::fmt::Write as _;
3009
3010 for key in unused {
3011 let mut message = format!("unused manifest key: {}", key);
3012 if TOP_LEVEL_CONFIG_KEYS.iter().any(|c| c == key) {
3013 write!(
3014 &mut message,
3015 "\nhelp: {key} is a valid .cargo/config.toml key"
3016 )
3017 .unwrap();
3018 }
3019 warnings.push(message);
3020 }
3021}
3022
3023fn unused_dep_keys(
3024 dep_name: &str,
3025 kind: &str,
3026 unused_keys: Vec<String>,
3027 warnings: &mut Vec<String>,
3028) {
3029 for unused in unused_keys {
3030 let key = format!("unused manifest key: {kind}.{dep_name}.{unused}");
3031 warnings.push(key);
3032 }
3033}
3034
3035pub fn prepare_for_publish(
3037 me: &Package,
3038 ws: &Workspace<'_>,
3039 packaged_files: Option<&[PathBuf]>,
3040) -> CargoResult<Package> {
3041 let contents = me.manifest().contents();
3042 let document = me.manifest().document();
3043 let original_toml = prepare_toml_for_publish(
3044 me.manifest().normalized_toml(),
3045 ws,
3046 me.root(),
3047 packaged_files,
3048 )?;
3049 let normalized_toml = original_toml.clone();
3050 let features = me.manifest().unstable_features().clone();
3051 let workspace_config = me.manifest().workspace_config().clone();
3052 let source_id = me.package_id().source_id();
3053 let mut warnings = Default::default();
3054 let mut errors = Default::default();
3055 let gctx = ws.gctx();
3056 let manifest = to_real_manifest(
3057 contents.map(|c| c.to_owned()),
3058 document.cloned(),
3059 original_toml,
3060 normalized_toml,
3061 features,
3062 workspace_config,
3063 source_id,
3064 me.manifest_path(),
3065 me.manifest().is_embedded(),
3066 gctx,
3067 &mut warnings,
3068 &mut errors,
3069 )?;
3070 let new_pkg = Package::new(manifest, me.manifest_path());
3071 Ok(new_pkg)
3072}
3073
3074fn prepare_toml_for_publish(
3078 me: &manifest::TomlManifest,
3079 ws: &Workspace<'_>,
3080 package_root: &Path,
3081 packaged_files: Option<&[PathBuf]>,
3082) -> CargoResult<manifest::TomlManifest> {
3083 let gctx = ws.gctx();
3084
3085 if me
3086 .cargo_features
3087 .iter()
3088 .flat_map(|f| f.iter())
3089 .any(|f| f == "open-namespaces")
3090 {
3091 anyhow::bail!("cannot publish with `open-namespaces`")
3092 }
3093
3094 let mut package = me.package().unwrap().clone();
3095 package.workspace = None;
3096 if let Some(custom_build_scripts) = package.normalized_build().expect("previously normalized") {
3098 let mut included_scripts = Vec::new();
3099 for script in custom_build_scripts {
3100 let path = Path::new(script).to_path_buf();
3101 let included = packaged_files.map(|i| i.contains(&path)).unwrap_or(true);
3102 if included {
3103 let path = path
3104 .into_os_string()
3105 .into_string()
3106 .map_err(|_err| anyhow::format_err!("non-UTF8 `package.build`"))?;
3107 let path = normalize_path_string_sep(path);
3108 included_scripts.push(path);
3109 } else {
3110 ws.gctx().shell().warn(format!(
3111 "ignoring `package.build` entry `{}` as it is not included in the published package",
3112 path.display()
3113 ))?;
3114 }
3115 }
3116
3117 package.build = Some(match included_scripts.len() {
3118 0 => TomlPackageBuild::Auto(false),
3119 1 => TomlPackageBuild::SingleScript(included_scripts[0].clone()),
3120 _ => TomlPackageBuild::MultipleScript(included_scripts),
3121 });
3122 }
3123 let current_resolver = package
3124 .resolver
3125 .as_ref()
3126 .map(|r| ResolveBehavior::from_manifest(r))
3127 .unwrap_or_else(|| {
3128 package
3129 .edition
3130 .as_ref()
3131 .and_then(|e| e.as_value())
3132 .map(|e| Edition::from_str(e))
3133 .unwrap_or(Ok(Edition::Edition2015))
3134 .map(|e| e.default_resolve_behavior())
3135 })?;
3136 if ws.resolve_behavior() != current_resolver {
3137 package.resolver = Some(ws.resolve_behavior().to_manifest());
3142 }
3143 if let Some(license_file) = &package.license_file {
3144 let license_file = license_file
3145 .as_value()
3146 .context("license file should have been resolved before `prepare_for_publish()`")?;
3147 let license_path = Path::new(&license_file);
3148 let abs_license_path = paths::normalize_path(&package_root.join(license_path));
3149 if let Ok(license_file) = abs_license_path.strip_prefix(package_root) {
3150 package.license_file = Some(manifest::InheritableField::Value(
3151 normalize_path_string_sep(
3152 license_file
3153 .to_str()
3154 .ok_or_else(|| anyhow::format_err!("non-UTF8 `package.license-file`"))?
3155 .to_owned(),
3156 ),
3157 ));
3158 } else {
3159 package.license_file = Some(manifest::InheritableField::Value(
3162 license_path
3163 .file_name()
3164 .unwrap()
3165 .to_str()
3166 .unwrap()
3167 .to_string(),
3168 ));
3169 }
3170 }
3171
3172 if let Some(readme) = &package.readme {
3173 let readme = readme
3174 .as_value()
3175 .context("readme should have been resolved before `prepare_for_publish()`")?;
3176 match readme {
3177 manifest::StringOrBool::String(readme) => {
3178 let readme_path = Path::new(&readme);
3179 let abs_readme_path = paths::normalize_path(&package_root.join(readme_path));
3180 if let Ok(readme_path) = abs_readme_path.strip_prefix(package_root) {
3181 package.readme = Some(manifest::InheritableField::Value(StringOrBool::String(
3182 normalize_path_string_sep(
3183 readme_path
3184 .to_str()
3185 .ok_or_else(|| {
3186 anyhow::format_err!("non-UTF8 `package.license-file`")
3187 })?
3188 .to_owned(),
3189 ),
3190 )));
3191 } else {
3192 package.readme = Some(manifest::InheritableField::Value(
3195 manifest::StringOrBool::String(
3196 readme_path
3197 .file_name()
3198 .unwrap()
3199 .to_str()
3200 .unwrap()
3201 .to_string(),
3202 ),
3203 ));
3204 }
3205 }
3206 manifest::StringOrBool::Bool(_) => {}
3207 }
3208 }
3209
3210 let lib = if let Some(target) = &me.lib {
3211 prepare_target_for_publish(target, packaged_files, "library", ws.gctx())?
3212 } else {
3213 None
3214 };
3215 let bin = prepare_targets_for_publish(me.bin.as_ref(), packaged_files, "binary", ws.gctx())?;
3216 let example =
3217 prepare_targets_for_publish(me.example.as_ref(), packaged_files, "example", ws.gctx())?;
3218 let test = prepare_targets_for_publish(me.test.as_ref(), packaged_files, "test", ws.gctx())?;
3219 let bench =
3220 prepare_targets_for_publish(me.bench.as_ref(), packaged_files, "benchmark", ws.gctx())?;
3221
3222 let all = |_d: &manifest::TomlDependency| true;
3223 let mut manifest = manifest::TomlManifest {
3224 cargo_features: me.cargo_features.clone(),
3225 package: Some(package),
3226 project: None,
3227 badges: me.badges.clone(),
3228 features: me.features.clone(),
3229 lib,
3230 bin,
3231 example,
3232 test,
3233 bench,
3234 dependencies: map_deps(gctx, me.dependencies.as_ref(), all)?,
3235 dev_dependencies: map_deps(
3236 gctx,
3237 me.dev_dependencies(),
3238 manifest::TomlDependency::is_version_specified,
3239 )?,
3240 dev_dependencies2: None,
3241 build_dependencies: map_deps(gctx, me.build_dependencies(), all)?,
3242 build_dependencies2: None,
3243 target: match me.target.as_ref().map(|target_map| {
3244 target_map
3245 .iter()
3246 .map(|(k, v)| {
3247 Ok((
3248 k.clone(),
3249 manifest::TomlPlatform {
3250 dependencies: map_deps(gctx, v.dependencies.as_ref(), all)?,
3251 dev_dependencies: map_deps(
3252 gctx,
3253 v.dev_dependencies(),
3254 manifest::TomlDependency::is_version_specified,
3255 )?,
3256 dev_dependencies2: None,
3257 build_dependencies: map_deps(gctx, v.build_dependencies(), all)?,
3258 build_dependencies2: None,
3259 },
3260 ))
3261 })
3262 .collect()
3263 }) {
3264 Some(Ok(v)) => Some(v),
3265 Some(Err(e)) => return Err(e),
3266 None => None,
3267 },
3268 lints: me.lints.clone(),
3269 hints: me.hints.clone(),
3270 workspace: None,
3271 profile: me.profile.clone(),
3272 patch: None,
3273 replace: None,
3274 _unused_keys: Default::default(),
3275 };
3276 strip_features(&mut manifest);
3277 return Ok(manifest);
3278
3279 fn strip_features(manifest: &mut TomlManifest) {
3280 fn insert_dep_name(
3281 dep_name_set: &mut BTreeSet<manifest::PackageName>,
3282 deps: Option<&BTreeMap<manifest::PackageName, manifest::InheritableDependency>>,
3283 ) {
3284 let Some(deps) = deps else {
3285 return;
3286 };
3287 deps.iter().for_each(|(k, _v)| {
3288 dep_name_set.insert(k.clone());
3289 });
3290 }
3291 let mut dep_name_set = BTreeSet::new();
3292 insert_dep_name(&mut dep_name_set, manifest.dependencies.as_ref());
3293 insert_dep_name(&mut dep_name_set, manifest.dev_dependencies());
3294 insert_dep_name(&mut dep_name_set, manifest.build_dependencies());
3295 if let Some(target_map) = manifest.target.as_ref() {
3296 target_map.iter().for_each(|(_k, v)| {
3297 insert_dep_name(&mut dep_name_set, v.dependencies.as_ref());
3298 insert_dep_name(&mut dep_name_set, v.dev_dependencies());
3299 insert_dep_name(&mut dep_name_set, v.build_dependencies());
3300 });
3301 }
3302 let features = manifest.features.as_mut();
3303
3304 let Some(features) = features else {
3305 return;
3306 };
3307
3308 for feature in features.values_mut() {
3309 let enables = match feature {
3310 FeatureDefinition::Array(enables) => enables,
3311 FeatureDefinition::Metadata(metadata) => &mut metadata.enables,
3312 };
3313 enables.retain(|feature_dep| {
3314 let feature_value = FeatureValue::new(feature_dep.as_str().into());
3315 match feature_value {
3316 FeatureValue::Dep { dep_name } | FeatureValue::DepFeature { dep_name, .. } => {
3317 let k = &manifest::PackageName::new(dep_name.to_string()).unwrap();
3318 dep_name_set.contains(k)
3319 }
3320 _ => true,
3321 }
3322 });
3323 }
3324 }
3325
3326 fn map_deps(
3327 gctx: &GlobalContext,
3328 deps: Option<&BTreeMap<manifest::PackageName, manifest::InheritableDependency>>,
3329 filter: impl Fn(&manifest::TomlDependency) -> bool,
3330 ) -> CargoResult<Option<BTreeMap<manifest::PackageName, manifest::InheritableDependency>>> {
3331 let Some(deps) = deps else {
3332 return Ok(None);
3333 };
3334 let deps = deps
3335 .iter()
3336 .filter(|(_k, v)| {
3337 if let manifest::InheritableDependency::Value(def) = v {
3338 filter(def)
3339 } else {
3340 false
3341 }
3342 })
3343 .map(|(k, v)| Ok((k.clone(), map_dependency(gctx, v)?)))
3344 .collect::<CargoResult<BTreeMap<_, _>>>()?;
3345 Ok(Some(deps))
3346 }
3347
3348 fn map_dependency(
3349 gctx: &GlobalContext,
3350 dep: &manifest::InheritableDependency,
3351 ) -> CargoResult<manifest::InheritableDependency> {
3352 let dep = match dep {
3353 manifest::InheritableDependency::Value(manifest::TomlDependency::Detailed(d)) => {
3354 let mut d = d.clone();
3355 d.path.take();
3357 d.base.take();
3358 d.git.take();
3360 d.branch.take();
3361 d.tag.take();
3362 d.rev.take();
3363 if let Some(registry) = d.registry.take() {
3365 d.registry_index = Some(gctx.get_registry_index(®istry)?.to_string());
3366 }
3367 Ok(d)
3368 }
3369 manifest::InheritableDependency::Value(manifest::TomlDependency::Simple(s)) => {
3370 Ok(manifest::TomlDetailedDependency {
3371 version: Some(s.clone()),
3372 ..Default::default()
3373 })
3374 }
3375 _ => unreachable!(),
3376 };
3377 dep.map(manifest::TomlDependency::Detailed)
3378 .map(manifest::InheritableDependency::Value)
3379 }
3380}
3381
3382pub fn prepare_targets_for_publish(
3383 targets: Option<&Vec<manifest::TomlTarget>>,
3384 packaged_files: Option<&[PathBuf]>,
3385 context: &str,
3386 gctx: &GlobalContext,
3387) -> CargoResult<Option<Vec<manifest::TomlTarget>>> {
3388 let Some(targets) = targets else {
3389 return Ok(None);
3390 };
3391
3392 let mut prepared = Vec::with_capacity(targets.len());
3393 for target in targets {
3394 let Some(target) = prepare_target_for_publish(target, packaged_files, context, gctx)?
3395 else {
3396 continue;
3397 };
3398 prepared.push(target);
3399 }
3400
3401 if prepared.is_empty() {
3402 Ok(None)
3403 } else {
3404 Ok(Some(prepared))
3405 }
3406}
3407
3408pub fn prepare_target_for_publish(
3409 target: &manifest::TomlTarget,
3410 packaged_files: Option<&[PathBuf]>,
3411 context: &str,
3412 gctx: &GlobalContext,
3413) -> CargoResult<Option<manifest::TomlTarget>> {
3414 let path = target.path.as_ref().expect("previously normalized");
3415 let path = &path.0;
3416 if let Some(packaged_files) = packaged_files {
3417 if !packaged_files.contains(&path) {
3418 let name = target.name.as_ref().expect("previously normalized");
3419 gctx.shell().warn(format!(
3420 "ignoring {context} `{name}` as `{}` is not included in the published package",
3421 path.display()
3422 ))?;
3423 return Ok(None);
3424 }
3425 }
3426
3427 let mut target = target.clone();
3428 let path = normalize_path_sep(path.to_path_buf(), context)?;
3429 target.path = Some(manifest::PathValue(path.into()));
3430
3431 Ok(Some(target))
3432}
3433
3434fn normalize_path_sep(path: PathBuf, context: &str) -> CargoResult<PathBuf> {
3435 let path = path
3436 .into_os_string()
3437 .into_string()
3438 .map_err(|_err| anyhow::format_err!("non-UTF8 path for {context}"))?;
3439 let path = normalize_path_string_sep(path);
3440 Ok(path.into())
3441}
3442
3443pub fn normalize_path_string_sep(path: String) -> String {
3444 if std::path::MAIN_SEPARATOR != '/' {
3445 path.replace(std::path::MAIN_SEPARATOR, "/")
3446 } else {
3447 path
3448 }
3449}