pesde/src/resolver.rs

372 lines
14 KiB
Rust
Raw Normal View History

use crate::{
lockfile::{insert_node, DependencyGraph, DependencyGraphNode},
manifest::DependencyType,
names::PackageNames,
source::{
2024-07-28 17:19:54 +01:00
pesde::PesdePackageSource,
2024-08-11 15:16:25 +01:00
refs::PackageRefs,
2024-07-28 17:19:54 +01:00
specifiers::DependencySpecifiers,
traits::{PackageRef, PackageSource},
version_id::VersionId,
PackageSources,
},
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
pub fn dependency_graph(
&self,
previous_graph: Option<&DependencyGraph>,
2024-07-17 18:38:01 +01:00
refreshed_sources: &mut HashSet<PackageSources>,
) -> 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
.all_dependencies()
2024-07-17 18:38:01 +01:00
.map_err(|e| Box::new(e.into()))?
.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-07-17 18:38:01 +01:00
if all_specifiers
.remove(&(specifier.clone(), node.ty))
.is_none()
{
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;
}
log::debug!("resolved {}@{} from old dependency graph", name, version);
insert_node(
&mut graph,
name.clone(),
version.clone(),
node.clone(),
true,
);
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(),
false,
);
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
.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
)
})
.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;
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 {
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()
} 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()
};
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 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()),
),
};
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)
.map_err(|e| Box::new(e.into()))?;
2024-07-22 22:16:04 +01:00
let Some(target_version_id) = graph
.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(
format!("{specifier} ({target})"),
2024-07-22 22:16:04 +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 {
graph
.get_mut(&dependant_name)
2024-07-22 22:16:04 +01:00
.and_then(|versions| versions.get_mut(&dependant_version_id))
.and_then(|node| {
node.dependencies
2024-07-22 22:16:04 +01:00
.insert(name.clone(), (target_version_id.clone(), alias.clone()))
});
}
2024-08-11 15:16:25 +01:00
let pkg_ref = &resolved[&target_version_id];
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))
{
log::debug!(
"{}{}@{} already resolved",
"\t".repeat(depth),
name,
2024-07-22 22:16:04 +01:00
target_version_id
);
2024-08-11 15:16:25 +01:00
if matches!(already_resolved.pkg_ref, PackageRefs::Git(_))
!= matches!(pkg_ref, PackageRefs::Git(_))
{
log::warn!(
"resolved package {name}@{target_version_id} has a different source than the previously resolved one, this may cause issues",
);
}
if already_resolved.ty == DependencyType::Peer && ty == DependencyType::Standard {
already_resolved.ty = ty;
}
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(),
node.clone(),
depth == 0,
);
log::debug!(
"{}resolved {}@{} from new dependency graph",
"\t".repeat(depth),
name,
2024-07-22 22:16:04 +01:00
target_version_id
);
for (dependency_alias, (dependency_spec, dependency_ty)) in
pkg_ref.dependencies().clone()
{
if dependency_ty == DependencyType::Dev {
// dev dependencies of dependencies are not included in the graph
// they should not even be stored in the index, so this is just a check to avoid potential issues
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)
);
}
queue.push_back((
dependency_alias,
2024-07-22 21:00:09 +01:00
overridden.cloned().unwrap_or(dependency_spec),
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(),
));
}
}
for (name, versions) in &graph {
2024-07-22 22:16:04 +01:00
for (version_id, node) in versions {
if node.ty == DependencyType::Peer {
2024-07-22 22:16:04 +01:00
log::warn!("peer dependency {name}@{version_id} was not resolved");
}
}
}
Ok(graph)
}
}
2024-08-03 21:18:38 +01:00
/// Errors that can occur when resolving dependencies
pub mod errors {
use thiserror::Error;
2024-08-03 21:18:38 +01:00
/// Errors that can occur when creating a dependency graph
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum DependencyGraphError {
2024-08-03 21:18:38 +01:00
/// An error occurred while deserializing the manifest
#[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
#[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")]
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
#[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
#[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),
}
}