Skip to content

Commit

Permalink
refactor: Encapsulate channel resolution logic for CLI (#1781)
Browse files Browse the repository at this point in the history
  • Loading branch information
olivier-lacroix authored Aug 12, 2024
1 parent 155b782 commit 41efb09
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 163 deletions.
62 changes: 60 additions & 2 deletions src/cli/cli_config.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
use crate::cli::has_specs::HasSpecs;
use crate::environment::LockFileUsage;
use crate::DependencyType;
use crate::Project;
use clap::Parser;
use indexmap::IndexSet;
use itertools::Itertools;
use pixi_config::ConfigCli;
use pixi_config::{Config, ConfigCli};
use pixi_consts::consts;
use pixi_manifest::FeaturesExt;
use pixi_manifest::{FeatureName, SpecType};
use rattler_conda_types::Platform;
use rattler_conda_types::ChannelConfig;
use rattler_conda_types::{Channel, NamedChannelOrUrl, Platform};
use std::collections::HashMap;
use std::path::PathBuf;

Expand All @@ -18,6 +22,60 @@ pub struct ProjectConfig {
pub manifest_path: Option<PathBuf>,
}

/// Channel configuration
#[derive(Parser, Debug, Default)]
pub struct ChannelsConfig {
/// The channels to consider as a name or a url.
/// Multiple channels can be specified by using this field multiple times.
///
/// When specifying a channel, it is common that the selected channel also
/// depends on the `conda-forge` channel.
///
/// By default, if no channel is provided, `conda-forge` is used.
#[clap(long = "channel", short = 'c', value_name = "CHANNEL")]
channels: Vec<NamedChannelOrUrl>,
}

impl ChannelsConfig {
/// Parses the channels, getting channel config and default channels from config
pub fn resolve_from_config(&self, config: &Config) -> IndexSet<Channel> {
self.resolve(config.global_channel_config(), config.default_channels())
}

/// Parses the channels, getting channel config and default channels from project
pub fn resolve_from_project(&self, project: Option<&Project>) -> IndexSet<Channel> {
match project {
Some(project) => {
let channels = project
.default_environment()
.channels()
.into_iter()
.cloned()
.collect_vec();
self.resolve(&project.channel_config(), channels)
}
None => self.resolve_from_config(&Config::load_global()),
}
}

/// Parses the channels from specified channel config and default channels
fn resolve(
&self,
channel_config: &ChannelConfig,
default_channels: Vec<NamedChannelOrUrl>,
) -> IndexSet<Channel> {
let channels = if self.channels.is_empty() {
default_channels
} else {
self.channels.clone()
};
channels
.into_iter()
.map(|c| c.into_channel(channel_config))
.collect()
}
}

/// Configuration for how to update the prefix
#[derive(Parser, Debug, Default, Clone)]
pub struct PrefixUpdateConfig {
Expand Down
35 changes: 10 additions & 25 deletions src/cli/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ use rattler::{
install::{IndicatifReporter, Installer},
package_cache::PackageCache,
};
use rattler_conda_types::{
ChannelConfig, GenericVirtualPackage, MatchSpec, NamedChannelOrUrl, PackageName, Platform,
};
use rattler_conda_types::{GenericVirtualPackage, MatchSpec, PackageName, Platform};
use rattler_solve::{resolvo::Solver, SolverImpl, SolverTask};
use rattler_virtual_packages::VirtualPackage;
use reqwest_middleware::ClientWithMiddleware;
Expand All @@ -22,6 +20,8 @@ use pixi_config::{self, Config, ConfigCli};
use pixi_progress::{await_in_progress, global_multi_progress, wrap_in_progress};
use pixi_utils::{reqwest::build_reqwest_clients, PrefixGuard};

use super::cli_config::ChannelsConfig;

/// Run a command in a temporary environment.
#[derive(Parser, Debug, Default)]
#[clap(trailing_var_arg = true, arg_required_else_help = true)]
Expand All @@ -35,9 +35,8 @@ pub struct Args {
#[clap(long = "spec", short = 's')]
pub specs: Vec<MatchSpec>,

/// The channel to install the packages from.
#[clap(long = "channel", short = 'c')]
pub channels: Vec<NamedChannelOrUrl>,
#[clap(flatten)]
channels: ChannelsConfig,

/// If specified a new environment is always created even if one already
/// exists.
Expand All @@ -56,7 +55,7 @@ pub struct EnvironmentHash {
}

impl EnvironmentHash {
pub fn from_args(args: &Args, channel_config: &ChannelConfig) -> Self {
pub fn from_args(args: &Args, config: &Config) -> Self {
Self {
command: args
.command
Expand All @@ -66,13 +65,9 @@ impl EnvironmentHash {
specs: args.specs.clone(),
channels: args
.channels
.resolve_from_config(config)
.iter()
.map(|c| {
c.clone()
.into_channel(channel_config)
.base_url()
.to_string()
})
.map(|c| c.base_url().to_string())
.collect(),
}
}
Expand Down Expand Up @@ -123,7 +118,7 @@ pub async fn create_exec_prefix(
config: &Config,
client: &ClientWithMiddleware,
) -> miette::Result<Prefix> {
let environment_name = EnvironmentHash::from_args(args, config.global_channel_config()).name();
let environment_name = EnvironmentHash::from_args(args, config).name();
let prefix = Prefix::new(cache_dir.join("cached-envs-v0").join(environment_name));

let mut guard = PrefixGuard::new(prefix.root())
Expand Down Expand Up @@ -169,21 +164,11 @@ pub async fn create_exec_prefix(
args.specs.clone()
};

// Parse the channels
let channels = if args.channels.is_empty() {
config.default_channels()
} else {
args.channels.clone()
};
let channels = channels
.into_iter()
.map(|channel| channel.into_channel(config.global_channel_config()));

// Get the repodata for the specs
let repodata = await_in_progress("fetching repodata for environment", |_| async {
gateway
.query(
channels,
args.channels.resolve_from_config(config),
[Platform::current(), Platform::NoArch],
specs.clone(),
)
Expand Down
32 changes: 8 additions & 24 deletions src/cli/global/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ use rattler::{
package_cache::PackageCache,
};
use rattler_conda_types::{
GenericVirtualPackage, MatchSpec, NamedChannelOrUrl, PackageName, Platform, PrefixRecord,
RepoDataRecord,
GenericVirtualPackage, MatchSpec, PackageName, Platform, PrefixRecord, RepoDataRecord,
};
use rattler_shell::{
activation::{ActivationVariables, Activator, PathModificationBehavior},
Expand All @@ -26,11 +25,13 @@ use rattler_virtual_packages::VirtualPackage;
use reqwest_middleware::ClientWithMiddleware;

use super::common::{channel_name_from_prefix, find_designated_package, BinDir, BinEnvDir};
use crate::{
cli::cli_config::ChannelsConfig, cli::has_specs::HasSpecs, prefix::Prefix,
rlimit::try_increase_rlimit_to_sensible,
};
use pixi_config::{self, Config, ConfigCli};
use pixi_progress::{await_in_progress, global_multi_progress, wrap_in_progress};

use crate::{cli::has_specs::HasSpecs, prefix::Prefix, rlimit::try_increase_rlimit_to_sensible};

/// Installs the defined package in a global accessible location.
#[derive(Parser, Debug)]
#[clap(arg_required_else_help = true)]
Expand All @@ -39,17 +40,8 @@ pub struct Args {
#[arg(num_args = 1..)]
packages: Vec<String>,

/// Represents the channels from which the package will be installed.
/// Multiple channels can be specified by using this field multiple times.
///
/// When specifying a channel, it is common that the selected channel also
/// depends on the `conda-forge` channel.
/// For example: `pixi global install --channel conda-forge --channel
/// bioconda`.
///
/// By default, if no channel is provided, `conda-forge` is used.
#[clap(short, long)]
channel: Vec<NamedChannelOrUrl>,
#[clap(flatten)]
channels: ChannelsConfig,

#[clap(short, long, default_value_t = Platform::current())]
platform: Platform,
Expand Down Expand Up @@ -296,15 +288,7 @@ pub fn prompt_user_to_continue(
pub async fn execute(args: Args) -> miette::Result<()> {
// Figure out what channels we are using
let config = Config::with_cli_config(&args.config);
let channels = if args.channel.is_empty() {
config.default_channels()
} else {
args.channel.clone()
}
.iter()
.cloned()
.map(|c| c.into_channel(config.global_channel_config()))
.collect_vec();
let channels = args.channels.resolve_from_config(&config);

let specs = args.specs()?;

Expand Down
35 changes: 7 additions & 28 deletions src/cli/global/upgrade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@ use indicatif::ProgressBar;
use itertools::Itertools;
use miette::{Context, IntoDiagnostic, Report};
use pixi_utils::reqwest::build_reqwest_clients;
use rattler_conda_types::{
Channel, GenericVirtualPackage, MatchSpec, NamedChannelOrUrl, PackageName, Platform,
};
use rattler_conda_types::{Channel, GenericVirtualPackage, MatchSpec, PackageName, Platform};
use rattler_solve::{resolvo::Solver, SolverImpl, SolverTask};
use rattler_virtual_packages::VirtualPackage;
use tokio::task::JoinSet;

use super::{common::find_installed_package, install::globally_install_package};
use crate::cli::has_specs::HasSpecs;
use crate::cli::{cli_config::ChannelsConfig, has_specs::HasSpecs};
use pixi_config::Config;
use pixi_progress::{global_multi_progress, long_running_progress_style, wrap_in_progress};

Expand All @@ -26,18 +24,8 @@ pub struct Args {
#[arg(required = true)]
pub specs: Vec<String>,

/// Represents the channels from which to upgrade specified package.
/// Multiple channels can be specified by using this field multiple times.
///
/// When specifying a channel, it is common that the selected channel also
/// depends on the `conda-forge` channel.
/// For example: `pixi global upgrade --channel conda-forge --channel
/// bioconda`.
///
/// By default, if no channel is provided, `conda-forge` is used, the
/// channel the package was installed from will always be used.
#[clap(short, long)]
channel: Vec<NamedChannelOrUrl>,
#[clap(flatten)]
channels: ChannelsConfig,

/// The platform to install the package for.
#[clap(long, default_value_t = Platform::current())]
Expand All @@ -53,25 +41,16 @@ impl HasSpecs for Args {
pub async fn execute(args: Args) -> miette::Result<()> {
let config = Config::load_global();
let specs = args.specs()?;
upgrade_packages(specs, config, &args.channel, args.platform).await
upgrade_packages(specs, config, &args.channels, args.platform).await
}

pub(super) async fn upgrade_packages(
specs: IndexMap<PackageName, MatchSpec>,
config: Config,
cli_channels: &[NamedChannelOrUrl],
cli_channels: &ChannelsConfig,
platform: Platform,
) -> miette::Result<()> {
let default_channels = config.default_channels();
let channel_cli = if cli_channels.is_empty() {
default_channels.as_slice()
} else {
cli_channels
}
.iter()
.cloned()
.map(|c| c.into_channel(config.global_channel_config()))
.collect_vec();
let channel_cli = cli_channels.resolve_from_config(&config);

// Get channels and version of globally installed packages in parallel
let mut channels = HashMap::with_capacity(specs.len());
Expand Down
19 changes: 6 additions & 13 deletions src/cli/global/upgrade_all.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,19 @@
use clap::Parser;
use indexmap::IndexMap;

use rattler_conda_types::{MatchSpec, NamedChannelOrUrl, Platform};
use rattler_conda_types::{MatchSpec, Platform};

use pixi_config::{Config, ConfigCli};

use crate::cli::cli_config::ChannelsConfig;

use super::{list::list_global_packages, upgrade::upgrade_packages};

/// Upgrade all globally installed packages
#[derive(Parser, Debug)]
pub struct Args {
/// Represents the channels from which to upgrade packages.
/// Multiple channels can be specified by using this field multiple times.
///
/// When specifying a channel, it is common that the selected channel also
/// depends on the `conda-forge` channel.
/// For example: `pixi global upgrade-all --channel conda-forge --channel bioconda`.
///
/// By default, if no channel is provided, `conda-forge` is used, the channel
/// the package was installed from will always be used.
#[clap(short, long)]
channel: Vec<NamedChannelOrUrl>,
#[clap(flatten)]
channels: ChannelsConfig,

#[clap(flatten)]
config: ConfigCli,
Expand All @@ -45,5 +38,5 @@ pub async fn execute(args: Args) -> miette::Result<()> {
);
}

upgrade_packages(specs, config, &args.channel, args.platform).await
upgrade_packages(specs, config, &args.channels, args.platform).await
}
Loading

0 comments on commit 41efb09

Please sign in to comment.