Closed mpizenberg closed 3 years ago
I've made this example implementation of a dependency provider handling optional dependencies based on discussions we had on the subject.
The Index
type stores all packages in the most straightforward way I could find to reflect optional dependencies, identified by "features":
// index.rs
/// Each package is identified by its name.
pub type Package = String;
/// Features are identified by their name.
pub type Feature = String;
/// Global registry of known packages.
pub struct Index {
/// Specify dependencies of each package version.
pub packages: Map<Package, BTreeMap<Version, Deps>>,
}
/// Dependencies include mandatory dependencies and optional dependencies.
/// Optional dependencies are identified by an option called a "feature".
pub struct Deps {
/// The regular, mandatory dependencies.
pub mandatory: Map<Package, Dep>,
/// The optional, feature-gated dependencies.
pub optional: Map<Feature, Map<Package, Dep>>,
}
/// A dependency is specified with a range, and with a set of activated features.
pub struct Dep {
/// The range dependended upon.
pub range: Range<Version>,
/// The activated features for that dependency.
pub features: Set<Feature>,
}
As you can see, it does not duplicate packages by features. This is instead dynamically handled within the dependency provider implementation. In that one, the String
for package is replaced by an actual Package
type to make the difference between base packages and features.
// optional_deps.rs
pub enum Package {
Base(String),
Feature { base: String, feature: String },
}
impl Index {
/// List existing versions for a given package with newest versions first.
pub fn list_versions(&self, package: &Package) -> impl Iterator<Item = &Version> {
self.available_versions(package.base_pkg())
}
}
impl DependencyProvider<Package, Version> for Index {
fn choose_package_version<...>(
&self,
potential_packages: impl Iterator<Item = (T, U)>,
) -> ... {
Ok(pubgrub::solver::choose_package_with_fewest_versions(
|p| self.list_versions(p).cloned(),
potential_packages,
))
}
fn get_dependencies(
&self,
package: &Package,
version: &Version,
) -> ... {
let all_versions = self.packages.get(package.base_pkg());
...
let deps = all_versions.get(version);
...
match package {
// If we asked for a base package, we simply return the mandatory dependencies.
Package::Base(_) => Ok(Dependencies::Known(from_deps(&deps.mandatory))),
// Otherwise, we concatenate the feature deps with a dependency to the base package.
Package::Feature { base, feature } => match deps.optional.get(feature) {
None => Ok(Dependencies::Unknown),
Some(feature_deps) => {
let mut all_deps = from_deps(feature_deps);
all_deps.insert(
Package::Base(base.to_string()),
Range::exact(version.clone()),
);
Ok(Dependencies::Known(all_deps))
}
},
}
}
}
/// Helper function to convert Index deps into what is expected by the dependency provider.
fn from_deps(deps: &Map<String, Dep>) -> DependencyConstraints<Package, Version> { ... }
This is intended to serve as a base for the corresponding explanation on handling optional dependencies in the guide, that I'm currently writing.
Alright, I've added the suggested changes, except the RangeBounds
integration in pubgrub that needs more work. This now seems OK and I'd like to reuse the repository structure setup here in the following example so I'll merge this one. If something needs further change, we can do another PR.
Will serve as an example for the guide