| use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; |
| use std::fmt; |
| use std::marker::PhantomData; |
| use std::path::{Path, PathBuf}; |
| use std::rc::Rc; |
| use std::str::{self, FromStr}; |
| |
| use anyhow::{anyhow, bail, Context as _}; |
| use cargo_platform::Platform; |
| use cargo_util::paths; |
| use lazycell::LazyCell; |
| use log::{debug, trace}; |
| use semver::{self, VersionReq}; |
| use serde::de; |
| use serde::ser; |
| use serde::{Deserialize, Serialize}; |
| use toml_edit::easy as toml; |
| use url::Url; |
| |
| use crate::core::compiler::{CompileKind, CompileTarget}; |
| use crate::core::dependency::{Artifact, ArtifactTarget, DepKind}; |
| use crate::core::manifest::{ManifestMetadata, TargetSourcePath, Warnings}; |
| use crate::core::resolver::ResolveBehavior; |
| use crate::core::{ |
| find_workspace_root, resolve_relative_path, Dependency, Manifest, PackageId, Summary, Target, |
| }; |
| use crate::core::{Edition, EitherManifest, Feature, Features, VirtualManifest, Workspace}; |
| use crate::core::{GitReference, PackageIdSpec, SourceId, WorkspaceConfig, WorkspaceRootConfig}; |
| use crate::sources::{CRATES_IO_INDEX, CRATES_IO_REGISTRY}; |
| use crate::util::errors::{CargoResult, ManifestError}; |
| use crate::util::interning::InternedString; |
| use crate::util::{ |
| self, config::ConfigRelativePath, validate_package_name, Config, IntoUrl, VersionReqExt, |
| }; |
| |
| mod targets; |
| use self::targets::targets; |
| |
| pub use toml_edit::de::Error as TomlDeError; |
| pub use toml_edit::TomlError as TomlEditError; |
| |
| /// Loads a `Cargo.toml` from a file on disk. |
| /// |
| /// This could result in a real or virtual manifest being returned. |
| /// |
| /// A list of nested paths is also returned, one for each path dependency |
| /// within the manifest. For virtual manifests, these paths can only |
| /// come from patched or replaced dependencies. These paths are not |
| /// canonicalized. |
| pub fn read_manifest( |
| path: &Path, |
| source_id: SourceId, |
| config: &Config, |
| ) -> Result<(EitherManifest, Vec<PathBuf>), ManifestError> { |
| trace!( |
| "read_manifest; path={}; source-id={}", |
| path.display(), |
| source_id |
| ); |
| let contents = paths::read(path).map_err(|err| ManifestError::new(err, path.into()))?; |
| |
| read_manifest_from_str(&contents, path, source_id, config) |
| .with_context(|| format!("failed to parse manifest at `{}`", path.display())) |
| .map_err(|err| ManifestError::new(err, path.into())) |
| } |
| |
| /// Parse an already-loaded `Cargo.toml` as a Cargo manifest. |
| /// |
| /// This could result in a real or virtual manifest being returned. |
| /// |
| /// A list of nested paths is also returned, one for each path dependency |
| /// within the manifest. For virtual manifests, these paths can only |
| /// come from patched or replaced dependencies. These paths are not |
| /// canonicalized. |
| pub fn read_manifest_from_str( |
| contents: &str, |
| manifest_file: &Path, |
| source_id: SourceId, |
| config: &Config, |
| ) -> CargoResult<(EitherManifest, Vec<PathBuf>)> { |
| let package_root = manifest_file.parent().unwrap(); |
| |
| let toml = { |
| let pretty_filename = manifest_file |
| .strip_prefix(config.cwd()) |
| .unwrap_or(manifest_file); |
| parse_document(contents, pretty_filename, config)? |
| }; |
| |
| // Provide a helpful error message for a common user error. |
| if let Some(package) = toml.get("package").or_else(|| toml.get("project")) { |
| if let Some(feats) = package.get("cargo-features") { |
| let mut feats = feats.clone(); |
| if let Some(value) = feats.as_value_mut() { |
| // Only keep formatting inside of the `[]` and not formatting around it |
| value.decor_mut().clear(); |
| } |
| bail!( |
| "cargo-features = {} was found in the wrong location: it \ |
| should be set at the top of Cargo.toml before any tables", |
| feats.to_string() |
| ); |
| } |
| } |
| |
| let mut unused = BTreeSet::new(); |
| let manifest: TomlManifest = serde_ignored::deserialize(toml, |path| { |
| let mut key = String::new(); |
| stringify(&mut key, &path); |
| unused.insert(key); |
| })?; |
| let add_unused = |warnings: &mut Warnings| { |
| for key in unused { |
| warnings.add_warning(format!("unused manifest key: {}", key)); |
| if key == "profiles.debug" { |
| warnings.add_warning("use `[profile.dev]` to configure debug builds".to_string()); |
| } |
| } |
| }; |
| |
| let manifest = Rc::new(manifest); |
| if let Some(deps) = manifest |
| .workspace |
| .as_ref() |
| .and_then(|ws| ws.dependencies.as_ref()) |
| { |
| for (name, dep) in deps { |
| if dep.is_optional() { |
| bail!( |
| "{} is optional, but workspace dependencies cannot be optional", |
| name |
| ); |
| } |
| if let TomlDependency::Workspace(_) = dep { |
| bail!( |
| "{} was specified as `workspace.dependencies.{}.workspace = true`, but \ |
| workspace dependencies cannot specify `workspace = true`", |
| name, |
| name |
| ); |
| } |
| } |
| } |
| return if manifest.project.is_some() || manifest.package.is_some() { |
| let (mut manifest, paths) = |
| TomlManifest::to_real_manifest(&manifest, source_id, package_root, config)?; |
| add_unused(manifest.warnings_mut()); |
| if manifest.targets().iter().all(|t| t.is_custom_build()) { |
| bail!( |
| "no targets specified in the manifest\n\ |
| either src/lib.rs, src/main.rs, a [lib] section, or \ |
| [[bin]] section must be present" |
| ) |
| } |
| Ok((EitherManifest::Real(manifest), paths)) |
| } else { |
| let (mut m, paths) = |
| TomlManifest::to_virtual_manifest(&manifest, source_id, package_root, config)?; |
| add_unused(m.warnings_mut()); |
| Ok((EitherManifest::Virtual(m), paths)) |
| }; |
| |
| fn stringify(dst: &mut String, path: &serde_ignored::Path<'_>) { |
| use serde_ignored::Path; |
| |
| match *path { |
| Path::Root => {} |
| Path::Seq { parent, index } => { |
| stringify(dst, parent); |
| if !dst.is_empty() { |
| dst.push('.'); |
| } |
| dst.push_str(&index.to_string()); |
| } |
| Path::Map { parent, ref key } => { |
| stringify(dst, parent); |
| if !dst.is_empty() { |
| dst.push('.'); |
| } |
| dst.push_str(key); |
| } |
| Path::Some { parent } |
| | Path::NewtypeVariant { parent } |
| | Path::NewtypeStruct { parent } => stringify(dst, parent), |
| } |
| } |
| } |
| |
| /// Attempts to parse a string into a [`toml::Value`]. This is not specific to any |
| /// particular kind of TOML file. |
| /// |
| /// The purpose of this wrapper is to detect invalid TOML which was previously |
| /// accepted and display a warning to the user in that case. The `file` and `config` |
| /// parameters are only used by this fallback path. |
| pub fn parse(toml: &str, _file: &Path, _config: &Config) -> CargoResult<toml::Value> { |
| // At the moment, no compatibility checks are needed. |
| toml.parse() |
| .map_err(|e| anyhow::Error::from(e).context("could not parse input as TOML")) |
| } |
| |
| pub fn parse_document( |
| toml: &str, |
| _file: &Path, |
| _config: &Config, |
| ) -> CargoResult<toml_edit::Document> { |
| // At the moment, no compatibility checks are needed. |
| toml.parse() |
| .map_err(|e| anyhow::Error::from(e).context("could not parse input as TOML")) |
| } |
| |
| /// Warn about paths that have been deprecated and may conflict. |
| fn warn_on_deprecated(new_path: &str, name: &str, kind: &str, warnings: &mut Vec<String>) { |
| let old_path = new_path.replace("-", "_"); |
| warnings.push(format!( |
| "conflicting between `{new_path}` and `{old_path}` in the `{name}` {kind}.\n |
| `{old_path}` is ignored and not recommended for use in the future" |
| )) |
| } |
| |
| type TomlLibTarget = TomlTarget; |
| type TomlBinTarget = TomlTarget; |
| type TomlExampleTarget = TomlTarget; |
| type TomlTestTarget = TomlTarget; |
| type TomlBenchTarget = TomlTarget; |
| |
| #[derive(Clone, Debug, Serialize)] |
| #[serde(untagged)] |
| pub enum TomlDependency<P: Clone = String> { |
| /// In the simple format, only a version is specified, eg. |
| /// `package = "<version>"` |
| Simple(String), |
| /// `package.workspace = true` |
| Workspace(TomlWorkspaceDependency), |
| /// The simple format is equivalent to a detailed dependency |
| /// specifying only a version, eg. |
| /// `package = { version = "<version>" }` |
| Detailed(DetailedTomlDependency<P>), |
| } |
| |
| impl<'de, P: Deserialize<'de> + Clone> de::Deserialize<'de> for TomlDependency<P> { |
| fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
| where |
| D: de::Deserializer<'de>, |
| { |
| struct TomlDependencyVisitor<P>(PhantomData<P>); |
| |
| impl<'de, P: Deserialize<'de> + Clone> de::Visitor<'de> for TomlDependencyVisitor<P> { |
| type Value = TomlDependency<P>; |
| |
| fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { |
| formatter.write_str( |
| "a version string like \"0.9.8\" or a \ |
| detailed dependency like { version = \"0.9.8\" }", |
| ) |
| } |
| |
| fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> |
| where |
| E: de::Error, |
| { |
| Ok(TomlDependency::Simple(s.to_owned())) |
| } |
| |
| fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error> |
| where |
| V: de::MapAccess<'de>, |
| { |
| let mvd = de::value::MapAccessDeserializer::new(map); |
| let details: IntermediateDependency<P> = IntermediateDependency::deserialize(mvd)?; |
| if let Some(workspace) = details.workspace { |
| if workspace { |
| Ok(TomlDependency::Workspace(TomlWorkspaceDependency { |
| workspace: true, |
| features: details.features, |
| optional: details.optional, |
| })) |
| } else { |
| return Err(de::Error::custom("workspace cannot be false")); |
| } |
| } else { |
| Ok(TomlDependency::Detailed(DetailedTomlDependency { |
| version: details.version, |
| registry: details.registry, |
| registry_index: details.registry_index, |
| path: details.path, |
| git: details.git, |
| branch: details.branch, |
| tag: details.tag, |
| rev: details.rev, |
| features: details.features, |
| optional: details.optional, |
| default_features: details.default_features, |
| default_features2: details.default_features2, |
| package: details.package, |
| public: details.public, |
| artifact: details.artifact, |
| lib: details.lib, |
| target: details.target, |
| })) |
| } |
| } |
| } |
| |
| deserializer.deserialize_any(TomlDependencyVisitor(PhantomData)) |
| } |
| } |
| |
| pub trait ResolveToPath { |
| fn resolve(&self, config: &Config) -> PathBuf; |
| } |
| |
| impl ResolveToPath for String { |
| fn resolve(&self, _: &Config) -> PathBuf { |
| self.into() |
| } |
| } |
| |
| impl ResolveToPath for ConfigRelativePath { |
| fn resolve(&self, c: &Config) -> PathBuf { |
| self.resolve_path(c) |
| } |
| } |
| |
| // This is here due to parsing of TomlDependency works. |
| // At the time of writing it can not be derived in anyway I could find. |
| #[derive(Deserialize, Debug)] |
| #[serde(rename_all = "kebab-case")] |
| pub struct IntermediateDependency<P = String> { |
| workspace: Option<bool>, |
| version: Option<String>, |
| registry: Option<String>, |
| registry_index: Option<String>, |
| path: Option<P>, |
| git: Option<String>, |
| branch: Option<String>, |
| tag: Option<String>, |
| rev: Option<String>, |
| features: Option<Vec<String>>, |
| optional: Option<bool>, |
| default_features: Option<bool>, |
| #[serde(rename = "default_features")] |
| default_features2: Option<bool>, |
| package: Option<String>, |
| public: Option<bool>, |
| artifact: Option<StringOrVec>, |
| lib: Option<bool>, |
| target: Option<String>, |
| } |
| |
| #[derive(Deserialize, Serialize, Clone, Debug)] |
| pub struct TomlWorkspaceDependency { |
| workspace: bool, |
| features: Option<Vec<String>>, |
| optional: Option<bool>, |
| } |
| |
| #[derive(Deserialize, Serialize, Clone, Debug)] |
| #[serde(rename_all = "kebab-case")] |
| pub struct DetailedTomlDependency<P: Clone = String> { |
| version: Option<String>, |
| registry: Option<String>, |
| /// 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<String>, |
| // `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<P>, |
| git: Option<String>, |
| branch: Option<String>, |
| tag: Option<String>, |
| rev: Option<String>, |
| features: Option<Vec<String>>, |
| optional: Option<bool>, |
| default_features: Option<bool>, |
| #[serde(rename = "default_features")] |
| default_features2: Option<bool>, |
| package: Option<String>, |
| public: Option<bool>, |
| |
| /// One or more of `bin`, `cdylib`, `staticlib`, `bin:<name>`. |
| artifact: Option<StringOrVec>, |
| /// If set, the artifact should also be a dependency |
| lib: Option<bool>, |
| /// A platform name, like `x86_64-apple-darwin` |
| target: Option<String>, |
| } |
| |
| // Explicit implementation so we avoid pulling in P: Default |
| impl<P: Clone> Default for DetailedTomlDependency<P> { |
| 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(), |
| } |
| } |
| } |
| |
| /// This type is used to deserialize `Cargo.toml` files. |
| #[derive(Debug, Deserialize, Serialize)] |
| #[serde(rename_all = "kebab-case")] |
| pub struct TomlManifest { |
| cargo_features: Option<Vec<String>>, |
| package: Option<Box<TomlPackage>>, |
| project: Option<Box<TomlPackage>>, |
| profile: Option<TomlProfiles>, |
| lib: Option<TomlLibTarget>, |
| bin: Option<Vec<TomlBinTarget>>, |
| example: Option<Vec<TomlExampleTarget>>, |
| test: Option<Vec<TomlTestTarget>>, |
| bench: Option<Vec<TomlTestTarget>>, |
| dependencies: Option<BTreeMap<String, TomlDependency>>, |
| dev_dependencies: Option<BTreeMap<String, TomlDependency>>, |
| #[serde(rename = "dev_dependencies")] |
| dev_dependencies2: Option<BTreeMap<String, TomlDependency>>, |
| build_dependencies: Option<BTreeMap<String, TomlDependency>>, |
| #[serde(rename = "build_dependencies")] |
| build_dependencies2: Option<BTreeMap<String, TomlDependency>>, |
| features: Option<BTreeMap<InternedString, Vec<InternedString>>>, |
| target: Option<BTreeMap<String, TomlPlatform>>, |
| replace: Option<BTreeMap<String, TomlDependency>>, |
| patch: Option<BTreeMap<String, BTreeMap<String, TomlDependency>>>, |
| workspace: Option<TomlWorkspace>, |
| badges: Option<MaybeWorkspace<BTreeMap<String, BTreeMap<String, String>>>>, |
| } |
| |
| #[derive(Deserialize, Serialize, Clone, Debug, Default)] |
| pub struct TomlProfiles(BTreeMap<InternedString, TomlProfile>); |
| |
| impl TomlProfiles { |
| pub fn get_all(&self) -> &BTreeMap<InternedString, TomlProfile> { |
| &self.0 |
| } |
| |
| pub fn get(&self, name: &str) -> Option<&TomlProfile> { |
| self.0.get(name) |
| } |
| |
| pub fn validate(&self, features: &Features, warnings: &mut Vec<String>) -> CargoResult<()> { |
| for (name, profile) in &self.0 { |
| profile.validate(name, features, warnings)?; |
| } |
| Ok(()) |
| } |
| } |
| |
| #[derive(Clone, Debug, Eq, PartialEq)] |
| pub struct TomlOptLevel(pub String); |
| |
| impl<'de> de::Deserialize<'de> for TomlOptLevel { |
| fn deserialize<D>(d: D) -> Result<TomlOptLevel, D::Error> |
| where |
| D: de::Deserializer<'de>, |
| { |
| struct Visitor; |
| |
| impl<'de> de::Visitor<'de> for Visitor { |
| type Value = TomlOptLevel; |
| |
| fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { |
| formatter.write_str("an optimization level") |
| } |
| |
| fn visit_i64<E>(self, value: i64) -> Result<TomlOptLevel, E> |
| where |
| E: de::Error, |
| { |
| Ok(TomlOptLevel(value.to_string())) |
| } |
| |
| fn visit_str<E>(self, value: &str) -> Result<TomlOptLevel, E> |
| where |
| E: de::Error, |
| { |
| if value == "s" || value == "z" { |
| Ok(TomlOptLevel(value.to_string())) |
| } else { |
| Err(E::custom(format!( |
| "must be `0`, `1`, `2`, `3`, `s` or `z`, \ |
| but found the string: \"{}\"", |
| value |
| ))) |
| } |
| } |
| } |
| |
| d.deserialize_any(Visitor) |
| } |
| } |
| |
| impl ser::Serialize for TomlOptLevel { |
| fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> |
| where |
| S: ser::Serializer, |
| { |
| match self.0.parse::<u32>() { |
| Ok(n) => n.serialize(serializer), |
| Err(_) => self.0.serialize(serializer), |
| } |
| } |
| } |
| |
| #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] |
| #[serde(untagged, expecting = "expected a boolean or an integer")] |
| pub enum U32OrBool { |
| U32(u32), |
| Bool(bool), |
| } |
| |
| #[derive(Deserialize, Serialize, Clone, Debug, Default, Eq, PartialEq)] |
| #[serde(default, rename_all = "kebab-case")] |
| pub struct TomlProfile { |
| pub opt_level: Option<TomlOptLevel>, |
| pub lto: Option<StringOrBool>, |
| pub codegen_backend: Option<InternedString>, |
| pub codegen_units: Option<u32>, |
| pub debug: Option<U32OrBool>, |
| pub split_debuginfo: Option<String>, |
| pub debug_assertions: Option<bool>, |
| pub rpath: Option<bool>, |
| pub panic: Option<String>, |
| pub overflow_checks: Option<bool>, |
| pub incremental: Option<bool>, |
| pub dir_name: Option<InternedString>, |
| pub inherits: Option<InternedString>, |
| pub strip: Option<StringOrBool>, |
| // Note that `rustflags` is used for the cargo-feature `profile_rustflags` |
| pub rustflags: Option<Vec<InternedString>>, |
| // These two fields must be last because they are sub-tables, and TOML |
| // requires all non-tables to be listed first. |
| pub package: Option<BTreeMap<ProfilePackageSpec, TomlProfile>>, |
| pub build_override: Option<Box<TomlProfile>>, |
| } |
| |
| #[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] |
| pub enum ProfilePackageSpec { |
| Spec(PackageIdSpec), |
| All, |
| } |
| |
| impl ser::Serialize for ProfilePackageSpec { |
| fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error> |
| where |
| S: ser::Serializer, |
| { |
| self.to_string().serialize(s) |
| } |
| } |
| |
| impl<'de> de::Deserialize<'de> for ProfilePackageSpec { |
| fn deserialize<D>(d: D) -> Result<ProfilePackageSpec, D::Error> |
| 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) |
| } |
| } |
| } |
| |
| 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 TomlProfile { |
| pub fn validate( |
| &self, |
| name: &str, |
| features: &Features, |
| warnings: &mut Vec<String>, |
| ) -> CargoResult<()> { |
| self.validate_profile(name, features)?; |
| if let Some(ref profile) = self.build_override { |
| profile.validate_override("build-override")?; |
| profile.validate_profile(&format!("{name}.build-override"), features)?; |
| } |
| if let Some(ref packages) = self.package { |
| for (override_name, profile) in packages { |
| profile.validate_override("package")?; |
| profile.validate_profile(&format!("{name}.package.{override_name}"), features)?; |
| } |
| } |
| |
| // Profile name validation |
| Self::validate_name(name)?; |
| |
| if let Some(dir_name) = self.dir_name { |
| // This is disabled for now, as we would like to stabilize named |
| // profiles without this, and then decide in the future if it is |
| // needed. This helps simplify the UI a little. |
| bail!( |
| "dir-name=\"{}\" in profile `{}` is not currently allowed, \ |
| directory names are tied to the profile name for custom profiles", |
| dir_name, |
| name |
| ); |
| } |
| |
| // `inherits` validation |
| if matches!(self.inherits.map(|s| s.as_str()), Some("debug")) { |
| bail!( |
| "profile.{}.inherits=\"debug\" should be profile.{}.inherits=\"dev\"", |
| name, |
| name |
| ); |
| } |
| |
| match name { |
| "doc" => { |
| warnings.push("profile `doc` is deprecated and has no effect".to_string()); |
| } |
| "test" | "bench" => { |
| if self.panic.is_some() { |
| warnings.push(format!("`panic` setting is ignored for `{}` profile", name)) |
| } |
| } |
| _ => {} |
| } |
| |
| if let Some(panic) = &self.panic { |
| if panic != "unwind" && panic != "abort" { |
| bail!( |
| "`panic` setting of `{}` is not a valid setting, \ |
| must be `unwind` or `abort`", |
| panic |
| ); |
| } |
| } |
| |
| if let Some(StringOrBool::String(arg)) = &self.lto { |
| if arg == "true" || arg == "false" { |
| bail!( |
| "`lto` setting of string `\"{arg}\"` for `{name}` profile is not \ |
| a valid setting, must be a boolean (`true`/`false`) or a string \ |
| (`\"thin\"`/`\"fat\"`/`\"off\"`) or omitted.", |
| ); |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| /// Validate dir-names and profile names according to RFC 2678. |
| pub fn validate_name(name: &str) -> CargoResult<()> { |
| if let Some(ch) = name |
| .chars() |
| .find(|ch| !ch.is_alphanumeric() && *ch != '_' && *ch != '-') |
| { |
| bail!( |
| "invalid character `{}` in profile name `{}`\n\ |
| Allowed characters are letters, numbers, underscore, and hyphen.", |
| ch, |
| name |
| ); |
| } |
| |
| const SEE_DOCS: &str = "See https://doc.rust-lang.org/cargo/reference/profiles.html \ |
| for more on configuring profiles."; |
| |
| let lower_name = name.to_lowercase(); |
| if lower_name == "debug" { |
| bail!( |
| "profile name `{}` is reserved\n\ |
| To configure the default development profile, use the name `dev` \ |
| as in [profile.dev]\n\ |
| {}", |
| name, |
| SEE_DOCS |
| ); |
| } |
| if lower_name == "build-override" { |
| bail!( |
| "profile name `{}` is reserved\n\ |
| To configure build dependency settings, use [profile.dev.build-override] \ |
| and [profile.release.build-override]\n\ |
| {}", |
| name, |
| SEE_DOCS |
| ); |
| } |
| |
| // These are some arbitrary reservations. We have no plans to use |
| // these, but it seems safer to reserve a few just in case we want to |
| // add more built-in profiles in the future. We can also uses special |
| // syntax like cargo:foo if needed. But it is unlikely these will ever |
| // be used. |
| if matches!( |
| lower_name.as_str(), |
| "build" |
| | "check" |
| | "clean" |
| | "config" |
| | "fetch" |
| | "fix" |
| | "install" |
| | "metadata" |
| | "package" |
| | "publish" |
| | "report" |
| | "root" |
| | "run" |
| | "rust" |
| | "rustc" |
| | "rustdoc" |
| | "target" |
| | "tmp" |
| | "uninstall" |
| ) || lower_name.starts_with("cargo") |
| { |
| bail!( |
| "profile name `{}` is reserved\n\ |
| Please choose a different name.\n\ |
| {}", |
| name, |
| SEE_DOCS |
| ); |
| } |
| |
| Ok(()) |
| } |
| |
| /// Validates a profile. |
| /// |
| /// This is a shallow check, which is reused for the profile itself and any overrides. |
| fn validate_profile(&self, name: &str, features: &Features) -> CargoResult<()> { |
| if let Some(codegen_backend) = &self.codegen_backend { |
| features.require(Feature::codegen_backend())?; |
| if codegen_backend.contains(|c: char| !c.is_ascii_alphanumeric() && c != '_') { |
| bail!( |
| "`profile.{}.codegen-backend` setting of `{}` is not a valid backend name.", |
| name, |
| codegen_backend, |
| ); |
| } |
| } |
| if self.rustflags.is_some() { |
| features.require(Feature::profile_rustflags())?; |
| } |
| Ok(()) |
| } |
| |
| /// Validation that is specific to an override. |
| fn validate_override(&self, which: &str) -> CargoResult<()> { |
| if self.package.is_some() { |
| bail!("package-specific profiles cannot be nested"); |
| } |
| if self.build_override.is_some() { |
| bail!("build-override profiles cannot be nested"); |
| } |
| if self.panic.is_some() { |
| bail!("`panic` may not be specified in a `{}` profile", which) |
| } |
| if self.lto.is_some() { |
| bail!("`lto` may not be specified in a `{}` profile", which) |
| } |
| if self.rpath.is_some() { |
| bail!("`rpath` may not be specified in a `{}` profile", which) |
| } |
| Ok(()) |
| } |
| |
| /// Overwrite self's values with the given profile. |
| pub fn merge(&mut self, profile: &TomlProfile) { |
| if let Some(v) = &profile.opt_level { |
| self.opt_level = Some(v.clone()); |
| } |
| |
| if let Some(v) = &profile.lto { |
| self.lto = Some(v.clone()); |
| } |
| |
| if let Some(v) = profile.codegen_backend { |
| self.codegen_backend = Some(v); |
| } |
| |
| if let Some(v) = profile.codegen_units { |
| self.codegen_units = Some(v); |
| } |
| |
| if let Some(v) = &profile.debug { |
| self.debug = Some(v.clone()); |
| } |
| |
| if let Some(v) = profile.debug_assertions { |
| self.debug_assertions = Some(v); |
| } |
| |
| if let Some(v) = &profile.split_debuginfo { |
| self.split_debuginfo = Some(v.clone()); |
| } |
| |
| if let Some(v) = profile.rpath { |
| self.rpath = Some(v); |
| } |
| |
| if let Some(v) = &profile.panic { |
| self.panic = Some(v.clone()); |
| } |
| |
| if let Some(v) = profile.overflow_checks { |
| self.overflow_checks = Some(v); |
| } |
| |
| if let Some(v) = profile.incremental { |
| self.incremental = Some(v); |
| } |
| |
| if let Some(v) = &profile.rustflags { |
| self.rustflags = Some(v.clone()); |
| } |
| |
| if let Some(other_package) = &profile.package { |
| match &mut self.package { |
| Some(self_package) => { |
| for (spec, other_pkg_profile) in other_package { |
| match self_package.get_mut(spec) { |
| Some(p) => p.merge(other_pkg_profile), |
| None => { |
| self_package.insert(spec.clone(), other_pkg_profile.clone()); |
| } |
| } |
| } |
| } |
| None => self.package = Some(other_package.clone()), |
| } |
| } |
| |
| if let Some(other_bo) = &profile.build_override { |
| match &mut self.build_override { |
| Some(self_bo) => self_bo.merge(other_bo), |
| None => self.build_override = Some(other_bo.clone()), |
| } |
| } |
| |
| if let Some(v) = &profile.inherits { |
| self.inherits = Some(*v); |
| } |
| |
| if let Some(v) = &profile.dir_name { |
| self.dir_name = Some(*v); |
| } |
| |
| if let Some(v) = &profile.strip { |
| self.strip = Some(v.clone()); |
| } |
| } |
| } |
| |
| /// 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<String>); |
| |
| impl<'de> de::Deserialize<'de> for StringOrVec { |
| fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
| where |
| D: de::Deserializer<'de>, |
| { |
| struct Visitor; |
| |
| impl<'de> de::Visitor<'de> for Visitor { |
| type Value = StringOrVec; |
| |
| fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { |
| formatter.write_str("string or list of strings") |
| } |
| |
| fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> |
| where |
| E: de::Error, |
| { |
| Ok(StringOrVec(vec![s.to_string()])) |
| } |
| |
| fn visit_seq<V>(self, v: V) -> Result<Self::Value, V::Error> |
| where |
| V: de::SeqAccess<'de>, |
| { |
| let seq = de::value::SeqAccessDeserializer::new(v); |
| Vec::deserialize(seq).map(StringOrVec) |
| } |
| } |
| |
| deserializer.deserialize_any(Visitor) |
| } |
| } |
| |
| impl StringOrVec { |
| pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, String> { |
| self.0.iter() |
| } |
| } |
| |
| #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] |
| #[serde(untagged, expecting = "expected a boolean or a string")] |
| pub enum StringOrBool { |
| String(String), |
| Bool(bool), |
| } |
| |
| #[derive(PartialEq, Clone, Debug, Serialize)] |
| #[serde(untagged)] |
| pub enum VecStringOrBool { |
| VecString(Vec<String>), |
| Bool(bool), |
| } |
| |
| impl<'de> de::Deserialize<'de> for VecStringOrBool { |
| fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
| where |
| D: de::Deserializer<'de>, |
| { |
| struct Visitor; |
| |
| impl<'de> de::Visitor<'de> for Visitor { |
| type Value = VecStringOrBool; |
| |
| fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { |
| formatter.write_str("a boolean or vector of strings") |
| } |
| |
| fn visit_seq<V>(self, v: V) -> Result<Self::Value, V::Error> |
| where |
| V: de::SeqAccess<'de>, |
| { |
| let seq = de::value::SeqAccessDeserializer::new(v); |
| Vec::deserialize(seq).map(VecStringOrBool::VecString) |
| } |
| |
| fn visit_bool<E>(self, b: bool) -> Result<Self::Value, E> |
| where |
| E: de::Error, |
| { |
| Ok(VecStringOrBool::Bool(b)) |
| } |
| } |
| |
| deserializer.deserialize_any(Visitor) |
| } |
| } |
| |
| fn version_trim_whitespace<'de, D>( |
| deserializer: D, |
| ) -> Result<MaybeWorkspace<semver::Version>, D::Error> |
| where |
| D: de::Deserializer<'de>, |
| { |
| struct Visitor; |
| |
| impl<'de> de::Visitor<'de> for Visitor { |
| type Value = MaybeWorkspace<semver::Version>; |
| |
| fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { |
| formatter.write_str("SemVer version") |
| } |
| |
| fn visit_str<E>(self, string: &str) -> Result<Self::Value, E> |
| where |
| E: de::Error, |
| { |
| match string.trim().parse().map_err(de::Error::custom) { |
| Ok(parsed) => Ok(MaybeWorkspace::Defined(parsed)), |
| Err(e) => Err(e), |
| } |
| } |
| |
| fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error> |
| where |
| V: de::MapAccess<'de>, |
| { |
| let mvd = de::value::MapAccessDeserializer::new(map); |
| TomlWorkspaceField::deserialize(mvd).map(MaybeWorkspace::Workspace) |
| } |
| } |
| |
| deserializer.deserialize_any(Visitor) |
| } |
| |
| /// Enum that allows for the parsing of `field.workspace = true` in a Cargo.toml |
| /// |
| /// It allows for things to be inherited from a workspace or defined as needed |
| #[derive(Serialize, Clone, Debug)] |
| #[serde(untagged)] |
| pub enum MaybeWorkspace<T> { |
| Workspace(TomlWorkspaceField), |
| Defined(T), |
| } |
| |
| impl<'de, T: Deserialize<'de>> de::Deserialize<'de> for MaybeWorkspace<T> { |
| fn deserialize<D>(deserializer: D) -> Result<MaybeWorkspace<T>, D::Error> |
| where |
| D: de::Deserializer<'de>, |
| { |
| let value = serde_value::Value::deserialize(deserializer)?; |
| if let Ok(workspace) = TomlWorkspaceField::deserialize(serde_value::ValueDeserializer::< |
| D::Error, |
| >::new(value.clone())) |
| { |
| return Ok(MaybeWorkspace::Workspace(workspace)); |
| } |
| T::deserialize(serde_value::ValueDeserializer::<D::Error>::new(value)) |
| .map(MaybeWorkspace::Defined) |
| } |
| } |
| |
| impl<T> MaybeWorkspace<T> { |
| fn resolve<'a>( |
| self, |
| label: &str, |
| get_ws_field: impl FnOnce() -> CargoResult<T>, |
| ) -> CargoResult<T> { |
| match self { |
| MaybeWorkspace::Defined(value) => Ok(value), |
| MaybeWorkspace::Workspace(TomlWorkspaceField { workspace: true }) => get_ws_field() |
| .context(format!( |
| "error inheriting `{}` from workspace root manifest's `workspace.package.{}`", |
| label, label |
| )), |
| MaybeWorkspace::Workspace(TomlWorkspaceField { workspace: false }) => Err(anyhow!( |
| "`workspace=false` is unsupported for `package.{}`", |
| label, |
| )), |
| } |
| } |
| fn as_defined(&self) -> Option<&T> { |
| match self { |
| MaybeWorkspace::Workspace(_) => None, |
| MaybeWorkspace::Defined(defined) => Some(defined), |
| } |
| } |
| } |
| |
| #[derive(Deserialize, Serialize, Clone, Debug)] |
| pub struct TomlWorkspaceField { |
| workspace: bool, |
| } |
| |
| /// 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<MaybeWorkspace<String>>, |
| rust_version: Option<MaybeWorkspace<String>>, |
| name: InternedString, |
| #[serde(deserialize_with = "version_trim_whitespace")] |
| version: MaybeWorkspace<semver::Version>, |
| authors: Option<MaybeWorkspace<Vec<String>>>, |
| build: Option<StringOrBool>, |
| metabuild: Option<StringOrVec>, |
| #[serde(rename = "default-target")] |
| default_target: Option<String>, |
| #[serde(rename = "forced-target")] |
| forced_target: Option<String>, |
| links: Option<String>, |
| exclude: Option<MaybeWorkspace<Vec<String>>>, |
| include: Option<MaybeWorkspace<Vec<String>>>, |
| publish: Option<MaybeWorkspace<VecStringOrBool>>, |
| workspace: Option<String>, |
| im_a_teapot: Option<bool>, |
| autobins: Option<bool>, |
| autoexamples: Option<bool>, |
| autotests: Option<bool>, |
| autobenches: Option<bool>, |
| default_run: Option<String>, |
| |
| // Package metadata. |
| description: Option<MaybeWorkspace<String>>, |
| homepage: Option<MaybeWorkspace<String>>, |
| documentation: Option<MaybeWorkspace<String>>, |
| readme: Option<MaybeWorkspace<StringOrBool>>, |
| keywords: Option<MaybeWorkspace<Vec<String>>>, |
| categories: Option<MaybeWorkspace<Vec<String>>>, |
| license: Option<MaybeWorkspace<String>>, |
| license_file: Option<MaybeWorkspace<String>>, |
| repository: Option<MaybeWorkspace<String>>, |
| resolver: Option<String>, |
| |
| // Note that this field must come last due to the way toml serialization |
| // works which requires tables to be emitted after all values. |
| metadata: Option<toml::Value>, |
| } |
| |
| #[derive(Debug, Deserialize, Serialize, Clone)] |
| pub struct TomlWorkspace { |
| members: Option<Vec<String>>, |
| #[serde(rename = "default-members")] |
| default_members: Option<Vec<String>>, |
| exclude: Option<Vec<String>>, |
| resolver: Option<String>, |
| |
| // Properties that can be inherited by members. |
| package: Option<InheritableFields>, |
| dependencies: Option<BTreeMap<String, TomlDependency>>, |
| |
| // Note that this field must come last due to the way toml serialization |
| // works which requires tables to be emitted after all values. |
| metadata: Option<toml::Value>, |
| } |
| |
| /// A group of fields that are inheritable by members of the workspace |
| #[derive(Clone, Debug, Default, Deserialize, Serialize)] |
| 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<BTreeMap<String, TomlDependency>>, |
| version: Option<semver::Version>, |
| authors: Option<Vec<String>>, |
| description: Option<String>, |
| homepage: Option<String>, |
| documentation: Option<String>, |
| readme: Option<StringOrBool>, |
| keywords: Option<Vec<String>>, |
| categories: Option<Vec<String>>, |
| license: Option<String>, |
| #[serde(rename = "license-file")] |
| license_file: Option<String>, |
| repository: Option<String>, |
| publish: Option<VecStringOrBool>, |
| edition: Option<String>, |
| badges: Option<BTreeMap<String, BTreeMap<String, String>>>, |
| exclude: Option<Vec<String>>, |
| include: Option<Vec<String>>, |
| #[serde(rename = "rust-version")] |
| rust_version: Option<String>, |
| // 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, |
| } |
| |
| impl InheritableFields { |
| pub fn update_deps(&mut self, deps: Option<BTreeMap<String, TomlDependency>>) { |
| self.dependencies = deps; |
| } |
| |
| pub fn update_ws_path(&mut self, ws_root: PathBuf) { |
| self.ws_root = ws_root; |
| } |
| |
| pub fn dependencies(&self) -> CargoResult<BTreeMap<String, TomlDependency>> { |
| self.dependencies.clone().map_or( |
| Err(anyhow!("`workspace.dependencies` was not defined")), |
| |d| Ok(d), |
| ) |
| } |
| |
| pub fn get_dependency(&self, name: &str) -> CargoResult<TomlDependency> { |
| self.dependencies.clone().map_or( |
| Err(anyhow!("`workspace.dependencies` was not defined")), |
| |deps| { |
| deps.get(name).map_or( |
| Err(anyhow!( |
| "`dependency.{}` was not found in `workspace.dependencies`", |
| name |
| )), |
| |dep| Ok(dep.clone()), |
| ) |
| }, |
| ) |
| } |
| |
| pub fn version(&self) -> CargoResult<semver::Version> { |
| self.version.clone().map_or( |
| Err(anyhow!("`workspace.package.version` was not defined")), |
| |d| Ok(d), |
| ) |
| } |
| |
| pub fn authors(&self) -> CargoResult<Vec<String>> { |
| self.authors.clone().map_or( |
| Err(anyhow!("`workspace.package.authors` was not defined")), |
| |d| Ok(d), |
| ) |
| } |
| |
| pub fn description(&self) -> CargoResult<String> { |
| self.description.clone().map_or( |
| Err(anyhow!("`workspace.package.description` was not defined")), |
| |d| Ok(d), |
| ) |
| } |
| |
| pub fn homepage(&self) -> CargoResult<String> { |
| self.homepage.clone().map_or( |
| Err(anyhow!("`workspace.package.homepage` was not defined")), |
| |d| Ok(d), |
| ) |
| } |
| |
| pub fn documentation(&self) -> CargoResult<String> { |
| self.documentation.clone().map_or( |
| Err(anyhow!("`workspace.package.documentation` was not defined")), |
| |d| Ok(d), |
| ) |
| } |
| |
| pub fn readme(&self, package_root: &Path) -> CargoResult<StringOrBool> { |
| readme_for_package(self.ws_root.as_path(), self.readme.clone()).map_or( |
| Err(anyhow!("`workspace.package.readme` was not defined")), |
| |readme| { |
| let rel_path = |
| resolve_relative_path("readme", &self.ws_root, package_root, &readme)?; |
| Ok(StringOrBool::String(rel_path)) |
| }, |
| ) |
| } |
| |
| pub fn keywords(&self) -> CargoResult<Vec<String>> { |
| self.keywords.clone().map_or( |
| Err(anyhow!("`workspace.package.keywords` was not defined")), |
| |d| Ok(d), |
| ) |
| } |
| |
| pub fn categories(&self) -> CargoResult<Vec<String>> { |
| self.categories.clone().map_or( |
| Err(anyhow!("`workspace.package.categories` was not defined")), |
| |d| Ok(d), |
| ) |
| } |
| |
| pub fn license(&self) -> CargoResult<String> { |
| self.license.clone().map_or( |
| Err(anyhow!("`workspace.package.license` was not defined")), |
| |d| Ok(d), |
| ) |
| } |
| |
| pub fn license_file(&self, package_root: &Path) -> CargoResult<String> { |
| self.license_file.clone().map_or( |
| Err(anyhow!("`workspace.package.license_file` was not defined")), |
| |d| resolve_relative_path("license-file", &self.ws_root, package_root, &d), |
| ) |
| } |
| |
| pub fn repository(&self) -> CargoResult<String> { |
| self.repository.clone().map_or( |
| Err(anyhow!("`workspace.package.repository` was not defined")), |
| |d| Ok(d), |
| ) |
| } |
| |
| pub fn publish(&self) -> CargoResult<VecStringOrBool> { |
| self.publish.clone().map_or( |
| Err(anyhow!("`workspace.package.publish` was not defined")), |
| |d| Ok(d), |
| ) |
| } |
| |
| pub fn edition(&self) -> CargoResult<String> { |
| self.edition.clone().map_or( |
| Err(anyhow!("`workspace.package.edition` was not defined")), |
| |d| Ok(d), |
| ) |
| } |
| |
| pub fn rust_version(&self) -> CargoResult<String> { |
| self.rust_version.clone().map_or( |
| Err(anyhow!("`workspace.package.rust-version` was not defined")), |
| |d| Ok(d), |
| ) |
| } |
| |
| pub fn badges(&self) -> CargoResult<BTreeMap<String, BTreeMap<String, String>>> { |
| self.badges.clone().map_or( |
| Err(anyhow!("`workspace.package.badges` was not defined")), |
| |d| Ok(d), |
| ) |
| } |
| |
| pub fn exclude(&self) -> CargoResult<Vec<String>> { |
| self.exclude.clone().map_or( |
| Err(anyhow!("`workspace.package.exclude` was not defined")), |
| |d| Ok(d), |
| ) |
| } |
| |
| pub fn include(&self) -> CargoResult<Vec<String>> { |
| self.include.clone().map_or( |
| Err(anyhow!("`workspace.package.include` was not defined")), |
| |d| Ok(d), |
| ) |
| } |
| |
| pub fn ws_root(&self) -> &PathBuf { |
| &self.ws_root |
| } |
| } |
| |
| impl TomlPackage { |
| pub fn to_package_id( |
| &self, |
| source_id: SourceId, |
| version: semver::Version, |
| ) -> CargoResult<PackageId> { |
| PackageId::new(self.name, version, source_id) |
| } |
| } |
| |
| struct Context<'a, 'b> { |
| deps: &'a mut Vec<Dependency>, |
| source_id: SourceId, |
| nested_paths: &'a mut Vec<PathBuf>, |
| config: &'b Config, |
| warnings: &'a mut Vec<String>, |
| platform: Option<Platform>, |
| root: &'a Path, |
| features: &'a Features, |
| } |
| |
| impl TomlManifest { |
| /// Prepares the manifest for publishing. |
| // - Path and git components of dependency specifications are removed. |
| // - License path is updated to point within the package. |
| pub fn prepare_for_publish( |
| &self, |
| ws: &Workspace<'_>, |
| package_root: &Path, |
| ) -> CargoResult<TomlManifest> { |
| let config = ws.config(); |
| let mut package = self |
| .package |
| .as_ref() |
| .or_else(|| self.project.as_ref()) |
| .unwrap() |
| .clone(); |
| package.workspace = None; |
| let current_resolver = package |
| .resolver |
| .as_ref() |
| .map(|r| ResolveBehavior::from_manifest(r)) |
| .unwrap_or_else(|| { |
| package |
| .edition |
| .as_ref() |
| .and_then(|e| e.as_defined()) |
| .map(|e| Edition::from_str(e)) |
| .unwrap_or(Ok(Edition::Edition2015)) |
| .map(|e| e.default_resolve_behavior()) |
| })?; |
| if ws.resolve_behavior() != current_resolver { |
| // This ensures the published crate if built as a root (e.g. `cargo install`) will |
| // use the same resolver behavior it was tested with in the workspace. |
| // To avoid forcing a higher MSRV we don't explicitly set this if it would implicitly |
| // result in the same thing. |
| package.resolver = Some(ws.resolve_behavior().to_manifest()); |
| } |
| if let Some(license_file) = &package.license_file { |
| let license_file = license_file |
| .as_defined() |
| .context("license file should have been resolved before `prepare_for_publish()`")?; |
| let license_path = Path::new(&license_file); |
| let abs_license_path = paths::normalize_path(&package_root.join(license_path)); |
| 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( |
| license_path |
| .file_name() |
| .unwrap() |
| .to_str() |
| .unwrap() |
| .to_string(), |
| )); |
| } |
| } |
| |
| if let Some(readme) = &package.readme { |
| let readme = readme |
| .as_defined() |
| .context("readme should have been resolved before `prepare_for_publish()`")?; |
| match readme { |
| 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(), |
| ))); |
| } |
| } |
| StringOrBool::Bool(_) => {} |
| } |
| } |
| let all = |_d: &TomlDependency| true; |
| return Ok(TomlManifest { |
| package: Some(package), |
| project: None, |
| profile: self.profile.clone(), |
| lib: self.lib.clone(), |
| bin: self.bin.clone(), |
| example: self.example.clone(), |
| test: self.test.clone(), |
| bench: self.bench.clone(), |
| dependencies: map_deps(config, self.dependencies.as_ref(), all)?, |
| dev_dependencies: map_deps( |
| config, |
| self.dev_dependencies |
| .as_ref() |
| .or_else(|| self.dev_dependencies2.as_ref()), |
| TomlDependency::is_version_specified, |
| )?, |
| dev_dependencies2: None, |
| build_dependencies: map_deps( |
| config, |
| self.build_dependencies |
| .as_ref() |
| .or_else(|| self.build_dependencies2.as_ref()), |
| all, |
| )?, |
| build_dependencies2: None, |
| features: self.features.clone(), |
| target: match self.target.as_ref().map(|target_map| { |
| target_map |
| .iter() |
| .map(|(k, v)| { |
| Ok(( |
| k.clone(), |
| 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, |
| )?, |
| dev_dependencies2: None, |
| build_dependencies: map_deps( |
| config, |
| v.build_dependencies |
| .as_ref() |
| .or_else(|| v.build_dependencies2.as_ref()), |
| all, |
| )?, |
| build_dependencies2: None, |
| }, |
| )) |
| }) |
| .collect() |
| }) { |
| Some(Ok(v)) => Some(v), |
| Some(Err(e)) => return Err(e), |
| None => None, |
| }, |
| replace: None, |
| patch: None, |
| workspace: None, |
| badges: self.badges.clone(), |
| cargo_features: self.cargo_features.clone(), |
| }); |
| |
| fn map_deps( |
| config: &Config, |
| deps: Option<&BTreeMap<String, TomlDependency>>, |
| filter: impl Fn(&TomlDependency) -> bool, |
| ) -> CargoResult<Option<BTreeMap<String, TomlDependency>>> { |
| let deps = match deps { |
| Some(deps) => deps, |
| None => return Ok(None), |
| }; |
| let deps = deps |
| .iter() |
| .filter(|(_k, v)| filter(v)) |
| .map(|(k, v)| Ok((k.clone(), map_dependency(config, v)?))) |
| .collect::<CargoResult<BTreeMap<_, _>>>()?; |
| Ok(Some(deps)) |
| } |
| |
| fn map_dependency(config: &Config, dep: &TomlDependency) -> CargoResult<TomlDependency> { |
| match dep { |
| TomlDependency::Detailed(d) => { |
| let mut d = d.clone(); |
| // Path dependencies become crates.io deps. |
| d.path.take(); |
| // Same with git dependencies. |
| d.git.take(); |
| d.branch.take(); |
| d.tag.take(); |
| d.rev.take(); |
| // registry specifications are elaborated to the index URL |
| if let Some(registry) = d.registry.take() { |
| d.registry_index = Some(config.get_registry_index(®istry)?.to_string()); |
| } |
| Ok(TomlDependency::Detailed(d)) |
| } |
| TomlDependency::Simple(s) => Ok(TomlDependency::Detailed(DetailedTomlDependency { |
| version: Some(s.clone()), |
| ..Default::default() |
| })), |
| // Unreachable as we resolve everything before this |
| TomlDependency::Workspace(_) => unreachable!(), |
| } |
| } |
| } |
| |
| pub fn to_real_manifest( |
| me: &Rc<TomlManifest>, |
| source_id: SourceId, |
| package_root: &Path, |
| config: &Config, |
| ) -> CargoResult<(Manifest, Vec<PathBuf>)> { |
| fn get_ws( |
| config: &Config, |
| resolved_path: &Path, |
| workspace_config: &WorkspaceConfig, |
| ) -> CargoResult<InheritableFields> { |
| match workspace_config { |
| WorkspaceConfig::Root(root) => Ok(root.inheritable().clone()), |
| WorkspaceConfig::Member { |
| root: Some(ref path_to_root), |
| } => { |
| let path = resolved_path |
| .parent() |
| .unwrap() |
| .join(path_to_root) |
| .join("Cargo.toml"); |
| let root_path = paths::normalize_path(&path); |
| inheritable_from_path(config, root_path) |
| } |
| WorkspaceConfig::Member { root: None } => { |
| match find_workspace_root(&resolved_path, config)? { |
| Some(path_to_root) => inheritable_from_path(config, path_to_root), |
| None => Err(anyhow!("failed to find a workspace root")), |
| } |
| } |
| } |
| } |
| |
| let mut nested_paths = vec![]; |
| let mut warnings = vec![]; |
| let mut errors = vec![]; |
| |
| // Parse features first so they will be available when parsing other parts of the TOML. |
| let empty = Vec::new(); |
| let cargo_features = me.cargo_features.as_ref().unwrap_or(&empty); |
| let features = Features::new(cargo_features, config, &mut warnings, source_id.is_path())?; |
| |
| let mut package = match (&me.package, &me.project) { |
| (Some(_), Some(project)) => { |
| if source_id.is_path() { |
| config.shell().warn(format!( |
| "manifest at `{}` contains both `project` and `package`, \ |
| this could become a hard error in the future", |
| package_root.display() |
| ))?; |
| } |
| project.clone() |
| } |
| (Some(package), None) => package.clone(), |
| (None, Some(project)) => { |
| if source_id.is_path() { |
| config.shell().warn(format!( |
| "manifest at `{}` contains `[project]` instead of `[package]`, \ |
| this could become a hard error in the future", |
| package_root.display() |
| ))?; |
| } |
| project.clone() |
| } |
| (None, None) => bail!("no `package` section found"), |
| }; |
| |
| let workspace_config = match (me.workspace.as_ref(), package.workspace.as_ref()) { |
| (Some(toml_config), None) => { |
| let mut inheritable = toml_config.package.clone().unwrap_or_default(); |
| inheritable.update_ws_path(package_root.to_path_buf()); |
| inheritable.update_deps(toml_config.dependencies.clone()); |
| let ws_root_config = WorkspaceRootConfig::new( |
| package_root, |
| &toml_config.members, |
| &toml_config.default_members, |
| &toml_config.exclude, |
| &Some(inheritable), |
| &toml_config.metadata, |
| ); |
| config |
| .ws_roots |
| .borrow_mut() |
| .insert(package_root.to_path_buf(), ws_root_config.clone()); |
| WorkspaceConfig::Root(ws_root_config) |
| } |
| (None, root) => WorkspaceConfig::Member { |
| root: root.cloned(), |
| }, |
| (Some(..), Some(..)) => bail!( |
| "cannot configure both `package.workspace` and \ |
| `[workspace]`, only one can be specified" |
| ), |
| }; |
| |
| let package_name = package.name.trim(); |
| if package_name.is_empty() { |
| bail!("package name cannot be an empty string") |
| } |
| |
| validate_package_name(package_name, "package name", "")?; |
| |
| let resolved_path = package_root.join("Cargo.toml"); |
| |
| let inherit_cell: LazyCell<InheritableFields> = LazyCell::new(); |
| let inherit = |
| || inherit_cell.try_borrow_with(|| get_ws(config, &resolved_path, &workspace_config)); |
| |
| let version = package |
| .version |
| .clone() |
| .resolve("version", || inherit()?.version())?; |
| |
| package.version = MaybeWorkspace::Defined(version.clone()); |
| |
| let pkgid = package.to_package_id(source_id, version)?; |
| |
| let edition = if let Some(edition) = package.edition.clone() { |
| let edition: Edition = edition |
| .resolve("edition", || inherit()?.edition())? |
| .parse() |
| .with_context(|| "failed to parse the `edition` key")?; |
| package.edition = Some(MaybeWorkspace::Defined(edition.to_string())); |
| edition |
| } else { |
| Edition::Edition2015 |
| }; |
| // Add these lines if start a new unstable edition. |
| // ``` |
| // if edition == Edition::Edition20xx { |
| // features.require(Feature::edition20xx))?; |
| // } |
| // ``` |
| if !edition.is_stable() { |
| // Guard in case someone forgets to add .require() |
| return Err(util::errors::internal(format!( |
| "edition {} should be gated", |
| edition |
| ))); |
| } |
| |
| let rust_version = if let Some(rust_version) = &package.rust_version { |
| let rust_version = rust_version |
| .clone() |
| .resolve("rust_version", || inherit()?.rust_version())?; |
| let req = match semver::VersionReq::parse(&rust_version) { |
| // Exclude semver operators like `^` and pre-release identifiers |
| Ok(req) if rust_version.chars().all(|c| c.is_ascii_digit() || c == '.') => req, |
| _ => bail!("`rust-version` must be a value like \"1.32\""), |
| }; |
| if let Some(first_version) = edition.first_version() { |
| let unsupported = |
| semver::Version::new(first_version.major, first_version.minor - 1, 9999); |
| if req.matches(&unsupported) { |
| bail!( |
| "rust-version {} is older than first version ({}) required by \ |
| the specified edition ({})", |
| rust_version, |
| first_version, |
| edition, |
| ) |
| } |
| } |
| Some(rust_version.clone()) |
| } else { |
| None |
| }; |
| |
| if package.metabuild.is_some() { |
| features.require(Feature::metabuild())?; |
| } |
| |
| let resolve_behavior = match ( |
| package.resolver.as_ref(), |
| me.workspace.as_ref().and_then(|ws| ws.resolver.as_ref()), |
| ) { |
| (None, None) => None, |
| (Some(s), None) | (None, Some(s)) => Some(ResolveBehavior::from_manifest(s)?), |
| (Some(_), Some(_)) => { |
| bail!("cannot specify `resolver` field in both `[workspace]` and `[package]`") |
| } |
| }; |
| |
| // If we have no lib at all, use the inferred lib, if available. |
| // If we have a lib with a path, we're done. |
| // If we have a lib with no path, use the inferred lib or else the package name. |
| let targets = targets( |
| &features, |
| me, |
| package_name, |
| package_root, |
| edition, |
| &package.build, |
| &package.metabuild, |
| &mut warnings, |
| &mut errors, |
| )?; |
| |
| if targets.is_empty() { |
| debug!("manifest has no build targets"); |
| } |
| |
| if let Err(e) = unique_build_targets(&targets, package_root) { |
| warnings.push(format!( |
| "file found to be present in multiple \ |
| build targets: {}", |
| e |
| )); |
| } |
| |
| if let Some(links) = &package.links { |
| if !targets.iter().any(|t| t.is_custom_build()) { |
| bail!( |
| "package `{}` specifies that it links to `{}` but does not \ |
| have a custom build script", |
| pkgid, |
| links |
| ) |
| } |
| } |
| |
| let mut deps = Vec::new(); |
| |
| let mut cx = Context { |
| deps: &mut deps, |
| source_id, |
| nested_paths: &mut nested_paths, |
| config, |
| warnings: &mut warnings, |
| features: &features, |
| platform: None, |
| root: package_root, |
| }; |
| |
| fn process_dependencies( |
| cx: &mut Context<'_, '_>, |
| new_deps: Option<&BTreeMap<String, TomlDependency>>, |
| kind: Option<DepKind>, |
| workspace_config: &WorkspaceConfig, |
| inherit_cell: &LazyCell<InheritableFields>, |
| ) -> CargoResult<Option<BTreeMap<String, TomlDependency>>> { |
| let dependencies = match new_deps { |
| Some(dependencies) => dependencies, |
| None => return Ok(None), |
| }; |
| |
| let inherit = || { |
| inherit_cell.try_borrow_with(|| { |
| get_ws(cx.config, &cx.root.join("Cargo.toml"), &workspace_config) |
| }) |
| }; |
| |
| let mut deps: BTreeMap<String, TomlDependency> = BTreeMap::new(); |
| for (n, v) in dependencies.iter() { |
| let resolved = v.clone().resolve(n, cx, || inherit())?; |
| let dep = resolved.to_dependency(n, cx, kind)?; |
| validate_package_name(dep.name_in_toml().as_str(), "dependency name", "")?; |
| cx.deps.push(dep); |
| deps.insert(n.to_string(), resolved.clone()); |
| } |
| Ok(Some(deps)) |
| } |
| |
| // Collect the dependencies. |
| let dependencies = process_dependencies( |
| &mut cx, |
| me.dependencies.as_ref(), |
| None, |
| &workspace_config, |
| &inherit_cell, |
| )?; |
| if me.dev_dependencies.is_some() && me.dev_dependencies2.is_some() { |
| warn_on_deprecated("dev-dependencies", package_name, "package", cx.warnings); |
| } |
| let dev_deps = me |
| .dev_dependencies |
| .as_ref() |
| .or_else(|| me.dev_dependencies2.as_ref()); |
| let dev_deps = process_dependencies( |
| &mut cx, |
| dev_deps, |
| Some(DepKind::Development), |
| &workspace_config, |
| &inherit_cell, |
| )?; |
| if me.build_dependencies.is_some() && me.build_dependencies2.is_some() { |
| warn_on_deprecated("build-dependencies", package_name, "package", cx.warnings); |
| } |
| let build_deps = me |
| .build_dependencies |
| .as_ref() |
| .or_else(|| me.build_dependencies2.as_ref()); |
| let build_deps = process_dependencies( |
| &mut cx, |
| build_deps, |
| Some(DepKind::Build), |
| &workspace_config, |
| &inherit_cell, |
| )?; |
| |
| let mut target: BTreeMap<String, TomlPlatform> = BTreeMap::new(); |
| for (name, platform) in me.target.iter().flatten() { |
| cx.platform = { |
| let platform: Platform = name.parse()?; |
| platform.check_cfg_attributes(cx.warnings); |
| Some(platform) |
| }; |
| let deps = process_dependencies( |
| &mut cx, |
| platform.dependencies.as_ref(), |
| None, |
| &workspace_config, |
| &inherit_cell, |
| ) |
| .unwrap(); |
| if platform.build_dependencies.is_some() && platform.build_dependencies2.is_some() { |
| warn_on_deprecated("build-dependencies", name, "platform target", cx.warnings); |
| } |
| let build_deps = platform |
| .build_dependencies |
| .as_ref() |
| .or_else(|| platform.build_dependencies2.as_ref()); |
| let build_deps = process_dependencies( |
| &mut cx, |
| build_deps, |
| Some(DepKind::Build), |
| &workspace_config, |
| &inherit_cell, |
| ) |
| .unwrap(); |
| if platform.dev_dependencies.is_some() && platform.dev_dependencies2.is_some() { |
| warn_on_deprecated("dev-dependencies", name, "platform target", cx.warnings); |
| } |
| let dev_deps = platform |
| .dev_dependencies |
| .as_ref() |
| .or_else(|| platform.dev_dependencies2.as_ref()); |
| let dev_deps = process_dependencies( |
| &mut cx, |
| dev_deps, |
| Some(DepKind::Development), |
| &workspace_config, |
| &inherit_cell, |
| ) |
| .unwrap(); |
| target.insert( |
| name.clone(), |
| TomlPlatform { |
| dependencies: deps, |
| build_dependencies: build_deps, |
| build_dependencies2: None, |
| dev_dependencies: dev_deps, |
| dev_dependencies2: None, |
| }, |
| ); |
| } |
| |
| let target = if target.is_empty() { |
| None |
| } else { |
| Some(target) |
| }; |
| let replace = me.replace(&mut cx)?; |
| let patch = me.patch(&mut cx)?; |
| |
| { |
| let mut names_sources = BTreeMap::new(); |
| for dep in &deps { |
| let name = dep.name_in_toml(); |
| let prev = names_sources.insert(name.to_string(), dep.source_id()); |
| if prev.is_some() && prev != Some(dep.source_id()) { |
| bail!( |
| "Dependency '{}' has different source paths depending on the build \ |
| target. Each dependency must have a single canonical source path \ |
| irrespective of build target.", |
| name |
| ); |
| } |
| } |
| } |
| |
| let exclude = package |
| .exclude |
| .clone() |
| .map(|mw| mw.resolve("exclude", || inherit()?.exclude())) |
| .transpose()? |
| .unwrap_or_default(); |
| let include = package |
| .include |
| .clone() |
| .map(|mw| mw.resolve("include", || inherit()?.include())) |
| .transpose()? |
| .unwrap_or_default(); |
| let empty_features = BTreeMap::new(); |
| |
| let summary = Summary::new( |
| config, |
| pkgid, |
| deps, |
| me.features.as_ref().unwrap_or(&empty_features), |
| package.links.as_deref(), |
| )?; |
| |
| let metadata = ManifestMetadata { |
| description: package |
| .description |
| .clone() |
| .map(|mw| mw.resolve("description", || inherit()?.description())) |
| .transpose()?, |
| homepage: package |
| .homepage |
| .clone() |
| .map(|mw| mw.resolve("homepage", || inherit()?.homepage())) |
| .transpose()?, |
| documentation: package |
| .documentation |
| .clone() |
| .map(|mw| mw.resolve("documentation", || inherit()?.documentation())) |
| .transpose()?, |
| readme: readme_for_package( |
| package_root, |
| package |
| .readme |
| .clone() |
| .map(|mw| mw.resolve("readme", || inherit()?.readme(package_root))) |
| .transpose()?, |
| ), |
| authors: package |
| .authors |
| .clone() |
| .map(|mw| mw.resolve("authors", || inherit()?.authors())) |
| .transpose()? |
| .unwrap_or_default(), |
| license: package |
| .license |
| .clone() |
| .map(|mw| mw.resolve("license", || inherit()?.license())) |
| .transpose()?, |
| license_file: package |
| .license_file |
| .clone() |
| .map(|mw| mw.resolve("license", || inherit()?.license_file(package_root))) |
| .transpose()?, |
| repository: package |
| .repository |
| .clone() |
| .map(|mw| mw.resolve("repository", || inherit()?.repository())) |
| .transpose()?, |
| keywords: package |
| .keywords |
| .clone() |
| .map(|mw| mw.resolve("keywords", || inherit()?.keywords())) |
| .transpose()? |
| .unwrap_or_default(), |
| categories: package |
| .categories |
| .clone() |
| .map(|mw| mw.resolve("categories", || inherit()?.categories())) |
| .transpose()? |
| .unwrap_or_default(), |
| badges: me |
| .badges |
| .clone() |
| .map(|mw| mw.resolve("badges", || inherit()?.badges())) |
| .transpose()? |
| .unwrap_or_default(), |
| links: package.links.clone(), |
| }; |
| package.description = metadata |
| .description |
| .clone() |
| .map(|description| MaybeWorkspace::Defined(description)); |
| package.homepage = metadata |
| .homepage |
| .clone() |
| .map(|homepage| MaybeWorkspace::Defined(homepage)); |
| package.documentation = metadata |
| .documentation |
| .clone() |
| .map(|documentation| MaybeWorkspace::Defined(documentation)); |
| package.readme = metadata |
| .readme |
| .clone() |
| .map(|readme| MaybeWorkspace::Defined(StringOrBool::String(readme))); |
| package.authors = package |
| .authors |
| .as_ref() |
| .map(|_| MaybeWorkspace::Defined(metadata.authors.clone())); |
| package.license = metadata |
| .license |
| .clone() |
| .map(|license| MaybeWorkspace::Defined(license)); |
| package.license_file = metadata |
| .license_file |
| .clone() |
| .map(|license_file| MaybeWorkspace::Defined(license_file)); |
| package.repository = metadata |
| .repository |
| .clone() |
| .map(|repository| MaybeWorkspace::Defined(repository)); |
| package.keywords = package |
| .keywords |
| .as_ref() |
| .map(|_| 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)); |
| package.exclude = package |
| .exclude |
| .as_ref() |
| .map(|_| MaybeWorkspace::Defined(exclude.clone())); |
| package.include = package |
| .include |
| .as_ref() |
| .map(|_| MaybeWorkspace::Defined(include.clone())); |
| |
| let profiles = me.profile.clone(); |
| if let Some(profiles) = &profiles { |
| profiles.validate(&features, &mut warnings)?; |
| } |
| |
| let publish = package |
| .publish |
| .clone() |
| .map(|publish| publish.resolve("publish", || inherit()?.publish()).unwrap()); |
| |
| package.publish = publish.clone().map(|p| MaybeWorkspace::Defined(p)); |
| |
| let publish = match publish { |
| Some(VecStringOrBool::VecString(ref vecstring)) => Some(vecstring.clone()), |
| Some(VecStringOrBool::Bool(false)) => Some(vec![]), |
| None | Some(VecStringOrBool::Bool(true)) => None, |
| }; |
| |
| if summary.features().contains_key("default-features") { |
| warnings.push( |
| "`default-features = [\"..\"]` was found in [features]. \ |
| Did you mean to use `default = [\"..\"]`?" |
| .to_string(), |
| ) |
| } |
| |
| if let Some(run) = &package.default_run { |
| if !targets |
| .iter() |
| .filter(|t| t.is_bin()) |
| .any(|t| t.name() == run) |
| { |
| let suggestion = |
| util::closest_msg(run, targets.iter().filter(|t| t.is_bin()), |t| t.name()); |
| bail!("default-run target `{}` not found{}", run, suggestion); |
| } |
| } |
| |
| let default_kind = package |
| .default_target |
| .as_ref() |
| .map(|t| CompileTarget::new(&*t)) |
| .transpose()? |
| .map(CompileKind::Target); |
| let forced_kind = package |
| .forced_target |
| .as_ref() |
| .map(|t| CompileTarget::new(&*t)) |
| .transpose()? |
| .map(CompileKind::Target); |
| let custom_metadata = package.metadata.clone(); |
| let resolved_toml = TomlManifest { |
| cargo_features: me.cargo_features.clone(), |
| package: Some(package.clone()), |
| project: None, |
| profile: me.profile.clone(), |
| lib: me.lib.clone(), |
| bin: me.bin.clone(), |
| example: me.example.clone(), |
| test: me.test.clone(), |
| bench: me.bench.clone(), |
| dependencies, |
| dev_dependencies: dev_deps, |
| dev_dependencies2: None, |
| build_dependencies: build_deps, |
| build_dependencies2: None, |
| features: me.features.clone(), |
| target, |
| replace: me.replace.clone(), |
| patch: me.patch.clone(), |
| workspace: me.workspace.clone(), |
| badges: me |
| .badges |
| .as_ref() |
| .map(|_| MaybeWorkspace::Defined(metadata.badges.clone())), |
| }; |
| let mut manifest = Manifest::new( |
| summary, |
| default_kind, |
| forced_kind, |
| targets, |
| exclude, |
| include, |
| package.links.clone(), |
| metadata, |
| custom_metadata, |
| profiles, |
| publish, |
| replace, |
| patch, |
| workspace_config, |
| features, |
| edition, |
| rust_version, |
| package.im_a_teapot, |
| package.default_run.clone(), |
| Rc::new(resolved_toml), |
| package.metabuild.clone().map(|sov| sov.0), |
| resolve_behavior, |
| ); |
| if package.license_file.is_some() && package.license.is_some() { |
| manifest.warnings_mut().add_warning( |
| "only one of `license` or `license-file` is necessary\n\ |
| `license` should be used if the package license can be expressed \ |
| with a standard SPDX expression.\n\ |
| `license-file` should be used if the package uses a non-standard license.\n\ |
| See https://doc.rust-lang.org/cargo/reference/manifest.html#the-license-and-license-file-fields \ |
| for more information." |
| .to_string(), |
| ); |
| } |
| for warning in warnings { |
| manifest.warnings_mut().add_warning(warning); |
| } |
| for error in errors { |
| manifest.warnings_mut().add_critical_warning(error); |
| } |
| |
| manifest.feature_gate()?; |
| |
| Ok((manifest, nested_paths)) |
| } |
| |
| fn to_virtual_manifest( |
| me: &Rc<TomlManifest>, |
| source_id: SourceId, |
| root: &Path, |
| config: &Config, |
| ) -> CargoResult<(VirtualManifest, Vec<PathBuf>)> { |
| if me.project.is_some() { |
| bail!("this virtual manifest specifies a [project] section, which is not allowed"); |
| } |
| if me.package.is_some() { |
| bail!("this virtual manifest specifies a [package] section, which is not allowed"); |
| } |
| if me.lib.is_some() { |
| bail!("this virtual manifest specifies a [lib] section, which is not allowed"); |
| } |
| if me.bin.is_some() { |
| bail!("this virtual manifest specifies a [[bin]] section, which is not allowed"); |
| } |
| if me.example.is_some() { |
| bail!("this virtual manifest specifies a [[example]] section, which is not allowed"); |
| } |
| if me.test.is_some() { |
| bail!("this virtual manifest specifies a [[test]] section, which is not allowed"); |
| } |
| if me.bench.is_some() { |
| bail!("this virtual manifest specifies a [[bench]] section, which is not allowed"); |
| } |
| if me.dependencies.is_some() { |
| bail!("this virtual manifest specifies a [dependencies] section, which is not allowed"); |
| } |
| if me.dev_dependencies.is_some() || me.dev_dependencies2.is_some() { |
| bail!("this virtual manifest specifies a [dev-dependencies] section, which is not allowed"); |
| } |
| if me.build_dependencies.is_some() || me.build_dependencies2.is_some() { |
| bail!("this virtual manifest specifies a [build-dependencies] section, which is not allowed"); |
| } |
| if me.features.is_some() { |
| bail!("this virtual manifest specifies a [features] section, which is not allowed"); |
| } |
| if me.target.is_some() { |
| bail!("this virtual manifest specifies a [target] section, which is not allowed"); |
| } |
| if me.badges.is_some() { |
| bail!("this virtual manifest specifies a [badges] section, which is not allowed"); |
| } |
| |
| let mut nested_paths = Vec::new(); |
| let mut warnings = Vec::new(); |
| let mut deps = Vec::new(); |
| let empty = Vec::new(); |
| let cargo_features = me.cargo_features.as_ref().unwrap_or(&empty); |
| let features = Features::new(cargo_features, config, &mut warnings, source_id.is_path())?; |
| |
| let (replace, patch) = { |
| let mut cx = Context { |
| deps: &mut deps, |
| source_id, |
| nested_paths: &mut nested_paths, |
| config, |
| warnings: &mut warnings, |
| platform: None, |
| features: &features, |
| root, |
| }; |
| (me.replace(&mut cx)?, me.patch(&mut cx)?) |
| }; |
| let profiles = me.profile.clone(); |
| if let Some(profiles) = &profiles { |
| profiles.validate(&features, &mut warnings)?; |
| } |
| let resolve_behavior = me |
| .workspace |
| .as_ref() |
| .and_then(|ws| ws.resolver.as_deref()) |
| .map(|r| ResolveBehavior::from_manifest(r)) |
| .transpose()?; |
| let workspace_config = match me.workspace { |
| Some(ref toml_config) => { |
| let mut inheritable = toml_config.package.clone().unwrap_or_default(); |
| inheritable.update_ws_path(root.to_path_buf()); |
| inheritable.update_deps(toml_config.dependencies.clone()); |
| let ws_root_config = WorkspaceRootConfig::new( |
| root, |
| &toml_config.members, |
| &toml_config.default_members, |
| &toml_config.exclude, |
| &Some(inheritable), |
| &toml_config.metadata, |
| ); |
| config |
| .ws_roots |
| .borrow_mut() |
| .insert(root.to_path_buf(), ws_root_config.clone()); |
| WorkspaceConfig::Root(ws_root_config) |
| } |
| None => { |
| bail!("virtual manifests must be configured with [workspace]"); |
| } |
| }; |
| Ok(( |
| VirtualManifest::new( |
| replace, |
| patch, |
| workspace_config, |
| profiles, |
| features, |
| resolve_behavior, |
| ), |
| nested_paths, |
| )) |
| } |
| |
| fn replace(&self, cx: &mut Context<'_, '_>) -> CargoResult<Vec<(PackageIdSpec, Dependency)>> { |
| if self.patch.is_some() && self.replace.is_some() { |
| bail!("cannot specify both [replace] and [patch]"); |
| } |
| let mut replace = Vec::new(); |
| for (spec, replacement) in self.replace.iter().flatten() { |
| let mut spec = PackageIdSpec::parse(spec).with_context(|| { |
| format!( |
| "replacements must specify a valid semver \ |
| version to replace, but `{}` does not", |
| spec |
| ) |
| })?; |
| if spec.url().is_none() { |
| spec.set_url(CRATES_IO_INDEX.parse().unwrap()); |
| } |
| |
| if replacement.is_version_specified() { |
| bail!( |
| "replacements cannot specify a version \ |
| requirement, but found one for `{}`", |
| spec |
| ); |
| } |
| |
| let mut dep = replacement.to_dependency(spec.name().as_str(), cx, None)?; |
| let version = spec.version().ok_or_else(|| { |
| anyhow!( |
| "replacements must specify a version \ |
| to replace, but `{}` does not", |
| spec |
| ) |
| })?; |
| dep.set_version_req(VersionReq::exact(version)) |
| .lock_version(version); |
| replace.push((spec, dep)); |
| } |
| Ok(replace) |
| } |
| |
| fn patch(&self, cx: &mut Context<'_, '_>) -> CargoResult<HashMap<Url, Vec<Dependency>>> { |
| let mut patch = HashMap::new(); |
| for (url, deps) in self.patch.iter().flatten() { |
| let url = match &url[..] { |
| CRATES_IO_REGISTRY => CRATES_IO_INDEX.parse().unwrap(), |
| _ => cx |
| .config |
| .get_registry_index(url) |
| .or_else(|_| url.into_url()) |
| .with_context(|| { |
| format!("[patch] entry `{}` should be a URL or registry name", url) |
| })?, |
| }; |
| patch.insert( |
| url, |
| deps.iter() |
| .map(|(name, dep)| dep.to_dependency(name, cx, None)) |
| .collect::<CargoResult<Vec<_>>>()?, |
| ); |
| } |
| Ok(patch) |
| } |
| |
| /// Returns the path to the build script if one exists for this crate. |
| fn maybe_custom_build( |
| &self, |
| build: &Option<StringOrBool>, |
| package_root: &Path, |
| ) -> Option<PathBuf> { |
| 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)), |
| None => { |
| // If there is a `build.rs` file next to the `Cargo.toml`, assume it is |
| // a build script. |
| if build_rs.is_file() { |
| Some(build_rs) |
| } else { |
| None |
| } |
| } |
| } |
| } |
| |
| pub fn has_profiles(&self) -> bool { |
| self.profile.is_some() |
| } |
| |
| pub fn features(&self) -> Option<&BTreeMap<InternedString, Vec<InternedString>>> { |
| self.features.as_ref() |
| } |
| } |
| |
| fn inheritable_from_path( |
| config: &Config, |
| workspace_path: PathBuf, |
| ) -> CargoResult<InheritableFields> { |
| // Workspace path should have Cargo.toml at the end |
| let workspace_path_root = workspace_path.parent().unwrap(); |
| |
| // Let the borrow exit scope so that it can be picked up if there is a need to |
| // read a manifest |
| if let Some(ws_root) = config.ws_roots.borrow().get(workspace_path_root) { |
| return Ok(ws_root.inheritable().clone()); |
| }; |
| |
| let source_id = SourceId::for_path(workspace_path_root)?; |
| let (man, _) = read_manifest(&workspace_path, source_id, config)?; |
| match man.workspace_config() { |
| WorkspaceConfig::Root(root) => { |
| config |
| .ws_roots |
| .borrow_mut() |
| .insert(workspace_path, root.clone()); |
| Ok(root.inheritable().clone()) |
| } |
| _ => bail!( |
| "root of a workspace inferred but wasn't a root: {}", |
| workspace_path.display() |
| ), |
| } |
| } |
| |
| /// Returns the name of the README file for a [`TomlPackage`]. |
| pub fn readme_for_package(package_root: &Path, readme: Option<StringOrBool>) -> Option<String> { |
| 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()), |
| }, |
| } |
| } |
| |
| const DEFAULT_README_FILES: [&str; 3] = ["README.md", "README.txt", "README"]; |
| |
| /// Checks if a file with any of the default README file names exists in the package root. |
| /// If so, returns a `String` representing that name. |
| fn default_readme_from_package_root(package_root: &Path) -> Option<String> { |
| for &readme_filename in DEFAULT_README_FILES.iter() { |
| if package_root.join(readme_filename).is_file() { |
| return Some(readme_filename.to_string()); |
| } |
| } |
| |
| None |
| } |
| |
| /// Checks a list of build targets, and ensures the target names are unique within a vector. |
| /// If not, the name of the offending build target is returned. |
| fn unique_build_targets(targets: &[Target], package_root: &Path) -> Result<(), String> { |
| let mut seen = HashSet::new(); |
| for target in targets { |
| if let TargetSourcePath::Path(path) = target.src_path() { |
| let full = package_root.join(path); |
| if !seen.insert(full.clone()) { |
| return Err(full.display().to_string()); |
| } |
| } |
| } |
| Ok(()) |
| } |
| |
| impl<P: ResolveToPath + Clone> TomlDependency<P> { |
| pub(crate) fn to_dependency_split( |
| &self, |
| name: &str, |
| source_id: SourceId, |
| nested_paths: &mut Vec<PathBuf>, |
| config: &Config, |
| warnings: &mut Vec<String>, |
| platform: Option<Platform>, |
| root: &Path, |
| features: &Features, |
| kind: Option<DepKind>, |
| ) -> CargoResult<Dependency> { |
| self.to_dependency( |
| name, |
| &mut Context { |
| deps: &mut Vec::new(), |
| source_id, |
| nested_paths, |
| config, |
| warnings, |
| platform, |
| root, |
| features, |
| }, |
| kind, |
| ) |
| } |
| |
| fn to_dependency( |
| &self, |
| name: &str, |
| cx: &mut Context<'_, '_>, |
| kind: Option<DepKind>, |
| ) -> CargoResult<Dependency> { |
| match *self { |
| TomlDependency::Simple(ref version) => DetailedTomlDependency::<P> { |
| version: Some(version.clone()), |
| ..Default::default() |
| } |
| .to_dependency(name, cx, kind), |
| TomlDependency::Detailed(ref details) => details.to_dependency(name, cx, kind), |
| TomlDependency::Workspace(_) => unreachable!(), |
| } |
| } |
| |
| fn is_version_specified(&self) -> bool { |
| match self { |
| TomlDependency::Detailed(d) => d.version.is_some(), |
| TomlDependency::Simple(..) => true, |
| TomlDependency::Workspace(_) => unreachable!(), |
| } |
| } |
| |
| fn is_optional(&self) -> bool { |
| match self { |
| TomlDependency::Detailed(d) => d.optional.unwrap_or(false), |
| TomlDependency::Simple(..) => false, |
| TomlDependency::Workspace(w) => w.optional.unwrap_or(false), |
| } |
| } |
| } |
| |
| impl TomlDependency { |
| fn resolve<'a>( |
| self, |
| label: &str, |
| cx: &mut Context<'_, '_>, |
| get_inheritable: impl FnOnce() -> CargoResult<&'a InheritableFields>, |
| ) -> CargoResult<TomlDependency> { |
| match self { |
| TomlDependency::Detailed(d) => Ok(TomlDependency::Detailed(d)), |
| TomlDependency::Simple(s) => Ok(TomlDependency::Simple(s)), |
| TomlDependency::Workspace(TomlWorkspaceDependency { |
| workspace: true, |
| features, |
| optional, |
| }) => { |
| let inheritable = get_inheritable()?; |
| inheritable.get_dependency(label).context(format!( |
| "error reading `dependencies.{}` from workspace root manifest's `workspace.dependencies.{}`", |
| label, label |
| )).map(|dep| { |
| match dep { |
| TomlDependency::Simple(s) => { |
| if optional.is_some() || features.is_some() { |
| Ok(TomlDependency::Detailed(DetailedTomlDependency { |
| version: Some(s), |
| optional, |
| features, |
| ..Default::default() |
| })) |
| } else { |
| Ok(TomlDependency::Simple(s)) |
| } |
| }, |
| TomlDependency::Detailed(d) => { |
| let mut dep = d.clone(); |
| dep.add_features(features); |
| dep.update_optional(optional); |
| dep.resolve_path(label,inheritable.ws_root(), cx.root)?; |
| Ok(TomlDependency::Detailed(dep)) |
| }, |
| TomlDependency::Workspace(_) => { |
| unreachable!( |
| "We check that no workspace defines dependencies with \ |
| `{{ workspace = true }}` when we read a manifest from a string. \ |
| this should not happen but did on {}", |
| label |
| ) |
| }, |
| } |
| })? |
| } |
| TomlDependency::Workspace(TomlWorkspaceDependency { |
| workspace: false, .. |
| }) => Err(anyhow!( |
| "`workspace=false` is unsupported for `package.dependencies.{}`", |
| label, |
| )), |
| } |
| } |
| } |
| |
| impl<P: ResolveToPath + Clone> DetailedTomlDependency<P> { |
| fn to_dependency( |
| &self, |
| name_in_toml: &str, |
| cx: &mut Context<'_, '_>, |
| kind: Option<DepKind>, |
| ) -> CargoResult<Dependency> { |
| if self.version.is_none() && self.path.is_none() && self.git.is_none() { |
| let msg = format!( |
| "dependency ({}) specified without \ |
| providing a local path, Git repository, or \ |
| version to use. This will be considered an \ |
| error in future versions", |
| name_in_toml |
| ); |
| cx.warnings.push(msg); |
| } |
| |
| if let Some(version) = &self.version { |
| if version.contains('+') { |
| cx.warnings.push(format!( |
| "version requirement `{}` for dependency `{}` \ |
| includes semver metadata which will be ignored, removing the \ |
| metadata is recommended to avoid confusion", |
| version, name_in_toml |
| )); |
| } |
| } |
| |
| if self.git.is_none() { |
| let git_only_keys = [ |
| (&self.branch, "branch"), |
| (&self.tag, "tag"), |
| (&self.rev, "rev"), |
| ]; |
| |
| for &(key, key_name) in &git_only_keys { |
| if key.is_some() { |
| bail!( |
| "key `{}` is ignored for dependency ({}).", |
| key_name, |
| name_in_toml |
| ); |
| } |
| } |
| } |
| |
| // Early detection of potentially misused feature syntax |
| // instead of generating a "feature not found" error. |
| if let Some(features) = &self.features { |
| for feature in features { |
| if feature.contains('/') { |
| bail!( |
| "feature `{}` in dependency `{}` is not allowed to contain slashes\n\ |
| If you want to enable features of a transitive dependency, \ |
| the direct dependency needs to re-export those features from \ |
| the `[features]` table.", |
| feature, |
| name_in_toml |
| ); |
| } |
| if feature.starts_with("dep:") { |
| bail!( |
| "feature `{}` in dependency `{}` is not allowed to use explicit \ |
| `dep:` syntax\n\ |
| If you want to enable an optional dependency, specify the name \ |
| of the optional dependency without the `dep:` prefix, or specify \ |
| a feature from the dependency's `[features]` table that enables \ |
| the optional dependency.", |
| feature, |
| name_in_toml |
| ); |
| } |
| } |
| } |
| |
| let new_source_id = match ( |
| self.git.as_ref(), |
| self.path.as_ref(), |
| self.registry.as_ref(), |
| self.registry_index.as_ref(), |
| ) { |
| (Some(_), _, Some(_), _) | (Some(_), _, _, Some(_)) => bail!( |
| "dependency ({}) specification is ambiguous. \ |
| Only one of `git` or `registry` is allowed.", |
| name_in_toml |
| ), |
| (_, _, Some(_), Some(_)) => bail!( |
| "dependency ({}) specification is ambiguous. \ |
| Only one of `registry` or `registry-index` is allowed.", |
| name_in_toml |
| ), |
| (Some(git), maybe_path, _, _) => { |
| if maybe_path.is_some() { |
| bail!( |
| "dependency ({}) specification is ambiguous. \ |
| Only one of `git` or `path` is allowed.", |
| name_in_toml |
| ); |
| } |
| |
| let n_details = [&self.branch, &self.tag, &self.rev] |
| .iter() |
| .filter(|d| d.is_some()) |
| .count(); |
| |
| if n_details > 1 { |
| bail!( |
| "dependency ({}) specification is ambiguous. \ |
| Only one of `branch`, `tag` or `rev` is allowed.", |
| name_in_toml |
| ); |
| } |
| |
| let reference = self |
| .branch |
| .clone() |
| .map(GitReference::Branch) |
| .or_else(|| self.tag.clone().map(GitReference::Tag)) |
| .or_else(|| self.rev.clone().map(GitReference::Rev)) |
| .unwrap_or(GitReference::DefaultBranch); |
| let loc = git.into_url()?; |
| |
| if let Some(fragment) = loc.fragment() { |
| let msg = format!( |
| "URL fragment `#{}` in git URL is ignored for dependency ({}). \ |
| If you were trying to specify a specific git revision, \ |
| use `rev = \"{}\"` in the dependency declaration.", |
| fragment, name_in_toml, fragment |
| ); |
| cx.warnings.push(msg) |
| } |
| |
| SourceId::for_git(&loc, reference)? |
| } |
| (None, Some(path), _, _) => { |
| let path = path.resolve(cx.config); |
| cx.nested_paths.push(path.clone()); |
| // If the source ID for the package we're parsing is a path |
| // source, then we normalize the path here to get rid of |
| // components like `..`. |
| // |
| // The purpose of this is to get a canonical ID for the package |
| // that we're depending on to ensure that builds of this package |
| // always end up hashing to the same value no matter where it's |
| // built from. |
| if cx.source_id.is_path() { |
| let path = cx.root.join(path); |
| let path = paths::normalize_path(&path); |
| SourceId::for_path(&path)? |
| } else { |
| cx.source_id |
| } |
| } |
| (None, None, Some(registry), None) => SourceId::alt_registry(cx.config, registry)?, |
| (None, None, None, Some(registry_index)) => { |
| let url = registry_index.into_url()?; |
| SourceId::for_registry(&url)? |
| } |
| (None, None, None, None) => SourceId::crates_io(cx.config)?, |
| }; |
| |
| let (pkg_name, explicit_name_in_toml) = match self.package { |
| Some(ref s) => (&s[..], Some(name_in_toml)), |
| None => (name_in_toml, None), |
| }; |
| |
| let version = self.version.as_deref(); |
| let mut dep = Dependency::parse(pkg_name, version, new_source_id)?; |
| if self.default_features.is_some() && self.default_features2.is_some() { |
| warn_on_deprecated("default-features", name_in_toml, "dependency", cx.warnings); |
| } |
| dep.set_features(self.features.iter().flatten()) |
| .set_default_features( |
| self.default_features |
| .or(self.default_features2) |
| .unwrap_or(true), |
| ) |
| .set_optional(self.optional.unwrap_or(false)) |
| .set_platform(cx.platform.clone()); |
| if let Some(registry) = &self.registry { |
| let registry_id = SourceId::alt_registry(cx.config, registry)?; |
| dep.set_registry_id(registry_id); |
| } |
| if let Some(registry_index) = &self.registry_index { |
| let url = registry_index.into_url()?; |
| let registry_id = SourceId::for_registry(&url)?; |
| dep.set_registry_id(registry_id); |
| } |
| |
| if let Some(kind) = kind { |
| dep.set_kind(kind); |
| } |
| if let Some(name_in_toml) = explicit_name_in_toml { |
| dep.set_explicit_name_in_toml(name_in_toml); |
| } |
| |
| if let Some(p) = self.public { |
| cx.features.require(Feature::public_dependency())?; |
| |
| if dep.kind() != DepKind::Normal { |
| bail!("'public' specifier can only be used on regular dependencies, not {:?} dependencies", dep.kind()); |
| } |
| |
| dep.set_public(p); |
| } |
| |
| if let (Some(artifact), is_lib, target) = ( |
| self.artifact.as_ref(), |
| self.lib.unwrap_or(false), |
| self.target.as_deref(), |
| ) { |
| if cx.config.cli_unstable().bindeps { |
| let artifact = Artifact::parse(artifact, is_lib, target)?; |
| if dep.kind() != DepKind::Build |
| && artifact.target() == Some(ArtifactTarget::BuildDependencyAssumeTarget) |
| { |
| bail!( |
| r#"`target = "target"` in normal- or dev-dependencies has no effect ({})"#, |
| name_in_toml |
| ); |
| } |
| dep.set_artifact(artifact) |
| } else { |
| bail!("`artifact = …` requires `-Z bindeps` ({})", name_in_toml); |
| } |
| } else if self.lib.is_some() || self.target.is_some() { |
| for (is_set, specifier) in [ |
| (self.lib.is_some(), "lib"), |
| (self.target.is_some(), "target"), |
| ] { |
| if !is_set { |
| continue; |
| } |
| bail!( |
| "'{}' specifier cannot be used without an 'artifact = …' value ({})", |
| specifier, |
| name_in_toml |
| ) |
| } |
| } |
| Ok(dep) |
| } |
| } |
| |
| impl DetailedTomlDependency { |
| fn add_features(&mut self, features: Option<Vec<String>>) { |
| self.features = match (self.features.clone(), features.clone()) { |
| (Some(dep_feat), Some(inherit_feat)) => Some( |
| dep_feat |
| .into_iter() |
| .chain(inherit_feat) |
| .collect::<Vec<String>>(), |
| ), |
| (Some(dep_fet), None) => Some(dep_fet), |
| (None, Some(inherit_feat)) => Some(inherit_feat), |
| (None, None) => None, |
| }; |
| } |
| |
| fn update_optional(&mut self, optional: Option<bool>) { |
| self.optional = optional; |
| } |
| |
| fn resolve_path( |
| &mut self, |
| name: &str, |
| root_path: &Path, |
| package_root: &Path, |
| ) -> CargoResult<()> { |
| if let Some(rel_path) = &self.path { |
| self.path = Some(resolve_relative_path( |
| name, |
| root_path, |
| package_root, |
| rel_path, |
| )?) |
| } |
| Ok(()) |
| } |
| } |
| |
| #[derive(Default, Serialize, Deserialize, Debug, Clone)] |
| struct TomlTarget { |
| name: Option<String>, |
| |
| // The intention was to only accept `crate-type` here but historical |
| // versions of Cargo also accepted `crate_type`, so look for both. |
| #[serde(rename = "crate-type")] |
| crate_type: Option<Vec<String>>, |
| #[serde(rename = "crate_type")] |
| crate_type2: Option<Vec<String>>, |
| |
| path: Option<PathValue>, |
| // Note that `filename` is used for the cargo-feature `different_binary_name` |
| filename: Option<String>, |
| test: Option<bool>, |
| doctest: Option<bool>, |
| bench: Option<bool>, |
| doc: Option<bool>, |
| plugin: Option<bool>, |
| #[serde(rename = "proc-macro")] |
| proc_macro_raw: Option<bool>, |
| #[serde(rename = "proc_macro")] |
| proc_macro_raw2: Option<bool>, |
| harness: Option<bool>, |
| #[serde(rename = "required-features")] |
| required_features: Option<Vec<String>>, |
| edition: Option<String>, |
| } |
| |
| #[derive(Clone)] |
| struct PathValue(PathBuf); |
| |
| impl<'de> de::Deserialize<'de> for PathValue { |
| fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
| where |
| D: de::Deserializer<'de>, |
| { |
| Ok(PathValue(String::deserialize(deserializer)?.into())) |
| } |
| } |
| |
| impl ser::Serialize for PathValue { |
| fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> |
| where |
| S: ser::Serializer, |
| { |
| self.0.serialize(serializer) |
| } |
| } |
| |
| /// Corresponds to a `target` entry, but `TomlTarget` is already used. |
| #[derive(Serialize, Deserialize, Debug, Clone)] |
| struct TomlPlatform { |
| dependencies: Option<BTreeMap<String, TomlDependency>>, |
| #[serde(rename = "build-dependencies")] |
| build_dependencies: Option<BTreeMap<String, TomlDependency>>, |
| #[serde(rename = "build_dependencies")] |
| build_dependencies2: Option<BTreeMap<String, TomlDependency>>, |
| #[serde(rename = "dev-dependencies")] |
| dev_dependencies: Option<BTreeMap<String, TomlDependency>>, |
| #[serde(rename = "dev_dependencies")] |
| dev_dependencies2: Option<BTreeMap<String, TomlDependency>>, |
| } |
| |
| impl TomlTarget { |
| fn new() -> TomlTarget { |
| TomlTarget::default() |
| } |
| |
| fn name(&self) -> String { |
| match self.name { |
| Some(ref name) => name.clone(), |
| None => panic!("target name is required"), |
| } |
| } |
| |
| fn validate_proc_macro(&self, warnings: &mut Vec<String>) { |
| if self.proc_macro_raw.is_some() && self.proc_macro_raw2.is_some() { |
| warn_on_deprecated( |
| "proc-macro", |
| self.name().as_str(), |
| "library target", |
| warnings, |
| ); |
| } |
| } |
| |
| fn proc_macro(&self) -> Option<bool> { |
| self.proc_macro_raw.or(self.proc_macro_raw2).or_else(|| { |
| if let Some(types) = self.crate_types() { |
| if types.contains(&"proc-macro".to_string()) { |
| return Some(true); |
| } |
| } |
| None |
| }) |
| } |
| |
| fn validate_crate_types(&self, target_kind_human: &str, warnings: &mut Vec<String>) { |
| if self.crate_type.is_some() && self.crate_type2.is_some() { |
| warn_on_deprecated( |
| "crate-type", |
| self.name().as_str(), |
| format!("{target_kind_human} target").as_str(), |
| warnings, |
| ); |
| } |
| } |
| |
| fn crate_types(&self) -> Option<&Vec<String>> { |
| self.crate_type |
| .as_ref() |
| .or_else(|| self.crate_type2.as_ref()) |
| } |
| } |
| |
| impl fmt::Debug for PathValue { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| self.0.fmt(f) |
| } |
| } |