Skip to content

Commit

Permalink
Refactor commonalities between environment, solvegroup and groupedenv…
Browse files Browse the repository at this point in the history
…ironment in a trait
  • Loading branch information
olivier-lacroix committed Apr 28, 2024
1 parent 7f93887 commit 926ee98
Show file tree
Hide file tree
Showing 18 changed files with 227 additions and 312 deletions.
1 change: 1 addition & 0 deletions src/activation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use rattler_shell::{
shell::ShellEnum,
};

use crate::project::combine_feature::CombineFeature;
use crate::{
environment::{get_up_to_date_prefix, LockFileUsage},
project::{manifest::EnvironmentName, Environment},
Expand Down
2 changes: 1 addition & 1 deletion src/cli/add.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
config::ConfigCli,
environment::{get_up_to_date_prefix, verify_prefix_location_unchanged, LockFileUsage},
project::{DependencyType, Project, SpecType},
project::{combine_feature::CombineFeature, DependencyType, Project, SpecType},
FeatureName,
};
use clap::Parser;
Expand Down
1 change: 1 addition & 0 deletions src/cli/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use serde_with::DisplayFromStr;
use tokio::task::spawn_blocking;

use crate::progress::await_in_progress;
use crate::project::combine_feature::CombineFeature;
use crate::task::TaskName;
use crate::{config, EnvironmentName, FeatureName, Project};

Expand Down
1 change: 1 addition & 0 deletions src/cli/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use serde::Serialize;
use uv_distribution::RegistryWheelIndex;

use crate::lock_file::{UpdateLockFileOptions, UvResolutionContext};
use crate::project::combine_feature::CombineFeature;
use crate::pypi_tags::{get_pypi_tags, is_python_record};
use crate::Project;

Expand Down
2 changes: 1 addition & 1 deletion src/cli/project/channel/list.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::Project;
use crate::{project::combine_feature::CombineFeature, Project};
use clap::Parser;

#[derive(Parser, Debug, Default)]
Expand Down
2 changes: 1 addition & 1 deletion src/cli/project/platform/list.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::Project;
use crate::{project::combine_feature::CombineFeature, Project};

