diff --git a/src/bin/cargo/main.rs b/src/bin/cargo/main.rs index 1fd91c99a05..245622b6c97 100644 --- a/src/bin/cargo/main.rs +++ b/src/bin/cargo/main.rs @@ -4,7 +4,7 @@ use cargo::util::network::http::http_handle; use cargo::util::network::http::needs_custom_http_transport; -use cargo::util::toml::StringOrVec; +use cargo::util::toml::schema::StringOrVec; use cargo::util::CliError; use cargo::util::{self, closest_msg, command_prelude, CargoResult, CliResult, Config}; use cargo_util::{ProcessBuilder, ProcessError}; diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index ce4b96ba3db..ab43e99795a 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -93,8 +93,8 @@ use crate::core::{Feature, PackageId, Target, Verbosity}; use crate::util::errors::{CargoResult, VerboseError}; use crate::util::interning::InternedString; use crate::util::machine_message::{self, Message}; -use crate::util::toml::TomlDebugInfo; -use crate::util::toml::TomlTrimPaths; +use crate::util::toml::schema::TomlDebugInfo; +use crate::util::toml::schema::TomlTrimPaths; use crate::util::{add_path_args, internal, iter_join_onto, profile}; use cargo_util::{paths, ProcessBuilder, ProcessError}; use rustfix::diagnostics::Applicability; diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index c6a95b2eb98..72a267f0402 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -195,7 +195,7 @@ pub const SEE_CHANNELS: &str = /// [`LATEST_STABLE`]: Edition::LATEST_STABLE /// [this example]: https://github.com/rust-lang/cargo/blob/3ebb5f15a940810f250b68821149387af583a79e/src/doc/src/reference/unstable.md?plain=1#L1238-L1264 /// [`is_stable`]: Edition::is_stable -/// [`TomlManifest::to_real_manifest`]: crate::util::toml::TomlManifest::to_real_manifest +/// [`TomlManifest::to_real_manifest`]: crate::util::toml::schema::TomlManifest::to_real_manifest /// [`features!`]: macro.features.html #[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, Eq, PartialEq, Serialize, Deserialize)] pub enum Edition { diff --git a/src/cargo/core/manifest.rs b/src/cargo/core/manifest.rs index 7886abec302..66af40c10a3 100644 --- a/src/cargo/core/manifest.rs +++ b/src/cargo/core/manifest.rs @@ -18,7 +18,7 @@ use crate::core::{Dependency, PackageId, PackageIdSpec, SourceId, Summary}; use crate::core::{Edition, Feature, Features, WorkspaceConfig}; use crate::util::errors::*; use crate::util::interning::InternedString; -use crate::util::toml::{TomlManifest, TomlProfiles}; +use crate::util::toml::schema::{TomlManifest, TomlProfiles}; use crate::util::{short_hash, Config, Filesystem, RustVersion}; pub enum EitherManifest { diff --git a/src/cargo/core/mod.rs b/src/cargo/core/mod.rs index 9b56564a7c9..2add52d5c1c 100644 --- a/src/cargo/core/mod.rs +++ b/src/cargo/core/mod.rs @@ -14,7 +14,7 @@ pub use self::workspace::{ find_workspace_root, resolve_relative_path, MaybePackage, Workspace, WorkspaceConfig, WorkspaceRootConfig, }; -pub use crate::util::toml::InheritableFields; +pub use crate::util::toml::schema::InheritableFields; pub mod compiler; pub mod dependency; diff --git a/src/cargo/core/profiles.rs b/src/cargo/core/profiles.rs index 95354a3f6ad..ec53dbae59e 100644 --- a/src/cargo/core/profiles.rs +++ b/src/cargo/core/profiles.rs @@ -27,9 +27,9 @@ use crate::core::resolver::features::FeaturesFor; use crate::core::Feature; use crate::core::{PackageId, PackageIdSpec, Resolve, Shell, Target, Workspace}; use crate::util::interning::InternedString; -use crate::util::toml::TomlTrimPaths; -use crate::util::toml::TomlTrimPathsValue; -use crate::util::toml::{ +use crate::util::toml::schema::TomlTrimPaths; +use crate::util::toml::schema::TomlTrimPathsValue; +use crate::util::toml::schema::{ ProfilePackageSpec, StringOrBool, TomlDebugInfo, TomlProfile, TomlProfiles, }; use crate::util::{closest_msg, config, CargoResult, Config}; diff --git a/src/cargo/core/workspace.rs b/src/cargo/core/workspace.rs index dfcd56dd05e..4667c8029d2 100644 --- a/src/cargo/core/workspace.rs +++ b/src/cargo/core/workspace.rs @@ -22,7 +22,9 @@ use crate::sources::{PathSource, CRATES_IO_INDEX, CRATES_IO_REGISTRY}; use crate::util::edit_distance; use crate::util::errors::{CargoResult, ManifestError}; use crate::util::interning::InternedString; -use crate::util::toml::{read_manifest, InheritableFields, TomlDependency, TomlProfiles}; +use crate::util::toml::{ + read_manifest, schema::InheritableFields, schema::TomlDependency, schema::TomlProfiles, +}; use crate::util::RustVersion; use crate::util::{config::ConfigRelativePath, Config, Filesystem, IntoUrl}; use cargo_util::paths; diff --git a/src/cargo/lib.rs b/src/cargo/lib.rs index 908ff4ecc38..ce1c899dff6 100644 --- a/src/cargo/lib.rs +++ b/src/cargo/lib.rs @@ -93,7 +93,7 @@ //! Files that interact with cargo include //! //! - Package -//! - `Cargo.toml`: User-written project manifest, loaded with [`util::toml::TomlManifest`] and then +//! - `Cargo.toml`: User-written project manifest, loaded with [`util::toml::schema::TomlManifest`] and then //! translated to [`core::manifest::Manifest`] which maybe stored in a [`core::Package`]. //! - This is editable with [`util::toml_mut::manifest::LocalManifest`] //! - `Cargo.lock`: Generally loaded with [`ops::resolve_ws`] or a variant of it into a [`core::resolver::Resolve`] diff --git a/src/cargo/ops/cargo_package.rs b/src/cargo/ops/cargo_package.rs index 1a291045b9a..4d145d887c0 100644 --- a/src/cargo/ops/cargo_package.rs +++ b/src/cargo/ops/cargo_package.rs @@ -16,7 +16,7 @@ use crate::sources::PathSource; use crate::util::cache_lock::CacheLockMode; use crate::util::config::JobsConfig; use crate::util::errors::CargoResult; -use crate::util::toml::TomlManifest; +use crate::util::toml::schema::TomlManifest; use crate::util::{self, human_readable_bytes, restricted_names, Config, FileLock}; use crate::{drop_println, ops}; use anyhow::Context as _; diff --git a/src/cargo/util/command_prelude.rs b/src/cargo/util/command_prelude.rs index f5b897ea710..6e90d3228d2 100644 --- a/src/cargo/util/command_prelude.rs +++ b/src/cargo/util/command_prelude.rs @@ -7,7 +7,7 @@ use crate::util::important_paths::find_root_manifest_for_wd; use crate::util::interning::InternedString; use crate::util::is_rustup; use crate::util::restricted_names::is_glob_pattern; -use crate::util::toml::{StringOrVec, TomlProfile}; +use crate::util::toml::schema::{StringOrVec, TomlProfile}; use crate::util::validate_package_name; use crate::util::{ print_available_benches, print_available_binaries, print_available_examples, diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 46f76f42a6e..120dfcf84d2 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -1,6 +1,5 @@ use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::ffi::OsStr; -use std::fmt::{self, Display, Write}; use std::path::{Path, PathBuf}; use std::rc::Rc; use std::str::{self, FromStr}; @@ -10,10 +9,6 @@ use cargo_platform::Platform; use cargo_util::paths; use itertools::Itertools; use lazycell::LazyCell; -use serde::de::{self, IntoDeserializer as _, Unexpected}; -use serde::ser; -use serde::{Deserialize, Serialize}; -use serde_untagged::UntaggedEnumVisitor; use tracing::{debug, trace}; use url::Url; @@ -34,6 +29,7 @@ use crate::util::{ }; pub mod embedded; +pub mod schema; mod targets; use self::targets::targets; @@ -100,7 +96,7 @@ fn read_manifest_from_str( let mut unused = BTreeSet::new(); let deserializer = toml::de::Deserializer::new(contents); - let manifest: TomlManifest = serde_ignored::deserialize(deserializer, |path| { + let manifest: schema::TomlManifest = serde_ignored::deserialize(deserializer, |path| { let mut key = String::new(); stringify(&mut key, &path); unused.insert(key); @@ -130,8 +126,13 @@ fn read_manifest_from_str( } } return if manifest.project.is_some() || manifest.package.is_some() { - let (mut manifest, paths) = - TomlManifest::to_real_manifest(&manifest, embedded, source_id, package_root, config)?; + let (mut manifest, paths) = schema::TomlManifest::to_real_manifest( + &manifest, + embedded, + source_id, + package_root, + config, + )?; add_unused(manifest.warnings_mut()); if manifest.targets().iter().all(|t| t.is_custom_build()) { bail!( @@ -143,7 +144,7 @@ fn read_manifest_from_str( Ok((EitherManifest::Real(manifest), paths)) } else { let (mut m, paths) = - TomlManifest::to_virtual_manifest(&manifest, source_id, package_root, config)?; + schema::TomlManifest::to_virtual_manifest(&manifest, source_id, package_root, config)?; add_unused(m.warnings_mut()); Ok((EitherManifest::Virtual(m), paths)) }; @@ -188,36 +189,7 @@ fn warn_on_deprecated(new_path: &str, name: &str, kind: &str, warnings: &mut Vec )) } -/// This type is used to deserialize `Cargo.toml` files. -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct TomlManifest { - cargo_features: Option>, - package: Option>, - project: Option>, - profile: Option, - lib: Option, - bin: Option>, - example: Option>, - test: Option>, - bench: Option>, - dependencies: Option>, - dev_dependencies: Option>, - #[serde(rename = "dev_dependencies")] - dev_dependencies2: Option>, - build_dependencies: Option>, - #[serde(rename = "build_dependencies")] - build_dependencies2: Option>, - features: Option>>, - target: Option>, - replace: Option>, - patch: Option>>, - workspace: Option, - badges: Option, - lints: Option, -} - -impl TomlManifest { +impl schema::TomlManifest { /// Prepares the manifest for publishing. // - Path and git components of dependency specifications are removed. // - License path is updated to point within the package. @@ -225,7 +197,7 @@ impl TomlManifest { &self, ws: &Workspace<'_>, package_root: &Path, - ) -> CargoResult { + ) -> CargoResult { let config = ws.config(); let mut package = self .package @@ -263,7 +235,7 @@ impl TomlManifest { if abs_license_path.strip_prefix(package_root).is_err() { // This path points outside of the package root. `cargo package` // will copy it into the root, so adjust the path to this location. - package.license_file = Some(MaybeWorkspace::Defined( + package.license_file = Some(schema::MaybeWorkspace::Defined( license_path .file_name() .unwrap() @@ -279,27 +251,29 @@ impl TomlManifest { .as_defined() .context("readme should have been resolved before `prepare_for_publish()`")?; match readme { - StringOrBool::String(readme) => { + schema::StringOrBool::String(readme) => { let readme_path = Path::new(&readme); let abs_readme_path = paths::normalize_path(&package_root.join(readme_path)); if abs_readme_path.strip_prefix(package_root).is_err() { // This path points outside of the package root. `cargo package` // will copy it into the root, so adjust the path to this location. - package.readme = Some(MaybeWorkspace::Defined(StringOrBool::String( - readme_path - .file_name() - .unwrap() - .to_str() - .unwrap() - .to_string(), - ))); + package.readme = Some(schema::MaybeWorkspace::Defined( + schema::StringOrBool::String( + readme_path + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_string(), + ), + )); } } - StringOrBool::Bool(_) => {} + schema::StringOrBool::Bool(_) => {} } } - let all = |_d: &TomlDependency| true; - return Ok(TomlManifest { + let all = |_d: &schema::TomlDependency| true; + return Ok(schema::TomlManifest { package: Some(package), project: None, profile: self.profile.clone(), @@ -314,7 +288,7 @@ impl TomlManifest { self.dev_dependencies .as_ref() .or_else(|| self.dev_dependencies2.as_ref()), - TomlDependency::is_version_specified, + schema::TomlDependency::is_version_specified, )?, dev_dependencies2: None, build_dependencies: map_deps( @@ -332,14 +306,14 @@ impl TomlManifest { .map(|(k, v)| { Ok(( k.clone(), - TomlPlatform { + schema::TomlPlatform { dependencies: map_deps(config, v.dependencies.as_ref(), all)?, dev_dependencies: map_deps( config, v.dev_dependencies .as_ref() .or_else(|| v.dev_dependencies2.as_ref()), - TomlDependency::is_version_specified, + schema::TomlDependency::is_version_specified, )?, dev_dependencies2: None, build_dependencies: map_deps( @@ -369,14 +343,14 @@ impl TomlManifest { fn map_deps( config: &Config, - deps: Option<&BTreeMap>, - filter: impl Fn(&TomlDependency) -> bool, - ) -> CargoResult>> { + deps: Option<&BTreeMap>, + filter: impl Fn(&schema::TomlDependency) -> bool, + ) -> CargoResult>> { let Some(deps) = deps else { return Ok(None) }; let deps = deps .iter() .filter(|(_k, v)| { - if let MaybeWorkspace::Defined(def) = v { + if let schema::MaybeWorkspace::Defined(def) = v { filter(def) } else { false @@ -389,10 +363,10 @@ impl TomlManifest { fn map_dependency( config: &Config, - dep: &MaybeWorkspaceDependency, - ) -> CargoResult { + dep: &schema::MaybeWorkspaceDependency, + ) -> CargoResult { let dep = match dep { - MaybeWorkspace::Defined(TomlDependency::Detailed(d)) => { + schema::MaybeWorkspace::Defined(schema::TomlDependency::Detailed(d)) => { let mut d = d.clone(); // Path dependencies become crates.io deps. d.path.take(); @@ -407,19 +381,21 @@ impl TomlManifest { } Ok(d) } - MaybeWorkspace::Defined(TomlDependency::Simple(s)) => Ok(DetailedTomlDependency { - version: Some(s.clone()), - ..Default::default() - }), + schema::MaybeWorkspace::Defined(schema::TomlDependency::Simple(s)) => { + Ok(schema::DetailedTomlDependency { + version: Some(s.clone()), + ..Default::default() + }) + } _ => unreachable!(), }; - dep.map(TomlDependency::Detailed) - .map(MaybeWorkspace::Defined) + dep.map(schema::TomlDependency::Detailed) + .map(schema::MaybeWorkspace::Defined) } } pub fn to_real_manifest( - me: &Rc, + me: &Rc, embedded: bool, source_id: SourceId, package_root: &Path, @@ -429,7 +405,7 @@ impl TomlManifest { config: &Config, resolved_path: &Path, workspace_config: &WorkspaceConfig, - ) -> CargoResult { + ) -> CargoResult { match workspace_config { WorkspaceConfig::Root(root) => Ok(root.inheritable().clone()), WorkspaceConfig::Member { @@ -543,7 +519,7 @@ impl TomlManifest { let resolved_path = package_root.join("Cargo.toml"); - let inherit_cell: LazyCell = LazyCell::new(); + let inherit_cell: LazyCell = LazyCell::new(); let inherit = || inherit_cell.try_borrow_with(|| get_ws(config, &resolved_path, &workspace_config)); @@ -553,7 +529,7 @@ impl TomlManifest { .map(|version| version.resolve("version", || inherit()?.version())) .transpose()?; - package.version = version.clone().map(MaybeWorkspace::Defined); + package.version = version.clone().map(schema::MaybeWorkspace::Defined); let pkgid = package.to_package_id( source_id, @@ -567,7 +543,7 @@ impl TomlManifest { .resolve("edition", || inherit()?.edition())? .parse() .with_context(|| "failed to parse the `edition` key")?; - package.edition = Some(MaybeWorkspace::Defined(edition.to_string())); + package.edition = Some(schema::MaybeWorkspace::Defined(edition.to_string())); edition } else { Edition::Edition2015 @@ -691,11 +667,11 @@ impl TomlManifest { fn process_dependencies( cx: &mut Context<'_, '_>, - new_deps: Option<&BTreeMap>, + new_deps: Option<&BTreeMap>, kind: Option, workspace_config: &WorkspaceConfig, - inherit_cell: &LazyCell, - ) -> CargoResult>> { + inherit_cell: &LazyCell, + ) -> CargoResult>> { let Some(dependencies) = new_deps else { return Ok(None); }; @@ -706,7 +682,7 @@ impl TomlManifest { }) }; - let mut deps: BTreeMap = BTreeMap::new(); + let mut deps: BTreeMap = BTreeMap::new(); for (n, v) in dependencies.iter() { let resolved = v .clone() @@ -725,7 +701,10 @@ impl TomlManifest { }; unused_dep_keys(name_in_toml, &table_in_toml, v.unused_keys(), cx.warnings); cx.deps.push(dep); - deps.insert(n.to_string(), MaybeWorkspace::Defined(resolved.clone())); + deps.insert( + n.to_string(), + schema::MaybeWorkspace::Defined(resolved.clone()), + ); } Ok(Some(deps)) } @@ -773,10 +752,10 @@ impl TomlManifest { .map(|mw| mw.resolve(|| inherit()?.lints())) .transpose()?; let lints = verify_lints(lints)?; - let default = TomlLints::default(); + let default = schema::TomlLints::default(); let rustflags = lints_to_rustflags(lints.as_ref().unwrap_or(&default)); - let mut target: BTreeMap = BTreeMap::new(); + let mut target: BTreeMap = BTreeMap::new(); for (name, platform) in me.target.iter().flatten() { cx.platform = { let platform: Platform = name.parse()?; @@ -820,7 +799,7 @@ impl TomlManifest { )?; target.insert( name.clone(), - TomlPlatform { + schema::TomlPlatform { dependencies: deps, build_dependencies: build_deps, build_dependencies2: None, @@ -959,52 +938,54 @@ impl TomlManifest { package.description = metadata .description .clone() - .map(|description| MaybeWorkspace::Defined(description)); + .map(|description| schema::MaybeWorkspace::Defined(description)); package.homepage = metadata .homepage .clone() - .map(|homepage| MaybeWorkspace::Defined(homepage)); + .map(|homepage| schema::MaybeWorkspace::Defined(homepage)); package.documentation = metadata .documentation .clone() - .map(|documentation| MaybeWorkspace::Defined(documentation)); + .map(|documentation| schema::MaybeWorkspace::Defined(documentation)); package.readme = metadata .readme .clone() - .map(|readme| MaybeWorkspace::Defined(StringOrBool::String(readme))); + .map(|readme| schema::MaybeWorkspace::Defined(schema::StringOrBool::String(readme))); package.authors = package .authors .as_ref() - .map(|_| MaybeWorkspace::Defined(metadata.authors.clone())); + .map(|_| schema::MaybeWorkspace::Defined(metadata.authors.clone())); package.license = metadata .license .clone() - .map(|license| MaybeWorkspace::Defined(license)); + .map(|license| schema::MaybeWorkspace::Defined(license)); package.license_file = metadata .license_file .clone() - .map(|license_file| MaybeWorkspace::Defined(license_file)); + .map(|license_file| schema::MaybeWorkspace::Defined(license_file)); package.repository = metadata .repository .clone() - .map(|repository| MaybeWorkspace::Defined(repository)); + .map(|repository| schema::MaybeWorkspace::Defined(repository)); package.keywords = package .keywords .as_ref() - .map(|_| MaybeWorkspace::Defined(metadata.keywords.clone())); + .map(|_| schema::MaybeWorkspace::Defined(metadata.keywords.clone())); package.categories = package .categories .as_ref() - .map(|_| MaybeWorkspace::Defined(metadata.categories.clone())); - package.rust_version = rust_version.clone().map(|rv| MaybeWorkspace::Defined(rv)); + .map(|_| schema::MaybeWorkspace::Defined(metadata.categories.clone())); + package.rust_version = rust_version + .clone() + .map(|rv| schema::MaybeWorkspace::Defined(rv)); package.exclude = package .exclude .as_ref() - .map(|_| MaybeWorkspace::Defined(exclude.clone())); + .map(|_| schema::MaybeWorkspace::Defined(exclude.clone())); package.include = package .include .as_ref() - .map(|_| MaybeWorkspace::Defined(include.clone())); + .map(|_| schema::MaybeWorkspace::Defined(include.clone())); let profiles = me.profile.clone(); if let Some(profiles) = &profiles { @@ -1017,12 +998,12 @@ impl TomlManifest { .clone() .map(|publish| publish.resolve("publish", || inherit()?.publish()).unwrap()); - package.publish = publish.clone().map(|p| MaybeWorkspace::Defined(p)); + package.publish = publish.clone().map(|p| schema::MaybeWorkspace::Defined(p)); let publish = match publish { - Some(VecStringOrBool::VecString(ref vecstring)) => Some(vecstring.clone()), - Some(VecStringOrBool::Bool(false)) => Some(vec![]), - Some(VecStringOrBool::Bool(true)) => None, + Some(schema::VecStringOrBool::VecString(ref vecstring)) => Some(vecstring.clone()), + Some(schema::VecStringOrBool::Bool(false)) => Some(vec![]), + Some(schema::VecStringOrBool::Bool(true)) => None, None => version.is_none().then_some(vec![]), }; @@ -1063,7 +1044,7 @@ impl TomlManifest { .transpose()? .map(CompileKind::Target); let custom_metadata = package.metadata.clone(); - let resolved_toml = TomlManifest { + let resolved_toml = schema::TomlManifest { cargo_features: me.cargo_features.clone(), package: Some(package.clone()), project: None, @@ -1086,8 +1067,8 @@ impl TomlManifest { badges: me .badges .as_ref() - .map(|_| MaybeWorkspace::Defined(metadata.badges.clone())), - lints: lints.map(|lints| MaybeWorkspaceLints { + .map(|_| schema::MaybeWorkspace::Defined(metadata.badges.clone())), + lints: lints.map(|lints| schema::MaybeWorkspaceLints { workspace: false, lints, }), @@ -1142,7 +1123,7 @@ impl TomlManifest { } fn to_virtual_manifest( - me: &Rc, + me: &Rc, source_id: SourceId, root: &Path, config: &Config, @@ -1338,15 +1319,15 @@ impl TomlManifest { /// Returns the path to the build script if one exists for this crate. fn maybe_custom_build( &self, - build: &Option, + build: &Option, package_root: &Path, ) -> Option { let build_rs = package_root.join("build.rs"); match *build { // Explicitly no build script. - Some(StringOrBool::Bool(false)) => None, - Some(StringOrBool::Bool(true)) => Some(build_rs), - Some(StringOrBool::String(ref s)) => Some(PathBuf::from(s)), + Some(schema::StringOrBool::Bool(false)) => None, + Some(schema::StringOrBool::Bool(true)) => Some(build_rs), + Some(schema::StringOrBool::String(ref s)) => Some(PathBuf::from(s)), None => { // If there is a `build.rs` file next to the `Cargo.toml`, assume it is // a build script. @@ -1358,14 +1339,6 @@ impl TomlManifest { } } } - - pub fn has_profiles(&self) -> bool { - self.profile.is_some() - } - - pub fn features(&self) -> Option<&BTreeMap>> { - self.features.as_ref() - } } struct Context<'a, 'b> { @@ -1379,7 +1352,7 @@ struct Context<'a, 'b> { features: &'a Features, } -fn verify_lints(lints: Option) -> CargoResult> { +fn verify_lints(lints: Option) -> CargoResult> { let Some(lints) = lints else { return Ok(None); }; @@ -1410,7 +1383,7 @@ fn verify_lints(lints: Option) -> CargoResult> { Ok(Some(lints)) } -fn lints_to_rustflags(lints: &TomlLints) -> Vec { +fn lints_to_rustflags(lints: &schema::TomlLints) -> Vec { let mut rustflags = lints .iter() .flat_map(|(tool, lints)| { @@ -1450,7 +1423,7 @@ fn unused_dep_keys( fn inheritable_from_path( config: &Config, workspace_path: PathBuf, -) -> CargoResult { +) -> CargoResult { // Workspace path should have Cargo.toml at the end let workspace_path_root = workspace_path.parent().unwrap(); @@ -1477,14 +1450,17 @@ fn inheritable_from_path( } } -/// Returns the name of the README file for a [`TomlPackage`]. -pub fn readme_for_package(package_root: &Path, readme: Option<&StringOrBool>) -> Option { +/// Returns the name of the README file for a [`schema::TomlPackage`]. +pub fn readme_for_package( + package_root: &Path, + readme: Option<&schema::StringOrBool>, +) -> Option { match &readme { None => default_readme_from_package_root(package_root), Some(value) => match value { - StringOrBool::Bool(false) => None, - StringOrBool::Bool(true) => Some("README.md".to_string()), - StringOrBool::String(v) => Some(v.clone()), + schema::StringOrBool::Bool(false) => None, + schema::StringOrBool::Bool(true) => Some("README.md".to_string()), + schema::StringOrBool::String(v) => Some(v.clone()), }, } } @@ -1529,55 +1505,6 @@ fn unique_build_targets( Ok(()) } -#[derive(Debug, Deserialize, Serialize, Clone)] -#[serde(rename_all = "kebab-case")] -pub struct TomlWorkspace { - members: Option>, - exclude: Option>, - default_members: Option>, - resolver: Option, - metadata: Option, - - // Properties that can be inherited by members. - package: Option, - dependencies: Option>, - lints: Option, -} - -/// A group of fields that are inheritable by members of the workspace -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct InheritableFields { - // We use skip here since it will never be present when deserializing - // and we don't want it present when serializing - #[serde(skip)] - dependencies: Option>, - #[serde(skip)] - lints: Option, - - version: Option, - authors: Option>, - description: Option, - homepage: Option, - documentation: Option, - readme: Option, - keywords: Option>, - categories: Option>, - license: Option, - license_file: Option, - repository: Option, - publish: Option, - edition: Option, - badges: Option>>, - exclude: Option>, - include: Option>, - rust_version: Option, - // We use skip here since it will never be present when deserializing - // and we don't want it present when serializing - #[serde(skip)] - ws_root: PathBuf, -} - /// Defines simple getter methods for inheritable fields. macro_rules! inheritable_field_getter { ( $(($key:literal, $field:ident -> $ret:ty),)* ) => ( @@ -1593,11 +1520,11 @@ macro_rules! inheritable_field_getter { ) } -impl InheritableFields { +impl schema::InheritableFields { inheritable_field_getter! { // Please keep this list lexicographically ordered. - ("dependencies", dependencies -> BTreeMap), - ("lints", lints -> TomlLints), + ("dependencies", dependencies -> BTreeMap), + ("lints", lints -> schema::TomlLints), ("package.authors", authors -> Vec), ("package.badges", badges -> BTreeMap>), ("package.categories", categories -> Vec), @@ -1609,14 +1536,18 @@ impl InheritableFields { ("package.include", include -> Vec), ("package.keywords", keywords -> Vec), ("package.license", license -> String), - ("package.publish", publish -> VecStringOrBool), + ("package.publish", publish -> schema::VecStringOrBool), ("package.repository", repository -> String), ("package.rust-version", rust_version -> RustVersion), ("package.version", version -> semver::Version), } /// Gets a workspace dependency with the `name`. - pub fn get_dependency(&self, name: &str, package_root: &Path) -> CargoResult { + pub fn get_dependency( + &self, + name: &str, + package_root: &Path, + ) -> CargoResult { let Some(deps) = &self.dependencies else { bail!("`workspace.dependencies` was not defined"); }; @@ -1624,7 +1555,7 @@ impl InheritableFields { bail!("`dependency.{name}` was not found in `workspace.dependencies`"); }; let mut dep = dep.clone(); - if let TomlDependency::Detailed(detailed) = &mut dep { + if let schema::TomlDependency::Detailed(detailed) = &mut dep { detailed.resolve_path(name, self.ws_root(), package_root)?; } Ok(dep) @@ -1639,23 +1570,23 @@ impl InheritableFields { } /// Gets the field `workspace.package.readme`. - pub fn readme(&self, package_root: &Path) -> CargoResult { + pub fn readme(&self, package_root: &Path) -> CargoResult { let Some(readme) = readme_for_package(self.ws_root.as_path(), self.readme.as_ref()) else { bail!("`workspace.package.readme` was not defined"); }; resolve_relative_path("readme", &self.ws_root, package_root, &readme) - .map(StringOrBool::String) + .map(schema::StringOrBool::String) } pub fn ws_root(&self) -> &PathBuf { &self.ws_root } - pub fn update_deps(&mut self, deps: Option>) { + pub fn update_deps(&mut self, deps: Option>) { self.dependencies = deps; } - pub fn update_lints(&mut self, lints: Option) { + pub fn update_lints(&mut self, lints: Option) { self.lints = lints; } @@ -1664,56 +1595,7 @@ impl InheritableFields { } } -/// Represents the `package`/`project` sections of a `Cargo.toml`. -/// -/// Note that the order of the fields matters, since this is the order they -/// are serialized to a TOML file. For example, you cannot have values after -/// the field `metadata`, since it is a table and values cannot appear after -/// tables. -#[derive(Deserialize, Serialize, Clone, Debug)] -#[serde(rename_all = "kebab-case")] -pub struct TomlPackage { - edition: Option, - rust_version: Option, - name: String, - version: Option, - authors: Option, - build: Option, - metabuild: Option, - default_target: Option, - forced_target: Option, - links: Option, - exclude: Option, - include: Option, - publish: Option, - workspace: Option, - im_a_teapot: Option, - autobins: Option, - autoexamples: Option, - autotests: Option, - autobenches: Option, - default_run: Option, - - // Package metadata. - description: Option, - homepage: Option, - documentation: Option, - readme: Option, - keywords: Option, - categories: Option, - license: Option, - license_file: Option, - repository: Option, - resolver: Option, - - metadata: Option, - - /// Provide a helpful error message for a common user error. - #[serde(rename = "cargo-features", skip_serializing)] - _invalid_cargo_features: Option, -} - -impl TomlPackage { +impl schema::TomlPackage { pub fn to_package_id( &self, source_id: SourceId, @@ -1723,9 +1605,9 @@ impl TomlPackage { } } -/// This Trait exists to make [`MaybeWorkspace::Workspace`] generic. It makes deserialization of -/// [`MaybeWorkspace`] much easier, as well as making error messages for -/// [`MaybeWorkspace::resolve`] much nicer +/// This Trait exists to make [`schema::MaybeWorkspace::Workspace`] generic. It makes deserialization of +/// [`schema::MaybeWorkspace`] much easier, as well as making error messages for +/// [`schema::MaybeWorkspace::resolve`] much nicer /// /// Implementors should have a field `workspace` with the type of `bool`. It is used to ensure /// `workspace` is not `false` in a `Cargo.toml` @@ -1738,25 +1620,15 @@ pub trait WorkspaceInherit { fn workspace(&self) -> bool; } -/// An enum that allows for inheriting keys from a workspace in a Cargo.toml. -#[derive(Serialize, Copy, Clone, Debug)] -#[serde(untagged)] -pub enum MaybeWorkspace { - /// The "defined" type, or the type that that is used when not inheriting from a workspace. - Defined(T), - /// The type when inheriting from a workspace. - Workspace(W), -} - -impl MaybeWorkspace { +impl schema::MaybeWorkspace { fn resolve<'a>( self, label: &str, get_ws_inheritable: impl FnOnce() -> CargoResult, ) -> CargoResult { match self { - MaybeWorkspace::Defined(value) => Ok(value), - MaybeWorkspace::Workspace(w) => get_ws_inheritable().with_context(|| { + schema::MaybeWorkspace::Defined(value) => Ok(value), + schema::MaybeWorkspace::Workspace(w) => get_ws_inheritable().with_context(|| { format!( "error inheriting `{label}` from workspace root manifest's `workspace.{}.{label}`", w.inherit_toml_table(), @@ -1771,8 +1643,8 @@ impl MaybeWorkspace { get_ws_inheritable: impl FnOnce(&W) -> CargoResult, ) -> CargoResult { match self { - MaybeWorkspace::Defined(value) => Ok(value), - MaybeWorkspace::Workspace(w) => get_ws_inheritable(&w).with_context(|| { + schema::MaybeWorkspace::Defined(value) => Ok(value), + schema::MaybeWorkspace::Workspace(w) => get_ws_inheritable(&w).with_context(|| { format!( "error inheriting `{label}` from workspace root manifest's `workspace.{}.{label}`", w.inherit_toml_table(), @@ -1783,258 +1655,13 @@ impl MaybeWorkspace { fn as_defined(&self) -> Option<&T> { match self { - MaybeWorkspace::Workspace(_) => None, - MaybeWorkspace::Defined(defined) => Some(defined), + schema::MaybeWorkspace::Workspace(_) => None, + schema::MaybeWorkspace::Defined(defined) => Some(defined), } } } -//. This already has a `Deserialize` impl from version_trim_whitespace -type MaybeWorkspaceSemverVersion = MaybeWorkspace; -impl<'de> de::Deserialize<'de> for MaybeWorkspaceSemverVersion { - fn deserialize(d: D) -> Result - where - D: de::Deserializer<'de>, - { - UntaggedEnumVisitor::new() - .expecting("SemVer version") - .string( - |value| match value.trim().parse().map_err(de::Error::custom) { - Ok(parsed) => Ok(MaybeWorkspace::Defined(parsed)), - Err(e) => Err(e), - }, - ) - .map(|value| value.deserialize().map(MaybeWorkspace::Workspace)) - .deserialize(d) - } -} - -type MaybeWorkspaceString = MaybeWorkspace; -impl<'de> de::Deserialize<'de> for MaybeWorkspaceString { - fn deserialize(d: D) -> Result - where - D: de::Deserializer<'de>, - { - struct Visitor; - - impl<'de> de::Visitor<'de> for Visitor { - type Value = MaybeWorkspaceString; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - f.write_str("a string or workspace") - } - - fn visit_string(self, value: String) -> Result - where - E: de::Error, - { - Ok(MaybeWorkspaceString::Defined(value)) - } - - fn visit_map(self, map: V) -> Result - where - V: de::MapAccess<'de>, - { - let mvd = de::value::MapAccessDeserializer::new(map); - TomlWorkspaceField::deserialize(mvd).map(MaybeWorkspace::Workspace) - } - } - - d.deserialize_any(Visitor) - } -} - -type MaybeWorkspaceRustVersion = MaybeWorkspace; -impl<'de> de::Deserialize<'de> for MaybeWorkspaceRustVersion { - fn deserialize(d: D) -> Result - where - D: de::Deserializer<'de>, - { - struct Visitor; - - impl<'de> de::Visitor<'de> for Visitor { - type Value = MaybeWorkspaceRustVersion; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - f.write_str("a semver or workspace") - } - - fn visit_string(self, value: String) -> Result - where - E: de::Error, - { - let value = value.parse::().map_err(|e| E::custom(e))?; - Ok(MaybeWorkspaceRustVersion::Defined(value)) - } - - fn visit_map(self, map: V) -> Result - where - V: de::MapAccess<'de>, - { - let mvd = de::value::MapAccessDeserializer::new(map); - TomlWorkspaceField::deserialize(mvd).map(MaybeWorkspace::Workspace) - } - } - - d.deserialize_any(Visitor) - } -} - -type MaybeWorkspaceVecString = MaybeWorkspace, TomlWorkspaceField>; -impl<'de> de::Deserialize<'de> for MaybeWorkspaceVecString { - fn deserialize(d: D) -> Result - where - D: de::Deserializer<'de>, - { - struct Visitor; - - impl<'de> de::Visitor<'de> for Visitor { - type Value = MaybeWorkspaceVecString; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - f.write_str("a vector of strings or workspace") - } - fn visit_seq(self, v: A) -> Result - where - A: de::SeqAccess<'de>, - { - let seq = de::value::SeqAccessDeserializer::new(v); - Vec::deserialize(seq).map(MaybeWorkspace::Defined) - } - - fn visit_map(self, map: V) -> Result - where - V: de::MapAccess<'de>, - { - let mvd = de::value::MapAccessDeserializer::new(map); - TomlWorkspaceField::deserialize(mvd).map(MaybeWorkspace::Workspace) - } - } - - d.deserialize_any(Visitor) - } -} - -type MaybeWorkspaceStringOrBool = MaybeWorkspace; -impl<'de> de::Deserialize<'de> for MaybeWorkspaceStringOrBool { - fn deserialize(d: D) -> Result - where - D: de::Deserializer<'de>, - { - struct Visitor; - - impl<'de> de::Visitor<'de> for Visitor { - type Value = MaybeWorkspaceStringOrBool; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - f.write_str("a string, a bool, or workspace") - } - - fn visit_bool(self, v: bool) -> Result - where - E: de::Error, - { - let b = de::value::BoolDeserializer::new(v); - StringOrBool::deserialize(b).map(MaybeWorkspace::Defined) - } - - fn visit_string(self, v: String) -> Result - where - E: de::Error, - { - let string = de::value::StringDeserializer::new(v); - StringOrBool::deserialize(string).map(MaybeWorkspace::Defined) - } - - fn visit_map(self, map: V) -> Result - where - V: de::MapAccess<'de>, - { - let mvd = de::value::MapAccessDeserializer::new(map); - TomlWorkspaceField::deserialize(mvd).map(MaybeWorkspace::Workspace) - } - } - - d.deserialize_any(Visitor) - } -} - -type MaybeWorkspaceVecStringOrBool = MaybeWorkspace; -impl<'de> de::Deserialize<'de> for MaybeWorkspaceVecStringOrBool { - fn deserialize(d: D) -> Result - where - D: de::Deserializer<'de>, - { - struct Visitor; - - impl<'de> de::Visitor<'de> for Visitor { - type Value = MaybeWorkspaceVecStringOrBool; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - f.write_str("a boolean, a vector of strings, or workspace") - } - - fn visit_bool(self, v: bool) -> Result - where - E: de::Error, - { - let b = de::value::BoolDeserializer::new(v); - VecStringOrBool::deserialize(b).map(MaybeWorkspace::Defined) - } - - fn visit_seq(self, v: A) -> Result - where - A: de::SeqAccess<'de>, - { - let seq = de::value::SeqAccessDeserializer::new(v); - VecStringOrBool::deserialize(seq).map(MaybeWorkspace::Defined) - } - - fn visit_map(self, map: V) -> Result - where - V: de::MapAccess<'de>, - { - let mvd = de::value::MapAccessDeserializer::new(map); - TomlWorkspaceField::deserialize(mvd).map(MaybeWorkspace::Workspace) - } - } - - d.deserialize_any(Visitor) - } -} - -type MaybeWorkspaceBtreeMap = - MaybeWorkspace>, TomlWorkspaceField>; - -impl<'de> de::Deserialize<'de> for MaybeWorkspaceBtreeMap { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - let value = serde_value::Value::deserialize(deserializer)?; - - if let Ok(w) = TomlWorkspaceField::deserialize( - serde_value::ValueDeserializer::::new(value.clone()), - ) { - return if w.workspace() { - Ok(MaybeWorkspace::Workspace(w)) - } else { - Err(de::Error::custom("`workspace` cannot be false")) - }; - } - BTreeMap::deserialize(serde_value::ValueDeserializer::::new(value)) - .map(MaybeWorkspace::Defined) - } -} - -#[derive(Deserialize, Serialize, Copy, Clone, Debug)] -#[serde(rename_all = "kebab-case")] -pub struct TomlWorkspaceField { - #[serde(deserialize_with = "bool_no_false")] - workspace: bool, -} - -impl WorkspaceInherit for TomlWorkspaceField { +impl WorkspaceInherit for schema::TomlWorkspaceField { fn inherit_toml_table(&self) -> &str { "package" } @@ -2044,72 +1671,13 @@ impl WorkspaceInherit for TomlWorkspaceField { } } -fn bool_no_false<'de, D: de::Deserializer<'de>>(deserializer: D) -> Result { - let b: bool = Deserialize::deserialize(deserializer)?; - if b { - Ok(b) - } else { - Err(de::Error::custom("`workspace` cannot be false")) - } -} - -type MaybeWorkspaceDependency = MaybeWorkspace; - -impl MaybeWorkspaceDependency { - fn unused_keys(&self) -> Vec { - match self { - MaybeWorkspaceDependency::Defined(d) => d.unused_keys(), - MaybeWorkspaceDependency::Workspace(w) => w.unused_keys.keys().cloned().collect(), - } - } -} - -impl<'de> de::Deserialize<'de> for MaybeWorkspaceDependency { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - let value = serde_value::Value::deserialize(deserializer)?; - - if let Ok(w) = TomlWorkspaceDependency::deserialize(serde_value::ValueDeserializer::< - D::Error, - >::new(value.clone())) - { - return if w.workspace() { - Ok(MaybeWorkspace::Workspace(w)) - } else { - Err(de::Error::custom("`workspace` cannot be false")) - }; - } - TomlDependency::deserialize(serde_value::ValueDeserializer::::new(value)) - .map(MaybeWorkspace::Defined) - } -} - -#[derive(Deserialize, Serialize, Clone, Debug)] -#[serde(rename_all = "kebab-case")] -pub struct TomlWorkspaceDependency { - workspace: bool, - features: Option>, - default_features: Option, - #[serde(rename = "default_features")] - default_features2: Option, - optional: Option, - public: Option, - - /// This is here to provide a way to see the "unused manifest keys" when deserializing - #[serde(skip_serializing)] - #[serde(flatten)] - unused_keys: BTreeMap, -} - -impl TomlWorkspaceDependency { +impl schema::TomlWorkspaceDependency { fn resolve<'a>( &self, name: &str, - inheritable: impl FnOnce() -> CargoResult<&'a InheritableFields>, + inheritable: impl FnOnce() -> CargoResult<&'a schema::InheritableFields>, cx: &mut Context<'_, '_>, - ) -> CargoResult { + ) -> CargoResult { fn default_features_msg(label: &str, ws_def_feat: Option, cx: &mut Context<'_, '_>) { let ws_def_feat = match ws_def_feat { Some(true) => "true", @@ -2127,12 +1695,12 @@ impl TomlWorkspaceDependency { } inheritable()?.get_dependency(name, cx.root).map(|d| { match d { - TomlDependency::Simple(s) => { + schema::TomlDependency::Simple(s) => { if let Some(false) = self.default_features.or(self.default_features2) { default_features_msg(name, None, cx); } if self.optional.is_some() || self.features.is_some() || self.public.is_some() { - TomlDependency::Detailed(DetailedTomlDependency { + schema::TomlDependency::Detailed(schema::DetailedTomlDependency { version: Some(s), optional: self.optional, features: self.features.clone(), @@ -2140,10 +1708,10 @@ impl TomlWorkspaceDependency { ..Default::default() }) } else { - TomlDependency::Simple(s) + schema::TomlDependency::Simple(s) } } - TomlDependency::Detailed(d) => { + schema::TomlDependency::Detailed(d) => { let mut d = d.clone(); match ( self.default_features.or(self.default_features2), @@ -2175,14 +1743,14 @@ impl TomlWorkspaceDependency { } d.add_features(self.features.clone()); d.update_optional(self.optional); - TomlDependency::Detailed(d) + schema::TomlDependency::Detailed(d) } } }) } } -impl WorkspaceInherit for TomlWorkspaceDependency { +impl WorkspaceInherit for schema::TomlWorkspaceDependency { fn inherit_toml_table(&self) -> &str { "dependencies" } @@ -2192,28 +1760,7 @@ impl WorkspaceInherit for TomlWorkspaceDependency { } } -#[derive(Clone, Debug, Serialize)] -#[serde(untagged)] -pub enum TomlDependency { - /// In the simple format, only a version is specified, eg. - /// `package = ""` - Simple(String), - /// The simple format is equivalent to a detailed dependency - /// specifying only a version, eg. - /// `package = { version = "" }` - Detailed(DetailedTomlDependency

), -} - -impl TomlDependency { - fn unused_keys(&self) -> Vec { - match self { - TomlDependency::Simple(_) => vec![], - TomlDependency::Detailed(detailed) => detailed.unused_keys.keys().cloned().collect(), - } - } -} - -impl TomlDependency

