use crate::error::Error;
use crate::project::manifest::{Environment, Manifest, parse_version_req, split_package_name};
use crate::registry::index::{DownloadSource, Index};
use std::collections::{BTreeMap, HashMap, VecDeque};
pub struct ResolvedInstall {
pub name: String,
pub version: semver::Version,
pub environment: Option<Environment>,
pub source: DownloadSource,
pub index_url: String,
pub link: String,
}
pub fn resolve(manifest: &Manifest, refresh: bool) -> Result<Vec<ResolvedInstall>, Error> {
let prefer_environment = manifest.target.as_ref().map(|target| target.environment);
let mut indices: HashMap<String, Index> = HashMap::new();
let mut queue: VecDeque<(String, String, String, Option<String>)> = VecDeque::new();
for (alias, dependency) in &manifest.dependencies {
let url = manifest.index_url(dependency.index.as_deref())?;
queue.push_back((
dependency.name.to_lowercase(),
dependency.version.clone(),
url.to_string(),
Some(alias.clone()),
));
}
let mut resolved: BTreeMap<String, (ResolvedInstall, String)> = BTreeMap::new();
while let Some((name, req_text, index_url, link)) = queue.pop_front() {
let req = parse_version_req(&req_text)?;
if let Some((existing, first_req)) = resolved.get(&name) {
if req.matches(&existing.version) {
continue;
}
return Err(Error::DependencyConflict {
name,
first: first_req.clone(),
second: req_text,
});
}
let index = open_index(&mut indices, &index_url, refresh)?;
let package = index.resolve(&name, &req, prefer_environment)?;
for dependency in &package.dependencies {
queue.push_back((
dependency.name.clone(),
dependency.version_req.clone(),
dependency
.index_url
.clone()
.unwrap_or_else(|| index_url.clone()),
None,
));
}
let link = link.unwrap_or_else(|| {
split_package_name(&name)
.map(|(_, short)| short.to_string())
.unwrap_or_else(|_| name.replace('/', "_"))
});
resolved.insert(
name.clone(),
(
ResolvedInstall {
name,
version: package.version,
environment: package.environment,
source: package.source,
index_url,
link,
},
req_text,
),
);
}
Ok(resolved.into_values().map(|(install, _)| install).collect())
}
fn open_index<'a>(
indices: &'a mut HashMap<String, Index>,
url: &str,
refresh: bool,
) -> Result<&'a Index, Error> {
if !indices.contains_key(url) {
indices.insert(url.to_string(), Index::open(url, refresh)?);
}
Ok(&indices[url])
}