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