-
Notifications
You must be signed in to change notification settings - Fork 465
feat: ability to install package subsets #4404
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
25b81d2
f04d217
bc94ad6
cce0806
95df5b8
b60bcdc
12bca8f
f5b4e4e
8e45893
d6e71bb
e987718
669df63
1a8fa98
07755c9
ceee459
6993dfa
29a720c
e721fe0
aae6c25
03d4c74
a2dce30
86370c0
7e21b99
5deca78
995a612
5728b91
433c693
d062d6f
236ffdc
cf27f94
8ed736d
fbe3a16
e16b693
c4334aa
23a208e
66639fa
63b284c
d9ecd7b
d5941b8
57a0be6
aae596e
1d7e862
2d854de
70d35cc
1af850e
08cd5af
cb0c05a
a464dfa
ef6ce9b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
@@ -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>, | ||
| } | ||
|
|
||
| const SKIP_CUTOFF: usize = 5; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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()) | ||
|
|
@@ -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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
|
|
@@ -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?; | ||
|
|
||
|
|
@@ -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, | ||
|
|
@@ -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(()) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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, | ||
|
|
@@ -98,6 +102,7 @@ impl InstallPixiEnvironmentSpec { | |
| records, | ||
| prefix, | ||
| installed: None, | ||
| ignore_packages: None, | ||
| build_environment: BuildEnvironment::default(), | ||
| force_reinstall: HashSet::new(), | ||
| channels: Vec::new(), | ||
|
|
@@ -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 | ||
|
|
@@ -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()) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added these CLI args.