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