diff --git a/src/cli/exec.rs b/src/cli/exec.rs index 438418877f..8c77c56451 100644 --- a/src/cli/exec.rs +++ b/src/cli/exec.rs @@ -1,6 +1,5 @@ use std::{ hash::{DefaultHasher, Hash, Hasher}, - path::Path, str::FromStr, }; @@ -10,14 +9,12 @@ use rattler::{ install::{IndicatifReporter, Installer}, package_cache::PackageCache, }; -use rattler_conda_types::{GenericVirtualPackage, MatchSpec, PackageName, Platform}; -use rattler_solve::{resolvo::Solver, SolverImpl, SolverTask}; -use rattler_virtual_packages::VirtualPackage; +use rattler_conda_types::{MatchSpec, PackageName}; use reqwest_middleware::ClientWithMiddleware; -use crate::prefix::Prefix; +use crate::{cli::global::common::solve_package_records, prefix::Prefix}; use pixi_config::{self, Config, ConfigCli}; -use pixi_progress::{await_in_progress, global_multi_progress, wrap_in_progress}; +use pixi_progress::{global_multi_progress, wrap_in_progress}; use pixi_utils::{reqwest::build_reqwest_clients, PrefixGuard}; use super::cli_config::ChannelsConfig; @@ -84,14 +81,13 @@ impl EnvironmentHash { /// CLI entry point for `pixi runx` pub async fn execute(args: Args) -> miette::Result<()> { let config = Config::with_cli_config(&args.config); - let cache_dir = pixi_config::get_cache_dir().context("failed to determine cache directory")?; + let (_, client) = build_reqwest_clients(Some(&config)); let mut command_args = args.command.iter(); let command = command_args.next().ok_or_else(|| miette::miette!(help ="i.e when specifying specs explicitly use a command at the end: `pixi exec -s python==3.12 python`", "missing required command to execute",))?; - let (_, client) = build_reqwest_clients(Some(&config)); // Create the environment to run the command in. - let prefix = create_exec_prefix(&args, &cache_dir, &config, &client).await?; + let prefix = create_exec_prefix(&args, &config, &client).await?; // Get environment variables from the activation let activation_env = run_activation(&prefix).await?; @@ -114,11 +110,11 @@ pub async fn execute(args: Args) -> miette::Result<()> { /// Creates a prefix for the `pixi exec` command. pub async fn create_exec_prefix( args: &Args, - cache_dir: &Path, config: &Config, client: &ClientWithMiddleware, ) -> miette::Result { let environment_name = EnvironmentHash::from_args(args, config).name(); + let cache_dir = pixi_config::get_cache_dir().context("failed to determine cache directory")?; let prefix = Prefix::new(cache_dir.join("cached-envs-v0").join(environment_name)); let mut guard = PrefixGuard::new(prefix.root()) @@ -164,32 +160,11 @@ pub async fn create_exec_prefix( args.specs.clone() }; - // Get the repodata for the specs - let repodata = await_in_progress("fetching repodata for environment", |_| async { - gateway - .query( - args.channels.resolve_from_config(config), - [Platform::current(), Platform::NoArch], - specs.clone(), - ) - .recursive(true) - .execute() - .await - }) - .await - .into_diagnostic() - .context("failed to get repodata")?; - - // Determine virtual packages of the current platform - let virtual_packages = VirtualPackage::current() - .into_diagnostic() - .context("failed to determine virtual packages")? - .iter() - .cloned() - .map(GenericVirtualPackage::from) - .collect(); - // Solve the environment + let channels = args.channels.resolve_from_config(config); + let solved_records = solve_package_records(&gateway, channels, specs.clone()).await?; + + // Install the environment tracing::info!( "creating environment in {}", dunce::canonicalize(prefix.root()) @@ -197,17 +172,6 @@ pub async fn create_exec_prefix( .unwrap_or(prefix.root()) .display() ); - let solved_records = wrap_in_progress("solving environment", move || { - Solver.solve(SolverTask { - specs, - virtual_packages, - ..SolverTask::from_iter(&repodata) - }) - }) - .into_diagnostic() - .context("failed to solve environment")?; - - // Install the environment Installer::new() .with_download_client(client.clone()) .with_reporter( diff --git a/src/cli/global/common.rs b/src/cli/global/common.rs index 8f08c210fb..e0dc81c2d4 100644 --- a/src/cli/global/common.rs +++ b/src/cli/global/common.rs @@ -1,7 +1,14 @@ use std::path::PathBuf; -use miette::IntoDiagnostic; -use rattler_conda_types::{Channel, ChannelConfig, PackageName, PrefixRecord}; +use miette::{Context, IntoDiagnostic}; +use pixi_progress::{await_in_progress, wrap_in_progress}; +use rattler_conda_types::{ + Channel, ChannelConfig, GenericVirtualPackage, MatchSpec, PackageName, Platform, PrefixRecord, + RepoDataRecord, +}; +use rattler_repodata_gateway::Gateway; +use rattler_solve::{resolvo::Solver, SolverImpl, SolverTask}; +use rattler_virtual_packages::VirtualPackage; use crate::{prefix::Prefix, repodata}; use pixi_config::home_path; @@ -110,6 +117,60 @@ pub(super) fn channel_name_from_prefix( .unwrap_or_else(|_| prefix_package.repodata_record.channel.clone()) } +/// Solve package records from [`SparseRepoData`] for the given package MatchSpec +/// +/// # Returns +/// +/// The package records (with dependencies records) for the given package +/// MatchSpec +pub async fn solve_package_records( + gateway: &Gateway, + channels: ChannelIter, + specs: Vec, +) -> miette::Result> +where + AsChannel: Into, + ChannelIter: IntoIterator, +{ + // Get the repodata for the specs + let repodata = await_in_progress("fetching repodata for environment", |_| async { + gateway + .query( + channels, + [Platform::current(), Platform::NoArch], + specs.clone(), + ) + .recursive(true) + .execute() + .await + }) + .await + .into_diagnostic() + .context("failed to get repodata")?; + + // Determine virtual packages of the current platform + let virtual_packages = VirtualPackage::current() + .into_diagnostic() + .context("failed to determine virtual packages")? + .iter() + .cloned() + .map(GenericVirtualPackage::from) + .collect(); + + // Solve the environment + let solved_records = wrap_in_progress("solving environment", move || { + Solver.solve(SolverTask { + specs, + virtual_packages, + ..SolverTask::from_iter(&repodata) + }) + }) + .into_diagnostic() + .context("failed to solve environment")?; + + Ok(solved_records) +} + /// Find the globally installed package with the given [`PackageName`] /// /// # Returns diff --git a/src/cli/global/install.rs b/src/cli/global/install.rs index f96dabc055..4cc3470276 100644 --- a/src/cli/global/install.rs +++ b/src/cli/global/install.rs @@ -7,30 +7,28 @@ use std::{ use clap::Parser; use indexmap::IndexMap; use itertools::Itertools; -use miette::{Context, IntoDiagnostic}; +use miette::IntoDiagnostic; + use pixi_utils::reqwest::build_reqwest_clients; use rattler::{ install::{DefaultProgressFormatter, IndicatifReporter, Installer}, package_cache::PackageCache, }; -use rattler_conda_types::{ - GenericVirtualPackage, MatchSpec, PackageName, Platform, PrefixRecord, RepoDataRecord, -}; +use rattler_conda_types::{MatchSpec, PackageName, Platform, PrefixRecord, RepoDataRecord}; use rattler_shell::{ activation::{ActivationVariables, Activator, PathModificationBehavior}, shell::{Shell, ShellEnum}, }; -use rattler_solve::{resolvo::Solver, SolverImpl, SolverTask}; -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, + cli::{cli_config::ChannelsConfig, global::common::solve_package_records, 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 pixi_progress::{await_in_progress, global_multi_progress}; /// Installs the defined package in a global accessible location. #[derive(Parser, Debug)] @@ -286,8 +284,8 @@ pub fn prompt_user_to_continue( /// Install a global command pub async fn execute(args: Args) -> miette::Result<()> { - // Figure out what channels we are using let config = Config::with_cli_config(&args.config); + let (_, client) = build_reqwest_clients(Some(&config)); let channels = args.channels.resolve_from_config(&config); let specs = args.specs()?; @@ -297,52 +295,17 @@ pub async fn execute(args: Args) -> miette::Result<()> { return Ok(()); } - // Fetch the repodata - let (_, auth_client) = build_reqwest_clients(Some(&config)); - - let gateway = config.gateway(auth_client.clone()); - - let repodata = gateway - .query( - channels, - [args.platform, Platform::NoArch], - specs.values().cloned().collect_vec(), - ) - .recursive(true) - .await - .into_diagnostic()?; - - // Determine virtual packages of the current platform - let virtual_packages = VirtualPackage::current() - .into_diagnostic() - .context("failed to determine virtual packages")? - .iter() - .cloned() - .map(GenericVirtualPackage::from) - .collect(); - - // Solve the environment - let solver_specs = specs.clone(); - let solved_records = wrap_in_progress("solving environment", move || { - Solver.solve(SolverTask { - specs: solver_specs.values().cloned().collect_vec(), - virtual_packages, - ..SolverTask::from_iter(&repodata) - }) - }) - .into_diagnostic() - .context("failed to solve environment")?; + // Construct a gateway to get repodata. + let gateway = config.gateway(client.clone()); // Install the package(s) let mut executables = vec![]; - for (package_name, _) in specs { - let (prefix_package, scripts, _) = globally_install_package( - &package_name, - solved_records.clone(), - auth_client.clone(), - args.platform, - ) - .await?; + for (package_name, package_matchspec) in specs { + let records = + solve_package_records(&gateway, channels.clone(), vec![package_matchspec]).await?; + + let (prefix_package, scripts, _) = + globally_install_package(&package_name, records, client.clone(), args.platform).await?; let channel_name = channel_name_from_prefix(&prefix_package, config.global_channel_config()); let record = &prefix_package.repodata_record.package_record; diff --git a/src/cli/global/mod.rs b/src/cli/global/mod.rs index 0bc94513fe..c1c2c39777 100644 --- a/src/cli/global/mod.rs +++ b/src/cli/global/mod.rs @@ -1,6 +1,6 @@ use clap::Parser; -mod common; +pub mod common; mod install; mod list; mod remove; diff --git a/src/project/repodata.rs b/src/project/repodata.rs index 405d824bbe..e071b20d6d 100644 --- a/src/project/repodata.rs +++ b/src/project/repodata.rs @@ -1,24 +1,11 @@ use crate::project::Project; use rattler_repodata_gateway::Gateway; -use std::path::PathBuf; impl Project { /// Returns the [`Gateway`] used by this project. pub fn repodata_gateway(&self) -> &Gateway { - self.repodata_gateway.get_or_init(|| { - // Determine the cache directory and fall back to sane defaults otherwise. - let cache_dir = pixi_config::get_cache_dir().unwrap_or_else(|e| { - tracing::error!("failed to determine repodata cache directory: {e}"); - std::env::current_dir().unwrap_or_else(|_| PathBuf::from("./")) - }); - - // Construct the gateway - Gateway::builder() - .with_client(self.authenticated_client().clone()) - .with_cache_dir(cache_dir.join("repodata")) - .with_channel_config(self.config().into()) - .finish() - }) + self.repodata_gateway + .get_or_init(|| self.config.gateway(self.authenticated_client().clone())) } }