#![cfg_attr(docsrs, feature(doc_cfg))]
#![deny(missing_docs)]
use camino::Utf8PathBuf;
#[cfg(feature = "builder")]
use derive_builder::Builder;
use std::collections::BTreeMap;
use std::env;
use std::ffi::OsString;
use std::fmt;
use std::hash::Hash;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::str::{from_utf8, FromStr};
pub use camino;
pub use cargo_platform;
pub use semver;
use semver::Version;
#[cfg(feature = "builder")]
pub use dependency::DependencyBuilder;
pub use dependency::{Dependency, DependencyKind};
use diagnostic::Diagnostic;
pub use errors::{Error, Result};
#[cfg(feature = "unstable")]
pub use libtest::TestMessage;
#[allow(deprecated)]
pub use messages::parse_messages;
pub use messages::{
Artifact, ArtifactDebuginfo, ArtifactProfile, BuildFinished, BuildScript, CompilerMessage,
Message, MessageIter,
};
#[cfg(feature = "builder")]
pub use messages::{
ArtifactBuilder, ArtifactProfileBuilder, BuildFinishedBuilder, BuildScriptBuilder,
CompilerMessageBuilder,
};
use serde::{Deserialize, Deserializer, Serialize};
mod dependency;
pub mod diagnostic;
mod errors;
#[cfg(feature = "unstable")]
pub mod libtest;
mod messages;
macro_rules! str_newtype {
(
$(#[doc = $docs:literal])*
$name:ident
) => {
$(#[doc = $docs])*
#[derive(Serialize, Debug, Clone, Eq, PartialOrd, Ord, Hash)]
#[serde(transparent)]
pub struct $name<T: AsRef<str> = String>(T);
impl<T: AsRef<str>> $name<T> {
pub fn into_inner(self) -> T {
self.0
}
}
impl<T: AsRef<str>> AsRef<str> for $name<T> {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
impl<T: AsRef<str>> std::ops::Deref for $name<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: AsRef<str>> std::borrow::Borrow<str> for $name<T> {
fn borrow(&self) -> &str {
self.0.as_ref()
}
}
impl<'a> std::str::FromStr for $name<String> {
type Err = std::convert::Infallible;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Ok(Self::new(value.to_owned()))
}
}
impl<'de, T: AsRef<str> + serde::Deserialize<'de>> serde::Deserialize<'de> for $name<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let inner = T::deserialize(deserializer)?;
Ok(Self::new(inner))
}
}
impl<T: AsRef<str>> fmt::Display for $name<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.as_ref().fmt(f)
}
}
impl<T: AsRef<str>> $name<T> {
pub fn new(name: T) -> Self {
Self(name)
}
}
impl<T: AsRef<str>, Rhs: AsRef<str>> PartialEq<Rhs> for $name<T> {
fn eq(&self, other: &Rhs) -> bool {
self.as_ref() == other.as_ref()
}
}
};
}
str_newtype!(
FeatureName
);
str_newtype!(
PackageName
);
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[serde(transparent)]
pub struct PackageId {
pub repr: String,
}
impl fmt::Display for PackageId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.repr, f)
}
}
fn is_null(value: &serde_json::Value) -> bool {
matches!(value, serde_json::Value::Null)
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "builder", derive(Builder))]
#[non_exhaustive]
#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
pub struct Metadata {
pub packages: Vec<Package>,
pub workspace_members: Vec<PackageId>,
#[serde(default, skip_serializing_if = "WorkspaceDefaultMembers::is_missing")]
pub workspace_default_members: WorkspaceDefaultMembers,
pub resolve: Option<Resolve>,
pub workspace_root: Utf8PathBuf,
pub target_directory: Utf8PathBuf,
pub build_directory: Option<Utf8PathBuf>,
#[serde(rename = "metadata", default, skip_serializing_if = "is_null")]
pub workspace_metadata: serde_json::Value,
version: usize,
}
impl Metadata {
pub fn root_package(&self) -> Option<&Package> {
match &self.resolve {
Some(resolve) => {
let root = resolve.root.as_ref()?;
self.packages.iter().find(|pkg| &pkg.id == root)
}
None => {
let root_manifest_path = self.workspace_root.join("Cargo.toml");
self.packages
.iter()
.find(|pkg| pkg.manifest_path == root_manifest_path)
}
}
}
pub fn workspace_packages(&self) -> Vec<&Package> {
self.packages
.iter()
.filter(|&p| self.workspace_members.contains(&p.id))
.collect()
}
pub fn workspace_default_packages(&self) -> Vec<&Package> {
self.packages
.iter()
.filter(|&p| self.workspace_default_members.contains(&p.id))
.collect()
}
}
impl<'a> std::ops::Index<&'a PackageId> for Metadata {
type Output = Package;
fn index(&self, idx: &'a PackageId) -> &Self::Output {
self.packages
.iter()
.find(|p| p.id == *idx)
.unwrap_or_else(|| panic!("no package with this id: {idx:?}"))
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash, Default)]
#[serde(transparent)]
pub struct WorkspaceDefaultMembers(Option<Vec<PackageId>>);
impl WorkspaceDefaultMembers {
pub fn is_available(&self) -> bool {
self.0.is_some()
}
pub fn is_missing(&self) -> bool {
self.0.is_none()
}
}
impl core::ops::Deref for WorkspaceDefaultMembers {
type Target = [PackageId];
fn deref(&self) -> &Self::Target {
self.0
.as_ref()
.expect("WorkspaceDefaultMembers should only be dereferenced on Cargo versions >= 1.71")
}
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "builder", derive(Builder))]
#[non_exhaustive]
#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
pub struct Resolve {
pub nodes: Vec<Node>,
pub root: Option<PackageId>,
}
impl<'a> std::ops::Index<&'a PackageId> for Resolve {
type Output = Node;
fn index(&self, idx: &'a PackageId) -> &Self::Output {
self.nodes
.iter()
.find(|p| p.id == *idx)
.unwrap_or_else(|| panic!("no Node with this id: {idx:?}"))
}
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "builder", derive(Builder))]
#[non_exhaustive]
#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
pub struct Node {
pub id: PackageId,
#[serde(default)]
pub deps: Vec<NodeDep>,
pub dependencies: Vec<PackageId>,
#[serde(default)]
pub features: Vec<FeatureName>,
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "builder", derive(Builder))]
#[non_exhaustive]
#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
pub struct NodeDep {
pub name: String,
pub pkg: PackageId,
#[serde(default)]
pub dep_kinds: Vec<DepKindInfo>,
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "builder", derive(Builder))]
#[non_exhaustive]
#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
pub struct DepKindInfo {
#[serde(deserialize_with = "dependency::parse_dependency_kind")]
pub kind: DependencyKind,
pub target: Option<dependency::Platform>,
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "builder", derive(Builder))]
#[non_exhaustive]
#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
pub struct Package {
pub name: PackageName,
pub version: Version,
#[serde(default)]
#[cfg_attr(feature = "builder", builder(default))]
pub authors: Vec<String>,
pub id: PackageId,
#[cfg_attr(feature = "builder", builder(default))]
pub source: Option<Source>,
#[cfg_attr(feature = "builder", builder(default))]
pub description: Option<String>,
#[cfg_attr(feature = "builder", builder(default))]
pub dependencies: Vec<Dependency>,
#[cfg_attr(feature = "builder", builder(default))]
pub license: Option<String>,
#[cfg_attr(feature = "builder", builder(default))]
pub license_file: Option<Utf8PathBuf>,
#[cfg_attr(feature = "builder", builder(default))]
pub targets: Vec<Target>,
#[cfg_attr(feature = "builder", builder(default))]
pub features: BTreeMap<String, Vec<String>>,
pub manifest_path: Utf8PathBuf,
#[serde(default)]
#[cfg_attr(feature = "builder", builder(default))]
pub categories: Vec<String>,
#[serde(default)]
#[cfg_attr(feature = "builder", builder(default))]
pub keywords: Vec<String>,
#[cfg_attr(feature = "builder", builder(default))]
pub readme: Option<Utf8PathBuf>,
#[cfg_attr(feature = "builder", builder(default))]
pub repository: Option<String>,
#[cfg_attr(feature = "builder", builder(default))]
pub homepage: Option<String>,
#[cfg_attr(feature = "builder", builder(default))]
pub documentation: Option<String>,
#[serde(default)]
#[cfg_attr(feature = "builder", builder(default))]
pub edition: Edition,
#[serde(default, skip_serializing_if = "is_null")]
#[cfg_attr(feature = "builder", builder(default))]
pub metadata: serde_json::Value,
#[cfg_attr(feature = "builder", builder(default))]
pub links: Option<String>,
#[cfg_attr(feature = "builder", builder(default))]
pub publish: Option<Vec<String>>,
#[cfg_attr(feature = "builder", builder(default))]
pub default_run: Option<String>,
#[serde(default)]
#[serde(deserialize_with = "deserialize_rust_version")]
#[cfg_attr(feature = "builder", builder(default))]
pub rust_version: Option<Version>,
}
#[cfg(feature = "builder")]
impl PackageBuilder {
pub fn new(
name: impl Into<PackageName>,
version: impl Into<Version>,
id: impl Into<PackageId>,
path: impl Into<Utf8PathBuf>,
) -> Self {
Self::default()
.name(name)
.version(version)
.id(id)
.manifest_path(path)
}
}
impl Package {
pub fn license_file(&self) -> Option<Utf8PathBuf> {
self.license_file.as_ref().map(|file| {
self.manifest_path
.parent()
.unwrap_or(&self.manifest_path)
.join(file)
})
}
pub fn readme(&self) -> Option<Utf8PathBuf> {
self.readme.as_ref().map(|file| {
self.manifest_path
.parent()
.unwrap_or(&self.manifest_path)
.join(file)
})
}
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
#[serde(transparent)]
pub struct Source {
pub repr: String,
}
impl Source {
pub fn is_crates_io(&self) -> bool {
self.repr == "registry+https://github.com/rust-lang/crates.io-index"
}
}
impl fmt::Display for Source {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.repr, f)
}
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "builder", derive(Builder))]
#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
#[non_exhaustive]
pub struct Target {
pub name: String,
pub kind: Vec<TargetKind>,
#[serde(default)]
#[cfg_attr(feature = "builder", builder(default))]
pub crate_types: Vec<CrateType>,
#[serde(default)]
#[cfg_attr(feature = "builder", builder(default))]
#[serde(rename = "required-features")]
pub required_features: Vec<String>,
pub src_path: Utf8PathBuf,
#[serde(default)]
#[cfg_attr(feature = "builder", builder(default))]
pub edition: Edition,
#[serde(default = "default_true")]
#[cfg_attr(feature = "builder", builder(default = "true"))]
pub doctest: bool,
#[serde(default = "default_true")]
#[cfg_attr(feature = "builder", builder(default = "true"))]
pub test: bool,
#[serde(default = "default_true")]
#[cfg_attr(feature = "builder", builder(default = "true"))]
pub doc: bool,
}
macro_rules! methods_target_is_kind {
($($name:ident => $kind:expr),*) => {
$(
pub fn $name(&self) -> bool {
self.is_kind($kind)
}
)*
}
}
impl Target {
pub fn is_kind(&self, name: TargetKind) -> bool {
self.kind.iter().any(|kind| kind == &name)
}
methods_target_is_kind! {
is_lib => TargetKind::Lib,
is_bin => TargetKind::Bin,
is_example => TargetKind::Example,
is_test => TargetKind::Test,
is_bench => TargetKind::Bench,
is_custom_build => TargetKind::CustomBuild,
is_proc_macro => TargetKind::ProcMacro,
is_cdylib => TargetKind::CDyLib,
is_dylib => TargetKind::DyLib,
is_rlib => TargetKind::RLib,
is_staticlib => TargetKind::StaticLib
}
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[non_exhaustive]
pub enum TargetKind {
#[serde(rename = "bench")]
Bench,
#[serde(rename = "bin")]
Bin,
#[serde(rename = "custom-build")]
CustomBuild,
#[serde(rename = "cdylib")]
CDyLib,
#[serde(rename = "dylib")]
DyLib,
#[serde(rename = "example")]
Example,
#[serde(rename = "lib")]
Lib,
#[serde(rename = "proc-macro")]
ProcMacro,
#[serde(rename = "rlib")]
RLib,
#[serde(rename = "staticlib")]
StaticLib,
#[serde(rename = "test")]
Test,
#[serde(untagged)]
Unknown(String),
}
impl From<&str> for TargetKind {
fn from(value: &str) -> Self {
match value {
"example" => TargetKind::Example,
"test" => TargetKind::Test,
"bench" => TargetKind::Bench,
"custom-build" => TargetKind::CustomBuild,
"bin" => TargetKind::Bin,
"lib" => TargetKind::Lib,
"rlib" => TargetKind::RLib,
"dylib" => TargetKind::DyLib,
"cdylib" => TargetKind::CDyLib,
"staticlib" => TargetKind::StaticLib,
"proc-macro" => TargetKind::ProcMacro,
x => TargetKind::Unknown(x.to_string()),
}
}
}
impl FromStr for TargetKind {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Ok(TargetKind::from(s))
}
}
impl fmt::Display for TargetKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Bench => "bench".fmt(f),
Self::Bin => "bin".fmt(f),
Self::CustomBuild => "custom-build".fmt(f),
Self::CDyLib => "cdylib".fmt(f),
Self::DyLib => "dylib".fmt(f),
Self::Example => "example".fmt(f),
Self::Lib => "lib".fmt(f),
Self::ProcMacro => "proc-macro".fmt(f),
Self::RLib => "rlib".fmt(f),
Self::StaticLib => "staticlib".fmt(f),
Self::Test => "test".fmt(f),
Self::Unknown(x) => x.fmt(f),
}
}
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[non_exhaustive]
pub enum CrateType {
#[serde(rename = "bin")]
Bin,
#[serde(rename = "cdylib")]
CDyLib,
#[serde(rename = "dylib")]
DyLib,
#[serde(rename = "lib")]
Lib,
#[serde(rename = "proc-macro")]
ProcMacro,
#[serde(rename = "rlib")]
RLib,
#[serde(rename = "staticlib")]
StaticLib,
#[serde(untagged)]
Unknown(String),
}
impl From<&str> for CrateType {
fn from(value: &str) -> Self {
match value {
"bin" => CrateType::Bin,
"lib" => CrateType::Lib,
"rlib" => CrateType::RLib,
"dylib" => CrateType::DyLib,
"cdylib" => CrateType::CDyLib,
"staticlib" => CrateType::StaticLib,
"proc-macro" => CrateType::ProcMacro,
x => CrateType::Unknown(x.to_string()),
}
}
}
impl FromStr for CrateType {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Ok(CrateType::from(s))
}
}
impl fmt::Display for CrateType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Bin => "bin".fmt(f),
Self::CDyLib => "cdylib".fmt(f),
Self::DyLib => "dylib".fmt(f),
Self::Lib => "lib".fmt(f),
Self::ProcMacro => "proc-macro".fmt(f),
Self::RLib => "rlib".fmt(f),
Self::StaticLib => "staticlib".fmt(f),
Self::Unknown(x) => x.fmt(f),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[non_exhaustive]
#[derive(Default)]
pub enum Edition {
#[serde(rename = "2015")]
#[default]
E2015,
#[serde(rename = "2018")]
E2018,
#[serde(rename = "2021")]
E2021,
#[serde(rename = "2024")]
E2024,
#[doc(hidden)]
#[serde(rename = "2027")]
_E2027,
#[doc(hidden)]
#[serde(rename = "2030")]
_E2030,
}
impl Edition {
pub fn as_str(&self) -> &'static str {
use Edition::*;
match self {
E2015 => "2015",
E2018 => "2018",
E2021 => "2021",
E2024 => "2024",
_E2027 => "2027",
_E2030 => "2030",
}
}
}
impl fmt::Display for Edition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
fn default_true() -> bool {
true
}
#[derive(Debug, Clone)]
pub enum CargoOpt {
AllFeatures,
NoDefaultFeatures,
SomeFeatures(Vec<String>),
}
#[derive(Debug, Clone, Default)]
pub struct MetadataCommand {
cargo_path: Option<PathBuf>,
manifest_path: Option<PathBuf>,
current_dir: Option<PathBuf>,
no_deps: bool,
features: Vec<String>,
all_features: bool,
no_default_features: bool,
other_options: Vec<String>,
env: BTreeMap<OsString, Option<OsString>>,
verbose: bool,
}
impl MetadataCommand {
pub fn new() -> MetadataCommand {
MetadataCommand::default()
}
pub fn cargo_path(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
self.cargo_path = Some(path.into());
self
}
pub fn manifest_path(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
self.manifest_path = Some(path.into());
self
}
pub fn current_dir(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
self.current_dir = Some(path.into());
self
}
pub fn no_deps(&mut self) -> &mut MetadataCommand {
self.no_deps = true;
self
}
pub fn features(&mut self, features: CargoOpt) -> &mut MetadataCommand {
match features {
CargoOpt::SomeFeatures(features) => self.features.extend(features),
CargoOpt::NoDefaultFeatures => {
assert!(
!self.no_default_features,
"Do not supply CargoOpt::NoDefaultFeatures more than once!"
);
self.no_default_features = true;
}
CargoOpt::AllFeatures => {
assert!(
!self.all_features,
"Do not supply CargoOpt::AllFeatures more than once!"
);
self.all_features = true;
}
}
self
}
pub fn other_options(&mut self, options: impl Into<Vec<String>>) -> &mut MetadataCommand {
self.other_options = options.into();
self
}
pub fn env<K: Into<OsString>, V: Into<OsString>>(
&mut self,
key: K,
val: V,
) -> &mut MetadataCommand {
self.env.insert(key.into(), Some(val.into()));
self
}
pub fn env_remove<K: Into<OsString>>(&mut self, key: K) -> &mut MetadataCommand {
self.env.insert(key.into(), None);
self
}
pub fn verbose(&mut self, verbose: bool) -> &mut MetadataCommand {
self.verbose = verbose;
self
}
pub fn cargo_command(&self) -> Command {
let cargo = self
.cargo_path
.clone()
.or_else(|| env::var("CARGO").map(PathBuf::from).ok())
.unwrap_or_else(|| PathBuf::from("cargo"));
let mut cmd = Command::new(cargo);
cmd.args(["metadata", "--format-version", "1"]);
if self.no_deps {
cmd.arg("--no-deps");
}
if let Some(path) = self.current_dir.as_ref() {
cmd.current_dir(path);
}
if !self.features.is_empty() {
cmd.arg("--features").arg(self.features.join(","));
}
if self.all_features {
cmd.arg("--all-features");
}
if self.no_default_features {
cmd.arg("--no-default-features");
}
if let Some(manifest_path) = &self.manifest_path {
cmd.arg("--manifest-path").arg(manifest_path.as_os_str());
}
cmd.args(&self.other_options);
for (key, val) in &self.env {
match val {
Some(val) => cmd.env(key, val),
None => cmd.env_remove(key),
};
}
cmd
}
pub fn parse<T: AsRef<str>>(data: T) -> Result<Metadata> {
let meta = serde_json::from_str(data.as_ref())?;
Ok(meta)
}
pub fn exec(&self) -> Result<Metadata> {
let mut command = self.cargo_command();
if self.verbose {
command.stderr(Stdio::inherit());
}
let output = command.output()?;
if !output.status.success() {
return Err(Error::CargoMetadata {
stderr: String::from_utf8(output.stderr)?,
});
}
let stdout = from_utf8(&output.stdout)?
.lines()
.find(|line| line.starts_with('{'))
.ok_or(Error::NoJson)?;
Self::parse(stdout)
}
}
fn deserialize_rust_version<'de, D>(
deserializer: D,
) -> std::result::Result<Option<Version>, D::Error>
where
D: Deserializer<'de>,
{
let mut buf = match Option::<String>::deserialize(deserializer)? {
None => return Ok(None),
Some(buf) => buf,
};
for char in buf.chars() {
if char == '-' {
return Err(serde::de::Error::custom(
"pre-release identifiers are not supported in rust-version",
));
} else if char == '+' {
return Err(serde::de::Error::custom(
"build metadata is not supported in rust-version",
));
}
}
if buf.matches('.').count() == 1 {
buf.push_str(".0");
}
Ok(Some(
Version::parse(&buf).map_err(serde::de::Error::custom)?,
))
}
#[cfg(test)]
mod test {
use semver::Version;
#[derive(Debug, serde::Deserialize)]
struct BareVersion(
#[serde(deserialize_with = "super::deserialize_rust_version")] Option<semver::Version>,
);
fn bare_version(str: &str) -> Version {
serde_json::from_str::<BareVersion>(&format!(r#""{}""#, str))
.unwrap()
.0
.unwrap()
}
fn bare_version_err(str: &str) -> String {
serde_json::from_str::<BareVersion>(&format!(r#""{}""#, str))
.unwrap_err()
.to_string()
}
#[test]
fn test_deserialize_rust_version() {
assert_eq!(bare_version("1.2"), Version::new(1, 2, 0));
assert_eq!(bare_version("1.2.0"), Version::new(1, 2, 0));
assert_eq!(
bare_version_err("1.2.0-alpha"),
"pre-release identifiers are not supported in rust-version"
);
assert_eq!(
bare_version_err("1.2.0+123"),
"build metadata is not supported in rust-version"
);
}
#[test]
fn package_name_eq() {
let my_package_name = super::PackageName::new("my_package");
assert_eq!(my_package_name, "my_package");
}
}