cargo/util/toml/
mod.rs

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