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