Skip to content

Commit

Permalink
Refactor record solving
Browse files Browse the repository at this point in the history
  • Loading branch information
olivier-lacroix committed Aug 12, 2024
1 parent 155b782 commit 4af492f
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 123 deletions.
58 changes: 10 additions & 48 deletions src/cli/exec.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::{
hash::{DefaultHasher, Hash, Hasher},
path::Path,
str::FromStr,
};

Expand All @@ -10,16 +9,13 @@ use rattler::{
install::{IndicatifReporter, Installer},
package_cache::PackageCache,
};
use rattler_conda_types::{
ChannelConfig, GenericVirtualPackage, MatchSpec, NamedChannelOrUrl, PackageName, Platform,
};
use rattler_solve::{resolvo::Solver, SolverImpl, SolverTask};
use rattler_virtual_packages::VirtualPackage;
use rattler_conda_types::{ChannelConfig, MatchSpec, NamedChannelOrUrl, 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};

/// Run a command in a temporary environment.
Expand Down Expand Up @@ -89,14 +85,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?;
Expand All @@ -119,11 +114,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<Prefix> {
let environment_name = EnvironmentHash::from_args(args, config.global_channel_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())
Expand Down Expand Up @@ -179,50 +174,17 @@ pub async fn create_exec_prefix(
.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,
[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 = solve_package_records(&gateway, channels, specs.clone()).await?;

// Install the environment
tracing::info!(
"creating environment in {}",
dunce::canonicalize(prefix.root())
.as_deref()
.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(
Expand Down
65 changes: 63 additions & 2 deletions src/cli/global/common.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<AsChannel, ChannelIter>(
gateway: &Gateway,
channels: ChannelIter,
specs: Vec<MatchSpec>,
) -> miette::Result<Vec<RepoDataRecord>>
where
AsChannel: Into<Channel>,
ChannelIter: IntoIterator<Item = AsChannel>,
{
// 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
Expand Down
75 changes: 18 additions & 57 deletions src/cli/global/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,27 @@ 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, NamedChannelOrUrl, PackageName, Platform, PrefixRecord,
RepoDataRecord,
MatchSpec, NamedChannelOrUrl, 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 pixi_config::{self, Config, ConfigCli};
use pixi_progress::{await_in_progress, global_multi_progress, wrap_in_progress};

use super::common::{
channel_name_from_prefix, find_designated_package, solve_package_records, BinDir, BinEnvDir,
};
use crate::{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};

/// Installs the defined package in a global accessible location.
#[derive(Parser, Debug)]
Expand Down Expand Up @@ -294,17 +292,15 @@ 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 = 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();
.into_iter()
.map(|c| c.into_channel(config.global_channel_config()));

let specs = args.specs()?;

Expand All @@ -313,52 +309,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;
Expand Down
2 changes: 1 addition & 1 deletion src/cli/global/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use clap::Parser;

mod common;
pub mod common;
mod install;
mod list;
mod remove;
Expand Down
17 changes: 2 additions & 15 deletions src/project/repodata.rs
Original file line number Diff line number Diff line change
@@ -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()))
}
}

0 comments on commit 4af492f

Please sign in to comment.