Skip to main content

runmat_package/resolve/
candidate.rs

1use super::ResolutionRequirement;
2use crate::{
3    CanonicalPackageId, HostCapability, PackageInstanceId, RegistryId, ResolveError,
4    TargetPredicate,
5};
6use semver::VersionReq;
7use serde::{Deserialize, Serialize};
8use std::collections::{BTreeMap, BTreeSet};
9use std::future::Future;
10use std::pin::Pin;
11
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13pub struct CandidateMetadata {
14    pub instance: PackageInstanceId,
15    pub dependencies: Vec<ResolutionRequirement>,
16    pub features: BTreeMap<String, BTreeSet<String>>,
17    pub required_capabilities: BTreeSet<HostCapability>,
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub runmat_version: Option<VersionReq>,
20    pub singleton: bool,
21    pub yanked: bool,
22    pub available_offline: bool,
23    pub target_artifacts: BTreeSet<TargetPredicate>,
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub registry_metadata: Option<crate::RegistryReleaseMetadata>,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct CandidateQuery {
30    pub package: CanonicalPackageId,
31    pub source_registry: RegistryId,
32    pub offline: bool,
33}
34
35#[derive(Debug, Clone, PartialEq, Eq, Default)]
36pub struct SourceSelectionPolicy {
37    pub replacements: BTreeMap<RegistryId, RegistryId>,
38    pub offline: bool,
39}
40
41pub trait CandidateProvider {
42    fn candidates<'a>(
43        &'a self,
44        query: &'a CandidateQuery,
45    ) -> Pin<Box<dyn Future<Output = Result<Vec<CandidateMetadata>, ResolveError>> + 'a>>;
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Default)]
49pub struct CandidateIndex {
50    packages: BTreeMap<CanonicalPackageId, Vec<CandidateMetadata>>,
51}
52
53impl CandidateIndex {
54    pub fn insert(&mut self, candidate: CandidateMetadata) {
55        self.packages
56            .entry(candidate.instance.package.clone())
57            .or_default()
58            .push(candidate);
59    }
60
61    pub fn candidates(&self, package: &CanonicalPackageId) -> &[CandidateMetadata] {
62        self.packages.get(package).map(Vec::as_slice).unwrap_or(&[])
63    }
64
65    pub fn package_ids(&self) -> impl Iterator<Item = &CanonicalPackageId> {
66        self.packages.keys()
67    }
68}
69
70pub async fn acquire_candidates(
71    provider: &dyn CandidateProvider,
72    packages: impl IntoIterator<Item = CanonicalPackageId>,
73    offline: bool,
74) -> Result<CandidateIndex, ResolveError> {
75    acquire_candidates_with_policy(
76        provider,
77        packages,
78        &SourceSelectionPolicy {
79            replacements: BTreeMap::new(),
80            offline,
81        },
82    )
83    .await
84}
85
86pub async fn acquire_candidates_with_policy(
87    provider: &dyn CandidateProvider,
88    packages: impl IntoIterator<Item = CanonicalPackageId>,
89    policy: &SourceSelectionPolicy,
90) -> Result<CandidateIndex, ResolveError> {
91    let mut index = CandidateIndex::default();
92    let mut pending = packages.into_iter().collect::<BTreeSet<_>>();
93    let mut queried = BTreeSet::new();
94    while let Some(package) = pending.pop_first() {
95        if !queried.insert(package.clone()) {
96            continue;
97        }
98        let source_registry = replacement_registry(package.registry(), &policy.replacements)?;
99        let mut candidates = provider
100            .candidates(&CandidateQuery {
101                package: package.clone(),
102                source_registry,
103                offline: policy.offline,
104            })
105            .await?;
106        candidates.sort_by(|left, right| {
107            right
108                .instance
109                .version
110                .cmp(&left.instance.version)
111                .then_with(|| {
112                    left.instance
113                        .identity_digest
114                        .cmp(&right.instance.identity_digest)
115                })
116        });
117        for candidate in candidates {
118            if candidate.instance.package != package {
119                return Err(ResolveError::Provider(format!(
120                    "provider returned {} for query {package}",
121                    candidate.instance.package
122                )));
123            }
124            pending.extend(
125                candidate
126                    .dependencies
127                    .iter()
128                    .map(|dependency| dependency.package.clone()),
129            );
130            index.insert(candidate);
131        }
132    }
133    Ok(index)
134}
135
136fn replacement_registry(
137    source: &RegistryId,
138    replacements: &BTreeMap<RegistryId, RegistryId>,
139) -> Result<RegistryId, ResolveError> {
140    let mut current = source.clone();
141    let mut visited = BTreeSet::new();
142    while let Some(replacement) = replacements.get(&current) {
143        if !visited.insert(current.clone()) {
144            return Err(ResolveError::Provider(format!(
145                "registry source replacement cycle begins at `{source}`"
146            )));
147        }
148        current = replacement.clone();
149    }
150    Ok(current)
151}