Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
25b81d2
wip: working on creating package filtering
tdejager Aug 20, 2025
f04d217
fix: fmt
tdejager Aug 20, 2025
bc94ad6
Merge branch 'main' into feat/install-build-partial-prefix
tdejager Aug 20, 2025
cce0806
wip: working on the algorithm a bit more
tdejager Aug 20, 2025
95df5b8
fmt
tdejager Aug 20, 2025
b60bcdc
fix: clippy warns
tdejager Aug 20, 2025
12bca8f
fix: clippy
tdejager Aug 20, 2025
f5b4e4e
feat: finish data-structures for supporting --target, --skips, --skip…
tdejager Aug 21, 2025
8e45893
feat: fix clippy
tdejager Aug 21, 2025
d6e71bb
feat: refactor a bit
tdejager Aug 21, 2025
e987718
feat: integrate skipping throughout
tdejager Aug 22, 2025
669df63
fix: fmt
tdejager Aug 22, 2025
1a8fa98
feat: fix install test
tdejager Aug 22, 2025
07755c9
fix: lint
tdejager Aug 22, 2025
ceee459
Merge branch 'main' into feat/install-build-partial-prefix
tdejager Aug 22, 2025
6993dfa
Merge branch 'main' into feat/install-build-partial-prefix
tdejager Aug 22, 2025
29a720c
fix: warn about no pinning strategy for unused features (#4065)
kilian-hu Aug 23, 2025
e721fe0
feat: update to latest rattler (#4422)
tdejager Aug 25, 2025
aae6c25
feat(cli): add new pixi global tree command (#4427)
Carbonhell Aug 25, 2025
03d4c74
docs: Fix build getting started doc (#4415)
Tobias-Fischer Aug 25, 2025
a2dce30
feat: integrate with ignore
tdejager Aug 25, 2025
86370c0
fix: clippy
tdejager Aug 25, 2025
7e21b99
feat: update cli docs
tdejager Aug 25, 2025
5deca78
feat: cli docs
tdejager Aug 26, 2025
995a612
feat: install filter tests
tdejager Aug 26, 2025
5728b91
feat: install filter tests
tdejager Aug 26, 2025
433c693
fix: test message
tdejager Aug 26, 2025
d062d6f
feat: clippy fix
tdejager Aug 26, 2025
236ffdc
feat: clippy fix
tdejager Aug 26, 2025
cf27f94
Merge branch 'main' into feat/install-build-partial-prefix
tdejager Aug 26, 2025
8ed736d
feat: revert env hash
tdejager Aug 26, 2025
fbe3a16
Merge branch 'main' into feat/install-build-partial-prefix
ruben-arts Aug 28, 2025
e16b693
feat: change --package to --only
tdejager Aug 28, 2025
c4334aa
fix: update test name
tdejager Aug 28, 2025
23a208e
feat: fix some user facing issues
tdejager Aug 28, 2025
66639fa
fix: fmt
tdejager Aug 28, 2025
63b284c
fix: clippy
tdejager Aug 28, 2025
d9ecd7b
chore: rename function to better represent what it does
tdejager Aug 28, 2025
d5941b8
feat: install tests
tdejager Aug 28, 2025
57a0be6
feat: install tests
tdejager Aug 28, 2025
aae596e
feat: install tests
tdejager Aug 28, 2025
1d7e862
fix: fix diagnostics for single env
tdejager Aug 28, 2025
2d854de
fix: fmt
tdejager Aug 29, 2025
70d35cc
fix: fmt
tdejager Aug 29, 2025
1af850e
fix: clippy
tdejager Aug 29, 2025
08cd5af
feat:
tdejager Aug 29, 2025
cb0c05a
fix: clippy
tdejager Aug 29, 2025
a464dfa
Merge branch 'main' into feat/install-build-partial-prefix
tdejager Aug 29, 2025
ef6ce9b
feat: make pypi ignore logic as well
tdejager Aug 29, 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
115 changes: 86 additions & 29 deletions crates/pixi_cli/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use itertools::Itertools;
use pixi_config::ConfigCli;
use pixi_core::{
UpdateLockFileOptions, WorkspaceLocator,
environment::get_update_lock_file_and_prefixes,
environment::{InstallFilter, get_update_lock_file_and_prefixes},
lock_file::{ReinstallPackages, UpdateMode},
};
use std::fmt::Write;
Expand Down Expand Up @@ -49,12 +49,21 @@ pub struct Args {
#[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")]
/// Skip installation of specific packages present in the lockfile. This uses a soft exclusion: the package will be skipped but its dependencies are installed.
#[arg(long)]
pub skip: Option<Vec<String>>,

/// Skip a package and its entire dependency subtree. This performs a hard exclusion: the package and its dependencies are not installed unless reachable from another non-skipped root.
#[arg(long)]
pub skip_with_deps: Option<Vec<String>>,

/// Install and build only this package and its dependencies
#[arg(long)]
pub only: Option<String>,
Comment on lines +56 to +62
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added these CLI args.

}

const SKIP_CUTOFF: usize = 5;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This just for the display method.


pub async fn execute(args: Args) -> miette::Result<()> {
let workspace = WorkspaceLocator::for_cli()
.with_search_start(args.project_config.workspace_locator_start())
Expand Down Expand Up @@ -84,6 +93,12 @@ pub async fn execute(args: Args) -> miette::Result<()> {
.map(|env| workspace.environment_from_name_or_env_var(Some(env)))
.collect::<Result<Vec<_>, _>>()?;

// Build the install filter from CLI args
let filter = InstallFilter::new()
.skip_direct(args.skip.clone().unwrap_or_default())
.skip_with_deps(args.skip_with_deps.clone().unwrap_or_default())
.target_package(args.only.clone());
Comment on lines +97 to +100
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here you see an example of setting an install filter.


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

Expand All @@ -106,13 +121,79 @@ pub async fn execute(args: Args) -> miette::Result<()> {
// Message what's installed
let mut message = console::style(console::Emoji("✔ ", "")).green().to_string();

let skip_opts = args.skip.is_some() || args.skip_with_deps.is_some() || args.only.is_some();

if installed_envs.len() == 1 {
write!(
&mut message,
"The {} environment has been installed",
installed_envs[0].fancy_display(),
)
.unwrap();

if skip_opts {
let names = lock_file.get_filtered_package_names(
environments
.first()
.expect("at least one environment should be available"),
&filter,
)?;
let num_skipped = names.ignored.len();
let num_retained = names.retained.len();

// When only is set, also print the number of packages that will be installed
if args.only.is_some() {
write!(&mut message, ", including {} packages", num_retained).unwrap();
}

// Create set of unmatched packages, that matches the skip filter
let (matched, unmatched): (Vec<_>, Vec<_>) = args
.skip
.iter()
.flatten()
.chain(args.skip_with_deps.iter().flatten())
.partition(|name| names.ignored.contains(*name));

if !unmatched.is_empty() {
tracing::warn!(
"The skipped arg(s) '{}' did not match any packages in the lock file",
unmatched.into_iter().join(", ")
);
}

if !num_skipped > 0 {
if num_skipped > 0 && num_skipped < SKIP_CUTOFF {
let mut skipped_packages_vec: Vec<_> = names.ignored.into_iter().collect();
skipped_packages_vec.sort();

write!(
&mut message,
" excluding '{}'",
skipped_packages_vec.join("', '")
)
.unwrap();
} else if num_skipped > 0 {
let num_matched = matched.len();
if num_matched > 0 {
write!(
&mut message,
" excluding '{}' and {} other packages",
matched.into_iter().join("', '"),
num_skipped
)
.unwrap()
} else {
write!(&mut message, " excluding {} other packages", num_skipped).unwrap()
}
} else {
write!(
&mut message,
" no packages were skipped (check if cli args were correct)"
)
.unwrap();
}
}
}
} else {
write!(
&mut message,
Expand All @@ -131,30 +212,6 @@ pub async fn execute(args: Args) -> miette::Result<()> {
.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(())
Expand Down
4 changes: 2 additions & 2 deletions crates/pixi_cli/src/reinstall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use clap::Parser;
use fancy_display::FancyDisplay;
use itertools::Itertools;
use pixi_config::ConfigCli;
use pixi_core::environment::get_update_lock_file_and_prefix;
use pixi_core::environment::{InstallFilter, get_update_lock_file_and_prefix};
use pixi_core::lock_file::{ReinstallPackages, UpdateMode};
use pixi_core::{UpdateLockFileOptions, WorkspaceLocator};

Expand Down Expand Up @@ -87,7 +87,7 @@ pub async fn execute(args: Args) -> miette::Result<()> {
max_concurrent_solves: workspace.config().max_concurrent_solves(),
},
reinstall_packages.clone(),
&[],
&InstallFilter::default(),
)
.await?;

Expand Down
4 changes: 2 additions & 2 deletions crates/pixi_cli/src/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use miette::{Context, IntoDiagnostic};
use pixi_config::ConfigCli;
use pixi_core::{
DependencyType, UpdateLockFileOptions, WorkspaceLocator,
environment::get_update_lock_file_and_prefix,
environment::{InstallFilter, get_update_lock_file_and_prefix},
lock_file::{ReinstallPackages, UpdateMode},
};
use pixi_manifest::FeaturesExt;
Expand Down Expand Up @@ -125,7 +125,7 @@ pub async fn execute(args: Args) -> miette::Result<()> {
max_concurrent_solves: workspace.config().max_concurrent_solves(),
},
ReinstallPackages::default(),
&[],
&InstallFilter::default(),
)
.await?;
}
Expand Down
2 changes: 1 addition & 1 deletion crates/pixi_cli/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ pub async fn execute(args: Args) -> miette::Result<()> {
&executable_task.run_environment,
UpdateMode::QuickValidate,
&ReinstallPackages::default(),
&[],
&pixi_core::environment::InstallFilter::default(),
)
.await?;
}
Expand Down
4 changes: 2 additions & 2 deletions crates/pixi_cli/src/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use pixi_config::{ConfigCli, ConfigCliActivation, ConfigCliPrompt};
use pixi_core::{
UpdateLockFileOptions, WorkspaceLocator,
activation::CurrentEnvVarBehavior,
environment::get_update_lock_file_and_prefix,
environment::{InstallFilter, get_update_lock_file_and_prefix},
lock_file::{ReinstallPackages, UpdateMode},
prompt,
workspace::get_activated_environment_variables,
Expand Down Expand Up @@ -344,7 +344,7 @@ pub async fn execute(args: Args) -> miette::Result<()> {
max_concurrent_solves: workspace.config().max_concurrent_solves(),
},
ReinstallPackages::default(),
&[],
&InstallFilter::default(),
)
.await?;
let lock_file = lock_file_data.into_lock_file();
Expand Down
2 changes: 1 addition & 1 deletion crates/pixi_cli/src/shell_hook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ pub async fn execute(args: Args) -> miette::Result<()> {
max_concurrent_solves: workspace.config().max_concurrent_solves(),
},
ReinstallPackages::default(),
&[],
&pixi_core::environment::InstallFilter::default(),
)
.await?;

Expand Down
4 changes: 2 additions & 2 deletions crates/pixi_cli/src/workspace/channel/add.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use miette::IntoDiagnostic;
use pixi_core::{
UpdateLockFileOptions, WorkspaceLocator,
environment::get_update_lock_file_and_prefix,
environment::{InstallFilter, get_update_lock_file_and_prefix},
lock_file::{ReinstallPackages, UpdateMode},
};

Expand Down Expand Up @@ -31,7 +31,7 @@ pub async fn execute(args: AddRemoveArgs) -> miette::Result<()> {
max_concurrent_solves: workspace.workspace().config().max_concurrent_solves(),
},
ReinstallPackages::default(),
&[],
&InstallFilter::default(),
)
.await?;

Expand Down
4 changes: 2 additions & 2 deletions crates/pixi_cli/src/workspace/channel/remove.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use miette::IntoDiagnostic;
use pixi_core::{
UpdateLockFileOptions, WorkspaceLocator,
environment::get_update_lock_file_and_prefix,
environment::{InstallFilter, get_update_lock_file_and_prefix},
lock_file::{ReinstallPackages, UpdateMode},
};

Expand Down Expand Up @@ -29,7 +29,7 @@ pub async fn execute(args: AddRemoveArgs) -> miette::Result<()> {
max_concurrent_solves: workspace.workspace().config().max_concurrent_solves(),
},
ReinstallPackages::default(),
&[],
&InstallFilter::default(),
)
.await?;
let workspace = workspace.save().await.into_diagnostic()?;
Expand Down
4 changes: 2 additions & 2 deletions crates/pixi_cli/src/workspace/platform/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use rattler_conda_types::Platform;

use pixi_core::{
UpdateLockFileOptions, Workspace,
environment::{LockFileUsage, get_update_lock_file_and_prefix},
environment::{InstallFilter, LockFileUsage, get_update_lock_file_and_prefix},
lock_file::{ReinstallPackages, UpdateMode},
};

Expand Down Expand Up @@ -57,7 +57,7 @@ pub async fn execute(workspace: Workspace, args: Args) -> miette::Result<()> {
max_concurrent_solves: workspace.workspace().config().max_concurrent_solves(),
},
ReinstallPackages::default(),
&[],
&InstallFilter::default(),
)
.await?;
workspace.save().await.into_diagnostic()?;
Expand Down
4 changes: 2 additions & 2 deletions crates/pixi_cli/src/workspace/platform/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use rattler_conda_types::Platform;

use pixi_core::{
UpdateLockFileOptions, Workspace,
environment::{LockFileUsage, get_update_lock_file_and_prefix},
environment::{InstallFilter, LockFileUsage, get_update_lock_file_and_prefix},
lock_file::{ReinstallPackages, UpdateMode},
};

Expand Down Expand Up @@ -46,7 +46,7 @@ pub async fn execute(workspace: Workspace, args: Args) -> miette::Result<()> {
max_concurrent_solves: workspace.workspace().config().max_concurrent_solves(),
},
ReinstallPackages::default(),
&[],
&InstallFilter::default(),
)
.await?;
workspace.save().await.into_diagnostic()?;
Expand Down
14 changes: 14 additions & 0 deletions crates/pixi_command_dispatcher/src/install_pixi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ pub struct InstallPixiEnvironmentSpec {
#[serde(skip)]
pub records: Vec<PixiRecord>,

/// The packages to ignore, meaning dont remove if not present in records
/// do not update when also present in PixiRecord
pub ignore_packages: Option<HashSet<PackageName>>,

/// The location to create the prefix at.
#[serde(skip)]
pub prefix: Prefix,
Expand Down Expand Up @@ -98,6 +102,7 @@ impl InstallPixiEnvironmentSpec {
records,
prefix,
installed: None,
ignore_packages: None,
build_environment: BuildEnvironment::default(),
force_reinstall: HashSet::new(),
channels: Vec::new(),
Expand Down Expand Up @@ -132,6 +137,14 @@ impl InstallPixiEnvironmentSpec {
binary_records.reserve(source_records.len());
let mut build_futures = ExecutorFutures::new(command_dispatcher.executor());
for source_record in source_records {
// Do not build if package is explicitly ignored
if self
.ignore_packages
.as_ref()
.is_some_and(|ignore| ignore.contains(&source_record.package_record.name))
{
continue;
}
build_futures.push(async {
self.build_from_source(&command_dispatcher, &source_record)
.await
Expand Down Expand Up @@ -161,6 +174,7 @@ impl InstallPixiEnvironmentSpec {
.with_download_client(command_dispatcher.download_client().clone())
.with_package_cache(command_dispatcher.package_cache().clone())
.with_reinstall_packages(self.force_reinstall)
.with_ignored_packages(self.ignore_packages.unwrap_or_default())
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the new rattler feature that ignores installed packages.

.with_execute_link_scripts(command_dispatcher.allow_execute_link_scripts())
.with_installed_packages(installed_packages);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ impl InstantiateToolEnvironmentSpec {
prefix: prefix.clone(),
installed: None,
build_environment: self.build_environment,
ignore_packages: None,
force_reinstall: Default::default(),
channels: self.channels,
channel_config: self.channel_config,
Expand Down
2 changes: 2 additions & 0 deletions crates/pixi_command_dispatcher/src/source_build/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,7 @@ impl SourceBuildSpec {
.map_err(SourceBuildError::CreateBuildEnvironmentDirectory)
.map_err(CommandDispatcherError::Failed)?,
installed: None,
ignore_packages: None,
build_environment: self.build_environment.to_build_from_build(),
force_reinstall: Default::default(),
channels: self.channels.clone(),
Expand All @@ -492,6 +493,7 @@ impl SourceBuildSpec {
.map_err(SourceBuildError::CreateBuildEnvironmentDirectory)
.map_err(CommandDispatcherError::Failed)?,
installed: None,
ignore_packages: None,
build_environment: self.build_environment.to_build_from_build(),
force_reinstall: Default::default(),
channels: self.channels.clone(),
Expand Down
1 change: 1 addition & 0 deletions crates/pixi_command_dispatcher/tests/integration/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ pub async fn simple_test() {
prefix: Prefix::create(&prefix_dir).unwrap(),
installed: None,
build_environment: build_env,
ignore_packages: None,
force_reinstall: Default::default(),
channels: vec![
Url::from_str("https://prefix.dev/conda-forge")
Expand Down
Loading
Loading