Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 79 additions & 4 deletions crates/pixi_cli/src/install.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::path::PathBuf;

use clap::Parser;
use fancy_display::FancyDisplay;
use itertools::Itertools;
Expand All @@ -6,7 +8,10 @@ use pixi_core::{
UpdateLockFileOptions, WorkspaceLocator,
environment::get_update_lock_file_and_prefixes,
lock_file::{ReinstallPackages, UpdateMode},
prefix_override::PrefixOverrideGuard,
};
use pixi_manifest::FeaturesExt;
use rattler_conda_types::Platform;
use std::fmt::Write;

use crate::cli_config::WorkspaceConfig;
Expand All @@ -30,6 +35,9 @@ use crate::cli_config::WorkspaceConfig;
///
/// You can use `pixi reinstall` to reinstall all environments, one environment
/// or just some packages of an environment.
///
/// Use the `--to-prefix` flag to install packages to a custom directory instead
/// of the default environment location.
#[derive(Parser, Debug)]
pub struct Args {
#[clap(flatten)]
Expand All @@ -53,20 +61,48 @@ pub struct Args {
/// This can be useful for instance in a Dockerfile to skip local source dependencies when installing dependencies.
#[arg(long, requires = "frozen")]
pub skip: Option<Vec<String>>,

/// Install to a custom prefix directory instead of the default environment location
#[arg(long, value_name = "PREFIX", conflicts_with = "all")]
pub to_prefix: Option<PathBuf>,

/// The platform to install packages for (only used with --to-prefix)
#[arg(long, short = 'p', requires = "to_prefix")]
pub platform: Option<Platform>,
}

pub async fn execute(args: Args) -> miette::Result<()> {
use miette::{Context, IntoDiagnostic};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets just move this up to the top of the file.


let workspace = WorkspaceLocator::for_cli()
.with_search_start(args.project_config.workspace_locator_start())
.locate()?
.with_cli_config(args.config);

// Setup custom prefix if specified
if let Some(prefix_path) = &args.to_prefix {
tokio::fs::create_dir_all(prefix_path)
.await
.into_diagnostic()
.with_context(|| {
format!(
"Failed to create prefix directory: {}",
prefix_path.display()
)
})?;
}
Comment on lines +83 to +93
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed? I would assume this happens during installation.


// Install either:
//
// 1. specific environments
// 2. all environments
// 3. default environment (if no environments are specified)
let envs = if let Some(envs) = args.environment {
let envs = if args.to_prefix.is_some() {
vec![
args.environment
.and_then(|envs| envs.into_iter().next())
.unwrap_or_else(|| "default".to_string()),
]
} else if let Some(envs) = args.environment {
Comment on lines +99 to +105
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of this code? It no longer matches the comment above.

envs
} else if args.all {
workspace
Expand All @@ -84,6 +120,37 @@ pub async fn execute(args: Args) -> miette::Result<()> {
.map(|env| workspace.environment_from_name_or_env_var(Some(env)))
.collect::<Result<Vec<_>, _>>()?;

// Validate platform support if platform is specified
if let Some(platform) = args.platform {
for env in &environments {
if !env.platforms().contains(&platform) {
return Err(miette::miette!(
"Platform '{}' is not supported by environment '{}'. Supported platforms: {}",
platform,
env.name(),
env.platforms()
.iter()
.map(|p| p.to_string())
.collect::<Vec<_>>()
.join(", ")
));
}
}
}

// Use prefix override guard if installing to custom prefix
let _guard = args.to_prefix.as_ref().map(|prefix_path| {
if let Some(platform) = args.platform {
PrefixOverrideGuard::new_with_platform(
environments[0].name().to_string(),
prefix_path.clone(),
platform,
)
} else {
PrefixOverrideGuard::new(environments[0].name().to_string(), prefix_path.clone())
}
});

// Update the prefixes by installing all packages
let (lock_file, _) = get_update_lock_file_and_prefixes(
&environments,
Expand Down Expand Up @@ -122,13 +189,21 @@ pub async fn execute(args: Args) -> miette::Result<()> {
.unwrap();
}

if let Ok(Some(path)) = workspace.config().detached_environments().path() {
// Add location information to the message
if let Some(prefix_path) = &args.to_prefix {
write!(
&mut message,
" to '{}'",
console::style(prefix_path.display()).bold()
)
.unwrap();
} else if let Ok(Some(path)) = workspace.config().detached_environments().path() {
write!(
&mut message,
" in '{}'",
console::style(path.display()).bold()
)
.unwrap()
.unwrap();
}

if let Some(skip) = &args.skip {
Expand Down
3 changes: 3 additions & 0 deletions crates/pixi_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ pub mod diff;
pub mod environment;
mod install_pypi;
pub mod lock_file;
mod prefix;
pub mod prefix_override;
pub mod prompt;
pub mod repodata;

pub mod workspace;

pub mod signals;
Expand Down
1 change: 1 addition & 0 deletions crates/pixi_core/src/prefix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// Empty module - placeholder for future prefix utilities
75 changes: 75 additions & 0 deletions crates/pixi_core/src/prefix_override.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use rattler_conda_types::Platform;
use std::cell::RefCell;
use std::collections::HashMap;
use std::path::PathBuf;

thread_local! {
static PREFIX_OVERRIDES: RefCell<HashMap<String, PathBuf>> = RefCell::new(HashMap::new());
static PLATFORM_OVERRIDES: RefCell<HashMap<String, Platform>> = RefCell::new(HashMap::new());
}

/// A RAII guard that manages thread-local prefix overrides
pub struct PrefixOverrideGuard {
env_names: Vec<String>,
}

impl PrefixOverrideGuard {
/// Create a new prefix override guard for a single environment
pub fn new(env_name: String, custom_prefix: PathBuf) -> Self {
PREFIX_OVERRIDES.with(|overrides| {
overrides
.borrow_mut()
.insert(env_name.clone(), custom_prefix);
});

Self {
env_names: vec![env_name],
}
}

/// Create a new prefix override guard with platform override for a single environment
pub fn new_with_platform(env_name: String, custom_prefix: PathBuf, platform: Platform) -> Self {
PREFIX_OVERRIDES.with(|overrides| {
overrides
.borrow_mut()
.insert(env_name.clone(), custom_prefix);
});

PLATFORM_OVERRIDES.with(|overrides| {
overrides.borrow_mut().insert(env_name.clone(), platform);
});

Self {
env_names: vec![env_name],
}
}
}

impl Drop for PrefixOverrideGuard {
fn drop(&mut self) {
// Remove all overrides for the environments this guard was managing
PREFIX_OVERRIDES.with(|overrides| {
let mut map = overrides.borrow_mut();
for env_name in &self.env_names {
map.remove(env_name);
}
});

PLATFORM_OVERRIDES.with(|overrides| {
let mut map = overrides.borrow_mut();
for env_name in &self.env_names {
map.remove(env_name);
}
});
}
}

/// Get the prefix override for a specific environment, if any
pub fn get_prefix_override(env_name: &str) -> Option<PathBuf> {
PREFIX_OVERRIDES.with(|overrides| overrides.borrow().get(env_name).cloned())
}

/// Get the platform override for a specific environment, if any
pub fn get_platform_override(env_name: &str) -> Option<Platform> {
PLATFORM_OVERRIDES.with(|overrides| overrides.borrow().get(env_name).cloned())
}
18 changes: 18 additions & 0 deletions crates/pixi_core/src/workspace/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,18 @@ impl<'p> Environment<'p> {
}

/// Returns the directory where this environment is stored.
///
/// This method first checks for any thread-local prefix overrides before
/// falling back to the default environment directory.
pub fn dir(&self) -> std::path::PathBuf {
// Check for thread-local prefix override first
if let Some(override_path) =
crate::prefix_override::get_prefix_override(self.name().as_str())
{
return override_path;
}

// Fall back to default behavior
self.workspace
.environments_dir()
.join(self.environment.name.as_str())
Expand All @@ -118,6 +129,13 @@ impl<'p> Environment<'p> {

/// Returns the best platform for the current platform & environment.
pub fn best_platform(&self) -> Platform {
// Check for thread-local platform override first
if let Some(override_platform) =
crate::prefix_override::get_platform_override(self.name().as_str())
{
return override_platform;
}

let current = Platform::current();

// If the current platform is supported, return it.
Expand Down
6 changes: 6 additions & 0 deletions docs/reference/cli/pixi/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ pixi install [OPTIONS]
- <a id="arg---skip" href="#arg---skip">`--skip <SKIP>`</a>
: Skip installation of specific packages present in the lockfile. Requires --frozen. This can be useful for instance in a Dockerfile to skip local source dependencies when installing dependencies
<br>May be provided more than once.
- <a id="arg---to-prefix" href="#arg---to-prefix">`--to-prefix <PREFIX>`</a>
: Install to a custom prefix directory instead of the default environment location
- <a id="arg---platform" href="#arg---platform">`--platform (-p) <PLATFORM>`</a>
: The platform to install packages for (only used with --to-prefix)

## Config Options
- <a id="arg---auth-file" href="#arg---auth-file">`--auth-file <AUTH_FILE>`</a>
Expand Down Expand Up @@ -66,5 +70,7 @@ Running `pixi install` is not required before running other commands like `pixi

You can use `pixi reinstall` to reinstall all environments, one environment or just some packages of an environment.

Use the `--to-prefix` flag to install packages to a custom directory instead of the default environment location.


--8<-- "docs/reference/cli/pixi/install_extender:example"
2 changes: 2 additions & 0 deletions tests/integration_rust/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,8 @@ impl PixiControl {
config: Default::default(),
all: false,
skip: None,
to_prefix: None,
platform: None,
},
}
}
Expand Down
Loading