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