Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8ca2c1e
Add the ability to skip install of path dependencies
olivier-lacroix Feb 9, 2025
f3b1c15
Improve argument documentation
olivier-lacroix Feb 9, 2025
43476d9
Add integration test and doc
olivier-lacroix Feb 15, 2025
0f4dd2b
Merge branch 'main' into feat/no-path-dep
olivier-lacroix Feb 15, 2025
9a7c9df
fixes
olivier-lacroix Feb 15, 2025
5965f12
Test if pixi record is source
olivier-lacroix Feb 18, 2025
98bc642
Limit to pypi source
olivier-lacroix Feb 21, 2025
d5a45fa
Switch to --skip-local-sources
olivier-lacroix Feb 21, 2025
65c2ced
Update strings / doc
olivier-lacroix Feb 21, 2025
63e1628
Merge branch 'main' into feat/no-path-dep
olivier-lacroix Jul 25, 2025
61c19c7
fixes
olivier-lacroix Jul 25, 2025
d538e4e
Skip explicit packages
olivier-lacroix Jul 26, 2025
3a6c6cd
clid docs
olivier-lacroix Jul 26, 2025
8b8fe1e
Merge branch 'main' into feat/no-path-dep
olivier-lacroix Jul 26, 2025
193ac3e
Merge branch 'main' into feat/no-path-dep
olivier-lacroix Jul 30, 2025
f4722e5
Re-add conda test
olivier-lacroix Jul 30, 2025
28743e0
Remove --skip for reinstall
olivier-lacroix Jul 30, 2025
efb4558
Merge branch 'main' into feat/no-path-dep
tdejager Jul 31, 2025
440c736
Add function to get skipped packages
olivier-lacroix Aug 4, 2025
c3ae3a1
Merge branch 'main' into feat/no-path-dep
olivier-lacroix Aug 4, 2025
6569e6d
report
olivier-lacroix Aug 4, 2025
e563416
Add test on non-existent package being skipped
olivier-lacroix Aug 4, 2025
0f1d7fc
Filter at LockedPackageRef level
olivier-lacroix Aug 4, 2025
13db8a4
improve warning
olivier-lacroix Aug 4, 2025
4ddef55
improve message
olivier-lacroix Aug 4, 2025
00c2c21
Merge branch 'main' into feat/no-path-dep
olivier-lacroix Aug 4, 2025
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
3 changes: 3 additions & 0 deletions docs/reference/cli/pixi/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ pixi install [OPTIONS]
<br>May be provided more than once.
- <a id="arg---all" href="#arg---all">`--all (-a)`</a>
: Install all environments
- <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.

## Config Options
- <a id="arg---auth-file" href="#arg---auth-file">`--auth-file <AUTH_FILE>`</a>
Expand Down
73 changes: 55 additions & 18 deletions src/cli/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use clap::Parser;
use fancy_display::FancyDisplay;
use itertools::Itertools;
use pixi_config::ConfigCli;
use std::fmt::Write;

