use crate::{
graph::{
cargo_version_matches, BuildTargetImpl, BuildTargetKindImpl, DepRequiredOrOptional,
DependencyReqImpl, NamedFeatureDep, OwnedBuildTargetId, PackageGraph, PackageGraphData,
PackageIx, PackageLinkImpl, PackageMetadataImpl, PackagePublishImpl, PackageSourceImpl,
WorkspaceImpl,
},
sorted_set::SortedSet,
Error, PackageId,
};
use ahash::AHashMap;
use camino::{Utf8Path, Utf8PathBuf};
use cargo_metadata::{
DepKindInfo, Dependency, DependencyKind, Metadata, Node, NodeDep, Package, Target,
};
use fixedbitset::FixedBitSet;
use indexmap::{IndexMap, IndexSet};
use once_cell::sync::OnceCell;
use petgraph::prelude::*;
use semver::{Version, VersionReq};
use smallvec::SmallVec;
use std::{
borrow::Cow,
cell::RefCell,
collections::{BTreeMap, HashSet},
rc::Rc,
};
use target_spec::TargetSpec;
impl PackageGraph {
pub(crate) fn build(mut metadata: Metadata) -> Result<Self, Box<Error>> {
let resolve_nodes = metadata.resolve.map(|r| r.nodes).unwrap_or_default();
let workspace_members: HashSet<_> = metadata
.workspace_members
.into_iter()
.map(PackageId::from_metadata)
.collect();
let workspace_root = metadata.workspace_root;
let mut build_state = GraphBuildState::new(
&mut metadata.packages,
resolve_nodes,
&workspace_root,
&workspace_members,
)?;
let packages: AHashMap<_, _> = metadata
.packages
.into_iter()
.map(|package| build_state.process_package(package))
.collect::<Result<_, _>>()?;
let dep_graph = build_state.finish();
let workspace = WorkspaceImpl::new(
workspace_root,
metadata.target_directory,
metadata.workspace_metadata,
&packages,
workspace_members,
)?;
Ok(Self {
dep_graph,
sccs: OnceCell::new(),
feature_graph: OnceCell::new(),
data: PackageGraphData {
packages,
workspace,
},
})
}
}
impl WorkspaceImpl {
fn new(
workspace_root: impl Into<Utf8PathBuf>,
target_directory: impl Into<Utf8PathBuf>,
metadata_table: serde_json::Value,
packages: &AHashMap<PackageId, PackageMetadataImpl>,
members: impl IntoIterator<Item = PackageId>,
) -> Result<Self, Box<Error>> {
use std::collections::btree_map::Entry;
let workspace_root = workspace_root.into();
let mut members_by_path = BTreeMap::new();
let mut members_by_name = BTreeMap::new();
for id in members {
let package_metadata = packages.get(&id).ok_or_else(|| {
Error::PackageGraphConstructError(format!("workspace member '{}' not found", id))
})?;
let workspace_path = match &package_metadata.source {
PackageSourceImpl::Workspace(path) => path,
_ => {
return Err(Error::PackageGraphConstructError(format!(
"workspace member '{}' at path {:?} not in workspace",
id, package_metadata.manifest_path,
))
.into());
}
};
members_by_path.insert(workspace_path.to_path_buf(), id.clone());
match members_by_name.entry(package_metadata.name.clone().into_boxed_str()) {
Entry::Vacant(vacant) => {
vacant.insert(id.clone());
}
Entry::Occupied(occupied) => {
return Err(Error::PackageGraphConstructError(format!(
"duplicate package name in workspace: '{}' is name for '{}' and '{}'",
occupied.key(),
occupied.get(),
id
))
.into());
}
}
}
Ok(Self {
root: workspace_root,
target_directory: target_directory.into(),
metadata_table,
members_by_path,
members_by_name,
#[cfg(feature = "proptest1")]
name_list: OnceCell::new(),
})
}
}
struct GraphBuildState<'a> {
dep_graph: Graph<PackageId, PackageLinkImpl, Directed, PackageIx>,
package_data: AHashMap<PackageId, Rc<PackageDataValue>>,
by_package_name: AHashMap<String, Vec<Rc<PackageDataValue>>>,
resolve_data: AHashMap<PackageId, Vec<NodeDep>>,
workspace_root: &'a Utf8Path,
workspace_members: &'a HashSet<PackageId>,
}
impl<'a> GraphBuildState<'a> {
fn new(
packages: &mut [Package],
resolve_nodes: Vec<Node>,
workspace_root: &'a Utf8Path,
workspace_members: &'a HashSet<PackageId>,
) -> Result<Self, Box<Error>> {
let edge_count = resolve_nodes
.iter()
.map(|node| node.deps.len())
.sum::<usize>();
let mut dep_graph = Graph::with_capacity(packages.len(), edge_count);
let all_package_data: AHashMap<_, _> = packages
.iter_mut()
.map(|package| PackageDataValue::new(package, &mut dep_graph))
.collect::<Result<_, _>>()?;
let mut by_package_name: AHashMap<String, Vec<Rc<PackageDataValue>>> =
AHashMap::with_capacity(all_package_data.len());
for package_data in all_package_data.values() {
by_package_name
.entry(package_data.name.clone())
.or_default()
.push(package_data.clone());
}
let resolve_data: AHashMap<_, _> = resolve_nodes
.into_iter()
.map(|node| {
(
PackageId::from_metadata(node.id),
node.deps,
)
})
.collect();
Ok(Self {
dep_graph,
package_data: all_package_data,
by_package_name,
resolve_data,
workspace_root,
workspace_members,
})
}
fn process_package(
&mut self,
package: Package,
) -> Result<(PackageId, PackageMetadataImpl), Box<Error>> {
let package_id = PackageId::from_metadata(package.id);
let (package_data, build_targets) =
self.package_data_and_remove_build_targets(&package_id)?;
let source = if self.workspace_members.contains(&package_id) {
PackageSourceImpl::Workspace(self.workspace_path(&package_id, &package.manifest_path)?)
} else if let Some(source) = package.source {
if source.is_crates_io() {
PackageSourceImpl::CratesIo
} else {
PackageSourceImpl::External(source.repr.into())
}
} else {
let dirname = match package.manifest_path.parent() {
Some(dirname) => dirname,
None => {
return Err(Error::PackageGraphConstructError(format!(
"package '{}': manifest path '{}' does not have parent",
package_id, package.manifest_path,
))
.into());
}
};
PackageSourceImpl::create_path(dirname, self.workspace_root)
};
let resolved_deps = self.resolve_data.remove(&package_id).unwrap_or_default();
let dep_resolver = DependencyResolver::new(
&package_id,
&self.package_data,
&self.by_package_name,
&package.dependencies,
);
for NodeDep {
name: resolved_name,
pkg,
dep_kinds,
..
} in resolved_deps
{
let dep_id = PackageId::from_metadata(pkg);
let (dep_data, deps) = dep_resolver.resolve(&resolved_name, &dep_id, &dep_kinds)?;
let link = PackageLinkImpl::new(&package_id, &resolved_name, deps)?;
self.dep_graph
.update_edge(package_data.package_ix, dep_data.package_ix, link);
}
let has_default_feature = package.features.contains_key("default");
let optional_deps: IndexSet<_> = package
.dependencies
.into_iter()
.filter_map(|dep| {
if dep.optional {
match dep.rename {
Some(rename) => Some(rename.into_boxed_str()),
None => Some(dep.name.into_boxed_str()),
}
} else {
None
}
})
.collect();
let mut seen_explicit = FixedBitSet::with_capacity(optional_deps.len());
let mut named_features: IndexMap<_, _> = package
.features
.into_iter()
.map(|(feature_name, deps)| {
let mut parsed_deps = SmallVec::with_capacity(deps.len());
for dep in deps {
let dep = NamedFeatureDep::from_cargo_string(dep);
if let NamedFeatureDep::OptionalDependency(d) = &dep {
let index = optional_deps.get_index_of(d.as_ref()).ok_or_else(|| {
Error::PackageGraphConstructError(format!(
"package '{}': named feature {} specifies 'dep:{d}', but {d} is not an optional dependency",
package_id,
feature_name,
d = d))
})?;
seen_explicit.set(index, true);
}
parsed_deps.push(dep);
}
Ok((feature_name.into_boxed_str(), parsed_deps))
})
.collect::<Result<_, Error>>()?;
for (index, dep) in optional_deps.iter().enumerate() {
if !seen_explicit.contains(index) {
named_features.insert(
dep.clone(),
std::iter::once(NamedFeatureDep::OptionalDependency(dep.clone())).collect(),
);
}
}
let rust_version_req = package
.rust_version
.as_ref()
.map(|rust_version| VersionReq {
comparators: vec![semver::Comparator {
op: semver::Op::GreaterEq,
major: rust_version.major,
minor: Some(rust_version.minor),
patch: Some(rust_version.patch),
pre: semver::Prerelease::EMPTY,
}],
});
Ok((
package_id,
PackageMetadataImpl {
name: package.name,
version: package.version,
authors: package.authors,
description: package.description.map(|s| s.into()),
license: package.license.map(|s| s.into()),
license_file: package.license_file.map(|f| f.into()),
manifest_path: package.manifest_path.into(),
categories: package.categories,
keywords: package.keywords,
readme: package.readme.map(|s| s.into()),
repository: package.repository.map(|s| s.into()),
homepage: package.homepage.map(|s| s.into()),
documentation: package.documentation.map(|s| s.into()),
edition: package.edition.to_string().into_boxed_str(),
metadata_table: package.metadata,
links: package.links.map(|s| s.into()),
publish: PackagePublishImpl::new(package.publish),
default_run: package.default_run.map(|s| s.into()),
rust_version: package.rust_version,
rust_version_req,
named_features,
optional_deps,
package_ix: package_data.package_ix,
source,
build_targets,
has_default_feature,
},
))
}
fn package_data_and_remove_build_targets(
&self,
id: &PackageId,
) -> Result<(Rc<PackageDataValue>, BuildTargetMap), Box<Error>> {
let package_data = self.package_data.get(id).ok_or_else(|| {
Error::PackageGraphConstructError(format!("no package data found for package '{}'", id))
})?;
let package_data = package_data.clone();
let build_targets = std::mem::take(&mut *package_data.build_targets.borrow_mut());
Ok((package_data, build_targets))
}
fn workspace_path(
&self,
id: &PackageId,
manifest_path: &Utf8Path,
) -> Result<Box<Utf8Path>, Box<Error>> {
let workspace_path = pathdiff::diff_utf8_paths(manifest_path, self.workspace_root)
.ok_or_else(|| {
Error::PackageGraphConstructError(format!(
"failed to find path from workspace (root: {}) to member '{id}' at path {manifest_path}",
self.workspace_root
))
})?;
let workspace_path = workspace_path.parent().ok_or_else(|| {
Error::PackageGraphConstructError(format!(
"workspace member '{}' has invalid manifest path {:?}",
id, manifest_path
))
})?;
Ok(convert_relative_forward_slashes(workspace_path).into_boxed_path())
}
fn finish(self) -> Graph<PackageId, PackageLinkImpl, Directed, PackageIx> {
self.dep_graph
}
}
#[derive(Debug)]
struct PackageDataValue {
package_ix: NodeIndex<PackageIx>,
name: String,
resolved_name: ResolvedName,
build_targets: RefCell<BuildTargetMap>,
version: Version,
}
impl PackageDataValue {
fn new(
package: &mut Package,
dep_graph: &mut Graph<PackageId, PackageLinkImpl, Directed, PackageIx>,
) -> Result<(PackageId, Rc<Self>), Box<Error>> {
let package_id = PackageId::from_metadata(package.id.clone());
let package_ix = dep_graph.add_node(package_id.clone());
let mut build_targets = BuildTargets::new(&package_id);
for build_target in package.targets.drain(..) {
build_targets.add(build_target)?;
}
let build_targets = build_targets.finish();
let resolved_name = match build_targets.get(&OwnedBuildTargetId::Library) {
Some(target) => {
let lib_name = target
.lib_name
.as_deref()
.expect("lib_name is always specified for library targets");
if lib_name != package.name {
ResolvedName::LibNameSpecified(lib_name.to_string())
} else {
ResolvedName::LibNameNotSpecified(lib_name.replace('-', "_"))
}
}
None => {
ResolvedName::NoLibTarget
}
};
let value = PackageDataValue {
package_ix,
name: package.name.clone(),
resolved_name,
build_targets: RefCell::new(build_targets),
version: package.version.clone(),
};
Ok((package_id, Rc::new(value)))
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
enum ResolvedName {
LibNameSpecified(String),
LibNameNotSpecified(String),
NoLibTarget,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
struct ReqResolvedName<'g> {
renamed: Option<String>,
resolved_name: &'g ResolvedName,
}
impl<'g> ReqResolvedName<'g> {
fn new(renamed: Option<&str>, resolved_name: &'g ResolvedName) -> Self {
Self {
renamed: renamed.map(|s| s.replace('-', "_")),
resolved_name,
}
}
fn matches(&self, name: &str) -> bool {
if let Some(rename) = &self.renamed {
if rename == name {
return true;
}
}
match self.resolved_name {
ResolvedName::LibNameSpecified(resolved_name) => *resolved_name == name,
ResolvedName::LibNameNotSpecified(resolved_name) => *resolved_name == name,
ResolvedName::NoLibTarget => {
name.is_empty()
}
}
}
}
impl PackageSourceImpl {
fn create_path(path: &Utf8Path, workspace_root: &Utf8Path) -> Self {
let path_diff =
pathdiff::diff_utf8_paths(path, workspace_root).expect("workspace root is absolute");
Self::Path(convert_relative_forward_slashes(path_diff).into_boxed_path())
}
}
impl NamedFeatureDep {
fn from_cargo_string(input: impl Into<String>) -> Self {
let input = input.into();
match input.split_once('/') {
Some((dep_name, feature)) => {
if let Some(dep_name_without_q) = dep_name.strip_suffix('?') {
Self::dep_named_feature(dep_name_without_q, feature, true)
} else {
Self::dep_named_feature(dep_name, feature, false)
}
}
None => match input.strip_prefix("dep:") {
Some(dep_name) => Self::optional_dependency(dep_name),
None => Self::named_feature(input),
},
}
}
}
type BuildTargetMap = BTreeMap<OwnedBuildTargetId, BuildTargetImpl>;
struct BuildTargets<'a> {
package_id: &'a PackageId,
targets: BuildTargetMap,
}
impl<'a> BuildTargets<'a> {
fn new(package_id: &'a PackageId) -> Self {
Self {
package_id,
targets: BTreeMap::new(),
}
}
fn add(&mut self, target: Target) -> Result<(), Box<Error>> {
use std::collections::btree_map::Entry;
let mut target_kinds = target
.kind
.into_iter()
.map(|kind| kind.to_string())
.collect::<Vec<_>>();
let target_name = target.name.into_boxed_str();
let crate_types = SortedSet::new(
target
.crate_types
.into_iter()
.map(|ct| ct.to_string())
.collect::<Vec<_>>(),
);
if target_kinds.len() > 1 && Self::is_proc_macro(&target_kinds) {
return Err(Error::PackageGraphConstructError(format!(
"for package {}, proc-macro mixed with other kinds ({:?})",
self.package_id, target_kinds
))
.into());
}
if crate_types.len() > 1 && Self::is_proc_macro(&crate_types) {
return Err(Error::PackageGraphConstructError(format!(
"for package {}, proc-macro mixed with other crate types ({})",
self.package_id, crate_types
))
.into());
}
let (id, kind, lib_name) = if target_kinds.len() > 1 {
(
OwnedBuildTargetId::Library,
BuildTargetKindImpl::LibraryOrExample(crate_types),
Some(target_name),
)
} else if let Some(target_kind) = target_kinds.pop() {
let (id, lib_name) = match target_kind.as_str() {
"custom-build" => (OwnedBuildTargetId::BuildScript, Some(target_name)),
"bin" => (OwnedBuildTargetId::Binary(target_name), None),
"example" => (OwnedBuildTargetId::Example(target_name), None),
"test" => (OwnedBuildTargetId::Test(target_name), None),
"bench" => (OwnedBuildTargetId::Benchmark(target_name), None),
_other => {
(OwnedBuildTargetId::Library, Some(target_name))
}
};
let kind = match &id {
OwnedBuildTargetId::Library => {
if crate_types.as_slice() == ["proc-macro"] {
BuildTargetKindImpl::ProcMacro
} else {
BuildTargetKindImpl::LibraryOrExample(crate_types)
}
}
OwnedBuildTargetId::Example(_) => {
BuildTargetKindImpl::LibraryOrExample(crate_types)
}
_ => {
if crate_types.as_slice() != ["bin"] {
return Err(Error::PackageGraphConstructError(format!(
"for package {}: build target '{:?}' has invalid crate types '{}'",
self.package_id, id, crate_types,
))
.into());
}
BuildTargetKindImpl::Binary
}
};
(id, kind, lib_name)
} else {
return Err(Error::PackageGraphConstructError(format!(
"for package ID '{}': build target '{}' has no kinds",
self.package_id, target_name
))
.into());
};
match self.targets.entry(id) {
Entry::Occupied(occupied) => {
return Err(Error::PackageGraphConstructError(format!(
"for package ID '{}': duplicate build targets for {:?}",
self.package_id,
occupied.key()
))
.into());
}
Entry::Vacant(vacant) => {
vacant.insert(BuildTargetImpl {
kind,
lib_name,
required_features: target.required_features,
path: target.src_path.into_boxed_path(),
edition: target.edition.to_string().into_boxed_str(),
doc_tests: target.doctest,
});
}
}
Ok(())
}
fn is_proc_macro(list: &[String]) -> bool {
list.iter().any(|kind| *kind == "proc-macro")
}
fn finish(self) -> BuildTargetMap {
self.targets
}
}
struct DependencyResolver<'g> {
from_id: &'g PackageId,
package_data: &'g AHashMap<PackageId, Rc<PackageDataValue>>,
dep_reqs: DependencyReqs<'g>,
}
impl<'g> DependencyResolver<'g> {
fn new(
from_id: &'g PackageId,
package_data: &'g AHashMap<PackageId, Rc<PackageDataValue>>,
by_package_name: &'g AHashMap<String, Vec<Rc<PackageDataValue>>>,
package_deps: impl IntoIterator<Item = &'g Dependency>,
) -> Self {
let mut dep_reqs = DependencyReqs::default();
for dep in package_deps {
let Some(packages) = by_package_name.get(&dep.name) else {
continue;
};
for package in packages {
if cargo_version_matches(&dep.req, &package.version) {
let req_resolved_name =
ReqResolvedName::new(dep.rename.as_deref(), &package.resolved_name);
dep_reqs.push(req_resolved_name, dep);
}
}
}
Self {
from_id,
package_data,
dep_reqs,
}
}
fn resolve<'a>(
&'a self,
resolved_name: &'a str,
dep_id: &PackageId,
dep_kinds: &'a [DepKindInfo],
) -> Result<
(
&'g Rc<PackageDataValue>,
impl Iterator<Item = &'g Dependency> + 'a,
),
Error,
> {
let dep_data = self.package_data.get(dep_id).ok_or_else(|| {
Error::PackageGraphConstructError(format!(
"{}: no package data found for dependency '{}'",
self.from_id, dep_id
))
})?;
Ok((
dep_data,
self.dep_reqs
.matches_for(resolved_name, dep_data, dep_kinds),
))
}
}
#[derive(Clone, Debug, Default)]
struct DependencyReqs<'g> {
reqs: Vec<(ReqResolvedName<'g>, &'g Dependency)>,
}
impl<'g> DependencyReqs<'g> {
fn push(&mut self, resolved_name: ReqResolvedName<'g>, dependency: &'g Dependency) {
self.reqs.push((resolved_name, dependency));
}
fn matches_for<'a>(
&'a self,
resolved_name: &'a str,
package_data: &'a PackageDataValue,
dep_kinds: &'a [DepKindInfo],
) -> impl Iterator<Item = &'g Dependency> + 'a {
self.reqs
.iter()
.filter_map(move |(req_resolved_name, dep)| {
if !req_resolved_name.matches(resolved_name) {
return None;
}
if !cargo_version_matches(&dep.req, &package_data.version) {
return None;
}
if dep_kinds.is_empty() {
return Some(*dep);
}
dep_kinds
.iter()
.any(|dep_kind| dep_kind.kind == dep.kind && dep_kind.target == dep.target)
.then_some(*dep)
})
}
}
impl PackageLinkImpl {
fn new<'a>(
from_id: &PackageId,
resolved_name: &str,
deps: impl IntoIterator<Item = &'a Dependency>,
) -> Result<Self, Box<Error>> {
let mut version_req = None;
let mut normal = DependencyReqImpl::default();
let mut build = DependencyReqImpl::default();
let mut dev = DependencyReqImpl::default();
let mut dep_name: Option<String> = None;
for dep in deps {
let rename_or_name = dep.rename.as_ref().unwrap_or(&dep.name);
match &dep_name {
Some(dn) => {
if dn != rename_or_name {
}
}
None => {
dep_name = Some(rename_or_name.clone());
}
}
if dep.kind == DependencyKind::Development && dep.optional {
return Err(Error::PackageGraphConstructError(format!(
"for package '{}': dev-dependency '{}' marked optional",
from_id,
dep_name.expect("dep_name set above"),
))
.into());
}
if version_req.is_none() {
version_req = Some(dep.req.clone());
}
match dep.kind {
DependencyKind::Normal => normal.add_instance(from_id, dep)?,
DependencyKind::Build => build.add_instance(from_id, dep)?,
DependencyKind::Development => dev.add_instance(from_id, dep)?,
_ => {
continue;
}
};
}
let dep_name = dep_name.ok_or_else(|| {
Error::PackageGraphConstructError(format!(
"for package '{}': no dependencies found matching '{}'",
from_id, resolved_name,
))
})?;
let version_req = version_req.unwrap_or_else(|| {
panic!(
"requires at least one dependency instance: \
from `{from_id}` to `{dep_name}` (resolved name `{resolved_name}`)"
)
});
Ok(Self {
dep_name,
resolved_name: resolved_name.into(),
version_req,
normal,
build,
dev,
})
}
}
impl DependencyReqImpl {
fn add_instance(&mut self, from_id: &PackageId, dep: &Dependency) -> Result<(), Box<Error>> {
if dep.optional {
self.optional.add_instance(from_id, dep)
} else {
self.required.add_instance(from_id, dep)
}
}
}
impl DepRequiredOrOptional {
fn add_instance(&mut self, from_id: &PackageId, dep: &Dependency) -> Result<(), Box<Error>> {
let target_spec = match dep.target.as_ref() {
Some(spec_or_triple) => {
let spec_or_triple = format!("{}", spec_or_triple);
let target_spec: TargetSpec = spec_or_triple.parse().map_err(|err| {
Error::PackageGraphConstructError(format!(
"for package '{}': for dependency '{}', parsing target '{}' failed: {}",
from_id, dep.name, spec_or_triple, err
))
})?;
Some(target_spec)
}
None => None,
};
self.build_if.add_spec(target_spec.as_ref());
if dep.uses_default_features {
self.default_features_if.add_spec(target_spec.as_ref());
} else {
self.no_default_features_if.add_spec(target_spec.as_ref());
}
for feature in &dep.features {
self.feature_targets
.entry(feature.clone())
.or_default()
.add_spec(target_spec.as_ref());
}
Ok(())
}
}
impl PackagePublishImpl {
fn new(registries: Option<Vec<String>>) -> Self {
match registries {
None => PackagePublishImpl::Unrestricted,
Some(registries) => PackagePublishImpl::Registries(registries.into_boxed_slice()),
}
}
}
#[track_caller]
fn convert_relative_forward_slashes<'a>(rel_path: impl Into<Cow<'a, Utf8Path>>) -> Utf8PathBuf {
let rel_path = rel_path.into();
cfg_if::cfg_if! { if #[cfg(windows)] {
if rel_path.is_relative() {
rel_path.as_str().replace("\\", "/").into()
} else {
rel_path.into_owned()
}
} else {
rel_path.into_owned()
}}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_named_feature_dependency() {
assert_eq!(
NamedFeatureDep::from_cargo_string("dep/bar"),
NamedFeatureDep::dep_named_feature("dep", "bar", false),
);
assert_eq!(
NamedFeatureDep::from_cargo_string("dep?/bar"),
NamedFeatureDep::dep_named_feature("dep", "bar", true),
);
assert_eq!(
NamedFeatureDep::from_cargo_string("dep:bar"),
NamedFeatureDep::optional_dependency("bar"),
);
assert_eq!(
NamedFeatureDep::from_cargo_string("foo-bar"),
NamedFeatureDep::named_feature("foo-bar"),
);
}
#[test]
fn test_create_path() {
assert_eq!(
PackageSourceImpl::create_path("/data/foo".as_ref(), "/data/bar".as_ref()),
PackageSourceImpl::Path("../foo".into())
);
assert_eq!(
PackageSourceImpl::create_path("/tmp/foo".as_ref(), "/data/bar".as_ref()),
PackageSourceImpl::Path("../../tmp/foo".into())
);
}
#[test]
fn test_convert_relative_forward_slashes() {
let components = vec!["..", "..", "foo", "bar", "baz.txt"];
let path: Utf8PathBuf = components.into_iter().collect();
let path = convert_relative_forward_slashes(path);
assert_eq!(path.as_str(), "../../foo/bar/baz.txt");
}
#[track_caller]
fn verify_result_of_diff_utf8_paths(
path_manifest: &str,
path_workspace_root: &str,
expected_relative_path: &str,
) {
let relative_path = pathdiff::diff_utf8_paths(
Utf8Path::new(path_manifest),
Utf8Path::new(path_workspace_root),
)
.unwrap();
assert_eq!(relative_path, expected_relative_path);
}
#[test]
fn test_workspace_path_out_of_pocket() {
verify_result_of_diff_utf8_paths(
"/workspace/a/b/Crate/Cargo.toml",
"/workspace/a/b/.cargo/workspace",
r"../../Crate/Cargo.toml",
);
}
#[cfg(windows)] mod windows {
use super::*;
#[test]
fn test_create_path_windows() {
assert_eq!(
PackageSourceImpl::create_path("C:\\data\\foo".as_ref(), "C:\\data\\bar".as_ref()),
PackageSourceImpl::Path("../foo".into())
);
assert_eq!(
PackageSourceImpl::create_path("D:\\tmp\\foo".as_ref(), "C:\\data\\bar".as_ref()),
PackageSourceImpl::Path("D:\\tmp\\foo".into())
);
}
#[test]
fn test_convert_relative_forward_slashes_absolute() {
let components = vec![r"D:\", "X", "..", "foo", "bar", "baz.txt"];
let path: Utf8PathBuf = components.into_iter().collect();
let path = convert_relative_forward_slashes(path);
assert_eq!(path.as_str(), r"D:\X\..\foo\bar\baz.txt");
}
#[test]
fn test_workspace_path_out_of_pocket_on_windows_same_driver() {
verify_result_of_diff_utf8_paths(
r"C:\workspace\a\b\Crate\Cargo.toml",
r"C:\workspace\a\b\.cargo\workspace",
r"..\..\Crate\Cargo.toml",
);
}
#[test]
fn test_workspace_path_out_of_pocket_on_windows_different_driver() {
verify_result_of_diff_utf8_paths(
r"D:\workspace\a\b\Crate\Cargo.toml",
r"C:\workspace\a\b\.cargo\workspace",
r"D:\workspace\a\b\Crate\Cargo.toml",
);
}
}
}