2024-07-14 14:19:15 +01:00
|
|
|
use crate::{
|
|
|
|
lockfile::{insert_node, DependencyGraph, DependencyGraphNode},
|
|
|
|
manifest::DependencyType,
|
|
|
|
names::PackageNames,
|
|
|
|
source::{
|
2024-07-28 17:19:54 +01:00
|
|
|
pesde::PesdePackageSource,
|
|
|
|
specifiers::DependencySpecifiers,
|
|
|
|
traits::{PackageRef, PackageSource},
|
|
|
|
version_id::VersionId,
|
|
|
|
PackageSources,
|
2024-07-14 14:19:15 +01:00
|
|
|
},
|
|
|
|
Project, DEFAULT_INDEX_NAME,
|
|
|
|
};
|
|
|
|
use std::collections::{HashMap, HashSet, VecDeque};
|
|
|
|
|
|
|
|
impl Project {
|
2024-08-03 21:18:38 +01:00
|
|
|
/// Create a dependency graph from the project's manifest
|
2024-07-14 14:19:15 +01:00
|
|
|
pub fn dependency_graph(
|
|
|
|
&self,
|
|
|
|
previous_graph: Option<&DependencyGraph>,
|
2024-07-17 18:38:01 +01:00
|
|
|
refreshed_sources: &mut HashSet<PackageSources>,
|
2024-07-14 14:19:15 +01:00
|
|
|
) -> Result<DependencyGraph, Box<errors::DependencyGraphError>> {
|
|
|
|
let manifest = self.deser_manifest().map_err(|e| Box::new(e.into()))?;
|
|
|
|
|
2024-07-17 18:38:01 +01:00
|
|
|
let mut all_specifiers = manifest
|
2024-07-14 14:19:15 +01:00
|
|
|
.all_dependencies()
|
2024-07-17 18:38:01 +01:00
|
|
|
.map_err(|e| Box::new(e.into()))?
|
2024-07-14 14:19:15 +01:00
|
|
|
.into_iter()
|
|
|
|
.map(|(alias, (spec, ty))| ((spec, ty), alias))
|
|
|
|
.collect::<HashMap<_, _>>();
|
|
|
|
|
|
|
|
let mut graph = DependencyGraph::default();
|
|
|
|
|
|
|
|
if let Some(previous_graph) = previous_graph {
|
|
|
|
for (name, versions) in previous_graph {
|
|
|
|
for (version, node) in versions {
|
|
|
|
let Some((_, specifier)) = &node.direct else {
|
|
|
|
// this is not a direct dependency, will be added if it's still being used later
|
|
|
|
continue;
|
|
|
|
};
|
|
|
|
|
2024-09-03 15:01:48 +01:00
|
|
|
if matches!(specifier, DependencySpecifiers::Workspace(_)) {
|
|
|
|
// workspace dependencies must always be resolved brand new
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-10-15 22:52:00 +01:00
|
|
|
let Some(alias) = all_specifiers.remove(&(specifier.clone(), node.ty)) else {
|
2024-07-23 23:53:34 +01:00
|
|
|
log::debug!(
|
|
|
|
"dependency {name}@{version} from old dependency graph is no longer in the manifest",
|
|
|
|
);
|
2024-07-17 18:38:01 +01:00
|
|
|
continue;
|
2024-10-15 22:52:00 +01:00
|
|
|
};
|
2024-07-14 14:19:15 +01:00
|
|
|
|
|
|
|
log::debug!("resolved {}@{} from old dependency graph", name, version);
|
2024-07-22 18:40:30 +01:00
|
|
|
insert_node(
|
|
|
|
&mut graph,
|
|
|
|
name.clone(),
|
|
|
|
version.clone(),
|
2024-10-15 22:52:00 +01:00
|
|
|
DependencyGraphNode {
|
|
|
|
direct: Some((alias.clone(), specifier.clone())),
|
|
|
|
..node.clone()
|
|
|
|
},
|
2024-07-22 18:40:30 +01:00
|
|
|
true,
|
|
|
|
);
|
2024-07-14 14:19:15 +01:00
|
|
|
|
|
|
|
let mut queue = node
|
|
|
|
.dependencies
|
|
|
|
.iter()
|
|
|
|
.map(|(name, (version, _))| (name, version, 0usize))
|
|
|
|
.collect::<VecDeque<_>>();
|
|
|
|
|
|
|
|
while let Some((dep_name, dep_version, depth)) = queue.pop_front() {
|
|
|
|
if let Some(dep_node) = previous_graph
|
|
|
|
.get(dep_name)
|
|
|
|
.and_then(|v| v.get(dep_version))
|
|
|
|
{
|
|
|
|
log::debug!(
|
|
|
|
"{}resolved dependency {}@{} from {}@{}",
|
|
|
|
"\t".repeat(depth),
|
|
|
|
dep_name,
|
|
|
|
dep_version,
|
|
|
|
name,
|
|
|
|
version
|
|
|
|
);
|
|
|
|
insert_node(
|
|
|
|
&mut graph,
|
|
|
|
dep_name.clone(),
|
|
|
|
dep_version.clone(),
|
|
|
|
dep_node.clone(),
|
2024-07-22 18:40:30 +01:00
|
|
|
false,
|
2024-07-14 14:19:15 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
dep_node
|
|
|
|
.dependencies
|
|
|
|
.iter()
|
|
|
|
.map(|(name, (version, _))| (name, version, depth + 1))
|
|
|
|
.for_each(|dep| queue.push_back(dep));
|
|
|
|
} else {
|
|
|
|
log::warn!(
|
|
|
|
"dependency {}@{} from {}@{} not found in previous graph",
|
|
|
|
dep_name,
|
|
|
|
dep_version,
|
|
|
|
name,
|
|
|
|
version
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-17 18:38:01 +01:00
|
|
|
let mut queue = all_specifiers
|
2024-07-14 14:19:15 +01:00
|
|
|
.into_iter()
|
2024-07-22 21:00:09 +01:00
|
|
|
.map(|((spec, ty), alias)| {
|
|
|
|
(
|
|
|
|
alias.to_string(),
|
|
|
|
spec,
|
|
|
|
ty,
|
2024-07-22 22:16:04 +01:00
|
|
|
None::<(PackageNames, VersionId)>,
|
2024-07-22 21:00:09 +01:00
|
|
|
vec![alias.to_string()],
|
|
|
|
false,
|
2024-07-22 22:16:04 +01:00
|
|
|
manifest.target.kind(),
|
2024-07-22 21:00:09 +01:00
|
|
|
)
|
|
|
|
})
|
2024-07-14 14:19:15 +01:00
|
|
|
.collect::<VecDeque<_>>();
|
|
|
|
|
2024-07-22 22:16:04 +01:00
|
|
|
while let Some((alias, specifier, ty, dependant, path, overridden, target)) =
|
|
|
|
queue.pop_front()
|
|
|
|
{
|
2024-07-22 21:00:09 +01:00
|
|
|
let depth = path.len() - 1;
|
|
|
|
|
2024-07-14 14:19:15 +01:00
|
|
|
log::debug!(
|
|
|
|
"{}resolving {specifier} ({alias}) from {dependant:?}",
|
|
|
|
"\t".repeat(depth)
|
|
|
|
);
|
|
|
|
let source = match &specifier {
|
|
|
|
DependencySpecifiers::Pesde(specifier) => {
|
2024-07-22 21:00:09 +01:00
|
|
|
let index_url = if depth == 0 || overridden {
|
2024-07-14 14:19:15 +01:00
|
|
|
let index_name = specifier.index.as_deref().unwrap_or(DEFAULT_INDEX_NAME);
|
2024-07-24 12:19:20 +01:00
|
|
|
|
|
|
|
manifest
|
|
|
|
.indices
|
|
|
|
.get(index_name)
|
|
|
|
.ok_or(errors::DependencyGraphError::IndexNotFound(
|
|
|
|
index_name.to_string(),
|
|
|
|
))?
|
|
|
|
.clone()
|
2024-07-14 14:19:15 +01:00
|
|
|
} else {
|
|
|
|
let index_url = specifier.index.clone().unwrap();
|
|
|
|
|
|
|
|
index_url
|
|
|
|
.clone()
|
|
|
|
.try_into()
|
2024-07-24 12:19:20 +01:00
|
|
|
// specifiers in indices store the index url in this field
|
|
|
|
.unwrap()
|
2024-07-14 14:19:15 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
PackageSources::Pesde(PesdePackageSource::new(index_url))
|
|
|
|
}
|
2024-08-08 16:59:59 +01:00
|
|
|
#[cfg(feature = "wally-compat")]
|
|
|
|
DependencySpecifiers::Wally(specifier) => {
|
|
|
|
let index_url = if depth == 0 || overridden {
|
|
|
|
let index_name = specifier.index.as_deref().unwrap_or(DEFAULT_INDEX_NAME);
|
|
|
|
|
|
|
|
manifest
|
|
|
|
.wally_indices
|
|
|
|
.get(index_name)
|
|
|
|
.ok_or(errors::DependencyGraphError::WallyIndexNotFound(
|
|
|
|
index_name.to_string(),
|
|
|
|
))?
|
|
|
|
.clone()
|
|
|
|
} else {
|
|
|
|
let index_url = specifier.index.clone().unwrap();
|
|
|
|
|
|
|
|
index_url
|
|
|
|
.clone()
|
|
|
|
.try_into()
|
|
|
|
// specifiers in indices store the index url in this field
|
|
|
|
.unwrap()
|
|
|
|
};
|
2024-08-08 19:37:51 +01:00
|
|
|
|
2024-08-08 16:59:59 +01:00
|
|
|
PackageSources::Wally(crate::source::wally::WallyPackageSource::new(index_url))
|
|
|
|
}
|
2024-08-11 15:16:25 +01:00
|
|
|
DependencySpecifiers::Git(specifier) => PackageSources::Git(
|
|
|
|
crate::source::git::GitPackageSource::new(specifier.repo.clone()),
|
|
|
|
),
|
2024-09-03 15:01:48 +01:00
|
|
|
DependencySpecifiers::Workspace(_) => {
|
|
|
|
PackageSources::Workspace(crate::source::workspace::WorkspacePackageSource)
|
|
|
|
}
|
2024-07-14 14:19:15 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
if refreshed_sources.insert(source.clone()) {
|
|
|
|
source.refresh(self).map_err(|e| Box::new(e.into()))?;
|
|
|
|
}
|
|
|
|
|
|
|
|
let (name, resolved) = source
|
2024-07-22 22:16:04 +01:00
|
|
|
.resolve(&specifier, self, target)
|
2024-07-14 14:19:15 +01:00
|
|
|
.map_err(|e| Box::new(e.into()))?;
|
|
|
|
|
2024-07-22 22:16:04 +01:00
|
|
|
let Some(target_version_id) = graph
|
2024-07-14 14:19:15 +01:00
|
|
|
.get(&name)
|
|
|
|
.and_then(|versions| {
|
|
|
|
versions
|
|
|
|
.keys()
|
|
|
|
// only consider versions that are compatible with the specifier
|
|
|
|
.filter(|ver| resolved.contains_key(ver))
|
|
|
|
.max()
|
|
|
|
})
|
|
|
|
.or_else(|| resolved.last_key_value().map(|(ver, _)| ver))
|
|
|
|
.cloned()
|
|
|
|
else {
|
2024-07-22 22:16:04 +01:00
|
|
|
return Err(Box::new(errors::DependencyGraphError::NoMatchingVersion(
|
2024-08-12 00:17:26 +01:00
|
|
|
format!("{specifier} ({target})"),
|
2024-07-22 22:16:04 +01:00
|
|
|
)));
|
2024-07-14 14:19:15 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
let ty = if depth == 0 && ty == DependencyType::Peer {
|
|
|
|
DependencyType::Standard
|
|
|
|
} else {
|
|
|
|
ty
|
|
|
|
};
|
|
|
|
|
2024-07-22 22:16:04 +01:00
|
|
|
if let Some((dependant_name, dependant_version_id)) = dependant {
|
2024-07-14 14:19:15 +01:00
|
|
|
graph
|
|
|
|
.get_mut(&dependant_name)
|
2024-07-22 22:16:04 +01:00
|
|
|
.and_then(|versions| versions.get_mut(&dependant_version_id))
|
2024-07-14 14:19:15 +01:00
|
|
|
.and_then(|node| {
|
|
|
|
node.dependencies
|
2024-07-22 22:16:04 +01:00
|
|
|
.insert(name.clone(), (target_version_id.clone(), alias.clone()))
|
2024-07-14 14:19:15 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-08-11 15:16:25 +01:00
|
|
|
let pkg_ref = &resolved[&target_version_id];
|
|
|
|
|
2024-07-14 14:19:15 +01:00
|
|
|
if let Some(already_resolved) = graph
|
|
|
|
.get_mut(&name)
|
2024-07-22 22:16:04 +01:00
|
|
|
.and_then(|versions| versions.get_mut(&target_version_id))
|
2024-07-14 14:19:15 +01:00
|
|
|
{
|
|
|
|
log::debug!(
|
|
|
|
"{}{}@{} already resolved",
|
|
|
|
"\t".repeat(depth),
|
|
|
|
name,
|
2024-07-22 22:16:04 +01:00
|
|
|
target_version_id
|
2024-07-14 14:19:15 +01:00
|
|
|
);
|
|
|
|
|
2024-09-04 18:48:37 +01:00
|
|
|
if std::mem::discriminant(&already_resolved.pkg_ref)
|
|
|
|
!= std::mem::discriminant(pkg_ref)
|
2024-08-11 15:16:25 +01:00
|
|
|
{
|
|
|
|
log::warn!(
|
|
|
|
"resolved package {name}@{target_version_id} has a different source than the previously resolved one, this may cause issues",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-07-14 14:19:15 +01:00
|
|
|
if already_resolved.ty == DependencyType::Peer && ty == DependencyType::Standard {
|
|
|
|
already_resolved.ty = ty;
|
|
|
|
}
|
|
|
|
|
2024-09-06 22:38:44 +01:00
|
|
|
if already_resolved.direct.is_none() && depth == 0 {
|
|
|
|
already_resolved.direct = Some((alias.clone(), specifier.clone()));
|
|
|
|
}
|
|
|
|
|
2024-07-14 14:19:15 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
let node = DependencyGraphNode {
|
|
|
|
direct: if depth == 0 {
|
|
|
|
Some((alias.clone(), specifier.clone()))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
},
|
|
|
|
pkg_ref: pkg_ref.clone(),
|
|
|
|
dependencies: Default::default(),
|
|
|
|
ty,
|
|
|
|
};
|
|
|
|
insert_node(
|
|
|
|
&mut graph,
|
|
|
|
name.clone(),
|
2024-07-22 22:16:04 +01:00
|
|
|
target_version_id.clone(),
|
2024-07-14 14:19:15 +01:00
|
|
|
node.clone(),
|
2024-07-22 18:40:30 +01:00
|
|
|
depth == 0,
|
2024-07-14 14:19:15 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
log::debug!(
|
|
|
|
"{}resolved {}@{} from new dependency graph",
|
|
|
|
"\t".repeat(depth),
|
|
|
|
name,
|
2024-07-22 22:16:04 +01:00
|
|
|
target_version_id
|
2024-07-14 14:19:15 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
for (dependency_alias, (dependency_spec, dependency_ty)) in
|
|
|
|
pkg_ref.dependencies().clone()
|
|
|
|
{
|
|
|
|
if dependency_ty == DependencyType::Dev {
|
2024-08-14 18:55:58 +01:00
|
|
|
// dev dependencies of dependencies are to be ignored
|
2024-07-14 14:19:15 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-07-22 21:00:09 +01:00
|
|
|
let overridden = manifest.overrides.iter().find_map(|(key, spec)| {
|
|
|
|
key.0.iter().find_map(|override_path| {
|
|
|
|
// if the path up until the last element is the same as the current path,
|
|
|
|
// and the last element in the path is the dependency alias,
|
|
|
|
// then the specifier is to be overridden
|
|
|
|
(path.len() == override_path.len() - 1
|
|
|
|
&& path == override_path[..override_path.len() - 1]
|
|
|
|
&& override_path.last() == Some(&dependency_alias))
|
|
|
|
.then_some(spec)
|
|
|
|
})
|
|
|
|
});
|
|
|
|
|
2024-07-23 23:53:34 +01:00
|
|
|
if overridden.is_some() {
|
|
|
|
log::debug!(
|
|
|
|
"{}overridden specifier found for {dependency_alias} ({dependency_spec})",
|
|
|
|
"\t".repeat(depth)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-07-14 14:19:15 +01:00
|
|
|
queue.push_back((
|
|
|
|
dependency_alias,
|
2024-07-22 21:00:09 +01:00
|
|
|
overridden.cloned().unwrap_or(dependency_spec),
|
2024-07-14 14:19:15 +01:00
|
|
|
dependency_ty,
|
2024-07-22 22:16:04 +01:00
|
|
|
Some((name.clone(), target_version_id.clone())),
|
2024-07-22 21:00:09 +01:00
|
|
|
path.iter()
|
|
|
|
.cloned()
|
|
|
|
.chain(std::iter::once(alias.to_string()))
|
|
|
|
.collect(),
|
|
|
|
overridden.is_some(),
|
2024-07-22 22:16:04 +01:00
|
|
|
pkg_ref.target_kind(),
|
2024-07-14 14:19:15 +01:00
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (name, versions) in &graph {
|
2024-07-22 22:16:04 +01:00
|
|
|
for (version_id, node) in versions {
|
2024-07-14 14:19:15 +01:00
|
|
|
if node.ty == DependencyType::Peer {
|
2024-07-22 22:16:04 +01:00
|
|
|
log::warn!("peer dependency {name}@{version_id} was not resolved");
|
2024-07-14 14:19:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(graph)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-03 21:18:38 +01:00
|
|
|
/// Errors that can occur when resolving dependencies
|
2024-07-14 14:19:15 +01:00
|
|
|
pub mod errors {
|
|
|
|
use thiserror::Error;
|
|
|
|
|
2024-08-03 21:18:38 +01:00
|
|
|
/// Errors that can occur when creating a dependency graph
|
2024-07-14 14:19:15 +01:00
|
|
|
#[derive(Debug, Error)]
|
|
|
|
#[non_exhaustive]
|
|
|
|
pub enum DependencyGraphError {
|
2024-08-03 21:18:38 +01:00
|
|
|
/// An error occurred while deserializing the manifest
|
2024-07-14 14:19:15 +01:00
|
|
|
#[error("failed to deserialize manifest")]
|
|
|
|
ManifestRead(#[from] crate::errors::ManifestReadError),
|
2024-08-08 16:59:59 +01:00
|
|
|
|
2024-08-03 21:18:38 +01:00
|
|
|
/// An error occurred while reading all dependencies from the manifest
|
2024-07-14 14:19:15 +01:00
|
|
|
#[error("error getting all project dependencies")]
|
|
|
|
AllDependencies(#[from] crate::manifest::errors::AllDependenciesError),
|
|
|
|
|
2024-08-03 21:18:38 +01:00
|
|
|
/// An index was not found in the manifest
|
2024-08-08 16:59:59 +01:00
|
|
|
#[error("index named `{0}` not found in manifest")]
|
2024-07-14 14:19:15 +01:00
|
|
|
IndexNotFound(String),
|
|
|
|
|
2024-08-08 16:59:59 +01:00
|
|
|
/// A Wally index was not found in the manifest
|
|
|
|
#[cfg(feature = "wally-compat")]
|
|
|
|
#[error("wally index named `{0}` not found in manifest")]
|
|
|
|
WallyIndexNotFound(String),
|
|
|
|
|
2024-08-03 21:18:38 +01:00
|
|
|
/// An error occurred while refreshing a package source
|
2024-07-14 14:19:15 +01:00
|
|
|
#[error("error refreshing package source")]
|
|
|
|
Refresh(#[from] crate::source::errors::RefreshError),
|
2024-08-08 16:59:59 +01:00
|
|
|
|
2024-08-03 21:18:38 +01:00
|
|
|
/// An error occurred while resolving a package
|
2024-07-14 14:19:15 +01:00
|
|
|
#[error("error resolving package")]
|
|
|
|
Resolve(#[from] crate::source::errors::ResolveError),
|
2024-07-22 22:16:04 +01:00
|
|
|
|
2024-08-03 21:18:38 +01:00
|
|
|
/// No matching version was found for a specifier
|
2024-07-22 22:16:04 +01:00
|
|
|
#[error("no matching version found for {0}")]
|
|
|
|
NoMatchingVersion(String),
|
2024-07-14 14:19:15 +01:00
|
|
|
}
|
|
|
|
}
|