| use cargo_platform::Platform; | |
| use semver::VersionReq; | |
| use serde::Serialize; | |
| use serde::ser; | |
| use std::borrow::Cow; | |
| use std::fmt; | |
| use std::fmt::{Display, Formatter}; | |
| use std::path::{Path, PathBuf}; | |
| use std::sync::Arc; | |
| use tracing::trace; | |
| use crate::compiler::{CompileKind, CompileTarget}; | |
| use crate::context::Definition; | |
| use crate::util::OptVersionReq; | |
| use crate::util::VersionReqMatchMode; | |
| use crate::util::errors::CargoResult; | |
| use crate::util::interning::InternedString; | |
| use crate::workspace::{CliUnstable, Feature, Features, PackageId, SourceId, Summary}; | |
| /// Information about a dependency requested by a Cargo manifest. | |
| /// Cheap to copy. | |
| #[derive(PartialEq, Eq, Hash, Clone, Debug)] | |
| pub struct Dependency { | |
| inner: Arc<Inner>, | |
| } | |
| /// The data underlying a `Dependency`. | |
| #[derive(PartialEq, Eq, Hash, Clone, Debug)] | |
| struct Inner { | |
| name: InternedString, | |
| source_id: SourceId, | |
| /// Source ID for the registry as specified in the manifest. | |
| /// | |
| /// This will be None if it is not specified (crates.io dependency). | |
| /// This is different from `source_id` for example when both a `path` and | |
| /// `registry` is specified. Or in the case of a crates.io dependency, | |
| /// `source_id` will be crates.io and this will be None. | |
| registry_id: Option<SourceId>, | |
| req: OptVersionReq, | |
| specified_req: bool, | |
| kind: DepKind, | |
| only_match_name: bool, | |
| explicit_name_in_toml: Option<InternedString>, | |
| optional: bool, | |
| public: bool, | |
| default_features: bool, | |
| features: Vec<InternedString>, | |
| // The presence of this information turns a dependency into an artifact dependency. | |
| artifact: Option<Artifact>, | |
| // This dependency should be used only for this platform. | |
| // `None` means *all platforms*. | |
| platform: Option<Platform>, | |
| } | |
| #[derive(Serialize)] | |
| pub struct SerializedDependency { | |
| name: InternedString, | |
| source: SourceId, | |
| req: String, | |
| kind: DepKind, | |
| rename: Option<InternedString>, | |
| optional: bool, | |
| uses_default_features: bool, | |
| features: Vec<InternedString>, | |
| #[serde(skip_serializing_if = "Option::is_none")] | |
| artifact: Option<Artifact>, | |
| target: Option<Platform>, | |
| /// The registry URL this dependency is from. | |
| /// If None, then it comes from the default registry (crates.io). | |
| registry: Option<String>, | |
| /// The file system path for a local path dependency. | |
| #[serde(skip_serializing_if = "Option::is_none")] | |
| path: Option<PathBuf>, | |
| /// `public` flag is unset if `-Zpublic-dependency` is not enabled | |
| /// | |
| /// Once that feature is stabilized, `public` will not need to be `Option` | |
| #[serde(skip_serializing_if = "Option::is_none")] | |
| public: Option<bool>, | |
| } | |
| #[derive(PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Debug, Copy)] | |
| pub enum DepKind { | |
| Normal, | |
| Development, | |
| Build, | |
| } | |
| impl DepKind { | |
| pub fn kind_table(&self) -> &'static str { | |
| match self { | |
| DepKind::Normal => "dependencies", | |
| DepKind::Development => "dev-dependencies", | |
| DepKind::Build => "build-dependencies", | |
| } | |
| } | |
| } | |
| impl ser::Serialize for DepKind { | |
| fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error> | |
| where | |
| S: ser::Serializer, | |
| { | |
| match *self { | |
| DepKind::Normal => None, | |
| DepKind::Development => Some("dev"), | |
| DepKind::Build => Some("build"), | |
| } | |
| .serialize(s) | |
| } | |
| } | |
| impl Dependency { | |
| /// Attempt to create a `Dependency` from an entry in the manifest. | |
| pub fn parse( | |
| name: impl Into<InternedString>, | |
| version: Option<&str>, | |
| source_id: SourceId, | |
| ) -> CargoResult<Dependency> { | |
| let name = name.into(); | |
| let (specified_req, version_req) = match version { | |
| Some(v) => match VersionReq::parse(v) { | |
| Ok(req) => (true, OptVersionReq::Req(req)), | |
| Err(err) => { | |
| return Err(anyhow::Error::new(err).context(format!( | |
| "failed to parse the version requirement `{}` for dependency `{}`", | |
| v, name, | |
| ))); | |
| } | |
| }, | |
| None => (false, OptVersionReq::Any), | |
| }; | |
| let mut ret = Dependency::new_override(name, source_id); | |
| { | |
| let ptr = Arc::make_mut(&mut ret.inner); | |
| ptr.only_match_name = false; | |
| ptr.req = version_req; | |
| ptr.specified_req = specified_req; | |
| } | |
| Ok(ret) | |
| } | |
| pub fn new_override(name: InternedString, source_id: SourceId) -> Dependency { | |
| assert!(!name.is_empty()); | |
| Dependency { | |
| inner: Arc::new(Inner { | |
| name, | |
| source_id, | |
| registry_id: None, | |
| req: OptVersionReq::Any, | |
| kind: DepKind::Normal, | |
| only_match_name: true, | |
| optional: false, | |
| public: false, | |
| features: Vec::new(), | |
| default_features: true, | |
| specified_req: false, | |
| platform: None, | |
| explicit_name_in_toml: None, | |
| artifact: None, | |
| }), | |
| } | |
| } | |
| pub fn serialized( | |
| &self, | |
| unstable_flags: &CliUnstable, | |
| features: &Features, | |
| ) -> SerializedDependency { | |
| SerializedDependency { | |
| name: self.package_name(), | |
| source: self.source_id(), | |
| req: self.version_req().to_string(), | |
| kind: self.kind(), | |
| optional: self.is_optional(), | |
| uses_default_features: self.uses_default_features(), | |
| features: self.features().to_vec(), | |
| target: self.inner.platform.clone(), | |
| rename: self.explicit_name_in_toml(), | |
| registry: self.registry_id().as_ref().map(|sid| sid.url().to_string()), | |
| path: self.source_id().local_path(), | |
| artifact: self.inner.artifact.clone(), | |
| public: if unstable_flags.public_dependency | |
| || features.is_enabled(Feature::public_dependency()) | |
| { | |
| Some(self.inner.public) | |
| } else { | |
| None | |
| }, | |
| } | |
| } | |
| pub fn version_req(&self) -> &OptVersionReq { | |
| &self.inner.req | |
| } | |
| /// This is the name of this `Dependency` as listed in `Cargo.toml`. | |
| /// | |
| /// Or in other words, this is what shows up in the `[dependencies]` section | |
| /// on the left hand side. This is *not* the name of the package that's | |
| /// being depended on as the dependency can be renamed. For that use | |
| /// `package_name` below. | |
| /// | |
| /// Both of the dependencies below return `foo` for `name_in_toml`: | |
| /// | |
| /// ```toml | |
| /// [dependencies] | |
| /// foo = "0.1" | |
| /// ``` | |
| /// | |
| /// and ... | |
| /// | |
| /// ```toml | |
| /// [dependencies] | |
| /// foo = { version = "0.1", package = 'bar' } | |
| /// ``` | |
| pub fn name_in_toml(&self) -> InternedString { | |
| self.explicit_name_in_toml().unwrap_or(self.inner.name) | |
| } | |
| /// The name of the package that this `Dependency` depends on. | |
| /// | |
| /// Usually this is what's written on the left hand side of a dependencies | |
| /// section, but it can also be renamed via the `package` key. | |
| /// | |
| /// Both of the dependencies below return `foo` for `package_name`: | |
| /// | |
| /// ```toml | |
| /// [dependencies] | |
| /// foo = "0.1" | |
| /// ``` | |
| /// | |
| /// and ... | |
| /// | |
| /// ```toml | |
| /// [dependencies] | |
| /// bar = { version = "0.1", package = 'foo' } | |
| /// ``` | |
| pub fn package_name(&self) -> InternedString { | |
| self.inner.name | |
| } | |
| pub fn source_id(&self) -> SourceId { | |
| self.inner.source_id | |
| } | |
| pub fn registry_id(&self) -> Option<SourceId> { | |
| self.inner.registry_id | |
| } | |
| pub fn set_registry_id(&mut self, registry_id: SourceId) -> &mut Dependency { | |
| Arc::make_mut(&mut self.inner).registry_id = Some(registry_id); | |
| self | |
| } | |
| pub fn kind(&self) -> DepKind { | |
| self.inner.kind | |
| } | |
| pub fn is_public(&self) -> bool { | |
| self.inner.public | |
| } | |
| /// Sets whether the dependency is public. | |
| pub fn set_public(&mut self, public: bool) -> &mut Dependency { | |
| if public { | |
| // Setting 'public' only makes sense for normal dependencies | |
| assert_eq!(self.kind(), DepKind::Normal); | |
| } | |
| Arc::make_mut(&mut self.inner).public = public; | |
| self | |
| } | |
| pub fn specified_req(&self) -> bool { | |
| self.inner.specified_req | |
| } | |
| /// If none, this dependencies must be built for all platforms. | |
| /// If some, it must only be built for the specified platform. | |
| pub fn platform(&self) -> Option<&Platform> { | |
| self.inner.platform.as_ref() | |
| } | |
| /// The renamed name of this dependency, if any. | |
| /// | |
| /// If the `package` key is used in `Cargo.toml` then this returns the same | |
| /// value as `name_in_toml`. | |
| pub fn explicit_name_in_toml(&self) -> Option<InternedString> { | |
| self.inner.explicit_name_in_toml | |
| } | |
| /// The keys to this entry in a `Cargo.toml` file | |
| pub fn toml_path(&self) -> Vec<String> { | |
| let mut path = Vec::new(); | |
| if let Some(platform) = self.platform() { | |
| path.push("target".to_owned()); | |
| path.push(platform.to_string()); | |
| } | |
| path.push(self.kind().kind_table().to_owned()); | |
| path.push((&*self.name_in_toml()).to_owned()); | |
| path | |
| } | |
| pub fn set_kind(&mut self, kind: DepKind) -> &mut Dependency { | |
| if self.is_public() { | |
| // Setting 'public' only makes sense for normal dependencies | |
| assert_eq!(kind, DepKind::Normal); | |
| } | |
| Arc::make_mut(&mut self.inner).kind = kind; | |
| self | |
| } | |
| /// Sets the list of features requested for the package. | |
| pub fn set_features( | |
| &mut self, | |
| features: impl IntoIterator<Item = impl Into<InternedString>>, | |
| ) -> &mut Dependency { | |
| Arc::make_mut(&mut self.inner).features = features.into_iter().map(|s| s.into()).collect(); | |
| self | |
| } | |
| /// Sets whether the dependency requests default features of the package. | |
| pub fn set_default_features(&mut self, default_features: bool) -> &mut Dependency { | |
| Arc::make_mut(&mut self.inner).default_features = default_features; | |
| self | |
| } | |
| /// Sets whether the dependency is optional. | |
| pub fn set_optional(&mut self, optional: bool) -> &mut Dependency { | |
| Arc::make_mut(&mut self.inner).optional = optional; | |
| self | |
| } | |
| /// Sets the source ID for this dependency. | |
| pub fn set_source_id(&mut self, id: SourceId) -> &mut Dependency { | |
| Arc::make_mut(&mut self.inner).source_id = id; | |
| self | |
| } | |
| /// Sets the version requirement for this dependency. | |
| pub fn set_version_req(&mut self, req: OptVersionReq) -> &mut Dependency { | |
| Arc::make_mut(&mut self.inner).req = req; | |
| self | |
| } | |
| pub fn set_platform(&mut self, platform: Option<Platform>) -> &mut Dependency { | |
| Arc::make_mut(&mut self.inner).platform = platform; | |
| self | |
| } | |
| pub fn set_explicit_name_in_toml( | |
| &mut self, | |
| name: impl Into<InternedString>, | |
| ) -> &mut Dependency { | |
| Arc::make_mut(&mut self.inner).explicit_name_in_toml = Some(name.into()); | |
| self | |
| } | |
| /// Locks this dependency to depending on the specified package ID. | |
| pub fn lock_to(&mut self, id: PackageId, mode: VersionReqMatchMode) -> &mut Dependency { | |
| assert_eq!(self.inner.source_id, id.source_id()); | |
| trace!( | |
| "locking dep from `{}` with `{}` at {} to {}", | |
| self.package_name(), | |
| self.version_req(), | |
| self.source_id(), | |
| id | |
| ); | |
| let me = Arc::make_mut(&mut self.inner); | |
| me.req.lock_to(id.version(), mode); | |
| // Only update the `precise` of this source to preserve other | |
| // information about dependency's source which may not otherwise be | |
| // tested during equality/hashing. | |
| me.source_id = me.source_id.with_precise_from(id.source_id()); | |
| self | |
| } | |
| /// Locks this dependency to a specified version. | |
| /// | |
| /// Mainly used in dependency patching like `[patch]` or `[replace]`, which | |
| /// doesn't need to lock the entire dependency to a specific [`PackageId`]. | |
| pub fn lock_version( | |
| &mut self, | |
| version: &semver::Version, | |
| mode: VersionReqMatchMode, | |
| ) -> &mut Dependency { | |
| let me = Arc::make_mut(&mut self.inner); | |
| me.req.lock_to(version, mode); | |
| self | |
| } | |
| /// Returns `true` if this is a "locked" dependency. Basically a locked | |
| /// dependency has an exact version req, but not vice versa. | |
| pub fn is_locked(&self) -> bool { | |
| self.inner.req.is_locked() | |
| } | |
| /// Returns `false` if the dependency is only used to build the local package. | |
| pub fn is_transitive(&self) -> bool { | |
| match self.inner.kind { | |
| DepKind::Normal | DepKind::Build => true, | |
| DepKind::Development => false, | |
| } | |
| } | |
| pub fn is_build(&self) -> bool { | |
| matches!(self.inner.kind, DepKind::Build) | |
| } | |
| pub fn is_optional(&self) -> bool { | |
| self.inner.optional | |
| } | |
| /// Returns `true` if the default features of the dependency are requested. | |
| pub fn uses_default_features(&self) -> bool { | |
| self.inner.default_features | |
| } | |
| /// Returns the list of features that are requested by the dependency. | |
| pub fn features(&self) -> &[InternedString] { | |
| &self.inner.features | |
| } | |
| /// Returns `true` if the package (`sum`) can fulfill this dependency request. | |
| pub fn matches(&self, sum: &Summary, mode: VersionReqMatchMode) -> bool { | |
| self.matches_id(sum.package_id(), mode) | |
| } | |
| /// Returns `true` if the package (`id`) can fulfill this dependency request. | |
| pub fn matches_ignoring_source(&self, id: PackageId, mode: VersionReqMatchMode) -> bool { | |
| self.package_name() == id.name() && self.version_req().matches(id.version(), mode) | |
| } | |
| /// Returns `true` if the package (`id`) can fulfill this dependency request. | |
| pub fn matches_id(&self, id: PackageId, mode: VersionReqMatchMode) -> bool { | |
| self.inner.name == id.name() | |
| && (self.inner.only_match_name | |
| || (self.inner.req.matches(id.version(), mode) | |
| && self.inner.source_id == id.source_id())) | |
| } | |
| pub fn map_source(mut self, to_replace: SourceId, replace_with: SourceId) -> Dependency { | |
| if self.source_id() == to_replace { | |
| self.set_source_id(replace_with); | |
| } | |
| self | |
| } | |
| pub(crate) fn set_artifact(&mut self, artifact: Artifact) { | |
| Arc::make_mut(&mut self.inner).artifact = Some(artifact); | |
| } | |
| pub fn artifact(&self) -> Option<&Artifact> { | |
| self.inner.artifact.as_ref() | |
| } | |
| /// Dependencies are potential rust libs if they are not artifacts or they are an | |
| /// artifact which allows to be seen as library. | |
| /// Previously, every dependency was potentially seen as library. | |
| pub(crate) fn maybe_lib(&self) -> bool { | |
| self.artifact().map(|a| a.is_lib).unwrap_or(true) | |
| } | |
| } | |
| /// The presence of an artifact turns an ordinary dependency into an Artifact dependency. | |
| /// As such, it will build one or more different artifacts of possibly various kinds | |
| /// for making them available at build time for rustc invocations or runtime | |
| /// for build scripts. | |
| /// | |
| /// This information represents a requirement in the package this dependency refers to. | |
| #[derive(PartialEq, Eq, Hash, Clone, Debug)] | |
| pub struct Artifact { | |
| inner: Arc<Vec<ArtifactKind>>, | |
| is_lib: bool, | |
| target: Option<ArtifactTarget>, | |
| } | |
| #[derive(Serialize)] | |
| pub struct SerializedArtifact<'a> { | |
| kinds: &'a [ArtifactKind], | |
| lib: bool, | |
| target: Option<&'a str>, | |
| } | |
| impl ser::Serialize for Artifact { | |
| fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error> | |
| where | |
| S: ser::Serializer, | |
| { | |
| SerializedArtifact { | |
| kinds: self.kinds(), | |
| lib: self.is_lib, | |
| target: self.target.as_ref().map(ArtifactTarget::as_str), | |
| } | |
| .serialize(s) | |
| } | |
| } | |
| impl Artifact { | |
| pub(crate) fn parse( | |
| artifacts: &[impl AsRef<str>], | |
| is_lib: bool, | |
| target: Option<&str>, | |
| unstable_json: bool, | |
| ) -> CargoResult<Self> { | |
| let kinds = ArtifactKind::validate( | |
| artifacts | |
| .iter() | |
| .map(|s| ArtifactKind::parse(s.as_ref())) | |
| .collect::<Result<Vec<_>, _>>()?, | |
| )?; | |
| Ok(Artifact { | |
| inner: Arc::new(kinds), | |
| is_lib, | |
| target: target | |
| .map(|name| ArtifactTarget::parse(name, unstable_json)) | |
| .transpose()?, | |
| }) | |
| } | |
| pub fn kinds(&self) -> &[ArtifactKind] { | |
| &self.inner | |
| } | |
| pub fn is_lib(&self) -> bool { | |
| self.is_lib | |
| } | |
| pub fn target(&self) -> Option<ArtifactTarget> { | |
| self.target | |
| } | |
| } | |
| #[derive(PartialEq, Eq, Hash, Copy, Clone, Ord, PartialOrd, Debug)] | |
| pub enum ArtifactTarget { | |
| /// Only applicable to build-dependencies, causing them to be built | |
| /// for the given target (i.e. via `--target <tuple>`) instead of for the host. | |
| /// Has no effect on non-build dependencies. | |
| BuildDependencyAssumeTarget, | |
| /// The name of the platform tuple, like `x86_64-apple-darwin`, that this | |
| /// artifact will always be built for, no matter if it is a build, | |
| /// normal or dev dependency. | |
| Force(CompileTarget), | |
| } | |
| impl ArtifactTarget { | |
| pub fn parse(target: &str, unstable_json: bool) -> CargoResult<ArtifactTarget> { | |
| Ok(match target { | |
| "target" => ArtifactTarget::BuildDependencyAssumeTarget, | |
| name => ArtifactTarget::Force(CompileTarget::new(name, unstable_json)?), | |
| }) | |
| } | |
| pub fn as_str(&self) -> &str { | |
| match self { | |
| ArtifactTarget::BuildDependencyAssumeTarget => "target", | |
| ArtifactTarget::Force(target) => target.rustc_target().as_str(), | |
| } | |
| } | |
| pub fn to_compile_kind(&self) -> Option<CompileKind> { | |
| self.to_compile_target().map(CompileKind::Target) | |
| } | |
| pub fn to_compile_target(&self) -> Option<CompileTarget> { | |
| match self { | |
| ArtifactTarget::BuildDependencyAssumeTarget => None, | |
| ArtifactTarget::Force(target) => Some(*target), | |
| } | |
| } | |
| pub(crate) fn to_resolved_compile_kind( | |
| &self, | |
| root_unit_compile_kind: CompileKind, | |
| ) -> CompileKind { | |
| match self { | |
| ArtifactTarget::Force(target) => CompileKind::Target(*target), | |
| ArtifactTarget::BuildDependencyAssumeTarget => root_unit_compile_kind, | |
| } | |
| } | |
| pub(crate) fn to_resolved_compile_target( | |
| &self, | |
| root_unit_compile_kind: CompileKind, | |
| ) -> Option<CompileTarget> { | |
| match self.to_resolved_compile_kind(root_unit_compile_kind) { | |
| CompileKind::Host => None, | |
| CompileKind::Target(target) => Some(target), | |
| } | |
| } | |
| } | |
| #[derive(PartialEq, Eq, Hash, Copy, Clone, Ord, PartialOrd, Debug)] | |
| pub enum ArtifactKind { | |
| /// We represent all binaries in this dependency | |
| AllBinaries, | |
| /// We represent a single binary | |
| SelectedBinary(InternedString), | |
| Cdylib, | |
| Staticlib, | |
| } | |
| impl ser::Serialize for ArtifactKind { | |
| fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error> | |
| where | |
| S: ser::Serializer, | |
| { | |
| self.as_str().serialize(s) | |
| } | |
| } | |
| impl fmt::Display for ArtifactKind { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| f.write_str(&self.as_str()) | |
| } | |
| } | |
| impl ArtifactKind { | |
| /// Returns a string of crate type of the artifact being built. | |
| /// | |
| /// Note that the name of `SelectedBinary` would be dropped and displayed as `bin`. | |
| pub fn crate_type(&self) -> &'static str { | |
| match self { | |
| ArtifactKind::AllBinaries | ArtifactKind::SelectedBinary(_) => "bin", | |
| ArtifactKind::Cdylib => "cdylib", | |
| ArtifactKind::Staticlib => "staticlib", | |
| } | |
| } | |
| pub fn as_str(&self) -> Cow<'static, str> { | |
| match *self { | |
| ArtifactKind::SelectedBinary(name) => format!("bin:{}", name.as_str()).into(), | |
| _ => self.crate_type().into(), | |
| } | |
| } | |
| pub fn parse(kind: &str) -> CargoResult<Self> { | |
| Ok(match kind { | |
| "bin" => ArtifactKind::AllBinaries, | |
| "cdylib" => ArtifactKind::Cdylib, | |
| "staticlib" => ArtifactKind::Staticlib, | |
| _ => { | |
| return kind | |
| .strip_prefix("bin:") | |
| .map(|bin_name| ArtifactKind::SelectedBinary(bin_name.into())) | |
| .ok_or_else(|| { | |
| anyhow::anyhow!("'{}' is not a valid artifact specifier", kind) | |
| }); | |
| } | |
| }) | |
| } | |
| fn validate(kinds: Vec<ArtifactKind>) -> CargoResult<Vec<ArtifactKind>> { | |
| if kinds.iter().any(|k| matches!(k, ArtifactKind::AllBinaries)) | |
| && kinds | |
| .iter() | |
| .any(|k| matches!(k, ArtifactKind::SelectedBinary(_))) | |
| { | |
| anyhow::bail!( | |
| "Cannot specify both 'bin' and 'bin:<name>' binary artifacts, as 'bin' selects all available binaries." | |
| ); | |
| } | |
| let mut kinds_without_dupes = kinds.clone(); | |
| kinds_without_dupes.sort(); | |
| kinds_without_dupes.dedup(); | |
| let num_dupes = kinds.len() - kinds_without_dupes.len(); | |
| if num_dupes != 0 { | |
| anyhow::bail!( | |
| "Found {} duplicate binary artifact{}", | |
| num_dupes, | |
| (num_dupes > 1).then(|| "s").unwrap_or("") | |
| ); | |
| } | |
| Ok(kinds) | |
| } | |
| } | |
| /// Patch is a dependency override that knows where it has been defined. | |
| /// See [`PatchLocation`] for possible locations. | |
| #[derive(Clone, Debug)] | |
| pub struct Patch { | |
| pub dep: Dependency, | |
| pub loc: PatchLocation, | |
| } | |
| /// Place where a [`Patch`] has been defined. | |
| #[derive(Clone, Debug)] | |
| pub enum PatchLocation { | |
| /// Defined in a manifest. | |
| Manifest(PathBuf), | |
| /// Defined in cargo configuration. | |
| Config(Definition), | |
| } | |
| impl Display for PatchLocation { | |
| fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { | |
| match self { | |
| PatchLocation::Manifest(p) => Path::display(p).fmt(f), | |
| PatchLocation::Config(def) => def.fmt(f), | |
| } | |
| } | |
| } |