use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct ResolvedCrate {
pub name: String,
pub version: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResolvedGraph {
pub crates: BTreeSet<ResolvedCrate>,
pub roots: Vec<ResolvedCrate>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndexEntry {
pub name: String,
#[serde(rename = "vers")]
pub version: String,
#[serde(default)]
pub deps: Vec<IndexDep>,
#[serde(rename = "cksum")]
pub checksum: String,
#[serde(default)]
pub features: BTreeMap<String, Vec<String>>,
#[serde(default)]
pub features2: BTreeMap<String, Vec<String>>,
#[serde(default)]
pub yanked: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndexDep {
pub name: String,
pub req: String,
pub features: Vec<String>,
#[serde(default)]
pub optional: bool,
#[serde(default = "default_true")]
pub default_features: bool,
#[serde(default)]
pub target: Option<String>,
#[serde(default)]
pub kind: Option<String>,
#[serde(default)]
pub registry: Option<String>,
#[serde(default)]
pub package: Option<String>,
}
fn default_true() -> bool {
true
}
impl ResolvedGraph {
pub fn new() -> Self {
Self {
crates: BTreeSet::new(),
roots: Vec::new(),
}
}
pub fn crate_count(&self) -> usize {
self.crates.len()
}
pub fn crate_set(&self) -> BTreeSet<(String, String)> {
self.crates
.iter()
.map(|c| (c.name.clone(), c.version.clone()))
.collect()
}
}
#[derive(Debug, Clone)]
struct ResolveRequest {
name: String,
version_req: String,
use_default_features: bool,
}
pub struct Resolver {
index_cache: HashMap<String, Vec<IndexEntry>>,
queue: VecDeque<ResolveRequest>,
resolved: BTreeSet<ResolvedCrate>,
visited: HashSet<(String, String)>,
roots: Vec<ResolvedCrate>,
}
impl Resolver {
pub fn new() -> Self {
Self {
index_cache: HashMap::new(),
queue: VecDeque::new(),
resolved: BTreeSet::new(),
visited: HashSet::new(),
roots: Vec::new(),
}
}
pub fn seed(&mut self, deps: &BTreeMap<String, String>) {
for (name, version_req) in deps {
self.queue.push_back(ResolveRequest {
name: name.clone(),
version_req: version_req.clone(),
use_default_features: true,
});
}
}
pub fn provide_index(&mut self, name: &str, entries: Vec<IndexEntry>) {
self.index_cache.insert(name.to_string(), entries);
}
pub fn pending_crates(&self) -> Vec<String> {
let mut needed: BTreeSet<String> = BTreeSet::new();
for req in &self.queue {
let dep_name = req.name.clone();
if !self.index_cache.contains_key(&dep_name) {
needed.insert(dep_name);
}
}
needed.into_iter().collect()
}
pub fn has_work(&self) -> bool {
!self.queue.is_empty()
}
pub fn advance(&mut self) -> Result<()> {
let current_queue: Vec<ResolveRequest> = self.queue.drain(..).collect();
for req in current_queue {
let entries = match self.index_cache.get(&req.name) {
Some(e) => e,
None => {
tracing::warn!("no index data for {} (skipping)", req.name);
continue;
}
};
let matched = match find_best_match(entries, &req.name, &req.version_req) {
Ok(m) => m,
Err(e) => {
tracing::warn!("{}", e);
continue;
}
};
let key = (matched.name.clone(), matched.version.clone());
if self.visited.contains(&key) {
continue;
}
self.visited.insert(key);
let resolved_crate = ResolvedCrate {
name: matched.name.clone(),
version: matched.version.clone(),
};
if self.resolved.is_empty() || self.roots.iter().any(|r| r.name == req.name) {
if !self.roots.iter().any(|r| r.name == req.name) {
} else {
}
}
self.resolved.insert(resolved_crate.clone());
let enabled_features =
compute_enabled_features(&matched, req.use_default_features);
let activated_optional_deps =
compute_activated_deps(&matched, &enabled_features);
for dep in &matched.deps {
if dep.kind.as_deref() == Some("dev") || dep.kind.as_deref() == Some("build") {
continue;
}
if dep.optional && !activated_optional_deps.contains(&dep.name) {
continue;
}
let dep_name = dep.package.as_deref().unwrap_or(&dep.name);
self.queue.push_back(ResolveRequest {
name: dep_name.to_string(),
version_req: dep.req.clone(),
use_default_features: dep.default_features,
});
}
}
Ok(())
}
pub fn finish(self, direct_deps: &BTreeMap<String, String>) -> ResolvedGraph {
let roots: Vec<ResolvedCrate> = direct_deps
.keys()
.filter_map(|name| {
self.resolved
.iter()
.find(|c| c.name == *name)
.cloned()
})
.collect();
ResolvedGraph {
crates: self.resolved,
roots,
}
}
}
fn compute_enabled_features(entry: &IndexEntry, use_default: bool) -> HashSet<String> {
let mut enabled = HashSet::new();
let mut all_features: BTreeMap<String, Vec<String>> = entry.features.clone();
for (k, v) in &entry.features2 {
all_features
.entry(k.clone())
.or_default()
.extend(v.clone());
}
if use_default {
if let Some(defaults) = all_features.get("default") {
let mut stack: Vec<String> = defaults.clone();
while let Some(feat) = stack.pop() {
if enabled.insert(feat.clone()) {
if let Some(sub) = all_features.get(&feat) {
stack.extend(sub.clone());
}
}
}
}
}
enabled
}
fn compute_activated_deps(entry: &IndexEntry, enabled_features: &HashSet<String>) -> HashSet<String> {
let mut activated = HashSet::new();
let optional_dep_names: HashSet<&str> = entry
.deps
.iter()
.filter(|d| d.optional)
.map(|d| d.name.as_str())
.collect();
let mut all_features: BTreeMap<String, Vec<String>> = entry.features.clone();
for (k, v) in &entry.features2 {
all_features
.entry(k.clone())
.or_default()
.extend(v.clone());
}
for feat_name in enabled_features {
if optional_dep_names.contains(feat_name.as_str()) {
activated.insert(feat_name.clone());
}
if let Some(values) = all_features.get(feat_name) {
for value in values {
if let Some(dep_ref) = value.strip_prefix("dep:") {
activated.insert(dep_ref.to_string());
} else if optional_dep_names.contains(value.as_str()) {
activated.insert(value.clone());
} else if value.contains('/') {
let dep_part = value.split('/').next().unwrap();
if optional_dep_names.contains(dep_part) {
activated.insert(dep_part.to_string());
}
}
}
}
}
activated
}
fn find_best_match(entries: &[IndexEntry], name: &str, version_req: &str) -> Result<IndexEntry> {
let req = semver::VersionReq::parse(version_req)
.or_else(|_| semver::VersionReq::parse(&format!("={}", version_req)))
.map_err(|e| anyhow::anyhow!("invalid version req '{}' for {}: {}", version_req, name, e))?;
let mut candidates: Vec<&IndexEntry> = entries
.iter()
.filter(|e| !e.yanked)
.filter(|e| {
semver::Version::parse(&e.version)
.map(|v| req.matches(&v))
.unwrap_or(false)
})
.collect();
candidates.sort_by(|a, b| {
let va = semver::Version::parse(&a.version).unwrap();
let vb = semver::Version::parse(&b.version).unwrap();
vb.cmp(&va)
});
candidates
.into_iter()
.next()
.cloned()
.ok_or_else(|| anyhow::anyhow!("no matching version for {} {}", name, version_req))
}