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