use crate::{
UpdateLockFileOptions, WorkspaceLocator,
Expand Down Expand Up @@ -47,6 +48,11 @@ pub struct Args {
/// Install all environments
#[arg(long, short, conflicts_with = "environment")]
pub all: bool,

/// 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.
#[arg(long, requires = "frozen")]
pub skip: Option<Vec<String>>,
}

pub async fn execute(args: Args) -> miette::Result<()> {
Expand Down Expand Up @@ -79,7 +85,7 @@ pub async fn execute(args: Args) -> miette::Result<()> {
.collect::<Result<Vec<_>, _>>()?;

// Update the prefixes by installing all packages
get_update_lock_file_and_prefixes(
let (lock_file, _) = get_update_lock_file_and_prefixes(
&environments,
UpdateMode::Revalidate,
UpdateLockFileOptions {
Expand All @@ -88,37 +94,68 @@ pub async fn execute(args: Args) -> miette::Result<()> {
max_concurrent_solves: workspace.config().max_concurrent_solves(),
},
ReinstallPackages::default(),
&args.skip.clone().unwrap_or_default(),
)
.await?;

let installed_envs = environments
.into_iter()
.iter()
.map(|env| env.name())
.collect::<Vec<_>>();

// Message what's installed
let detached_envs_message =
if let Ok(Some(path)) = workspace.config().detached_environments().path() {
format!(" in '{}'", console::style(path.display()).bold())
} else {
"".to_string()
};
let mut message = console::style(console::Emoji("✔ ", "")).green().to_string();

if installed_envs.len() == 1 {
eprintln!(
"{}The {} environment has been installed{}.",
console::style(console::Emoji("✔ ", "")).green(),
write!(
&mut message,
"The {} environment has been installed",
installed_envs[0].fancy_display(),
detached_envs_message
);
)
.unwrap();
} else {
eprintln!(
"{}The following environments have been installed: {}\t{}",
console::style(console::Emoji("✔ ", "")).green(),
write!(
&mut message,
"The following environments have been installed: {}",
installed_envs.iter().map(|n| n.fancy_display()).join(", "),
detached_envs_message
);
)
.unwrap();
}

if let Ok(Some(path)) = workspace.config().detached_environments().path() {
write!(
&mut message,
" in '{}'",
console::style(path.display()).bold()
)
.unwrap()
}

if let Some(skip) = &args.skip {
let mut all_skipped_packages = std::collections::HashSet::new();
for env in &environments {
let skipped_packages = lock_file.get_skipped_package_names(env, skip)?;
all_skipped_packages.extend(skipped_packages);
}

if !all_skipped_packages.is_empty() {
let mut skipped_packages_vec: Vec<_> = all_skipped_packages.into_iter().collect();
skipped_packages_vec.sort();
write!(
&mut message,
" excluding '{}'",
skipped_packages_vec.join("', '")
)
.unwrap();
} else {
tracing::warn!(
"No packages were skipped. '{}' did not match any packages in the lockfile.",
skip.join("', '")
);
}
}

eprintln!("{}.", message);

Ok(())
}
1 change: 1 addition & 0 deletions src/cli/reinstall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ pub async fn execute(args: Args) -> miette::Result<()> {
max_concurrent_solves: workspace.config().max_concurrent_solves(),
},
reinstall_packages.clone(),
&[],
)
.await?;

Expand Down
1 change: 1 addition & 0 deletions src/cli/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ pub async fn execute(args: Args) -> miette::Result<()> {
max_concurrent_solves: workspace.config().max_concurrent_solves(),
},
ReinstallPackages::default(),
&[],
)
.await?;
}
Expand Down
1 change: 1 addition & 0 deletions src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ pub async fn execute(args: Args) -> miette::Result<()> {
&executable_task.run_environment,
args.prefix_update_config.update_mode(),
&ReinstallPackages::default(),
&[],
)
.await?;

Expand Down
1 change: 1 addition & 0 deletions src/cli/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ pub async fn execute(args: Args) -> miette::Result<()> {
max_concurrent_solves: workspace.config().max_concurrent_solves(),
},
ReinstallPackages::default(),
&[],
)
.await?;
let lock_file = lock_file_data.into_lock_file();
Expand Down
1 change: 1 addition & 0 deletions src/cli/shell_hook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ pub async fn execute(args: Args) -> miette::Result<()> {
max_concurrent_solves: workspace.config().max_concurrent_solves(),
},
ReinstallPackages::default(),
&[],
)
.await?;

Expand Down
1 change: 1 addition & 0 deletions src/cli/workspace/channel/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub async fn execute(args: AddRemoveArgs) -> miette::Result<()> {
max_concurrent_solves: workspace.workspace().config().max_concurrent_solves(),
},
ReinstallPackages::default(),
&[],
)
.await?;

