use std::{collections::HashMap, sync::Arc};
use anyhow::Result;
use cargo_component_core::{
lock::{LockFile, LockFileResolver, LockedPackage, LockedPackageVersion},
registry::{DependencyResolution, DependencyResolutionMap, DependencyResolver},
};
use cargo_metadata::PackageId;
use semver::Version;
use wasm_pkg_client::{
caching::{CachingClient, FileCache},
ContentDigest, PackageRef,
};
use crate::metadata::ComponentMetadata;
#[derive(Debug, Clone)]
pub struct PackageDependencyResolution<'a> {
pub metadata: &'a ComponentMetadata,
pub target_resolutions: DependencyResolutionMap,
pub resolutions: DependencyResolutionMap,
}
impl<'a> PackageDependencyResolution<'a> {
pub async fn new(
client: Arc<CachingClient<FileCache>>,
metadata: &'a ComponentMetadata,
lock_file: Option<LockFileResolver<'_>>,
) -> Result<PackageDependencyResolution<'a>> {
Ok(Self {
metadata,
target_resolutions: Self::resolve_target_deps(client.clone(), metadata, lock_file)
.await?,
resolutions: Self::resolve_deps(client, metadata, lock_file).await?,
})
}
pub fn all(&self) -> impl Iterator<Item = (&PackageRef, &DependencyResolution)> {
self.target_resolutions
.iter()
.chain(self.resolutions.iter())
}
async fn resolve_target_deps(
client: Arc<CachingClient<FileCache>>,
metadata: &ComponentMetadata,
lock_file: Option<LockFileResolver<'_>>,
) -> Result<DependencyResolutionMap> {
let target_deps = metadata.section.target.dependencies();
if target_deps.is_empty() {
return Ok(Default::default());
}
let mut resolver = DependencyResolver::new_with_client(client, lock_file)?;
for (name, dependency) in target_deps.iter() {
resolver.add_dependency(name, dependency).await?;
}
resolver.resolve().await
}
async fn resolve_deps(
client: Arc<CachingClient<FileCache>>,
metadata: &ComponentMetadata,
lock_file: Option<LockFileResolver<'_>>,
) -> Result<DependencyResolutionMap> {
if metadata.section.dependencies.is_empty() {
return Ok(Default::default());
}
let mut resolver = DependencyResolver::new_with_client(client, lock_file)?;
for (name, dependency) in &metadata.section.dependencies {
resolver.add_dependency(name, dependency).await?;
}
resolver.resolve().await
}
}
#[derive(Debug, Default, Clone)]
pub struct PackageResolutionMap<'a>(HashMap<PackageId, PackageDependencyResolution<'a>>);
impl<'a> PackageResolutionMap<'a> {
pub fn insert(&mut self, id: PackageId, resolution: PackageDependencyResolution<'a>) {
let prev = self.0.insert(id, resolution);
assert!(prev.is_none());
}
pub fn get(&self, id: &PackageId) -> Option<&PackageDependencyResolution<'a>> {
self.0.get(id)
}
pub fn to_lock_file(&self) -> LockFile {
type PackageKey = (PackageRef, Option<String>);
type VersionsMap = HashMap<String, (Version, ContentDigest)>;
let mut packages: HashMap<PackageKey, VersionsMap> = HashMap::new();
for resolution in self.0.values() {
for (_, dep) in resolution.all() {
match dep.key() {
Some((name, registry)) => {
let pkg = match dep {
DependencyResolution::Registry(pkg) => pkg,
DependencyResolution::Local(_) => unreachable!(),
};
let prev = packages
.entry((name.clone(), registry.map(str::to_string)))
.or_default()
.insert(
pkg.requirement.to_string(),
(pkg.version.clone(), pkg.digest.clone()),
);
if let Some((prev, _)) = prev {
assert!(prev == pkg.version)
}
}
None => continue,
}
}
}
let mut packages: Vec<_> = packages
.into_iter()
.map(|((name, registry), versions)| {
let mut versions: Vec<LockedPackageVersion> = versions
.into_iter()
.map(|(requirement, (version, digest))| LockedPackageVersion {
requirement,
version,
digest,
})
.collect();
versions.sort_by(|a, b| a.key().cmp(b.key()));
LockedPackage {
name,
registry,
versions,
}
})
.collect();
packages.sort_by(|a, b| a.key().cmp(&b.key()));
LockFile::new(packages)
}
}