use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use indexmap::IndexMap;
use semver::{Version, VersionReq};
use super::compat::CompatibilityResult;
use crate::config::{FilterMode, Manifest, SourceSpec};
use crate::error::ResolutionError;
use crate::lock::ItemKind;
use crate::source::ResolvedRef;
use crate::types::{ItemName, SourceId, SourceName};
#[derive(Debug, Clone)]
pub struct ResolvedGraph {
pub nodes: IndexMap<SourceName, ResolvedNode>,
pub order: Vec<SourceName>,
pub filters: HashMap<SourceName, Vec<FilterMode>>,
}
#[derive(Debug, Clone)]
pub struct ResolvedNode {
pub source_name: SourceName,
pub source_id: SourceId,
pub rooted_ref: RootedSourceRef,
pub resolved_ref: ResolvedRef,
pub latest_version: Option<Version>,
pub manifest: Option<Manifest>,
pub deps: Vec<SourceName>,
}
#[derive(Debug, Clone)]
pub struct RootedSourceRef {
pub checkout_root: PathBuf,
pub package_root: PathBuf,
}
#[derive(Debug, Clone)]
pub enum VersionConstraint {
Semver(VersionReq),
Latest,
RefPin(String),
}
impl std::fmt::Display for VersionConstraint {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
VersionConstraint::Semver(req) => write!(f, "{req}"),
VersionConstraint::Latest => write!(f, "latest"),
VersionConstraint::RefPin(reference) => write!(f, "ref:{reference}"),
}
}
}
#[derive(Debug, Clone)]
pub struct PendingItem {
pub package: SourceName,
pub item: ItemName,
pub kind: ItemKind,
pub constraint: VersionConstraint,
pub required_by: String,
pub is_local: bool,
pub spec: SourceSpec,
}
#[derive(Debug)]
pub enum VersionCheckResult {
NotSeen,
SameVersion,
PotentiallyConflicting {
existing: VersionConstraint,
requested: VersionConstraint,
},
DifferentVersion {
existing: VersionConstraint,
requested: VersionConstraint,
},
}
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
struct VisitedItem {
package: SourceName,
item: ItemName,
}
#[derive(Debug, Clone)]
pub struct ResolvedVersion {
pub constraint: VersionConstraint,
pub resolved_ref: ResolvedRef,
}
pub struct VisitedSet {
index: HashMap<(SourceName, ItemName), ResolvedVersion>,
}
impl Default for VisitedSet {
fn default() -> Self {
Self::new()
}
}
impl VisitedSet {
pub fn new() -> Self {
Self {
index: HashMap::new(),
}
}
fn index_key(package: &SourceName, item: &ItemName) -> (SourceName, ItemName) {
let key = VisitedItem {
package: package.clone(),
item: item.clone(),
};
(key.package, key.item)
}
pub fn check_version(
&self,
package: &SourceName,
item: &ItemName,
constraint: &VersionConstraint,
) -> VersionCheckResult {
match self.index.get(&Self::index_key(package, item)) {
None => VersionCheckResult::NotSeen,
Some(existing) => match existing
.constraint
.compatible_with_resolved(constraint, existing.resolved_ref.version.as_ref())
{
CompatibilityResult::Compatible => VersionCheckResult::SameVersion,
CompatibilityResult::PotentiallyConflicting => {
VersionCheckResult::PotentiallyConflicting {
existing: existing.constraint.clone(),
requested: constraint.clone(),
}
}
CompatibilityResult::Conflicting => VersionCheckResult::DifferentVersion {
existing: existing.constraint.clone(),
requested: constraint.clone(),
},
},
}
}
pub fn insert(
&mut self,
package: SourceName,
item: ItemName,
constraint: VersionConstraint,
resolved_ref: ResolvedRef,
) {
self.index.insert(
Self::index_key(&package, &item),
ResolvedVersion {
constraint,
resolved_ref,
},
);
}
pub fn iter(&self) -> impl Iterator<Item = (&(SourceName, ItemName), &ResolvedVersion)> {
self.index.iter()
}
}
pub struct PackageVersions {
versions: HashMap<SourceName, (ResolvedRef, VersionConstraint, String)>,
}
impl Default for PackageVersions {
fn default() -> Self {
Self::new()
}
}
impl PackageVersions {
pub fn new() -> Self {
Self {
versions: HashMap::new(),
}
}
pub fn check_or_insert(
&mut self,
package: &SourceName,
resolved: &ResolvedRef,
requested: &VersionConstraint,
required_by: &str,
is_local: bool,
) -> Result<(), ResolutionError> {
if is_local {
return Ok(());
}
match self.versions.entry(package.clone()) {
Entry::Vacant(entry) => {
entry.insert((resolved.clone(), requested.clone(), required_by.to_string()));
Ok(())
}
Entry::Occupied(entry) => {
let (existing_ref, existing_constraint, existing_by) = entry.get();
match existing_constraint.compatible_with_resolved(
requested,
existing_ref.version.as_ref().or(resolved.version.as_ref()),
) {
CompatibilityResult::Compatible
| CompatibilityResult::PotentiallyConflicting => {
if resolved_ref_matches(existing_ref, resolved) {
Ok(())
} else {
Err(ResolutionError::PackageVersionConflict {
package: package.to_string(),
existing: format!("{existing_ref:?} (required by {existing_by})"),
requested: format!("{resolved:?} (required by {required_by})"),
chain: required_by.to_string(),
})
}
}
CompatibilityResult::Conflicting => {
Err(ResolutionError::PackageVersionConflict {
package: package.to_string(),
existing: format!("{existing_constraint} (required by {existing_by})"),
requested: format!("{requested} (required by {required_by})"),
chain: required_by.to_string(),
})
}
}
}
}
}
}
fn resolved_ref_matches(existing: &ResolvedRef, incoming: &ResolvedRef) -> bool {
existing.source_name == incoming.source_name
&& existing.version == incoming.version
&& existing.version_tag == incoming.version_tag
&& existing.commit == incoming.commit
&& existing.tree_path == incoming.tree_path
}
#[derive(Debug, Clone, Default)]
pub struct ResolveOptions {
pub maximize: bool,
pub upgrade_targets: HashSet<SourceName>,
pub bump_direct_constraints: bool,
pub frozen: bool,
}