pub async fn execute(project: Project) -> miette::Result<()> {
project
Expand Down
1 change: 1 addition & 0 deletions src/cli/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use itertools::Itertools;
use rattler_conda_types::Platform;

use crate::lock_file::UpdateLockFileOptions;
use crate::project::combine_feature::CombineFeature;
use crate::Project;

/// Show a tree of project dependencies
Expand Down
1 change: 1 addition & 0 deletions src/environment.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::lock_file::UvResolutionContext;
use crate::progress::await_in_progress;
use crate::project::combine_feature::CombineFeature;
use crate::project::grouped_environment::GroupedEnvironmentName;
use crate::{
consts, install, install_pypi,
Expand Down
1 change: 1 addition & 0 deletions src/lock_file/outdated.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::{verify_environment_satisfiability, verify_platform_satisfiability};
use crate::lock_file::satisfiability::EnvironmentUnsat;
use crate::project::combine_feature::CombineFeature;
use crate::{consts, project::Environment, project::SolveGroup, Project};
use itertools::Itertools;
use rattler_conda_types::Platform;
Expand Down
1 change: 1 addition & 0 deletions src/lock_file/satisfiability.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::{PypiRecord, PypiRecordsByName, RepoDataRecordsByName};
use crate::project::combine_feature::CombineFeature;
use crate::project::manifest::python::{AsPep508Error, RequirementOrEditable};
use crate::{project::Environment, pypi_marker_env::determine_marker_environment};
use distribution_types::ParsedGitUrl;
Expand Down
1 change: 1 addition & 0 deletions src/lock_file/update.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::lock_file::{PypiRecord, UvResolutionContext};
use crate::project::combine_feature::CombineFeature;
use crate::project::grouped_environment::GroupedEnvironmentName;
use crate::pypi_mapping::{self, Reporter};
use crate::pypi_marker_env::determine_marker_environment;
Expand Down
140 changes: 140 additions & 0 deletions src/project/combine_feature.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
use std::{borrow::Cow, collections::HashSet};

use indexmap::{IndexMap, IndexSet};
use itertools::Either;
use rattler_conda_types::{Channel, Platform};

use crate::{Project, SpecType};

use super::{
manifest::{python::PyPiPackageName, Feature, PyPiRequirement, SystemRequirements},
Dependencies,
};

/// A trait that implement various methods for collections that combine attributes of Features
/// It is implemented by Environment, GroupedEnvironment and SolveGroup
pub trait CombineFeature<'p> {
fn features(&self) -> impl DoubleEndedIterator<Item = &'p Feature> + 'p;
fn project(&self) -> &'p Project;

/// Returns the channels associated with this collection.
///
/// Users can specify custom channels on a per feature basis. This method collects and
/// deduplicates all the channels from all the features in the order they are defined in the
/// manifest.
///
/// If a feature does not specify any channel the default channels from the project metadata are
/// used instead.
fn channels(&self) -> IndexSet<&'p Channel> {
// We reverse once before collecting into an IndexSet, and once after,
// to ensure the default channels of the project are added to the end of the list.
let mut channels: IndexSet<_> = self
.features()
.flat_map(|feature| match &feature.channels {
Some(channels) => channels,
None => &self.project().manifest.parsed.project.channels,
})
.rev()
.collect();
channels.reverse();

// The prioritized channels contain a priority, sort on this priority.
// Higher priority comes first. [-10, 1, 0 ,2] -> [2, 1, 0, -10]
channels
.sorted_by(|a, b| {
let a = a.priority.unwrap_or(0);
let b = b.priority.unwrap_or(0);
b.cmp(&a)
})
.map(|prioritized_channel| &prioritized_channel.channel)
.collect()
}

/// Returns the platforms that this collection is compatible with.
///
/// Which platforms a collection support depends on which platforms the selected features of
/// the collection supports. The platforms that are supported by the collection is the
/// intersection of the platforms supported by its features.
///
/// Features can specify which platforms they support through the `platforms` key. If a feature
/// does not specify any platforms the features defined by the project are used.
fn platforms(&self) -> HashSet<Platform> {
self.features()
.map(|feature| {
match &feature.platforms {
Some(platforms) => &platforms.value,
None => &self.project().manifest.parsed.project.platforms.value,
}
.iter()
.copied()
.collect::<HashSet<_>>()
})
.reduce(|accumulated_platforms, feat| {
accumulated_platforms.intersection(&feat).copied().collect()
})
.unwrap_or_default()
}

/// Returns the system requirements for this collection.
///
/// The system requirements of the collection are the union of the system requirements of all
/// the features in the collection. If multiple features specify a
/// requirement for the same system package, the highest is chosen.
fn local_system_requirements(&self) -> SystemRequirements {
self.features()
.map(|feature| &feature.system_requirements)
.fold(SystemRequirements::default(), |acc, req| {
acc.union(req)
.expect("system requirements should have been validated upfront")
})
}

/// Returns true if any of the features has any reference to a pypi dependency.
fn has_pypi_dependencies(&self) -> bool {
self.features().any(|f| f.has_pypi_dependencies())
}

/// Returns the PyPi dependencies to install for this collection.
///
/// The dependencies of all features are combined. This means that if two features define a
/// requirement for the same package that both requirements are returned. The different
/// requirements per package are sorted in the same order as the features they came from.
fn pypi_dependencies(
&self,
platform: Option<Platform>,
) -> IndexMap<PyPiPackageName, Vec<PyPiRequirement>> {
self.features()
.filter_map(|f| f.pypi_dependencies(platform))
.fold(IndexMap::default(), |mut acc, deps| {
// Either clone the values from the Cow or move the values from the owned map.
let deps_iter = match deps {
Cow::Borrowed(borrowed) => Either::Left(
borrowed
.into_iter()
.map(|(name, spec)| (name.clone(), spec.clone())),
),
Cow::Owned(owned) => Either::Right(owned.into_iter()),
};

// Add the requirements to the accumulator.
for (name, spec) in deps_iter {
acc.entry(name).or_default().push(spec);
}

acc
})
}

/// Returns the dependencies to install for this collection.
///
/// The dependencies of all features are combined. This means that if two features define a
/// requirement for the same package that both requirements are returned. The different
/// requirements per package are sorted in the same order as the features they came from.
fn dependencies(&self, kind: Option<SpecType>, platform: Option<Platform>) -> Dependencies {
self.features()
.filter_map(|f| f.dependencies(kind, platform))
.map(|deps| Dependencies::from(deps.into_owned()))
.reduce(|acc, deps| acc.union(&deps))
.unwrap_or_default()
}
}
Loading

0 comments on commit 926ee98

Please sign in to comment.