Expand Down
1 change: 1 addition & 0 deletions src/cli/workspace/channel/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub async fn execute(args: AddRemoveArgs) -> miette::Result<()> {
max_concurrent_solves: workspace.workspace().config().max_concurrent_solves(),
},
ReinstallPackages::default(),
&[],
)
.await?;
let workspace = workspace.save().await.into_diagnostic()?;
Expand Down
1 change: 1 addition & 0 deletions src/cli/workspace/platform/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ pub async fn execute(workspace: Workspace, args: Args) -> miette::Result<()> {
max_concurrent_solves: workspace.workspace().config().max_concurrent_solves(),
},
ReinstallPackages::default(),
&[],
)
.await?;
workspace.save().await.into_diagnostic()?;
Expand Down
1 change: 1 addition & 0 deletions src/cli/workspace/platform/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pub async fn execute(workspace: Workspace, args: Args) -> miette::Result<()> {
max_concurrent_solves: workspace.workspace().config().max_concurrent_solves(),
},
ReinstallPackages::default(),
&[],
)
.await?;
workspace.save().await.into_diagnostic()?;
Expand Down
40 changes: 22 additions & 18 deletions src/environment/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,28 +111,29 @@ impl LockedEnvironmentHash {
pub(crate) fn from_environment(
environment: rattler_lock::Environment,
platform: Platform,
skipped: &[String],
) -> Self {
let mut hasher = Xxh3::new();

if let Some(packages) = environment.packages(platform) {
for package in packages {
// Always has the url or path
package.location().to_owned().to_string().hash(&mut hasher);

match package {
// A select set of fields are used to hash the package
LockedPackageRef::Conda(pack) => {
if let Some(sha) = pack.record().sha256 {
sha.hash(&mut hasher);
} else if let Some(md5) = pack.record().md5 {
md5.hash(&mut hasher);
}
}
LockedPackageRef::Pypi(pack, env) => {
pack.editable.hash(&mut hasher);
env.extras.hash(&mut hasher);
for package in
LockFileDerivedData::filter_skipped_packages(environment.packages(platform), skipped)
{
// Always has the url or path
package.location().to_owned().to_string().hash(&mut hasher);

match package {
// A select set of fields are used to hash the package
LockedPackageRef::Conda(pack) => {
if let Some(sha) = pack.record().sha256 {
sha.hash(&mut hasher);
} else if let Some(md5) = pack.record().md5 {
md5.hash(&mut hasher);
}
}
LockedPackageRef::Pypi(pack, env) => {
pack.editable.hash(&mut hasher);
env.extras.hash(&mut hasher);
}
}
}

Expand Down Expand Up @@ -418,12 +419,14 @@ pub async fn get_update_lock_file_and_prefix<'env>(
update_mode: UpdateMode,
update_lock_file_options: UpdateLockFileOptions,
reinstall_packages: ReinstallPackages,
skipped: &[String],
) -> miette::Result<(LockFileDerivedData<'env>, Prefix)> {
let (lock_file, prefixes) = get_update_lock_file_and_prefixes(
&[environment.clone()],
update_mode,
update_lock_file_options,
reinstall_packages,
skipped,
)
.await?;
Ok((
Expand All @@ -442,6 +445,7 @@ pub async fn get_update_lock_file_and_prefixes<'env>(
update_mode: UpdateMode,
update_lock_file_options: UpdateLockFileOptions,
reinstall_packages: ReinstallPackages,
skipped: &[String],
) -> miette::Result<(LockFileDerivedData<'env>, Vec<Prefix>)> {
if environments.is_empty() {
return Err(miette::miette!("No environments provided to install."));
Expand Down Expand Up @@ -487,7 +491,7 @@ pub async fn get_update_lock_file_and_prefixes<'env>(
std::future::ready(Ok(Prefix::new(env.dir()))).left_future()
} else {
lock_file_ref
.prefix(env, update_mode, reinstall_packages)
.prefix(env, update_mode, reinstall_packages, skipped)
.right_future()
}
})
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub mod environment;
mod global;
mod install_pypi;
pub mod lock_file;
mod prefix;
pub mod prefix;
mod prompt;
pub(crate) mod repodata;
pub mod task;
Expand Down
Loading
Loading