{ +impl schema::TomlDependency

{ pub(crate) fn to_dependency_split( &self, name: &str, @@ -2249,87 +1796,17 @@ impl TomlDependency

{ kind: Option, ) -> CargoResult { match *self { - TomlDependency::Simple(ref version) => DetailedTomlDependency::

{ + schema::TomlDependency::Simple(ref version) => schema::DetailedTomlDependency::

{ version: Some(version.clone()), ..Default::default() } .to_dependency(name, cx, kind), - TomlDependency::Detailed(ref details) => details.to_dependency(name, cx, kind), - } - } - - fn is_version_specified(&self) -> bool { - match self { - TomlDependency::Detailed(d) => d.version.is_some(), - TomlDependency::Simple(..) => true, + schema::TomlDependency::Detailed(ref details) => details.to_dependency(name, cx, kind), } } - - fn is_optional(&self) -> bool { - match self { - TomlDependency::Detailed(d) => d.optional.unwrap_or(false), - TomlDependency::Simple(..) => false, - } - } -} - -impl<'de, P: Deserialize<'de> + Clone> de::Deserialize<'de> for TomlDependency

{ - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - UntaggedEnumVisitor::new() - .expecting( - "a version string like \"0.9.8\" or a \ - detailed dependency like { version = \"0.9.8\" }", - ) - .string(|value| Ok(TomlDependency::Simple(value.to_owned()))) - .map(|value| value.deserialize().map(TomlDependency::Detailed)) - .deserialize(deserializer) - } -} - -#[derive(Deserialize, Serialize, Clone, Debug)] -#[serde(rename_all = "kebab-case")] -pub struct DetailedTomlDependency { - version: Option, - registry: Option, - /// The URL of the `registry` field. - /// This is an internal implementation detail. When Cargo creates a - /// package, it replaces `registry` with `registry-index` so that the - /// manifest contains the correct URL. All users won't have the same - /// registry names configured, so Cargo can't rely on just the name for - /// crates published by other users. - registry_index: Option, - // `path` is relative to the file it appears in. If that's a `Cargo.toml`, it'll be relative to - // that TOML file, and if it's a `.cargo/config` file, it'll be relative to that file. - path: Option

, - git: Option, - branch: Option, - tag: Option, - rev: Option, - features: Option>, - optional: Option, - default_features: Option, - #[serde(rename = "default_features")] - default_features2: Option, - package: Option, - public: Option, - - /// One or more of `bin`, `cdylib`, `staticlib`, `bin:`. - artifact: Option, - /// If set, the artifact should also be a dependency - lib: Option, - /// A platform name, like `x86_64-apple-darwin` - target: Option, - - /// This is here to provide a way to see the "unused manifest keys" when deserializing - #[serde(skip_serializing)] - #[serde(flatten)] - unused_keys: BTreeMap, } -impl DetailedTomlDependency { +impl schema::DetailedTomlDependency { fn add_features(&mut self, features: Option>) { self.features = match (self.features.clone(), features.clone()) { (Some(dep_feat), Some(inherit_feat)) => Some( @@ -2366,7 +1843,7 @@ impl DetailedTomlDependency { } } -impl DetailedTomlDependency

{ +impl schema::DetailedTomlDependency

{ fn to_dependency( &self, name_in_toml: &str, @@ -2611,44 +2088,7 @@ impl DetailedTomlDependency

{ } } -// Explicit implementation so we avoid pulling in P: Default -impl Default for DetailedTomlDependency

{ - fn default() -> Self { - Self { - version: Default::default(), - registry: Default::default(), - registry_index: Default::default(), - path: Default::default(), - git: Default::default(), - branch: Default::default(), - tag: Default::default(), - rev: Default::default(), - features: Default::default(), - optional: Default::default(), - default_features: Default::default(), - default_features2: Default::default(), - package: Default::default(), - public: Default::default(), - artifact: Default::default(), - lib: Default::default(), - target: Default::default(), - unused_keys: Default::default(), - } - } -} - -#[derive(Deserialize, Serialize, Clone, Debug, Default)] -pub struct TomlProfiles(BTreeMap); - -impl TomlProfiles { - pub fn get_all(&self) -> &BTreeMap { - &self.0 - } - - pub fn get(&self, name: &str) -> Option<&TomlProfile> { - self.0.get(name) - } - +impl schema::TomlProfiles { /// Checks syntax validity and unstable feature gate for each profile. /// /// It's a bit unfortunate both `-Z` flags and `cargo-features` are required, @@ -2666,34 +2106,7 @@ impl TomlProfiles { } } -#[derive(Deserialize, Serialize, Clone, Debug, Default, Eq, PartialEq)] -#[serde(default, rename_all = "kebab-case")] -pub struct TomlProfile { - pub opt_level: Option, - pub lto: Option, - pub codegen_backend: Option, - pub codegen_units: Option, - pub debug: Option, - pub split_debuginfo: Option, - pub debug_assertions: Option, - pub rpath: Option, - pub panic: Option, - pub overflow_checks: Option, - pub incremental: Option, - pub dir_name: Option, - pub inherits: Option, - pub strip: Option, - // Note that `rustflags` is used for the cargo-feature `profile_rustflags` - pub rustflags: Option>, - // These two fields must be last because they are sub-tables, and TOML - // requires all non-tables to be listed first. - pub package: Option>, - pub build_override: Option>, - /// Unstable feature `-Ztrim-paths`. - pub trim_paths: Option, -} - -impl TomlProfile { +impl schema::TomlProfile { /// Checks stytax validity and unstable feature gate for a given profile. pub fn validate( &self, @@ -2764,7 +2177,7 @@ impl TomlProfile { } } - if let Some(StringOrBool::String(arg)) = &self.lto { + if let Some(schema::StringOrBool::String(arg)) = &self.lto { if arg == "true" || arg == "false" { bail!( "`lto` setting of string `\"{arg}\"` for `{name}` profile is not \ @@ -2924,7 +2337,7 @@ impl TomlProfile { } /// Overwrite self's values with the given profile. - pub fn merge(&mut self, profile: &TomlProfile) { + pub fn merge(&mut self, profile: &schema::TomlProfile) { if let Some(v) = &profile.opt_level { self.opt_level = Some(v.clone()); } @@ -3014,327 +2427,7 @@ impl TomlProfile { } } -#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] -pub enum ProfilePackageSpec { - Spec(PackageIdSpec), - All, -} - -impl fmt::Display for ProfilePackageSpec { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - ProfilePackageSpec::Spec(spec) => spec.fmt(f), - ProfilePackageSpec::All => f.write_str("*"), - } - } -} - -impl ser::Serialize for ProfilePackageSpec { - fn serialize(&self, s: S) -> Result - where - S: ser::Serializer, - { - self.to_string().serialize(s) - } -} - -impl<'de> de::Deserialize<'de> for ProfilePackageSpec { - fn deserialize(d: D) -> Result - where - D: de::Deserializer<'de>, - { - let string = String::deserialize(d)?; - if string == "*" { - Ok(ProfilePackageSpec::All) - } else { - PackageIdSpec::parse(&string) - .map_err(de::Error::custom) - .map(ProfilePackageSpec::Spec) - } - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct TomlOptLevel(pub String); - -impl ser::Serialize for TomlOptLevel { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - match self.0.parse::() { - Ok(n) => n.serialize(serializer), - Err(_) => self.0.serialize(serializer), - } - } -} - -impl<'de> de::Deserialize<'de> for TomlOptLevel { - fn deserialize(d: D) -> Result - where - D: de::Deserializer<'de>, - { - use serde::de::Error as _; - UntaggedEnumVisitor::new() - .expecting("an optimization level") - .i64(|value| Ok(TomlOptLevel(value.to_string()))) - .string(|value| { - if value == "s" || value == "z" { - Ok(TomlOptLevel(value.to_string())) - } else { - Err(serde_untagged::de::Error::custom(format!( - "must be `0`, `1`, `2`, `3`, `s` or `z`, \ - but found the string: \"{}\"", - value - ))) - } - }) - .deserialize(d) - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)] -pub enum TomlDebugInfo { - None, - LineDirectivesOnly, - LineTablesOnly, - Limited, - Full, -} - -impl Display for TomlDebugInfo { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - TomlDebugInfo::None => f.write_char('0'), - TomlDebugInfo::Limited => f.write_char('1'), - TomlDebugInfo::Full => f.write_char('2'), - TomlDebugInfo::LineDirectivesOnly => f.write_str("line-directives-only"), - TomlDebugInfo::LineTablesOnly => f.write_str("line-tables-only"), - } - } -} - -impl ser::Serialize for TomlDebugInfo { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - match self { - Self::None => 0.serialize(serializer), - Self::LineDirectivesOnly => "line-directives-only".serialize(serializer), - Self::LineTablesOnly => "line-tables-only".serialize(serializer), - Self::Limited => 1.serialize(serializer), - Self::Full => 2.serialize(serializer), - } - } -} - -impl<'de> de::Deserialize<'de> for TomlDebugInfo { - fn deserialize(d: D) -> Result - where - D: de::Deserializer<'de>, - { - use serde::de::Error as _; - let expecting = "a boolean, 0, 1, 2, \"line-tables-only\", or \"line-directives-only\""; - UntaggedEnumVisitor::new() - .expecting(expecting) - .bool(|value| { - Ok(if value { - TomlDebugInfo::Full - } else { - TomlDebugInfo::None - }) - }) - .i64(|value| { - let debuginfo = match value { - 0 => TomlDebugInfo::None, - 1 => TomlDebugInfo::Limited, - 2 => TomlDebugInfo::Full, - _ => { - return Err(serde_untagged::de::Error::invalid_value( - Unexpected::Signed(value), - &expecting, - )) - } - }; - Ok(debuginfo) - }) - .string(|value| { - let debuginfo = match value { - "none" => TomlDebugInfo::None, - "limited" => TomlDebugInfo::Limited, - "full" => TomlDebugInfo::Full, - "line-directives-only" => TomlDebugInfo::LineDirectivesOnly, - "line-tables-only" => TomlDebugInfo::LineTablesOnly, - _ => { - return Err(serde_untagged::de::Error::invalid_value( - Unexpected::Str(value), - &expecting, - )) - } - }; - Ok(debuginfo) - }) - .deserialize(d) - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize)] -#[serde(untagged, rename_all = "kebab-case")] -pub enum TomlTrimPaths { - Values(Vec), - All, -} - -impl TomlTrimPaths { - pub fn none() -> Self { - TomlTrimPaths::Values(Vec::new()) - } - - pub fn is_none(&self) -> bool { - match self { - TomlTrimPaths::Values(v) => v.is_empty(), - TomlTrimPaths::All => false, - } - } -} - -impl<'de> de::Deserialize<'de> for TomlTrimPaths { - fn deserialize(d: D) -> Result - where - D: de::Deserializer<'de>, - { - use serde::de::Error as _; - let expecting = r#"a boolean, "none", "diagnostics", "macro", "object", "all", or an array with these options"#; - UntaggedEnumVisitor::new() - .expecting(expecting) - .bool(|value| { - Ok(if value { - TomlTrimPaths::All - } else { - TomlTrimPaths::none() - }) - }) - .string(|v| match v { - "none" => Ok(TomlTrimPaths::none()), - "all" => Ok(TomlTrimPaths::All), - v => { - let d = v.into_deserializer(); - let err = |_: D::Error| { - serde_untagged::de::Error::custom(format!("expected {expecting}")) - }; - TomlTrimPathsValue::deserialize(d) - .map_err(err) - .map(|v| v.into()) - } - }) - .seq(|seq| { - let seq: Vec = seq.deserialize()?; - let seq: Vec<_> = seq - .into_iter() - .map(|s| TomlTrimPathsValue::deserialize(s.into_deserializer())) - .collect::>()?; - Ok(seq.into()) - }) - .deserialize(d) - } -} - -impl fmt::Display for TomlTrimPaths { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - TomlTrimPaths::All => write!(f, "all"), - TomlTrimPaths::Values(v) if v.is_empty() => write!(f, "none"), - TomlTrimPaths::Values(v) => { - let mut iter = v.iter(); - if let Some(value) = iter.next() { - write!(f, "{value}")?; - } - for value in iter { - write!(f, ",{value}")?; - } - Ok(()) - } - } - } -} - -impl From for TomlTrimPaths { - fn from(value: TomlTrimPathsValue) -> Self { - TomlTrimPaths::Values(vec![value]) - } -} - -impl From> for TomlTrimPaths { - fn from(value: Vec) -> Self { - TomlTrimPaths::Values(value) - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub enum TomlTrimPathsValue { - Diagnostics, - Macro, - Object, -} - -impl TomlTrimPathsValue { - pub fn as_str(&self) -> &'static str { - match self { - TomlTrimPathsValue::Diagnostics => "diagnostics", - TomlTrimPathsValue::Macro => "macro", - TomlTrimPathsValue::Object => "object", - } - } -} - -impl fmt::Display for TomlTrimPathsValue { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.as_str()) - } -} - -type TomlLibTarget = TomlTarget; -type TomlBinTarget = TomlTarget; -type TomlExampleTarget = TomlTarget; -type TomlTestTarget = TomlTarget; -type TomlBenchTarget = TomlTarget; - -#[derive(Default, Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "kebab-case")] -struct TomlTarget { - name: Option, - - // The intention was to only accept `crate-type` here but historical - // versions of Cargo also accepted `crate_type`, so look for both. - crate_type: Option>, - #[serde(rename = "crate_type")] - crate_type2: Option>, - - path: Option, - // Note that `filename` is used for the cargo-feature `different_binary_name` - filename: Option, - test: Option, - doctest: Option, - bench: Option, - doc: Option, - plugin: Option, - doc_scrape_examples: Option, - #[serde(rename = "proc-macro")] - proc_macro_raw: Option, - #[serde(rename = "proc_macro")] - proc_macro_raw2: Option, - harness: Option, - required_features: Option>, - edition: Option, -} - -impl TomlTarget { - fn new() -> TomlTarget { - TomlTarget::default() - } - +impl schema::TomlTarget { fn name(&self) -> String { match self.name { Some(ref name) => name.clone(), @@ -3382,39 +2475,11 @@ impl TomlTarget { } } -/// Corresponds to a `target` entry, but `TomlTarget` is already used. -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "kebab-case")] -struct TomlPlatform { - dependencies: Option>, - build_dependencies: Option>, - #[serde(rename = "build_dependencies")] - build_dependencies2: Option>, - dev_dependencies: Option>, - #[serde(rename = "dev_dependencies")] - dev_dependencies2: Option>, -} - -#[derive(Deserialize, Serialize, Debug, Clone)] -#[serde(expecting = "a lints table")] -#[serde(rename_all = "kebab-case")] -pub struct MaybeWorkspaceLints { - #[serde(skip_serializing_if = "is_false")] - #[serde(deserialize_with = "bool_no_false", default)] - workspace: bool, - #[serde(flatten)] - lints: TomlLints, -} - -fn is_false(b: &bool) -> bool { - !b -} - -impl MaybeWorkspaceLints { +impl schema::MaybeWorkspaceLints { fn resolve<'a>( self, - get_ws_inheritable: impl FnOnce() -> CargoResult, - ) -> CargoResult { + get_ws_inheritable: impl FnOnce() -> CargoResult, + ) -> CargoResult { if self.workspace { if !self.lints.is_empty() { anyhow::bail!("cannot override `workspace.lints` in `lints`, either remove the overrides or `lints.workspace = true` and manually specify the lints"); @@ -3428,65 +2493,7 @@ impl MaybeWorkspaceLints { } } -pub type TomlLints = BTreeMap; - -pub type TomlToolLints = BTreeMap; - -#[derive(Serialize, Debug, Clone)] -#[serde(untagged)] -pub enum TomlLint { - Level(TomlLintLevel), - Config(TomlLintConfig), -} - -impl<'de> Deserialize<'de> for TomlLint { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - UntaggedEnumVisitor::new() - .string(|string| { - TomlLintLevel::deserialize(string.into_deserializer()).map(TomlLint::Level) - }) - .map(|map| map.deserialize().map(TomlLint::Config)) - .deserialize(deserializer) - } -} - -impl TomlLint { - fn level(&self) -> TomlLintLevel { - match self { - Self::Level(level) => *level, - Self::Config(config) => config.level, - } - } - - fn priority(&self) -> i8 { - match self { - Self::Level(_) => 0, - Self::Config(config) => config.priority, - } - } -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "kebab-case")] -pub struct TomlLintConfig { - level: TomlLintLevel, - #[serde(default)] - priority: i8, -} - -#[derive(Serialize, Deserialize, Debug, Copy, Clone)] -#[serde(rename_all = "kebab-case")] -pub enum TomlLintLevel { - Forbid, - Deny, - Warn, - Allow, -} - -impl TomlLintLevel { +impl schema::TomlLintLevel { fn flag(&self) -> &'static str { match self { Self::Forbid => "--forbid", @@ -3497,23 +2504,6 @@ impl TomlLintLevel { } } -#[derive(Copy, Clone, Debug)] -#[non_exhaustive] -struct InvalidCargoFeatures {} - -impl<'de> de::Deserialize<'de> for InvalidCargoFeatures { - fn deserialize(_d: D) -> Result - where - D: de::Deserializer<'de>, - { - use serde::de::Error as _; - - Err(D::Error::custom( - "the field `cargo-features` should be set at the top of Cargo.toml before any tables", - )) - } -} - pub trait ResolveToPath { fn resolve(&self, config: &Config) -> PathBuf; } @@ -3529,93 +2519,3 @@ impl ResolveToPath for ConfigRelativePath { self.resolve_path(c) } } - -/// A StringOrVec can be parsed from either a TOML string or array, -/// but is always stored as a vector. -#[derive(Clone, Debug, Serialize, Eq, PartialEq, PartialOrd, Ord)] -pub struct StringOrVec(Vec); - -impl StringOrVec { - pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, String> { - self.0.iter() - } -} - -impl<'de> de::Deserialize<'de> for StringOrVec { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - UntaggedEnumVisitor::new() - .expecting("string or list of strings") - .string(|value| Ok(StringOrVec(vec![value.to_owned()]))) - .seq(|value| value.deserialize().map(StringOrVec)) - .deserialize(deserializer) - } -} - -#[derive(Clone, Debug, Serialize, Eq, PartialEq)] -#[serde(untagged)] -pub enum StringOrBool { - String(String), - Bool(bool), -} - -impl<'de> Deserialize<'de> for StringOrBool { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - UntaggedEnumVisitor::new() - .bool(|b| Ok(StringOrBool::Bool(b))) - .string(|s| Ok(StringOrBool::String(s.to_owned()))) - .deserialize(deserializer) - } -} - -#[derive(PartialEq, Clone, Debug, Serialize)] -#[serde(untagged)] -pub enum VecStringOrBool { - VecString(Vec), - Bool(bool), -} - -impl<'de> de::Deserialize<'de> for VecStringOrBool { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - UntaggedEnumVisitor::new() - .expecting("a boolean or vector of strings") - .bool(|value| Ok(VecStringOrBool::Bool(value))) - .seq(|value| value.deserialize().map(VecStringOrBool::VecString)) - .deserialize(deserializer) - } -} - -#[derive(Clone)] -struct PathValue(PathBuf); - -impl fmt::Debug for PathValue { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl ser::Serialize for PathValue { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - self.0.serialize(serializer) - } -} - -impl<'de> de::Deserialize<'de> for PathValue { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - Ok(PathValue(String::deserialize(deserializer)?.into())) - } -} diff --git a/src/cargo/util/toml/schema.rs b/src/cargo/util/toml/schema.rs new file mode 100644 index 00000000000..f884195d1cc --- /dev/null +++ b/src/cargo/util/toml/schema.rs @@ -0,0 +1,1134 @@ +use std::collections::BTreeMap; +use std::fmt::{self, Display, Write}; +use std::path::PathBuf; +use std::str; + +use serde::de::{self, IntoDeserializer as _, Unexpected}; +use serde::ser; +use serde::{Deserialize, Serialize}; +use serde_untagged::UntaggedEnumVisitor; + +use crate::core::PackageIdSpec; +use crate::util::RustVersion; + +/// This type is used to deserialize `Cargo.toml` files. +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct TomlManifest { + pub cargo_features: Option>, + pub package: Option>, + pub project: Option>, + pub profile: Option, + pub lib: Option, + pub bin: Option>, + pub example: Option>, + pub test: Option>, + pub bench: Option>, + pub dependencies: Option>, + pub dev_dependencies: Option>, + #[serde(rename = "dev_dependencies")] + pub dev_dependencies2: Option>, + pub build_dependencies: Option>, + #[serde(rename = "build_dependencies")] + pub build_dependencies2: Option>, + pub features: Option>>, + pub target: Option>, + pub replace: Option>, + pub patch: Option>>, + pub workspace: Option, + pub badges: Option, + pub lints: Option, +} + +impl TomlManifest { + pub fn has_profiles(&self) -> bool { + self.profile.is_some() + } + + pub fn features(&self) -> Option<&BTreeMap>> { + self.features.as_ref() + } +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +#[serde(rename_all = "kebab-case")] +pub struct TomlWorkspace { + pub members: Option>, + pub exclude: Option>, + pub default_members: Option>, + pub resolver: Option, + pub metadata: Option, + + // Properties that can be inherited by members. + pub package: Option, + pub dependencies: Option>, + pub lints: Option, +} + +/// A group of fields that are inheritable by members of the workspace +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct InheritableFields { + // We use skip here since it will never be present when deserializing + // and we don't want it present when serializing + #[serde(skip)] + pub dependencies: Option>, + #[serde(skip)] + pub lints: Option, + + pub version: Option, + pub authors: Option>, + pub description: Option, + pub homepage: Option, + pub documentation: Option, + pub readme: Option, + pub keywords: Option>, + pub categories: Option>, + pub license: Option, + pub license_file: Option, + pub repository: Option, + pub publish: Option, + pub edition: Option, + pub badges: Option>>, + pub exclude: Option>, + pub include: Option>, + pub rust_version: Option, + // We use skip here since it will never be present when deserializing + // and we don't want it present when serializing + #[serde(skip)] + pub ws_root: PathBuf, +} + +/// Represents the `package`/`project` sections of a `Cargo.toml`. +/// +/// Note that the order of the fields matters, since this is the order they +/// are serialized to a TOML file. For example, you cannot have values after +/// the field `metadata`, since it is a table and values cannot appear after +/// tables. +#[derive(Deserialize, Serialize, Clone, Debug)] +#[serde(rename_all = "kebab-case")] +pub struct TomlPackage { + pub edition: Option, + pub rust_version: Option, + pub name: String, + pub version: Option, + pub authors: Option, + pub build: Option, + pub metabuild: Option, + pub default_target: Option, + pub forced_target: Option, + pub links: Option, + pub exclude: Option, + pub include: Option, + pub publish: Option, + pub workspace: Option, + pub im_a_teapot: Option, + pub autobins: Option, + pub autoexamples: Option, + pub autotests: Option, + pub autobenches: Option, + pub default_run: Option, + + // Package metadata. + pub description: Option, + pub homepage: Option, + pub documentation: Option, + pub readme: Option, + pub keywords: Option, + pub categories: Option, + pub license: Option, + pub license_file: Option, + pub repository: Option, + pub resolver: Option, + + pub metadata: Option, + + /// Provide a helpful error message for a common user error. + #[serde(rename = "cargo-features", skip_serializing)] + pub _invalid_cargo_features: Option, +} + +/// An enum that allows for inheriting keys from a workspace in a Cargo.toml. +#[derive(Serialize, Copy, Clone, Debug)] +#[serde(untagged)] +pub enum MaybeWorkspace { + /// The "defined" type, or the type that that is used when not inheriting from a workspace. + Defined(T), + /// The type when inheriting from a workspace. + Workspace(W), +} + +//. This already has a `Deserialize` impl from version_trim_whitespace +pub type MaybeWorkspaceSemverVersion = MaybeWorkspace; +impl<'de> de::Deserialize<'de> for MaybeWorkspaceSemverVersion { + fn deserialize(d: D) -> Result + where + D: de::Deserializer<'de>, + { + UntaggedEnumVisitor::new() + .expecting("SemVer version") + .string( + |value| match value.trim().parse().map_err(de::Error::custom) { + Ok(parsed) => Ok(MaybeWorkspace::Defined(parsed)), + Err(e) => Err(e), + }, + ) + .map(|value| value.deserialize().map(MaybeWorkspace::Workspace)) + .deserialize(d) + } +} + +pub type MaybeWorkspaceString = MaybeWorkspace; +impl<'de> de::Deserialize<'de> for MaybeWorkspaceString { + fn deserialize(d: D) -> Result + where + D: de::Deserializer<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = MaybeWorkspaceString; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + f.write_str("a string or workspace") + } + + fn visit_string(self, value: String) -> Result + where + E: de::Error, + { + Ok(MaybeWorkspaceString::Defined(value)) + } + + fn visit_map(self, map: V) -> Result + where + V: de::MapAccess<'de>, + { + let mvd = de::value::MapAccessDeserializer::new(map); + TomlWorkspaceField::deserialize(mvd).map(MaybeWorkspace::Workspace) + } + } + + d.deserialize_any(Visitor) + } +} + +pub type MaybeWorkspaceRustVersion = MaybeWorkspace; +impl<'de> de::Deserialize<'de> for MaybeWorkspaceRustVersion { + fn deserialize(d: D) -> Result + where + D: de::Deserializer<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = MaybeWorkspaceRustVersion; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + f.write_str("a semver or workspace") + } + + fn visit_string(self, value: String) -> Result + where + E: de::Error, + { + let value = value.parse::().map_err(|e| E::custom(e))?; + Ok(MaybeWorkspaceRustVersion::Defined(value)) + } + + fn visit_map(self, map: V) -> Result + where + V: de::MapAccess<'de>, + { + let mvd = de::value::MapAccessDeserializer::new(map); + TomlWorkspaceField::deserialize(mvd).map(MaybeWorkspace::Workspace) + } + } + + d.deserialize_any(Visitor) + } +} + +pub type MaybeWorkspaceVecString = MaybeWorkspace, TomlWorkspaceField>; +impl<'de> de::Deserialize<'de> for MaybeWorkspaceVecString { + fn deserialize(d: D) -> Result + where + D: de::Deserializer<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = MaybeWorkspaceVecString; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str("a vector of strings or workspace") + } + fn visit_seq(self, v: A) -> Result + where + A: de::SeqAccess<'de>, + { + let seq = de::value::SeqAccessDeserializer::new(v); + Vec::deserialize(seq).map(MaybeWorkspace::Defined) + } + + fn visit_map(self, map: V) -> Result + where + V: de::MapAccess<'de>, + { + let mvd = de::value::MapAccessDeserializer::new(map); + TomlWorkspaceField::deserialize(mvd).map(MaybeWorkspace::Workspace) + } + } + + d.deserialize_any(Visitor) + } +} + +pub type MaybeWorkspaceStringOrBool = MaybeWorkspace; +impl<'de> de::Deserialize<'de> for MaybeWorkspaceStringOrBool { + fn deserialize(d: D) -> Result + where + D: de::Deserializer<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = MaybeWorkspaceStringOrBool; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str("a string, a bool, or workspace") + } + + fn visit_bool(self, v: bool) -> Result + where + E: de::Error, + { + let b = de::value::BoolDeserializer::new(v); + StringOrBool::deserialize(b).map(MaybeWorkspace::Defined) + } + + fn visit_string(self, v: String) -> Result + where + E: de::Error, + { + let string = de::value::StringDeserializer::new(v); + StringOrBool::deserialize(string).map(MaybeWorkspace::Defined) + } + + fn visit_map(self, map: V) -> Result + where + V: de::MapAccess<'de>, + { + let mvd = de::value::MapAccessDeserializer::new(map); + TomlWorkspaceField::deserialize(mvd).map(MaybeWorkspace::Workspace) + } + } + + d.deserialize_any(Visitor) + } +} + +pub type MaybeWorkspaceVecStringOrBool = MaybeWorkspace; +impl<'de> de::Deserialize<'de> for MaybeWorkspaceVecStringOrBool { + fn deserialize(d: D) -> Result + where + D: de::Deserializer<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = MaybeWorkspaceVecStringOrBool; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str("a boolean, a vector of strings, or workspace") + } + + fn visit_bool(self, v: bool) -> Result + where + E: de::Error, + { + let b = de::value::BoolDeserializer::new(v); + VecStringOrBool::deserialize(b).map(MaybeWorkspace::Defined) + } + + fn visit_seq(self, v: A) -> Result + where + A: de::SeqAccess<'de>, + { + let seq = de::value::SeqAccessDeserializer::new(v); + VecStringOrBool::deserialize(seq).map(MaybeWorkspace::Defined) + } + + fn visit_map(self, map: V) -> Result + where + V: de::MapAccess<'de>, + { + let mvd = de::value::MapAccessDeserializer::new(map); + TomlWorkspaceField::deserialize(mvd).map(MaybeWorkspace::Workspace) + } + } + + d.deserialize_any(Visitor) + } +} + +pub type MaybeWorkspaceBtreeMap = + MaybeWorkspace>, TomlWorkspaceField>; + +impl<'de> de::Deserialize<'de> for MaybeWorkspaceBtreeMap { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + let value = serde_value::Value::deserialize(deserializer)?; + + if let Ok(w) = TomlWorkspaceField::deserialize( + serde_value::ValueDeserializer::::new(value.clone()), + ) { + return if w.workspace { + Ok(MaybeWorkspace::Workspace(w)) + } else { + Err(de::Error::custom("`workspace` cannot be false")) + }; + } + BTreeMap::deserialize(serde_value::ValueDeserializer::::new(value)) + .map(MaybeWorkspace::Defined) + } +} + +#[derive(Deserialize, Serialize, Copy, Clone, Debug)] +#[serde(rename_all = "kebab-case")] +pub struct TomlWorkspaceField { + #[serde(deserialize_with = "bool_no_false")] + pub workspace: bool, +} + +fn bool_no_false<'de, D: de::Deserializer<'de>>(deserializer: D) -> Result { + let b: bool = Deserialize::deserialize(deserializer)?; + if b { + Ok(b) + } else { + Err(de::Error::custom("`workspace` cannot be false")) + } +} + +pub type MaybeWorkspaceDependency = MaybeWorkspace; + +impl MaybeWorkspaceDependency { + pub fn unused_keys(&self) -> Vec { + match self { + MaybeWorkspaceDependency::Defined(d) => d.unused_keys(), + MaybeWorkspaceDependency::Workspace(w) => w.unused_keys.keys().cloned().collect(), + } + } +} + +impl<'de> de::Deserialize<'de> for MaybeWorkspaceDependency { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + let value = serde_value::Value::deserialize(deserializer)?; + + if let Ok(w) = TomlWorkspaceDependency::deserialize(serde_value::ValueDeserializer::< + D::Error, + >::new(value.clone())) + { + return if w.workspace { + Ok(MaybeWorkspace::Workspace(w)) + } else { + Err(de::Error::custom("`workspace` cannot be false")) + }; + } + TomlDependency::deserialize(serde_value::ValueDeserializer::::new(value)) + .map(MaybeWorkspace::Defined) + } +} + +#[derive(Deserialize, Serialize, Clone, Debug)] +#[serde(rename_all = "kebab-case")] +pub struct TomlWorkspaceDependency { + pub workspace: bool, + pub features: Option>, + pub default_features: Option, + #[serde(rename = "default_features")] + pub default_features2: Option, + pub optional: Option, + pub public: Option, + + /// This is here to provide a way to see the "unused manifest keys" when deserializing + #[serde(skip_serializing)] + #[serde(flatten)] + pub unused_keys: BTreeMap, +} + +#[derive(Clone, Debug, Serialize)] +#[serde(untagged)] +pub enum TomlDependency { + /// In the simple format, only a version is specified, eg. + /// `package = ""` + Simple(String), + /// The simple format is equivalent to a detailed dependency + /// specifying only a version, eg. + /// `package = { version = "" }` + Detailed(DetailedTomlDependency

), +} + +impl TomlDependency { + pub fn is_version_specified(&self) -> bool { + match self { + TomlDependency::Detailed(d) => d.version.is_some(), + TomlDependency::Simple(..) => true, + } + } + + pub fn is_optional(&self) -> bool { + match self { + TomlDependency::Detailed(d) => d.optional.unwrap_or(false), + TomlDependency::Simple(..) => false, + } + } + + pub fn unused_keys(&self) -> Vec { + match self { + TomlDependency::Simple(_) => vec![], + TomlDependency::Detailed(detailed) => detailed.unused_keys.keys().cloned().collect(), + } + } +} + +impl<'de, P: Deserialize<'de> + Clone> de::Deserialize<'de> for TomlDependency

{ + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + UntaggedEnumVisitor::new() + .expecting( + "a version string like \"0.9.8\" or a \ + detailed dependency like { version = \"0.9.8\" }", + ) + .string(|value| Ok(TomlDependency::Simple(value.to_owned()))) + .map(|value| value.deserialize().map(TomlDependency::Detailed)) + .deserialize(deserializer) + } +} + +#[derive(Deserialize, Serialize, Clone, Debug)] +#[serde(rename_all = "kebab-case")] +pub struct DetailedTomlDependency { + pub version: Option, + pub registry: Option, + /// The URL of the `registry` field. + /// This is an internal implementation detail. When Cargo creates a + /// package, it replaces `registry` with `registry-index` so that the + /// manifest contains the correct URL. All users won't have the same + /// registry names configured, so Cargo can't rely on just the name for + /// crates published by other users. + pub registry_index: Option, + // `path` is relative to the file it appears in. If that's a `Cargo.toml`, it'll be relative to + // that TOML file, and if it's a `.cargo/config` file, it'll be relative to that file. + pub path: Option

, + pub git: Option, + pub branch: Option, + pub tag: Option, + pub rev: Option, + pub features: Option>, + pub optional: Option, + pub default_features: Option, + #[serde(rename = "default_features")] + pub default_features2: Option, + pub package: Option, + pub public: Option, + + /// One or more of `bin`, `cdylib`, `staticlib`, `bin:`. + pub artifact: Option, + /// If set, the artifact should also be a dependency + pub lib: Option, + /// A platform name, like `x86_64-apple-darwin` + pub target: Option, + + /// This is here to provide a way to see the "unused manifest keys" when deserializing + #[serde(skip_serializing)] + #[serde(flatten)] + pub unused_keys: BTreeMap, +} + +// Explicit implementation so we avoid pulling in P: Default +impl Default for DetailedTomlDependency

{ + fn default() -> Self { + Self { + version: Default::default(), + registry: Default::default(), + registry_index: Default::default(), + path: Default::default(), + git: Default::default(), + branch: Default::default(), + tag: Default::default(), + rev: Default::default(), + features: Default::default(), + optional: Default::default(), + default_features: Default::default(), + default_features2: Default::default(), + package: Default::default(), + public: Default::default(), + artifact: Default::default(), + lib: Default::default(), + target: Default::default(), + unused_keys: Default::default(), + } + } +} + +#[derive(Deserialize, Serialize, Clone, Debug, Default)] +pub struct TomlProfiles(pub BTreeMap); + +impl TomlProfiles { + pub fn get_all(&self) -> &BTreeMap { + &self.0 + } + + pub fn get(&self, name: &str) -> Option<&TomlProfile> { + self.0.get(name) + } +} + +#[derive(Deserialize, Serialize, Clone, Debug, Default, Eq, PartialEq)] +#[serde(default, rename_all = "kebab-case")] +pub struct TomlProfile { + pub opt_level: Option, + pub lto: Option, + pub codegen_backend: Option, + pub codegen_units: Option, + pub debug: Option, + pub split_debuginfo: Option, + pub debug_assertions: Option, + pub rpath: Option, + pub panic: Option, + pub overflow_checks: Option, + pub incremental: Option, + pub dir_name: Option, + pub inherits: Option, + pub strip: Option, + // Note that `rustflags` is used for the cargo-feature `profile_rustflags` + pub rustflags: Option>, + // These two fields must be last because they are sub-tables, and TOML + // requires all non-tables to be listed first. + pub package: Option>, + pub build_override: Option>, + /// Unstable feature `-Ztrim-paths`. + pub trim_paths: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] +pub enum ProfilePackageSpec { + Spec(PackageIdSpec), + All, +} + +impl fmt::Display for ProfilePackageSpec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ProfilePackageSpec::Spec(spec) => spec.fmt(f), + ProfilePackageSpec::All => f.write_str("*"), + } + } +} + +impl ser::Serialize for ProfilePackageSpec { + fn serialize(&self, s: S) -> Result + where + S: ser::Serializer, + { + self.to_string().serialize(s) + } +} + +impl<'de> de::Deserialize<'de> for ProfilePackageSpec { + fn deserialize(d: D) -> Result + where + D: de::Deserializer<'de>, + { + let string = String::deserialize(d)?; + if string == "*" { + Ok(ProfilePackageSpec::All) + } else { + PackageIdSpec::parse(&string) + .map_err(de::Error::custom) + .map(ProfilePackageSpec::Spec) + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TomlOptLevel(pub String); + +impl ser::Serialize for TomlOptLevel { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + match self.0.parse::() { + Ok(n) => n.serialize(serializer), + Err(_) => self.0.serialize(serializer), + } + } +} + +impl<'de> de::Deserialize<'de> for TomlOptLevel { + fn deserialize(d: D) -> Result + where + D: de::Deserializer<'de>, + { + use serde::de::Error as _; + UntaggedEnumVisitor::new() + .expecting("an optimization level") + .i64(|value| Ok(TomlOptLevel(value.to_string()))) + .string(|value| { + if value == "s" || value == "z" { + Ok(TomlOptLevel(value.to_string())) + } else { + Err(serde_untagged::de::Error::custom(format!( + "must be `0`, `1`, `2`, `3`, `s` or `z`, \ + but found the string: \"{}\"", + value + ))) + } + }) + .deserialize(d) + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)] +pub enum TomlDebugInfo { + None, + LineDirectivesOnly, + LineTablesOnly, + Limited, + Full, +} + +impl Display for TomlDebugInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TomlDebugInfo::None => f.write_char('0'), + TomlDebugInfo::Limited => f.write_char('1'), + TomlDebugInfo::Full => f.write_char('2'), + TomlDebugInfo::LineDirectivesOnly => f.write_str("line-directives-only"), + TomlDebugInfo::LineTablesOnly => f.write_str("line-tables-only"), + } + } +} + +impl ser::Serialize for TomlDebugInfo { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + match self { + Self::None => 0.serialize(serializer), + Self::LineDirectivesOnly => "line-directives-only".serialize(serializer), + Self::LineTablesOnly => "line-tables-only".serialize(serializer), + Self::Limited => 1.serialize(serializer), + Self::Full => 2.serialize(serializer), + } + } +} + +impl<'de> de::Deserialize<'de> for TomlDebugInfo { + fn deserialize(d: D) -> Result + where + D: de::Deserializer<'de>, + { + use serde::de::Error as _; + let expecting = "a boolean, 0, 1, 2, \"line-tables-only\", or \"line-directives-only\""; + UntaggedEnumVisitor::new() + .expecting(expecting) + .bool(|value| { + Ok(if value { + TomlDebugInfo::Full + } else { + TomlDebugInfo::None + }) + }) + .i64(|value| { + let debuginfo = match value { + 0 => TomlDebugInfo::None, + 1 => TomlDebugInfo::Limited, + 2 => TomlDebugInfo::Full, + _ => { + return Err(serde_untagged::de::Error::invalid_value( + Unexpected::Signed(value), + &expecting, + )) + } + }; + Ok(debuginfo) + }) + .string(|value| { + let debuginfo = match value { + "none" => TomlDebugInfo::None, + "limited" => TomlDebugInfo::Limited, + "full" => TomlDebugInfo::Full, + "line-directives-only" => TomlDebugInfo::LineDirectivesOnly, + "line-tables-only" => TomlDebugInfo::LineTablesOnly, + _ => { + return Err(serde_untagged::de::Error::invalid_value( + Unexpected::Str(value), + &expecting, + )) + } + }; + Ok(debuginfo) + }) + .deserialize(d) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize)] +#[serde(untagged, rename_all = "kebab-case")] +pub enum TomlTrimPaths { + Values(Vec), + All, +} + +impl TomlTrimPaths { + pub fn none() -> Self { + TomlTrimPaths::Values(Vec::new()) + } + + pub fn is_none(&self) -> bool { + match self { + TomlTrimPaths::Values(v) => v.is_empty(), + TomlTrimPaths::All => false, + } + } +} + +impl<'de> de::Deserialize<'de> for TomlTrimPaths { + fn deserialize(d: D) -> Result + where + D: de::Deserializer<'de>, + { + use serde::de::Error as _; + let expecting = r#"a boolean, "none", "diagnostics", "macro", "object", "all", or an array with these options"#; + UntaggedEnumVisitor::new() + .expecting(expecting) + .bool(|value| { + Ok(if value { + TomlTrimPaths::All + } else { + TomlTrimPaths::none() + }) + }) + .string(|v| match v { + "none" => Ok(TomlTrimPaths::none()), + "all" => Ok(TomlTrimPaths::All), + v => { + let d = v.into_deserializer(); + let err = |_: D::Error| { + serde_untagged::de::Error::custom(format!("expected {expecting}")) + }; + TomlTrimPathsValue::deserialize(d) + .map_err(err) + .map(|v| v.into()) + } + }) + .seq(|seq| { + let seq: Vec = seq.deserialize()?; + let seq: Vec<_> = seq + .into_iter() + .map(|s| TomlTrimPathsValue::deserialize(s.into_deserializer())) + .collect::>()?; + Ok(seq.into()) + }) + .deserialize(d) + } +} + +impl fmt::Display for TomlTrimPaths { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TomlTrimPaths::All => write!(f, "all"), + TomlTrimPaths::Values(v) if v.is_empty() => write!(f, "none"), + TomlTrimPaths::Values(v) => { + let mut iter = v.iter(); + if let Some(value) = iter.next() { + write!(f, "{value}")?; + } + for value in iter { + write!(f, ",{value}")?; + } + Ok(()) + } + } + } +} + +impl From for TomlTrimPaths { + fn from(value: TomlTrimPathsValue) -> Self { + TomlTrimPaths::Values(vec![value]) + } +} + +impl From> for TomlTrimPaths { + fn from(value: Vec) -> Self { + TomlTrimPaths::Values(value) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum TomlTrimPathsValue { + Diagnostics, + Macro, + Object, +} + +impl TomlTrimPathsValue { + pub fn as_str(&self) -> &'static str { + match self { + TomlTrimPathsValue::Diagnostics => "diagnostics", + TomlTrimPathsValue::Macro => "macro", + TomlTrimPathsValue::Object => "object", + } + } +} + +impl fmt::Display for TomlTrimPathsValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +pub type TomlLibTarget = TomlTarget; +pub type TomlBinTarget = TomlTarget; +pub type TomlExampleTarget = TomlTarget; +pub type TomlTestTarget = TomlTarget; +pub type TomlBenchTarget = TomlTarget; + +#[derive(Default, Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "kebab-case")] +pub struct TomlTarget { + pub name: Option, + + // The intention was to only accept `crate-type` here but historical + // versions of Cargo also accepted `crate_type`, so look for both. + pub crate_type: Option>, + #[serde(rename = "crate_type")] + pub crate_type2: Option>, + + pub path: Option, + // Note that `filename` is used for the cargo-feature `different_binary_name` + pub filename: Option, + pub test: Option, + pub doctest: Option, + pub bench: Option, + pub doc: Option, + pub plugin: Option, + pub doc_scrape_examples: Option, + #[serde(rename = "proc-macro")] + pub proc_macro_raw: Option, + #[serde(rename = "proc_macro")] + pub proc_macro_raw2: Option, + pub harness: Option, + pub required_features: Option>, + pub edition: Option, +} + +impl TomlTarget { + pub fn new() -> TomlTarget { + TomlTarget::default() + } +} + +/// Corresponds to a `target` entry, but `TomlTarget` is already used. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "kebab-case")] +pub struct TomlPlatform { + pub dependencies: Option>, + pub build_dependencies: Option>, + #[serde(rename = "build_dependencies")] + pub build_dependencies2: Option>, + pub dev_dependencies: Option>, + #[serde(rename = "dev_dependencies")] + pub dev_dependencies2: Option>, +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +#[serde(expecting = "a lints table")] +#[serde(rename_all = "kebab-case")] +pub struct MaybeWorkspaceLints { + #[serde(skip_serializing_if = "is_false")] + #[serde(deserialize_with = "bool_no_false", default)] + pub workspace: bool, + #[serde(flatten)] + pub lints: TomlLints, +} + +fn is_false(b: &bool) -> bool { + !b +} + +pub type TomlLints = BTreeMap; + +pub type TomlToolLints = BTreeMap; + +#[derive(Serialize, Debug, Clone)] +#[serde(untagged)] +pub enum TomlLint { + Level(TomlLintLevel), + Config(TomlLintConfig), +} + +impl<'de> Deserialize<'de> for TomlLint { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + UntaggedEnumVisitor::new() + .string(|string| { + TomlLintLevel::deserialize(string.into_deserializer()).map(TomlLint::Level) + }) + .map(|map| map.deserialize().map(TomlLint::Config)) + .deserialize(deserializer) + } +} + +impl TomlLint { + pub fn level(&self) -> TomlLintLevel { + match self { + Self::Level(level) => *level, + Self::Config(config) => config.level, + } + } + + pub fn priority(&self) -> i8 { + match self { + Self::Level(_) => 0, + Self::Config(config) => config.priority, + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "kebab-case")] +pub struct TomlLintConfig { + pub level: TomlLintLevel, + #[serde(default)] + pub priority: i8, +} + +#[derive(Serialize, Deserialize, Debug, Copy, Clone)] +#[serde(rename_all = "kebab-case")] +pub enum TomlLintLevel { + Forbid, + Deny, + Warn, + Allow, +} + +#[derive(Copy, Clone, Debug)] +pub struct InvalidCargoFeatures {} + +impl<'de> de::Deserialize<'de> for InvalidCargoFeatures { + fn deserialize(_d: D) -> Result + where + D: de::Deserializer<'de>, + { + use serde::de::Error as _; + + Err(D::Error::custom( + "the field `cargo-features` should be set at the top of Cargo.toml before any tables", + )) + } +} + +/// A StringOrVec can be parsed from either a TOML string or array, +/// but is always stored as a vector. +#[derive(Clone, Debug, Serialize, Eq, PartialEq, PartialOrd, Ord)] +pub struct StringOrVec(pub Vec); + +impl StringOrVec { + pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, String> { + self.0.iter() + } +} + +impl<'de> de::Deserialize<'de> for StringOrVec { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + UntaggedEnumVisitor::new() + .expecting("string or list of strings") + .string(|value| Ok(StringOrVec(vec![value.to_owned()]))) + .seq(|value| value.deserialize().map(StringOrVec)) + .deserialize(deserializer) + } +} + +#[derive(Clone, Debug, Serialize, Eq, PartialEq)] +#[serde(untagged)] +pub enum StringOrBool { + String(String), + Bool(bool), +} + +impl<'de> Deserialize<'de> for StringOrBool { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + UntaggedEnumVisitor::new() + .bool(|b| Ok(StringOrBool::Bool(b))) + .string(|s| Ok(StringOrBool::String(s.to_owned()))) + .deserialize(deserializer) + } +} + +#[derive(PartialEq, Clone, Debug, Serialize)] +#[serde(untagged)] +pub enum VecStringOrBool { + VecString(Vec), + Bool(bool), +} + +impl<'de> de::Deserialize<'de> for VecStringOrBool { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + UntaggedEnumVisitor::new() + .expecting("a boolean or vector of strings") + .bool(|value| Ok(VecStringOrBool::Bool(value))) + .seq(|value| value.deserialize().map(VecStringOrBool::VecString)) + .deserialize(deserializer) + } +} + +#[derive(Clone)] +pub struct PathValue(pub PathBuf); + +impl fmt::Debug for PathValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl ser::Serialize for PathValue { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + self.0.serialize(serializer) + } +} + +impl<'de> de::Deserialize<'de> for PathValue { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + Ok(PathValue(String::deserialize(deserializer)?.into())) + } +} diff --git a/src/cargo/util/toml/targets.rs b/src/cargo/util/toml/targets.rs index 8455ae95b3e..bfc1419c89f 100644 --- a/src/cargo/util/toml/targets.rs +++ b/src/cargo/util/toml/targets.rs @@ -14,7 +14,7 @@ use std::collections::HashSet; use std::fs::{self, DirEntry}; use std::path::{Path, PathBuf}; -use super::{ +use super::schema::{ PathValue, StringOrBool, StringOrVec, TomlBenchTarget, TomlBinTarget, TomlExampleTarget, TomlLibTarget, TomlManifest, TomlTarget, TomlTestTarget, }; diff --git a/tests/testsuite/config.rs b/tests/testsuite/config.rs index 22d6a2b2f97..e5078bd8ed0 100644 --- a/tests/testsuite/config.rs +++ b/tests/testsuite/config.rs @@ -2,9 +2,9 @@ use cargo::core::{PackageIdSpec, Shell}; use cargo::util::config::{self, Config, Definition, JobsConfig, SslVersionConfig, StringList}; -use cargo::util::toml::TomlTrimPaths; -use cargo::util::toml::TomlTrimPathsValue; -use cargo::util::toml::{self as cargo_toml, TomlDebugInfo, VecStringOrBool as VSOB}; +use cargo::util::toml::schema::TomlTrimPaths; +use cargo::util::toml::schema::TomlTrimPathsValue; +use cargo::util::toml::schema::{self as cargo_toml, TomlDebugInfo, VecStringOrBool as VSOB}; use cargo::CargoResult; use cargo_test_support::compare; use cargo_test_support::{panic_error, paths, project, symlink_supported, t}; diff --git a/tests/testsuite/profile_config.rs b/tests/testsuite/profile_config.rs index 143c050f962..710a0d8ef3f 100644 --- a/tests/testsuite/profile_config.rs +++ b/tests/testsuite/profile_config.rs @@ -1,6 +1,6 @@ //! Tests for profiles defined in config files. -use cargo::util::toml::TomlDebugInfo; +use cargo::util::toml::schema::TomlDebugInfo; use cargo_test_support::paths::CargoPathExt; use cargo_test_support::registry::Package; use cargo_test_support::{basic_lib_manifest, paths, project};