Skip to main content

cargo/util/toml/
mod.rs

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