From afe606eb14c2f0768511b09854c53433b17c499c Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Mon, 11 Aug 2025 16:48:08 +0200 Subject: [PATCH 01/29] wip: process of removing --no-lockfile-update --- src/cli/cli_config.rs | 16 ++++++++---- src/cli/remove.rs | 26 +++++++++---------- src/cli/shell.rs | 3 +-- src/cli/shell_hook.rs | 3 +-- src/cli/tree.rs | 2 +- src/cli/workspace/channel/add.rs | 3 +-- src/cli/workspace/channel/remove.rs | 3 +-- .../workspace/export/conda_explicit_spec.rs | 2 +- src/workspace/workspace_mut.rs | 8 ++---- tests/integration_rust/add_tests.rs | 12 ++++++--- tests/integration_rust/common/builders.rs | 15 ++++++----- tests/integration_rust/install_tests.rs | 10 ++++--- 12 files changed, 54 insertions(+), 49 deletions(-) diff --git a/src/cli/cli_config.rs b/src/cli/cli_config.rs index 3818bb1c1b..6e7dfd37c2 100644 --- a/src/cli/cli_config.rs +++ b/src/cli/cli_config.rs @@ -113,16 +113,22 @@ pub struct LockFileUpdateConfig { impl LockFileUpdateConfig { pub fn lock_file_usage(&self) -> miette::Result { + // Error on deprecated flag usage + if self.no_lockfile_update { + return Err(miette::miette!( + help = + "Use '--frozen --no-install', to skip both lock-file updates and installation.", + "The '--no-lockfile-update' flag has been deprecated due to inconsistent behavior across commands.\n\n\ + This flag will be removed in a future version." + )); + } + let usage: LockFileUsage = self .lock_file_usage .clone() .try_into() .map_err(|e: crate::cli::LockFileUsageError| miette::miette!(e))?; - if self.no_lockfile_update { - Ok(LockFileUsage::Frozen) - } else { - Ok(usage) - } + Ok(usage) } } diff --git a/src/cli/remove.rs b/src/cli/remove.rs index 752c7602fb..ee0d3408ed 100644 --- a/src/cli/remove.rs +++ b/src/cli/remove.rs @@ -115,20 +115,18 @@ pub async fn execute(args: Args) -> miette::Result<()> { // TODO: update all environments touched by this feature defined. // updating prefix after removing from toml - if !lock_file_update_config.no_lockfile_update { - get_update_lock_file_and_prefix( - &workspace.default_environment(), - UpdateMode::Revalidate, - UpdateLockFileOptions { - lock_file_usage: lock_file_update_config.lock_file_usage()?, - no_install: prefix_update_config.no_install, - max_concurrent_solves: workspace.config().max_concurrent_solves(), - }, - ReinstallPackages::default(), - &[], - ) - .await?; - } + get_update_lock_file_and_prefix( + &workspace.default_environment(), + UpdateMode::Revalidate, + UpdateLockFileOptions { + lock_file_usage: lock_file_update_config.lock_file_usage()?, + no_install: prefix_update_config.no_install, + max_concurrent_solves: workspace.config().max_concurrent_solves(), + }, + ReinstallPackages::default(), + &[], + ) + .await?; dependency_config.display_success("Removed", Default::default()); diff --git a/src/cli/shell.rs b/src/cli/shell.rs index 0549885421..af277a0ede 100644 --- a/src/cli/shell.rs +++ b/src/cli/shell.rs @@ -283,8 +283,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { UpdateMode::QuickValidate, UpdateLockFileOptions { lock_file_usage: args.lock_file_update_config.lock_file_usage()?, - no_install: args.prefix_update_config.no_install - && args.lock_file_update_config.no_lockfile_update, + no_install: args.prefix_update_config.no_install, max_concurrent_solves: workspace.config().max_concurrent_solves(), }, ReinstallPackages::default(), diff --git a/src/cli/shell_hook.rs b/src/cli/shell_hook.rs index 640db5c0d0..ebc70e7bb8 100644 --- a/src/cli/shell_hook.rs +++ b/src/cli/shell_hook.rs @@ -163,8 +163,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { args.prefix_update_config.update_mode(), UpdateLockFileOptions { lock_file_usage: args.lock_file_update_config.lock_file_usage()?, - no_install: args.prefix_update_config.no_install - && args.lock_file_update_config.no_lockfile_update, + no_install: args.prefix_update_config.no_install, max_concurrent_solves: workspace.config().max_concurrent_solves(), }, ReinstallPackages::default(), diff --git a/src/cli/tree.rs b/src/cli/tree.rs index 8ccf220fdc..a3af01efe0 100644 --- a/src/cli/tree.rs +++ b/src/cli/tree.rs @@ -85,7 +85,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { let lock_file = workspace .update_lock_file(UpdateLockFileOptions { lock_file_usage: args.lock_file_update_config.lock_file_usage()?, - no_install: args.lock_file_update_config.no_lockfile_update, + no_install: false, max_concurrent_solves: workspace.config().max_concurrent_solves(), }) .await diff --git a/src/cli/workspace/channel/add.rs b/src/cli/workspace/channel/add.rs index d5ee7550f9..030bebfbbf 100644 --- a/src/cli/workspace/channel/add.rs +++ b/src/cli/workspace/channel/add.rs @@ -27,8 +27,7 @@ pub async fn execute(args: AddRemoveArgs) -> miette::Result<()> { UpdateMode::Revalidate, UpdateLockFileOptions { lock_file_usage: LockFileUsage::Update, - no_install: args.prefix_update_config.no_install - && args.lock_file_update_config.no_lockfile_update, + no_install: args.prefix_update_config.no_install, max_concurrent_solves: workspace.workspace().config().max_concurrent_solves(), }, ReinstallPackages::default(), diff --git a/src/cli/workspace/channel/remove.rs b/src/cli/workspace/channel/remove.rs index 8eba053ebc..7123076e64 100644 --- a/src/cli/workspace/channel/remove.rs +++ b/src/cli/workspace/channel/remove.rs @@ -25,8 +25,7 @@ pub async fn execute(args: AddRemoveArgs) -> miette::Result<()> { UpdateMode::Revalidate, UpdateLockFileOptions { lock_file_usage: LockFileUsage::Update, - no_install: args.prefix_update_config.no_install - && args.lock_file_update_config.no_lockfile_update, + no_install: args.prefix_update_config.no_install, max_concurrent_solves: workspace.workspace().config().max_concurrent_solves(), }, ReinstallPackages::default(), diff --git a/src/cli/workspace/export/conda_explicit_spec.rs b/src/cli/workspace/export/conda_explicit_spec.rs index 5e9e4e78a4..c0dc4163a8 100644 --- a/src/cli/workspace/export/conda_explicit_spec.rs +++ b/src/cli/workspace/export/conda_explicit_spec.rs @@ -171,7 +171,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { let lockfile = workspace .update_lock_file(UpdateLockFileOptions { lock_file_usage: args.lock_file_update_config.lock_file_usage()?, - no_install: args.lock_file_update_config.no_lockfile_update, + no_install: false, max_concurrent_solves: workspace.config().max_concurrent_solves(), }) .await? diff --git a/src/workspace/workspace_mut.rs b/src/workspace/workspace_mut.rs index 6ed9223256..a60e7829d0 100644 --- a/src/workspace/workspace_mut.rs +++ b/src/workspace/workspace_mut.rs @@ -363,10 +363,7 @@ impl WorkspaceMut { was_outdated: _, } = UpdateContext::builder(self.workspace()) .with_lock_file(unlocked_lock_file) - .with_no_install( - (prefix_update_config.no_install && lock_file_update_config.no_lockfile_update) - || dry_run, - ) + .with_no_install(prefix_update_config.no_install || dry_run) .finish() .await? .update() @@ -415,11 +412,10 @@ impl WorkspaceMut { glob_hash_cache, was_outdated: true, }; - if !lock_file_update_config.no_lockfile_update && !dry_run { + if !dry_run { updated_lock_file.write_to_disk()?; } if !prefix_update_config.no_install - && !lock_file_update_config.no_lockfile_update && !dry_run && self.workspace().environments().len() == 1 && default_environment_is_affected diff --git a/tests/integration_rust/add_tests.rs b/tests/integration_rust/add_tests.rs index 08f9e344bf..9f095aa36b 100644 --- a/tests/integration_rust/add_tests.rs +++ b/tests/integration_rust/add_tests.rs @@ -85,12 +85,14 @@ async fn add_with_channel() { pixi.init().no_fast_prefix_overwrite(true).await.unwrap(); pixi.add("conda-forge::py_rattler") - .without_lockfile_update() + .with_install(false) + .with_frozen(true) .await .unwrap(); pixi.add("https://prefix.dev/conda-forge::_r-mutex") - .without_lockfile_update() + .with_install(false) + .with_frozen(true) .await .unwrap(); @@ -880,7 +882,8 @@ preview = ['pixi-build']"#, // Add a package pixi.add("boost-check") .with_git_url(Url::parse("git+ssh://git@github.com/wolfv/pixi-build-examples.git").unwrap()) - .with_no_lockfile_update(true) + .with_install(false) + .with_frozen(true) .await .unwrap(); @@ -999,7 +1002,8 @@ preview = ["pixi-build"] .add("boost-check") .with_git_url(Url::parse("https://github.com/wolfv/pixi-build-examples.git").unwrap()) .with_git_subdir("boost-check".to_string()) - .with_no_lockfile_update(true) + .with_install(false) + .with_frozen(true) .await; assert!(result.is_ok()); diff --git a/tests/integration_rust/common/builders.rs b/tests/integration_rust/common/builders.rs index 03dc910636..16cd8a9179 100644 --- a/tests/integration_rust/common/builders.rs +++ b/tests/integration_rust/common/builders.rs @@ -128,11 +128,9 @@ pub trait HasPrefixUpdateConfig: Sized { pub trait HasLockFileUpdateConfig: Sized { fn lock_file_update_config(&mut self) -> &mut LockFileUpdateConfig; - /// Skip updating lockfile, this will only check if it can add a - /// dependencies. If it can add it will only add it to the manifest. - /// Install will be skipped by default. - fn without_lockfile_update(mut self) -> Self { - self.lock_file_update_config().no_lockfile_update = true; + /// Set the frozen flag to skip lock-file updates + fn with_frozen(mut self, frozen: bool) -> Self { + self.lock_file_update_config().lock_file_usage.frozen = frozen; self } } @@ -235,8 +233,13 @@ impl AddBuilder { self } + /// Deprecated: Use .with_frozen(true).with_install(false) instead pub fn with_no_lockfile_update(mut self, no_lockfile_update: bool) -> Self { - self.args.lock_file_update_config.no_lockfile_update = no_lockfile_update; + if no_lockfile_update { + // Since no_lockfile_update is deprecated, we simulate the behavior by setting frozen=true and no_install=true + self.args.lock_file_update_config.lock_file_usage.frozen = true; + self.args.prefix_update_config.no_install = true; + } self } } diff --git a/tests/integration_rust/install_tests.rs b/tests/integration_rust/install_tests.rs index 066f63264c..5334cfab94 100644 --- a/tests/integration_rust/install_tests.rs +++ b/tests/integration_rust/install_tests.rs @@ -204,7 +204,8 @@ async fn install_locked_with_config() { // Add new version of python only to the manifest pixi.add("python==3.9.0") - .without_lockfile_update() + .with_frozen(true) + .with_install(false) .await .unwrap(); @@ -278,7 +279,8 @@ async fn install_frozen() { // Add new version of python only to the manifest pixi.add("python==3.10.1") - .without_lockfile_update() + .with_frozen(true) + .with_install(false) .await .unwrap(); @@ -841,13 +843,13 @@ dependencies = [] [dependencies] python = "3.12.*" setuptools = ">=72,<73" - + [pypi-dependencies.package-b] path = "./package-b" [pypi-dependencies.package-tdjager] path = "./package-tdjager" - + "#, platform = current_platform, ); From 8e91c5c0245f8fc2a955ddadb81e48b649ec9247 Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Tue, 12 Aug 2025 10:31:29 +0200 Subject: [PATCH 02/29] fix: updated the strings --- src/cli/cli_config.rs | 6 +++--- src/cli/completion.rs | 2 +- .../pixi__cli__completion__tests__nushell_completion.snap | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/cli/cli_config.rs b/src/cli/cli_config.rs index 6e7dfd37c2..f9336a1196 100644 --- a/src/cli/cli_config.rs +++ b/src/cli/cli_config.rs @@ -102,7 +102,8 @@ impl ChannelsConfig { #[derive(Parser, Debug, Default, Clone)] pub struct LockFileUpdateConfig { - /// Don't update lockfile, implies the no-install as well. + /// Legacy flag to skip lock-file updates, behavior was inconsistent across commands + /// so it has been deprecated. #[clap(long, help_heading = consts::CLAP_UPDATE_OPTIONS)] pub no_lockfile_update: bool, @@ -116,8 +117,7 @@ impl LockFileUpdateConfig { // Error on deprecated flag usage if self.no_lockfile_update { return Err(miette::miette!( - help = - "Use '--frozen --no-install', to skip both lock-file updates and installation.", + help = "Use '--frozen' to skip lock-file updates.\n\nUse '--no-install' to skip installation.\n\n", "The '--no-lockfile-update' flag has been deprecated due to inconsistent behavior across commands.\n\n\ This flag will be removed in a future version." )); diff --git a/src/cli/completion.rs b/src/cli/completion.rs index 1a37ef8774..15e23e473f 100644 --- a/src/cli/completion.rs +++ b/src/cli/completion.rs @@ -297,7 +297,7 @@ _arguments "${_arguments_options[@]}" \ export extern "pixi run" [ ...task: string@"nu-complete pixi run" # The pixi task or a task shell command you want to run in the project's environment, which can be an executable in the environment's PATH --manifest-path: string # The path to `pixi.toml`, `pyproject.toml`, or the project directory - --no-lockfile-update # Don't update lockfile, implies the no-install as well + --no-lockfile-update # Legacy flag, do not use, will be removed in subsequent version --frozen # Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file --locked # Check if lockfile is up-to-date before installing the environment, aborts when lockfile isn't up-to-date with the manifest file --no-install # Don't modify the environment, only modify the lock-file diff --git a/src/cli/snapshots/pixi__cli__completion__tests__nushell_completion.snap b/src/cli/snapshots/pixi__cli__completion__tests__nushell_completion.snap index 6325238e1b..bf2fffd47f 100644 --- a/src/cli/snapshots/pixi__cli__completion__tests__nushell_completion.snap +++ b/src/cli/snapshots/pixi__cli__completion__tests__nushell_completion.snap @@ -1,13 +1,12 @@ --- source: src/cli/completion.rs expression: result -snapshot_kind: text --- # Runs task in project export extern "pixi run" [ ...task: string@"nu-complete pixi run" # The pixi task or a task shell command you want to run in the project's environment, which can be an executable in the environment's PATH --manifest-path: string # The path to `pixi.toml`, `pyproject.toml`, or the project directory - --no-lockfile-update # Don't update lockfile, implies the no-install as well + --no-lockfile-update # Legacy flag, do not use, will be removed in subsequent version --frozen # Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file --locked # Check if lockfile is up-to-date before installing the environment, aborts when lockfile isn't up-to-date with the manifest file --no-install # Don't modify the environment, only modify the lock-file From e75a14ce1013eafdd93c6de25be3811d822ee505 Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Tue, 12 Aug 2025 16:39:09 +0200 Subject: [PATCH 03/29] feat: make flags more sensible --- src/cli/add.rs | 10 +-- src/cli/cli_config.rs | 10 ++- src/cli/import.rs | 6 +- src/cli/list.rs | 8 ++- src/cli/lock.rs | 14 ++--- src/cli/remove.rs | 23 ++++--- src/cli/run.rs | 11 ++-- src/cli/shell.rs | 11 ++-- src/cli/shell_hook.rs | 10 +-- src/cli/tree.rs | 8 ++- src/cli/upgrade.rs | 8 ++- src/cli/workspace/channel/add.rs | 6 +- src/cli/workspace/channel/mod.rs | 8 ++- src/cli/workspace/channel/remove.rs | 6 +- .../workspace/export/conda_explicit_spec.rs | 8 ++- src/environment/mod.rs | 12 ++-- src/lock_file/update.rs | 62 +++++++++---------- src/workspace/workspace_mut.rs | 10 ++- tests/integration_python/test_run_cli.py | 9 +-- tests/integration_rust/add_tests.rs | 2 +- tests/integration_rust/common/builders.rs | 46 +++++++++----- tests/integration_rust/common/mod.rs | 25 +++++--- tests/integration_rust/install_tests.rs | 2 +- tests/integration_rust/solve_group_tests.rs | 2 +- tests/integration_rust/upgrade_tests.rs | 2 +- 25 files changed, 191 insertions(+), 128 deletions(-) diff --git a/src/cli/add.rs b/src/cli/add.rs index 5e12be64e1..4c47a16a48 100644 --- a/src/cli/add.rs +++ b/src/cli/add.rs @@ -9,7 +9,7 @@ use rattler_conda_types::{MatchSpec, PackageName}; use super::{cli_config::LockFileUpdateConfig, has_specs::HasSpecs}; use crate::{ WorkspaceLocator, - cli::cli_config::{DependencyConfig, PrefixUpdateConfig, WorkspaceConfig}, + cli::cli_config::{DependencyConfig, NoInstallConfig, WorkspaceConfig}, environment::sanity_check_workspace, workspace::DependencyType, }; @@ -83,7 +83,7 @@ pub struct Args { pub dependency_config: DependencyConfig, #[clap(flatten)] - pub prefix_update_config: PrefixUpdateConfig, + pub no_install_config: NoInstallConfig, #[clap(flatten)] pub lock_file_update_config: LockFileUpdateConfig, @@ -97,9 +97,9 @@ pub struct Args { } pub async fn execute(args: Args) -> miette::Result<()> { - let (dependency_config, prefix_update_config, lock_file_update_config, workspace_config) = ( + let (dependency_config, no_install_config, lock_file_update_config, workspace_config) = ( args.dependency_config, - args.prefix_update_config, + args.no_install_config, args.lock_file_update_config, args.workspace_config, ); @@ -191,7 +191,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { match_specs, pypi_deps, source_specs, - &prefix_update_config, + no_install_config.no_install, &lock_file_update_config, &dependency_config.feature, &dependency_config.platforms, diff --git a/src/cli/cli_config.rs b/src/cli/cli_config.rs index f9336a1196..35bc51f940 100644 --- a/src/cli/cli_config.rs +++ b/src/cli/cli_config.rs @@ -132,19 +132,23 @@ impl LockFileUpdateConfig { } } -/// Configuration for how to update the prefix +/// Configuration for skipping installation #[derive(Parser, Debug, Default, Clone)] -pub struct PrefixUpdateConfig { +pub struct NoInstallConfig { /// Don't modify the environment, only modify the lock-file. #[arg(long, help_heading = consts::CLAP_UPDATE_OPTIONS)] pub no_install: bool, +} +/// Configuration for environment validation +#[derive(Parser, Debug, Default, Clone)] +pub struct RevalidateConfig { /// Run the complete environment validation. This will reinstall a broken environment. #[arg(long, help_heading = consts::CLAP_UPDATE_OPTIONS)] pub revalidate: bool, } -impl PrefixUpdateConfig { +impl RevalidateConfig { /// Which `[UpdateMode]` to use pub(crate) fn update_mode(&self) -> UpdateMode { if self.revalidate { diff --git a/src/cli/import.rs b/src/cli/import.rs index 47023188ad..eaa61c59c5 100644 --- a/src/cli/import.rs +++ b/src/cli/import.rs @@ -18,7 +18,7 @@ use thiserror::Error; use super::cli_config::LockFileUpdateConfig; use crate::{ WorkspaceLocator, - cli::cli_config::{PrefixUpdateConfig, WorkspaceConfig}, + cli::cli_config::{NoInstallConfig, RevalidateConfig, WorkspaceConfig}, environment::sanity_check_workspace, }; @@ -59,7 +59,9 @@ pub struct Args { pub feature: Option, #[clap(flatten)] - pub prefix_update_config: PrefixUpdateConfig, + pub no_install_config: NoInstallConfig, + #[clap(flatten)] + pub revalidate_config: RevalidateConfig, #[clap(flatten)] pub lock_file_update_config: LockFileUpdateConfig, diff --git a/src/cli/list.rs b/src/cli/list.rs index 6f20df1416..68cbe3b300 100644 --- a/src/cli/list.rs +++ b/src/cli/list.rs @@ -22,7 +22,7 @@ use serde::Serialize; use uv_configuration::ConfigSettings; use uv_distribution::RegistryWheelIndex; -use super::cli_config::LockFileUpdateConfig; +use super::cli_config::{LockFileUpdateConfig, NoInstallConfig}; use crate::{ WorkspaceLocator, cli::cli_config::WorkspaceConfig, @@ -74,6 +74,9 @@ pub struct Args { #[clap(flatten)] pub lock_file_update_config: LockFileUpdateConfig, + #[clap(flatten)] + pub no_install_config: NoInstallConfig, + /// Only list packages that are explicitly defined in the workspace. #[arg(short = 'x', long)] pub explicit: bool, @@ -185,10 +188,11 @@ pub async fn execute(args: Args) -> miette::Result<()> { let lock_file = workspace .update_lock_file(UpdateLockFileOptions { lock_file_usage: args.lock_file_update_config.lock_file_usage()?, - no_install: false, + no_install: args.no_install_config.no_install, max_concurrent_solves: workspace.config().max_concurrent_solves(), }) .await? + .0 .into_lock_file(); // Load the platform diff --git a/src/cli/lock.rs b/src/cli/lock.rs index 143706130b..a6b1cf6c5c 100644 --- a/src/cli/lock.rs +++ b/src/cli/lock.rs @@ -1,6 +1,7 @@ use clap::Parser; use miette::{Context, IntoDiagnostic}; +use crate::cli::cli_config::NoInstallConfig; use crate::lock_file::LockFileDerivedData; use crate::{ WorkspaceLocator, @@ -18,6 +19,9 @@ pub struct Args { #[clap(flatten)] pub workspace_config: WorkspaceConfig, + #[clap(flatten)] + pub no_install_config: NoInstallConfig, + /// Output the changes in JSON format. #[clap(long)] pub json: bool, @@ -36,14 +40,10 @@ pub async fn execute(args: Args) -> miette::Result<()> { // Update the lock-file, and extract it from the derived data to drop additional resources // created for the solve. let original_lock_file = workspace.load_lock_file().await?; - let LockFileDerivedData { - lock_file, - was_outdated, - .. - } = workspace + let (LockFileDerivedData { lock_file, .. }, lock_updated) = workspace .update_lock_file(UpdateLockFileOptions { lock_file_usage: LockFileUsage::Update, - no_install: false, + no_install: args.no_install_config.no_install, max_concurrent_solves: workspace.config().max_concurrent_solves(), }) .await?; @@ -57,7 +57,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { let json_diff = LockFileJsonDiff::new(Some(&workspace), diff); let json = serde_json::to_string_pretty(&json_diff).expect("failed to convert to json"); println!("{}", json); - } else if was_outdated { + } else if lock_updated { eprintln!( "{}Updated lock-file", console::style(console::Emoji("✔ ", "")).green() diff --git a/src/cli/remove.rs b/src/cli/remove.rs index ee0d3408ed..232f5bb20d 100644 --- a/src/cli/remove.rs +++ b/src/cli/remove.rs @@ -1,9 +1,9 @@ use super::{cli_config::LockFileUpdateConfig, has_specs::HasSpecs}; use crate::{ DependencyType, UpdateLockFileOptions, WorkspaceLocator, - cli::cli_config::{DependencyConfig, PrefixUpdateConfig, WorkspaceConfig}, + cli::cli_config::{DependencyConfig, NoInstallConfig, RevalidateConfig, WorkspaceConfig}, environment::get_update_lock_file_and_prefix, - lock_file::{ReinstallPackages, UpdateMode}, + lock_file::ReinstallPackages, }; use clap::Parser; use miette::{Context, IntoDiagnostic}; @@ -30,7 +30,9 @@ pub struct Args { pub dependency_config: DependencyConfig, #[clap(flatten)] - pub prefix_update_config: PrefixUpdateConfig, + pub no_install_config: NoInstallConfig, + #[clap(flatten)] + pub revalidate_config: RevalidateConfig, #[clap(flatten)] pub lock_file_update_config: LockFileUpdateConfig, @@ -40,9 +42,16 @@ pub struct Args { } pub async fn execute(args: Args) -> miette::Result<()> { - let (dependency_config, prefix_update_config, lock_file_update_config, workspace_config) = ( + let ( + dependency_config, + no_install_config, + revalidate_config, + lock_file_update_config, + workspace_config, + ) = ( args.dependency_config, - args.prefix_update_config, + args.no_install_config, + args.revalidate_config, args.lock_file_update_config, args.workspace_config, ); @@ -117,10 +126,10 @@ pub async fn execute(args: Args) -> miette::Result<()> { // updating prefix after removing from toml get_update_lock_file_and_prefix( &workspace.default_environment(), - UpdateMode::Revalidate, + revalidate_config.update_mode(), UpdateLockFileOptions { lock_file_usage: lock_file_update_config.lock_file_usage()?, - no_install: prefix_update_config.no_install, + no_install: no_install_config.no_install, max_concurrent_solves: workspace.config().max_concurrent_solves(), }, ReinstallPackages::default(), diff --git a/src/cli/run.rs b/src/cli/run.rs index af0f4a2161..a457504a56 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -21,7 +21,7 @@ use tracing::Level; use super::cli_config::LockFileUpdateConfig; use crate::{ Workspace, WorkspaceLocator, - cli::cli_config::{PrefixUpdateConfig, WorkspaceConfig}, + cli::cli_config::{NoInstallConfig, RevalidateConfig, WorkspaceConfig}, environment::sanity_check_workspace, lock_file::{ReinstallPackages, UpdateLockFileOptions}, task::{ @@ -50,7 +50,9 @@ pub struct Args { pub workspace_config: WorkspaceConfig, #[clap(flatten)] - pub prefix_update_config: PrefixUpdateConfig, + pub no_install_config: NoInstallConfig, + #[clap(flatten)] + pub revalidate_config: RevalidateConfig, #[clap(flatten)] pub lock_file_update_config: LockFileUpdateConfig, @@ -130,7 +132,8 @@ pub async fn execute(args: Args) -> miette::Result<()> { max_concurrent_solves: workspace.config().max_concurrent_solves(), ..UpdateLockFileOptions::default() }) - .await?; + .await? + .0; // Spawn a task that listens for ctrl+c and resets the cursor. tokio::spawn(async { @@ -257,7 +260,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { lock_file .prefix( &executable_task.run_environment, - args.prefix_update_config.update_mode(), + args.revalidate_config.update_mode(), &ReinstallPackages::default(), &[], ) diff --git a/src/cli/shell.rs b/src/cli/shell.rs index af277a0ede..57aec86752 100644 --- a/src/cli/shell.rs +++ b/src/cli/shell.rs @@ -8,14 +8,13 @@ use rattler_shell::{ shell::{CmdExe, PowerShell, Shell, ShellEnum, ShellScript}, }; -use crate::lock_file::UpdateMode; use crate::workspace::get_activated_environment_variables; use crate::{ UpdateLockFileOptions, WorkspaceLocator, activation::CurrentEnvVarBehavior, environment::get_update_lock_file_and_prefix, prompt, }; use crate::{ - cli::cli_config::{PrefixUpdateConfig, WorkspaceConfig}, + cli::cli_config::{NoInstallConfig, RevalidateConfig, WorkspaceConfig}, lock_file::ReinstallPackages, }; use pixi_config::{ConfigCli, ConfigCliActivation, ConfigCliPrompt}; @@ -34,7 +33,9 @@ pub struct Args { workspace_config: WorkspaceConfig, #[clap(flatten)] - pub prefix_update_config: PrefixUpdateConfig, + pub no_install_config: NoInstallConfig, + #[clap(flatten)] + pub revalidate_config: RevalidateConfig, #[clap(flatten)] pub lock_file_update_config: LockFileUpdateConfig, @@ -280,10 +281,10 @@ pub async fn execute(args: Args) -> miette::Result<()> { #[allow(unused_variables)] let (lock_file_data, prefix) = get_update_lock_file_and_prefix( &environment, - UpdateMode::QuickValidate, + args.revalidate_config.update_mode(), UpdateLockFileOptions { lock_file_usage: args.lock_file_update_config.lock_file_usage()?, - no_install: args.prefix_update_config.no_install, + no_install: args.no_install_config.no_install, max_concurrent_solves: workspace.config().max_concurrent_solves(), }, ReinstallPackages::default(), diff --git a/src/cli/shell_hook.rs b/src/cli/shell_hook.rs index ebc70e7bb8..6354996522 100644 --- a/src/cli/shell_hook.rs +++ b/src/cli/shell_hook.rs @@ -14,7 +14,7 @@ use serde_json; use crate::{ UpdateLockFileOptions, Workspace, WorkspaceLocator, activation::{CurrentEnvVarBehavior, get_activator}, - cli::cli_config::{PrefixUpdateConfig, WorkspaceConfig}, + cli::cli_config::{NoInstallConfig, RevalidateConfig, WorkspaceConfig}, environment::get_update_lock_file_and_prefix, lock_file::ReinstallPackages, prompt, @@ -38,7 +38,9 @@ pub struct Args { pub project_config: WorkspaceConfig, #[clap(flatten)] - pub prefix_update_config: PrefixUpdateConfig, + pub no_install_config: NoInstallConfig, + #[clap(flatten)] + pub revalidate_config: RevalidateConfig, #[clap(flatten)] pub lock_file_update_config: LockFileUpdateConfig, @@ -160,10 +162,10 @@ pub async fn execute(args: Args) -> miette::Result<()> { let (lock_file_data, _prefix) = get_update_lock_file_and_prefix( &environment, - args.prefix_update_config.update_mode(), + args.revalidate_config.update_mode(), UpdateLockFileOptions { lock_file_usage: args.lock_file_update_config.lock_file_usage()?, - no_install: args.prefix_update_config.no_install, + no_install: args.no_install_config.no_install, max_concurrent_solves: workspace.config().max_concurrent_solves(), }, ReinstallPackages::default(), diff --git a/src/cli/tree.rs b/src/cli/tree.rs index a3af01efe0..314db816c3 100644 --- a/src/cli/tree.rs +++ b/src/cli/tree.rs @@ -19,7 +19,7 @@ use crate::{ workspace::Environment, }; -use super::cli_config::LockFileUpdateConfig; +use super::cli_config::{LockFileUpdateConfig, NoInstallConfig}; /// Show a tree of workspace dependencies #[derive(Debug, Parser)] @@ -54,6 +54,9 @@ pub struct Args { #[clap(flatten)] pub lock_file_update_config: LockFileUpdateConfig, + #[clap(flatten)] + pub no_install_config: NoInstallConfig, + /// Invert tree and show what depends on given package in the regex argument #[arg(short, long, requires = "regex")] pub invert: bool, @@ -85,11 +88,12 @@ pub async fn execute(args: Args) -> miette::Result<()> { let lock_file = workspace .update_lock_file(UpdateLockFileOptions { lock_file_usage: args.lock_file_update_config.lock_file_usage()?, - no_install: false, + no_install: args.no_install_config.no_install, max_concurrent_solves: workspace.config().max_concurrent_solves(), }) .await .wrap_err("Failed to update lock file")? + .0 .into_lock_file(); let platform = args.platform.unwrap_or_else(|| environment.best_platform()); diff --git a/src/cli/upgrade.rs b/src/cli/upgrade.rs index 96e21adb5b..7f39a0dc7a 100644 --- a/src/cli/upgrade.rs +++ b/src/cli/upgrade.rs @@ -1,6 +1,6 @@ use std::cmp::Ordering; -use super::cli_config::{LockFileUpdateConfig, PrefixUpdateConfig}; +use super::cli_config::{LockFileUpdateConfig, NoInstallConfig, RevalidateConfig}; use crate::{ WorkspaceLocator, cli::cli_config::WorkspaceConfig, @@ -28,7 +28,9 @@ pub struct Args { pub workspace_config: WorkspaceConfig, #[clap(flatten)] - pub prefix_update_config: PrefixUpdateConfig, + pub no_install_config: NoInstallConfig, + #[clap(flatten)] + pub revalidate_config: RevalidateConfig, #[clap(flatten)] pub lock_file_update_config: LockFileUpdateConfig, @@ -91,7 +93,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { match_specs, pypi_deps, IndexMap::default(), - &args.prefix_update_config, + args.no_install_config.no_install, &args.lock_file_update_config, &args.specs.feature, &[], diff --git a/src/cli/workspace/channel/add.rs b/src/cli/workspace/channel/add.rs index 030bebfbbf..023a05ae38 100644 --- a/src/cli/workspace/channel/add.rs +++ b/src/cli/workspace/channel/add.rs @@ -1,7 +1,7 @@ use crate::{ UpdateLockFileOptions, WorkspaceLocator, environment::{LockFileUsage, get_update_lock_file_and_prefix}, - lock_file::{ReinstallPackages, UpdateMode}, + lock_file::ReinstallPackages, }; use miette::IntoDiagnostic; @@ -24,10 +24,10 @@ pub async fn execute(args: AddRemoveArgs) -> miette::Result<()> { // TODO: Update all environments touched by the features defined. get_update_lock_file_and_prefix( &workspace.workspace().default_environment(), - UpdateMode::Revalidate, + args.revalidate_config.update_mode(), UpdateLockFileOptions { lock_file_usage: LockFileUsage::Update, - no_install: args.prefix_update_config.no_install, + no_install: args.no_install_config.no_install, max_concurrent_solves: workspace.workspace().config().max_concurrent_solves(), }, ReinstallPackages::default(), diff --git a/src/cli/workspace/channel/mod.rs b/src/cli/workspace/channel/mod.rs index 615c034a2f..19e64da69e 100644 --- a/src/cli/workspace/channel/mod.rs +++ b/src/cli/workspace/channel/mod.rs @@ -2,7 +2,9 @@ pub mod add; pub mod list; pub mod remove; -use crate::cli::cli_config::{LockFileUpdateConfig, PrefixUpdateConfig, WorkspaceConfig}; +use crate::cli::cli_config::{ + LockFileUpdateConfig, NoInstallConfig, RevalidateConfig, WorkspaceConfig, +}; use clap::Parser; use miette::IntoDiagnostic; use pixi_config::ConfigCli; @@ -35,7 +37,9 @@ pub struct AddRemoveArgs { pub prepend: bool, #[clap(flatten)] - pub prefix_update_config: PrefixUpdateConfig, + pub no_install_config: NoInstallConfig, + #[clap(flatten)] + pub revalidate_config: RevalidateConfig, #[clap(flatten)] pub lock_file_update_config: LockFileUpdateConfig, diff --git a/src/cli/workspace/channel/remove.rs b/src/cli/workspace/channel/remove.rs index 7123076e64..b152a9ca6d 100644 --- a/src/cli/workspace/channel/remove.rs +++ b/src/cli/workspace/channel/remove.rs @@ -1,4 +1,4 @@ -use crate::lock_file::{ReinstallPackages, UpdateMode}; +use crate::lock_file::ReinstallPackages; use crate::{ UpdateLockFileOptions, WorkspaceLocator, environment::{LockFileUsage, get_update_lock_file_and_prefix}, @@ -22,10 +22,10 @@ pub async fn execute(args: AddRemoveArgs) -> miette::Result<()> { // Try to update the lock-file without the removed channels get_update_lock_file_and_prefix( &workspace.workspace().default_environment(), - UpdateMode::Revalidate, + args.revalidate_config.update_mode(), UpdateLockFileOptions { lock_file_usage: LockFileUsage::Update, - no_install: args.prefix_update_config.no_install, + no_install: args.no_install_config.no_install, max_concurrent_solves: workspace.workspace().config().max_concurrent_solves(), }, ReinstallPackages::default(), diff --git a/src/cli/workspace/export/conda_explicit_spec.rs b/src/cli/workspace/export/conda_explicit_spec.rs index c0dc4163a8..0c885bd18d 100644 --- a/src/cli/workspace/export/conda_explicit_spec.rs +++ b/src/cli/workspace/export/conda_explicit_spec.rs @@ -5,7 +5,7 @@ use std::{ use crate::{ WorkspaceLocator, - cli::cli_config::{LockFileUpdateConfig, WorkspaceConfig}, + cli::cli_config::{LockFileUpdateConfig, NoInstallConfig, WorkspaceConfig}, lock_file::UpdateLockFileOptions, }; use clap::Parser; @@ -45,6 +45,9 @@ pub struct Args { #[clap(flatten)] pub lock_file_update_config: LockFileUpdateConfig, + #[clap(flatten)] + pub no_install_config: NoInstallConfig, + #[clap(flatten)] config: ConfigCli, } @@ -171,10 +174,11 @@ pub async fn execute(args: Args) -> miette::Result<()> { let lockfile = workspace .update_lock_file(UpdateLockFileOptions { lock_file_usage: args.lock_file_update_config.lock_file_usage()?, - no_install: false, + no_install: args.no_install_config.no_install, max_concurrent_solves: workspace.config().max_concurrent_solves(), }) .await? + .0 .into_lock_file(); let mut environments = Vec::new(); diff --git a/src/environment/mod.rs b/src/environment/mod.rs index a6899b892c..e16f8d2ba9 100644 --- a/src/environment/mod.rs +++ b/src/environment/mod.rs @@ -394,12 +394,9 @@ pub enum LockFileUsage { } impl LockFileUsage { - /// Returns true if the lock-file should be updated if it is out of date. - pub(crate) fn allows_lock_file_updates(self) -> bool { - match self { - LockFileUsage::Update => true, - LockFileUsage::Locked | LockFileUsage::Frozen => false, - } + /// Returns true if the process should error when the lock-file + pub(crate) fn allow_updates(self) -> bool { + !matches!(self, LockFileUsage::Locked) } /// Returns true if the lock-file should be checked if it is out of date. @@ -480,7 +477,8 @@ pub async fn get_update_lock_file_and_prefixes<'env>( no_install, max_concurrent_solves: update_lock_file_options.max_concurrent_solves, }) - .await?; + .await? + .0; // Get the prefix from the lock-file. let lock_file_ref = &lock_file; diff --git a/src/lock_file/update.rs b/src/lock_file/update.rs index 4b68e6656c..9c0126b9f6 100644 --- a/src/lock_file/update.rs +++ b/src/lock_file/update.rs @@ -75,7 +75,7 @@ impl Workspace { pub async fn update_lock_file( &self, options: UpdateLockFileOptions, - ) -> miette::Result> { + ) -> miette::Result<(LockFileDerivedData<'_>, bool)> { let lock_file = self.load_lock_file().await?; let glob_hash_cache = GlobHashCache::default(); @@ -97,18 +97,20 @@ impl Workspace { if !options.lock_file_usage.should_check_if_out_of_date() { tracing::info!("skipping check if lock-file is up-to-date"); - return Ok(LockFileDerivedData { - workspace: self, - lock_file, - package_cache, - updated_conda_prefixes: Default::default(), - updated_pypi_prefixes: Default::default(), - uv_context: Default::default(), - io_concurrency_limit: IoConcurrencyLimit::default(), - command_dispatcher, - glob_hash_cache, - was_outdated: false, - }); + return Ok(( + LockFileDerivedData { + workspace: self, + lock_file, + package_cache, + updated_conda_prefixes: Default::default(), + updated_pypi_prefixes: Default::default(), + uv_context: Default::default(), + io_concurrency_limit: IoConcurrencyLimit::default(), + command_dispatcher, + glob_hash_cache, + }, + false, + )); } // Check which environments are out of date. @@ -122,23 +124,25 @@ impl Workspace { tracing::info!("the lock-file is up-to-date"); // If no-environment is outdated we can return early. - return Ok(LockFileDerivedData { - workspace: self, - lock_file, - package_cache, - updated_conda_prefixes: Default::default(), - updated_pypi_prefixes: Default::default(), - uv_context: Default::default(), - io_concurrency_limit: IoConcurrencyLimit::default(), - command_dispatcher, - glob_hash_cache, - was_outdated: false, - }); + return Ok(( + LockFileDerivedData { + workspace: self, + lock_file, + package_cache, + updated_conda_prefixes: Default::default(), + updated_pypi_prefixes: Default::default(), + uv_context: Default::default(), + io_concurrency_limit: IoConcurrencyLimit::default(), + command_dispatcher, + glob_hash_cache, + }, + false, + )); } // If the lock-file is out of date, but we're not allowed to update it, we // should exit. - if !options.lock_file_usage.allows_lock_file_updates() { + if !options.lock_file_usage.allow_updates() { miette::bail!("lock-file not up-to-date with the workspace"); } @@ -158,7 +162,7 @@ impl Workspace { // Write the lock-file to disk lock_file_derived_data.write_to_disk()?; - Ok(lock_file_derived_data) + Ok((lock_file_derived_data, true)) } /// Loads the lockfile for the workspace or returns `Lockfile::default` if @@ -258,9 +262,6 @@ pub struct LockFileDerivedData<'p> { /// An object that caches input hashes pub glob_hash_cache: GlobHashCache, - - /// Whether the lock file was outdated - pub was_outdated: bool, } /// The mode to use when updating a prefix. @@ -1723,7 +1724,6 @@ impl<'p> UpdateContext<'p> { io_concurrency_limit: self.io_concurrency_limit, command_dispatcher: self.command_dispatcher, glob_hash_cache: self.glob_hash_cache, - was_outdated: true, }) } } diff --git a/src/workspace/workspace_mut.rs b/src/workspace/workspace_mut.rs index a60e7829d0..f69e20a728 100644 --- a/src/workspace/workspace_mut.rs +++ b/src/workspace/workspace_mut.rs @@ -24,7 +24,7 @@ use toml_edit::DocumentMut; use crate::{ Workspace, - cli::cli_config::{LockFileUpdateConfig, PrefixUpdateConfig}, + cli::cli_config::LockFileUpdateConfig, diff::LockFileDiff, environment::LockFileUsage, lock_file::{LockFileDerivedData, ReinstallPackages, UpdateContext, UpdateMode}, @@ -235,7 +235,7 @@ impl WorkspaceMut { match_specs: MatchSpecs, pypi_deps: PypiDeps, source_specs: SourceSpecs, - prefix_update_config: &PrefixUpdateConfig, + no_install: bool, lock_file_update_config: &LockFileUpdateConfig, feature_name: &FeatureName, platforms: &[Platform], @@ -360,10 +360,9 @@ impl WorkspaceMut { command_dispatcher, glob_hash_cache, io_concurrency_limit, - was_outdated: _, } = UpdateContext::builder(self.workspace()) .with_lock_file(unlocked_lock_file) - .with_no_install(prefix_update_config.no_install || dry_run) + .with_no_install(no_install || dry_run) .finish() .await? .update() @@ -410,12 +409,11 @@ impl WorkspaceMut { io_concurrency_limit, command_dispatcher, glob_hash_cache, - was_outdated: true, }; if !dry_run { updated_lock_file.write_to_disk()?; } - if !prefix_update_config.no_install + if !no_install && !dry_run && self.workspace().environments().len() == 1 && default_environment_is_affected diff --git a/tests/integration_python/test_run_cli.py b/tests/integration_python/test_run_cli.py index 1f4d617484..3aae2c2dd4 100644 --- a/tests/integration_python/test_run_cli.py +++ b/tests/integration_python/test_run_cli.py @@ -1599,9 +1599,10 @@ def test_signal_forwarding(pixi: Path, tmp_pixi_workspace: Path) -> None: ) else: raise AssertionError("Output file was not created") - + + def test_run_environment_variable_not_be_overridden(pixi: Path, tmp_pixi_workspace: Path) -> None: - """Test environment variables should not be overridden by excluded keys.""" + """Test environment variables should not be overridden by excluded keys.""" manifest = tmp_pixi_workspace.joinpath("pixi.toml") toml = """ [workspace] @@ -1625,5 +1626,5 @@ def test_run_environment_variable_not_be_overridden(pixi: Path, tmp_pixi_workspa verify_cli_command( [pixi, "run", "--manifest-path", manifest, "test"], stdout_contains="my_project", - stdout_excludes="$(pixi workspace name get)" - ) \ No newline at end of file + stdout_excludes="$(pixi workspace name get)", + ) diff --git a/tests/integration_rust/add_tests.rs b/tests/integration_rust/add_tests.rs index 9f095aa36b..5eeb2da99b 100644 --- a/tests/integration_rust/add_tests.rs +++ b/tests/integration_rust/add_tests.rs @@ -10,7 +10,7 @@ use url::Url; use crate::common::{ LockFileExt, PixiControl, - builders::{HasDependencyConfig, HasLockFileUpdateConfig, HasPrefixUpdateConfig}, + builders::{HasDependencyConfig, HasLockFileUpdateConfig, HasNoInstallConfig}, package_database::{Package, PackageDatabase}, }; diff --git a/tests/integration_rust/common/builders.rs b/tests/integration_rust/common/builders.rs index 16cd8a9179..583bdcf891 100644 --- a/tests/integration_rust/common/builders.rs +++ b/tests/integration_rust/common/builders.rs @@ -24,7 +24,7 @@ //! ``` use pixi::cli::{ - cli_config::{GitRev, LockFileUpdateConfig, PrefixUpdateConfig, WorkspaceConfig}, + cli_config::{GitRev, LockFileUpdateConfig, WorkspaceConfig}, lock, }; use std::{ @@ -39,7 +39,9 @@ use futures::FutureExt; use pixi::{ DependencyType, cli::{ - add, cli_config::DependencyConfig, init, install, remove, search, task, update, workspace, + add, + cli_config::{DependencyConfig, NoInstallConfig, RevalidateConfig}, + init, install, remove, search, task, update, workspace, }, }; use pixi_manifest::{EnvironmentName, FeatureName, SpecType, task::Dependency}; @@ -111,14 +113,23 @@ impl IntoFuture for InitBuilder { .boxed_local() } } -/// A trait used by AddBuilder and RemoveBuilder to set their inner -/// DependencyConfig -pub trait HasPrefixUpdateConfig: Sized { - fn prefix_update_config(&mut self) -> &mut PrefixUpdateConfig; +/// A trait used by builders to access NoInstallConfig +pub trait HasNoInstallConfig: Sized { + fn no_install_config(&mut self) -> &mut NoInstallConfig; /// Set whether to also install the environment. By default, the environment /// is NOT installed to reduce test times. fn with_install(mut self, install: bool) -> Self { - self.prefix_update_config().no_install = !install; + self.no_install_config().no_install = !install; + self + } +} + +/// A trait used by builders to access RevalidateConfig +pub trait HasRevalidateConfig: Sized { + fn revalidate_config(&mut self) -> &mut RevalidateConfig; + /// Set whether to revalidate the environment (reinstall broken environment) + fn with_revalidate(mut self, revalidate: bool) -> Self { + self.revalidate_config().revalidate = revalidate; self } } @@ -238,7 +249,7 @@ impl AddBuilder { if no_lockfile_update { // Since no_lockfile_update is deprecated, we simulate the behavior by setting frozen=true and no_install=true self.args.lock_file_update_config.lock_file_usage.frozen = true; - self.args.prefix_update_config.no_install = true; + self.args.no_install_config.no_install = true; } self } @@ -250,12 +261,13 @@ impl HasDependencyConfig for AddBuilder { } } -impl HasPrefixUpdateConfig for AddBuilder { - fn prefix_update_config(&mut self) -> &mut PrefixUpdateConfig { - &mut self.args.prefix_update_config +impl HasNoInstallConfig for AddBuilder { + fn no_install_config(&mut self) -> &mut NoInstallConfig { + &mut self.args.no_install_config } } + impl HasLockFileUpdateConfig for AddBuilder { fn lock_file_update_config(&mut self) -> &mut LockFileUpdateConfig { &mut self.args.lock_file_update_config @@ -301,9 +313,15 @@ impl HasDependencyConfig for RemoveBuilder { } } -impl HasPrefixUpdateConfig for RemoveBuilder { - fn prefix_update_config(&mut self) -> &mut PrefixUpdateConfig { - &mut self.args.prefix_update_config +impl HasNoInstallConfig for RemoveBuilder { + fn no_install_config(&mut self) -> &mut NoInstallConfig { + &mut self.args.no_install_config + } +} + +impl HasRevalidateConfig for RemoveBuilder { + fn revalidate_config(&mut self) -> &mut RevalidateConfig { + &mut self.args.revalidate_config } } diff --git a/tests/integration_rust/common/mod.rs b/tests/integration_rust/common/mod.rs index 57ad1a4d4a..b353682730 100644 --- a/tests/integration_rust/common/mod.rs +++ b/tests/integration_rust/common/mod.rs @@ -19,7 +19,7 @@ use pixi::{ UpdateLockFileOptions, Workspace, cli::{ LockFileUsageConfig, add, - cli_config::{ChannelsConfig, LockFileUpdateConfig, PrefixUpdateConfig, WorkspaceConfig}, + cli_config::{ChannelsConfig, LockFileUpdateConfig, NoInstallConfig, RevalidateConfig, WorkspaceConfig}, init::{self, GitAttributes}, install::Args, lock, remove, run, search, @@ -362,10 +362,8 @@ impl PixiControl { manifest_path: Some(self.manifest_path()), }, dependency_config: AddBuilder::dependency_config_with_specs(specs), - prefix_update_config: PrefixUpdateConfig { + no_install_config: NoInstallConfig { no_install: true, - - revalidate: false, }, lock_file_update_config: LockFileUpdateConfig { no_lockfile_update: false, @@ -401,8 +399,10 @@ impl PixiControl { manifest_path: Some(self.manifest_path()), }, dependency_config: AddBuilder::dependency_config_with_specs(vec![spec]), - prefix_update_config: PrefixUpdateConfig { + no_install_config: NoInstallConfig { no_install: true, + }, + revalidate_config: RevalidateConfig { revalidate: false, }, lock_file_update_config: LockFileUpdateConfig { @@ -422,8 +422,10 @@ impl PixiControl { manifest_path: Some(self.manifest_path()), }, channel: vec![], - prefix_update_config: PrefixUpdateConfig { + no_install_config: NoInstallConfig { no_install: true, + }, + revalidate_config: RevalidateConfig { revalidate: false, }, lock_file_update_config: LockFileUpdateConfig { @@ -447,8 +449,10 @@ impl PixiControl { manifest_path: Some(self.manifest_path()), }, channel: vec![], - prefix_update_config: PrefixUpdateConfig { + no_install_config: NoInstallConfig { no_install: true, + }, + revalidate_config: RevalidateConfig { revalidate: false, }, lock_file_update_config: LockFileUpdateConfig { @@ -504,7 +508,8 @@ impl PixiControl { lock_file_usage: args.lock_file_update_config.lock_file_usage()?, ..UpdateLockFileOptions::default() }) - .await?; + .await? + .0; // Create a task graph from the command line arguments. let search_env = SearchEnvironments::from_opt_env( @@ -613,6 +618,7 @@ impl PixiControl { Ok(project .update_lock_file(UpdateLockFileOptions::default()) .await? + .0 .into_lock_file()) } @@ -624,6 +630,9 @@ impl PixiControl { workspace_config: WorkspaceConfig { manifest_path: Some(self.manifest_path()), }, + no_install_config: NoInstallConfig { + no_install: false, + }, check: false, json: false, }, diff --git a/tests/integration_rust/install_tests.rs b/tests/integration_rust/install_tests.rs index 5334cfab94..fd43f33c1e 100644 --- a/tests/integration_rust/install_tests.rs +++ b/tests/integration_rust/install_tests.rs @@ -34,7 +34,7 @@ use uv_python::PythonEnvironment; use crate::common::{ LockFileExt, PixiControl, builders::{ - HasDependencyConfig, HasLockFileUpdateConfig, HasPrefixUpdateConfig, string_from_iter, + HasDependencyConfig, HasLockFileUpdateConfig, HasNoInstallConfig, string_from_iter, }, logging::try_init_test_subscriber, package_database::{Package, PackageDatabase}, diff --git a/tests/integration_rust/solve_group_tests.rs b/tests/integration_rust/solve_group_tests.rs index ab9d7a0b88..67db574940 100644 --- a/tests/integration_rust/solve_group_tests.rs +++ b/tests/integration_rust/solve_group_tests.rs @@ -14,7 +14,7 @@ use url::Url; use crate::common::{ LockFileExt, PixiControl, - builders::{HasDependencyConfig, HasPrefixUpdateConfig}, + builders::{HasDependencyConfig, HasNoInstallConfig}, client::OfflineMiddleware, package_database::{Package, PackageDatabase}, }; diff --git a/tests/integration_rust/upgrade_tests.rs b/tests/integration_rust/upgrade_tests.rs index 8e16dbc634..486647d911 100644 --- a/tests/integration_rust/upgrade_tests.rs +++ b/tests/integration_rust/upgrade_tests.rs @@ -44,7 +44,7 @@ async fn pypi_dependency_index_preserved_on_upgrade() { match_specs, pypi_deps, IndexMap::default(), - &args.prefix_update_config, + args.no_install_config.no_install, &args.lock_file_update_config, &args.specs.feature, &[], From 782815eecd1f4b487ecc4ac60def9c78479d8b29 Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Tue, 12 Aug 2025 16:39:21 +0200 Subject: [PATCH 04/29] lint --- tests/integration_rust/common/builders.rs | 1 - tests/integration_rust/common/mod.rs | 37 ++++++++--------------- 2 files changed, 12 insertions(+), 26 deletions(-) diff --git a/tests/integration_rust/common/builders.rs b/tests/integration_rust/common/builders.rs index 583bdcf891..482b58804a 100644 --- a/tests/integration_rust/common/builders.rs +++ b/tests/integration_rust/common/builders.rs @@ -267,7 +267,6 @@ impl HasNoInstallConfig for AddBuilder { } } - impl HasLockFileUpdateConfig for AddBuilder { fn lock_file_update_config(&mut self) -> &mut LockFileUpdateConfig { &mut self.args.lock_file_update_config diff --git a/tests/integration_rust/common/mod.rs b/tests/integration_rust/common/mod.rs index b353682730..fe9401af89 100644 --- a/tests/integration_rust/common/mod.rs +++ b/tests/integration_rust/common/mod.rs @@ -19,7 +19,10 @@ use pixi::{ UpdateLockFileOptions, Workspace, cli::{ LockFileUsageConfig, add, - cli_config::{ChannelsConfig, LockFileUpdateConfig, NoInstallConfig, RevalidateConfig, WorkspaceConfig}, + cli_config::{ + ChannelsConfig, LockFileUpdateConfig, NoInstallConfig, RevalidateConfig, + WorkspaceConfig, + }, init::{self, GitAttributes}, install::Args, lock, remove, run, search, @@ -362,9 +365,7 @@ impl PixiControl { manifest_path: Some(self.manifest_path()), }, dependency_config: AddBuilder::dependency_config_with_specs(specs), - no_install_config: NoInstallConfig { - no_install: true, - }, + no_install_config: NoInstallConfig { no_install: true }, lock_file_update_config: LockFileUpdateConfig { no_lockfile_update: false, lock_file_usage: LockFileUsageConfig::default(), @@ -399,12 +400,8 @@ impl PixiControl { manifest_path: Some(self.manifest_path()), }, dependency_config: AddBuilder::dependency_config_with_specs(vec![spec]), - no_install_config: NoInstallConfig { - no_install: true, - }, - revalidate_config: RevalidateConfig { - revalidate: false, - }, + no_install_config: NoInstallConfig { no_install: true }, + revalidate_config: RevalidateConfig { revalidate: false }, lock_file_update_config: LockFileUpdateConfig { no_lockfile_update: false, lock_file_usage: LockFileUsageConfig::default(), @@ -422,12 +419,8 @@ impl PixiControl { manifest_path: Some(self.manifest_path()), }, channel: vec![], - no_install_config: NoInstallConfig { - no_install: true, - }, - revalidate_config: RevalidateConfig { - revalidate: false, - }, + no_install_config: NoInstallConfig { no_install: true }, + revalidate_config: RevalidateConfig { revalidate: false }, lock_file_update_config: LockFileUpdateConfig { no_lockfile_update: false, lock_file_usage: LockFileUsageConfig::default(), @@ -449,12 +442,8 @@ impl PixiControl { manifest_path: Some(self.manifest_path()), }, channel: vec![], - no_install_config: NoInstallConfig { - no_install: true, - }, - revalidate_config: RevalidateConfig { - revalidate: false, - }, + no_install_config: NoInstallConfig { no_install: true }, + revalidate_config: RevalidateConfig { revalidate: false }, lock_file_update_config: LockFileUpdateConfig { no_lockfile_update: false, lock_file_usage: LockFileUsageConfig::default(), @@ -630,9 +619,7 @@ impl PixiControl { workspace_config: WorkspaceConfig { manifest_path: Some(self.manifest_path()), }, - no_install_config: NoInstallConfig { - no_install: false, - }, + no_install_config: NoInstallConfig { no_install: false }, check: false, json: false, }, From f9dc266b1874e9bf88218c2dbfa6fa0ad936c845 Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Thu, 14 Aug 2025 09:22:25 +0200 Subject: [PATCH 05/29] fix: lint --- src/cli/global/edit.rs | 2 +- src/cli/import.rs | 4 +++- src/cli/remove.rs | 7 ++++--- src/cli/run.rs | 4 +++- src/cli/shell.rs | 3 +-- src/cli/shell_hook.rs | 4 +++- src/cli/upgrade.rs | 4 +++- src/cli/workspace/channel/add.rs | 2 +- src/cli/workspace/channel/mod.rs | 4 +++- src/cli/workspace/channel/remove.rs | 2 +- src/cli/workspace/description/set.rs | 2 +- src/cli/workspace/environment/add.rs | 2 +- src/cli/workspace/environment/list.rs | 2 +- src/cli/workspace/environment/remove.rs | 2 +- src/cli/workspace/name/set.rs | 2 +- src/cli/workspace/platform/list.rs | 2 +- src/cli/workspace/requires_pixi/set.rs | 2 +- src/cli/workspace/requires_pixi/unset.rs | 2 +- src/cli/workspace/system_requirements/list.rs | 4 ++-- src/cli/workspace/version/bump.rs | 2 +- src/cli/workspace/version/get.rs | 2 +- 21 files changed, 35 insertions(+), 25 deletions(-) diff --git a/src/cli/global/edit.rs b/src/cli/global/edit.rs index 2f8fb4e2f5..0dced55683 100644 --- a/src/cli/global/edit.rs +++ b/src/cli/global/edit.rs @@ -1,7 +1,7 @@ -use pixi_core::global::Project; use clap::Parser; use fs_err as fs; use miette::IntoDiagnostic; +use pixi_core::global::Project; /// Edit the global manifest file /// diff --git a/src/cli/import.rs b/src/cli/import.rs index 1c9b01ee62..3450f599eb 100644 --- a/src/cli/import.rs +++ b/src/cli/import.rs @@ -16,7 +16,9 @@ use uv_requirements_txt::RequirementsTxt; use miette::{Diagnostic, IntoDiagnostic, Result}; use thiserror::Error; -use crate::cli::cli_config::{LockFileUpdateConfig, NoInstallConfig, RevalidateConfig, WorkspaceConfig}; +use crate::cli::cli_config::{ + LockFileUpdateConfig, NoInstallConfig, RevalidateConfig, WorkspaceConfig, +}; #[derive(Parser, Debug, Clone, PartialEq, ValueEnum)] pub enum ImportFileFormat { diff --git a/src/cli/remove.rs b/src/cli/remove.rs index 9066ac91a6..3c7c7bea12 100644 --- a/src/cli/remove.rs +++ b/src/cli/remove.rs @@ -3,12 +3,13 @@ use miette::{Context, IntoDiagnostic}; use pixi_config::ConfigCli; use pixi_core::{ DependencyType, UpdateLockFileOptions, WorkspaceLocator, - environment::get_update_lock_file_and_prefix, - lock_file::ReinstallPackages, + environment::get_update_lock_file_and_prefix, lock_file::ReinstallPackages, }; use pixi_manifest::FeaturesExt; -use crate::cli::cli_config::{DependencyConfig, NoInstallConfig, RevalidateConfig, WorkspaceConfig}; +use crate::cli::cli_config::{ + DependencyConfig, NoInstallConfig, RevalidateConfig, WorkspaceConfig, +}; use crate::cli::{cli_config::LockFileUpdateConfig, has_specs::HasSpecs}; /// Removes dependencies from the workspace. diff --git a/src/cli/run.rs b/src/cli/run.rs index 0e4fff21cb..7e55e7cad7 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -29,7 +29,9 @@ use pixi_core::{ workspace::{Environment, errors::UnsupportedPlatformError}, }; -use crate::cli::cli_config::{LockFileUpdateConfig, NoInstallConfig, RevalidateConfig, WorkspaceConfig}; +use crate::cli::cli_config::{ + LockFileUpdateConfig, NoInstallConfig, RevalidateConfig, WorkspaceConfig, +}; /// Runs task in the pixi environment. /// diff --git a/src/cli/shell.rs b/src/cli/shell.rs index 6d2db1e3d0..2b3083f156 100644 --- a/src/cli/shell.rs +++ b/src/cli/shell.rs @@ -11,8 +11,7 @@ use rattler_shell::{ use pixi_config::{ConfigCli, ConfigCliActivation, ConfigCliPrompt}; use pixi_core::{ UpdateLockFileOptions, WorkspaceLocator, activation::CurrentEnvVarBehavior, - environment::get_update_lock_file_and_prefix, prompt, - lock_file::ReinstallPackages, + environment::get_update_lock_file_and_prefix, lock_file::ReinstallPackages, prompt, workspace::get_activated_environment_variables, }; diff --git a/src/cli/shell_hook.rs b/src/cli/shell_hook.rs index cba61db903..5485e44697 100644 --- a/src/cli/shell_hook.rs +++ b/src/cli/shell_hook.rs @@ -20,7 +20,9 @@ use pixi_core::{ workspace::{Environment, HasWorkspaceRef, get_activated_environment_variables}, }; -use crate::cli::cli_config::{LockFileUpdateConfig, NoInstallConfig, RevalidateConfig, WorkspaceConfig}; +use crate::cli::cli_config::{ + LockFileUpdateConfig, NoInstallConfig, RevalidateConfig, WorkspaceConfig, +}; /// Print the pixi environment activation script. /// diff --git a/src/cli/upgrade.rs b/src/cli/upgrade.rs index bd13daa40e..73c41cb66e 100644 --- a/src/cli/upgrade.rs +++ b/src/cli/upgrade.rs @@ -17,7 +17,9 @@ use pixi_pypi_spec::PixiPypiSpec; use pixi_spec::PixiSpec; use rattler_conda_types::{MatchSpec, StringMatcher}; -use crate::cli::cli_config::{LockFileUpdateConfig, NoInstallConfig, RevalidateConfig, WorkspaceConfig}; +use crate::cli::cli_config::{ + LockFileUpdateConfig, NoInstallConfig, RevalidateConfig, WorkspaceConfig, +}; /// Checks if there are newer versions of the dependencies and upgrades them in the lockfile and manifest file. /// diff --git a/src/cli/workspace/channel/add.rs b/src/cli/workspace/channel/add.rs index 3e7be4704c..2e9758c6c8 100644 --- a/src/cli/workspace/channel/add.rs +++ b/src/cli/workspace/channel/add.rs @@ -1,9 +1,9 @@ +use miette::IntoDiagnostic; use pixi_core::{ UpdateLockFileOptions, WorkspaceLocator, environment::{LockFileUsage, get_update_lock_file_and_prefix}, lock_file::ReinstallPackages, }; -use miette::IntoDiagnostic; use super::AddRemoveArgs; diff --git a/src/cli/workspace/channel/mod.rs b/src/cli/workspace/channel/mod.rs index b04570441c..6832221840 100644 --- a/src/cli/workspace/channel/mod.rs +++ b/src/cli/workspace/channel/mod.rs @@ -8,7 +8,9 @@ use pixi_config::ConfigCli; use pixi_manifest::{FeatureName, PrioritizedChannel}; use rattler_conda_types::{ChannelConfig, NamedChannelOrUrl}; -use crate::cli::cli_config::{LockFileUpdateConfig, NoInstallConfig, RevalidateConfig, WorkspaceConfig}; +use crate::cli::cli_config::{ + LockFileUpdateConfig, NoInstallConfig, RevalidateConfig, WorkspaceConfig, +}; /// Commands to manage workspace channels. #[derive(Parser, Debug, Clone)] diff --git a/src/cli/workspace/channel/remove.rs b/src/cli/workspace/channel/remove.rs index c5813a9e6a..dacd94952b 100644 --- a/src/cli/workspace/channel/remove.rs +++ b/src/cli/workspace/channel/remove.rs @@ -1,9 +1,9 @@ +use miette::IntoDiagnostic; use pixi_core::{ UpdateLockFileOptions, WorkspaceLocator, environment::{LockFileUsage, get_update_lock_file_and_prefix}, lock_file::ReinstallPackages, }; -use miette::IntoDiagnostic; use super::AddRemoveArgs; diff --git a/src/cli/workspace/description/set.rs b/src/cli/workspace/description/set.rs index 1885d15c86..a4522efc1f 100644 --- a/src/cli/workspace/description/set.rs +++ b/src/cli/workspace/description/set.rs @@ -1,6 +1,6 @@ -use pixi_core::Workspace; use clap::Parser; use miette::IntoDiagnostic; +use pixi_core::Workspace; #[derive(Parser, Debug, Default)] pub struct Args { diff --git a/src/cli/workspace/environment/add.rs b/src/cli/workspace/environment/add.rs index 6dcae9e76b..0c261719c7 100644 --- a/src/cli/workspace/environment/add.rs +++ b/src/cli/workspace/environment/add.rs @@ -1,6 +1,6 @@ -use pixi_core::Workspace; use clap::Parser; use miette::IntoDiagnostic; +use pixi_core::Workspace; use pixi_manifest::EnvironmentName; #[derive(Parser, Debug)] diff --git a/src/cli/workspace/environment/list.rs b/src/cli/workspace/environment/list.rs index b14f1a051e..e051d9ed8c 100644 --- a/src/cli/workspace/environment/list.rs +++ b/src/cli/workspace/environment/list.rs @@ -1,7 +1,7 @@ -use pixi_core::Workspace; use fancy_display::FancyDisplay; use itertools::Itertools; use pixi_consts::consts; +use pixi_core::Workspace; use pixi_manifest::HasFeaturesIter; pub async fn execute(workspace: Workspace) -> miette::Result<()> { diff --git a/src/cli/workspace/environment/remove.rs b/src/cli/workspace/environment/remove.rs index 5e9983a4f4..649ad236c2 100644 --- a/src/cli/workspace/environment/remove.rs +++ b/src/cli/workspace/environment/remove.rs @@ -1,6 +1,6 @@ -use pixi_core::Workspace; use clap::Parser; use miette::IntoDiagnostic; +use pixi_core::Workspace; #[derive(Parser, Debug, Default)] pub struct Args { diff --git a/src/cli/workspace/name/set.rs b/src/cli/workspace/name/set.rs index 8b40bb0132..e086ab735b 100644 --- a/src/cli/workspace/name/set.rs +++ b/src/cli/workspace/name/set.rs @@ -1,6 +1,6 @@ -use pixi_core::Workspace; use clap::Parser; use miette::IntoDiagnostic; +use pixi_core::Workspace; #[derive(Parser, Debug)] pub struct Args { diff --git a/src/cli/workspace/platform/list.rs b/src/cli/workspace/platform/list.rs index 4f88f10096..0e649cede0 100644 --- a/src/cli/workspace/platform/list.rs +++ b/src/cli/workspace/platform/list.rs @@ -1,5 +1,5 @@ -use pixi_core::Workspace; use fancy_display::FancyDisplay; +use pixi_core::Workspace; use pixi_manifest::FeaturesExt; pub async fn execute(workspace: Workspace) -> miette::Result<()> { diff --git a/src/cli/workspace/requires_pixi/set.rs b/src/cli/workspace/requires_pixi/set.rs index fa37f07148..14ffc36bf0 100644 --- a/src/cli/workspace/requires_pixi/set.rs +++ b/src/cli/workspace/requires_pixi/set.rs @@ -1,6 +1,6 @@ -use pixi_core::Workspace; use clap::Parser; use miette::IntoDiagnostic; +use pixi_core::Workspace; #[derive(Parser, Debug)] pub struct Args { diff --git a/src/cli/workspace/requires_pixi/unset.rs b/src/cli/workspace/requires_pixi/unset.rs index e4c94757b0..5bea87837a 100644 --- a/src/cli/workspace/requires_pixi/unset.rs +++ b/src/cli/workspace/requires_pixi/unset.rs @@ -1,5 +1,5 @@ -use pixi_core::Workspace; use miette::IntoDiagnostic; +use pixi_core::Workspace; pub async fn execute(workspace: Workspace) -> miette::Result<()> { let mut workspace = workspace.modify()?; diff --git a/src/cli/workspace/system_requirements/list.rs b/src/cli/workspace/system_requirements/list.rs index efbeaeb9f8..12c22fc354 100644 --- a/src/cli/workspace/system_requirements/list.rs +++ b/src/cli/workspace/system_requirements/list.rs @@ -1,8 +1,8 @@ -use pixi_core::Workspace; -use pixi_core::workspace::Environment; use clap::Parser; use fancy_display::FancyDisplay; use miette::IntoDiagnostic; +use pixi_core::Workspace; +use pixi_core::workspace::Environment; use pixi_manifest::{EnvironmentName, SystemRequirements}; use serde::Serialize; diff --git a/src/cli/workspace/version/bump.rs b/src/cli/workspace/version/bump.rs index 3c76223ee7..829ad92a01 100644 --- a/src/cli/workspace/version/bump.rs +++ b/src/cli/workspace/version/bump.rs @@ -1,5 +1,5 @@ -use pixi_core::Workspace; use miette::{Context, IntoDiagnostic}; +use pixi_core::Workspace; use rattler_conda_types::VersionBumpType; pub async fn execute(workspace: Workspace, bump_type: VersionBumpType) -> miette::Result<()> { diff --git a/src/cli/workspace/version/get.rs b/src/cli/workspace/version/get.rs index cf9470dd24..9713ba68f2 100644 --- a/src/cli/workspace/version/get.rs +++ b/src/cli/workspace/version/get.rs @@ -1,5 +1,5 @@ -use pixi_core::Workspace; use clap::Parser; +use pixi_core::Workspace; #[derive(Parser, Debug, Default)] pub struct Args {} From 3e0f830017a96fa372e6901836bc86cec6594803 Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Thu, 14 Aug 2025 10:27:58 +0200 Subject: [PATCH 06/29] feat: remove `--revalidate` everywhere --- src/cli/cli_config.rs | 30 +++++++++++------------ src/cli/import.rs | 5 +--- src/cli/remove.rs | 11 +++------ src/cli/run.rs | 9 +++---- src/cli/shell.rs | 9 +++---- src/cli/shell_hook.rs | 9 +++---- src/cli/upgrade.rs | 5 +--- src/cli/workspace/channel/add.rs | 4 +-- src/cli/workspace/channel/mod.rs | 5 +--- src/cli/workspace/channel/remove.rs | 4 +-- tests/integration_rust/common/builders.rs | 16 +----------- tests/integration_rust/common/mod.rs | 5 +--- 12 files changed, 35 insertions(+), 77 deletions(-) diff --git a/src/cli/cli_config.rs b/src/cli/cli_config.rs index 8c4261c662..3795f2f8d7 100644 --- a/src/cli/cli_config.rs +++ b/src/cli/cli_config.rs @@ -10,7 +10,6 @@ use pixi_consts::consts; use pixi_core::DependencyType; use pixi_core::Workspace; use pixi_core::environment::LockFileUsage; -use pixi_core::lock_file::UpdateMode; use pixi_core::workspace::DiscoveryStart; use pixi_manifest::FeaturesExt; use pixi_manifest::{FeatureName, SpecType}; @@ -140,25 +139,24 @@ pub struct NoInstallConfig { pub no_install: bool, } -/// Configuration for environment validation -#[derive(Parser, Debug, Default, Clone)] -pub struct RevalidateConfig { - /// Run the complete environment validation. This will reinstall a broken environment. - #[arg(long, help_heading = consts::CLAP_UPDATE_OPTIONS)] - pub revalidate: bool, -} +impl NoInstallConfig { + /// Creates a new NoInstallConfig with the specified value + pub fn new(no_install: bool) -> Self { + Self { no_install } + } -impl RevalidateConfig { - /// Which `[UpdateMode]` to use - pub(crate) fn update_mode(&self) -> UpdateMode { - if self.revalidate { - UpdateMode::Revalidate - } else { - UpdateMode::QuickValidate - } + /// Creates a NoInstallConfig that skips installation + pub fn skip_install() -> Self { + Self::new(true) + } + + /// Creates a NoInstallConfig that allows installation + pub fn allow_install() -> Self { + Self::new(false) } } + #[derive(Parser, Debug, Default, Clone)] pub struct GitRev { /// The git branch diff --git a/src/cli/import.rs b/src/cli/import.rs index 3450f599eb..eb0d4ccb2d 100644 --- a/src/cli/import.rs +++ b/src/cli/import.rs @@ -17,7 +17,7 @@ use miette::{Diagnostic, IntoDiagnostic, Result}; use thiserror::Error; use crate::cli::cli_config::{ - LockFileUpdateConfig, NoInstallConfig, RevalidateConfig, WorkspaceConfig, + LockFileUpdateConfig, NoInstallConfig, WorkspaceConfig, }; #[derive(Parser, Debug, Clone, PartialEq, ValueEnum)] @@ -58,9 +58,6 @@ pub struct Args { #[clap(flatten)] pub no_install_config: NoInstallConfig, - #[clap(flatten)] - pub revalidate_config: RevalidateConfig, - #[clap(flatten)] pub lock_file_update_config: LockFileUpdateConfig, diff --git a/src/cli/remove.rs b/src/cli/remove.rs index 3c7c7bea12..ce56deb05f 100644 --- a/src/cli/remove.rs +++ b/src/cli/remove.rs @@ -3,12 +3,12 @@ use miette::{Context, IntoDiagnostic}; use pixi_config::ConfigCli; use pixi_core::{ DependencyType, UpdateLockFileOptions, WorkspaceLocator, - environment::get_update_lock_file_and_prefix, lock_file::ReinstallPackages, + environment::get_update_lock_file_and_prefix, lock_file::{ReinstallPackages, UpdateMode}, }; use pixi_manifest::FeaturesExt; use crate::cli::cli_config::{ - DependencyConfig, NoInstallConfig, RevalidateConfig, WorkspaceConfig, + DependencyConfig, NoInstallConfig, WorkspaceConfig, }; use crate::cli::{cli_config::LockFileUpdateConfig, has_specs::HasSpecs}; @@ -33,9 +33,6 @@ pub struct Args { #[clap(flatten)] pub no_install_config: NoInstallConfig, - #[clap(flatten)] - pub revalidate_config: RevalidateConfig, - #[clap(flatten)] pub lock_file_update_config: LockFileUpdateConfig, @@ -47,13 +44,11 @@ pub async fn execute(args: Args) -> miette::Result<()> { let ( dependency_config, no_install_config, - revalidate_config, lock_file_update_config, workspace_config, ) = ( args.dependency_config, args.no_install_config, - args.revalidate_config, args.lock_file_update_config, args.workspace_config, ); @@ -128,7 +123,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { // updating prefix after removing from toml get_update_lock_file_and_prefix( &workspace.default_environment(), - revalidate_config.update_mode(), + UpdateMode::QuickValidate, UpdateLockFileOptions { lock_file_usage: lock_file_update_config.lock_file_usage()?, no_install: no_install_config.no_install, diff --git a/src/cli/run.rs b/src/cli/run.rs index 7e55e7cad7..0f140bf666 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -21,7 +21,7 @@ use tracing::Level; use pixi_core::{ Workspace, WorkspaceLocator, environment::sanity_check_workspace, - lock_file::{ReinstallPackages, UpdateLockFileOptions}, + lock_file::{ReinstallPackages, UpdateLockFileOptions, UpdateMode}, task::{ AmbiguousTask, CanSkip, ExecutableTask, FailedToParseShellScript, InvalidWorkingDirectory, SearchEnvironments, TaskAndEnvironment, TaskGraph, get_task_env, @@ -30,7 +30,7 @@ use pixi_core::{ }; use crate::cli::cli_config::{ - LockFileUpdateConfig, NoInstallConfig, RevalidateConfig, WorkspaceConfig, + LockFileUpdateConfig, NoInstallConfig, WorkspaceConfig, }; /// Runs task in the pixi environment. @@ -53,9 +53,6 @@ pub struct Args { #[clap(flatten)] pub no_install_config: NoInstallConfig, - #[clap(flatten)] - pub revalidate_config: RevalidateConfig, - #[clap(flatten)] pub lock_file_update_config: LockFileUpdateConfig, @@ -262,7 +259,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { lock_file .prefix( &executable_task.run_environment, - args.revalidate_config.update_mode(), + UpdateMode::QuickValidate, &ReinstallPackages::default(), &[], ) diff --git a/src/cli/shell.rs b/src/cli/shell.rs index 2b3083f156..f3ed435592 100644 --- a/src/cli/shell.rs +++ b/src/cli/shell.rs @@ -11,11 +11,11 @@ use rattler_shell::{ use pixi_config::{ConfigCli, ConfigCliActivation, ConfigCliPrompt}; use pixi_core::{ UpdateLockFileOptions, WorkspaceLocator, activation::CurrentEnvVarBehavior, - environment::get_update_lock_file_and_prefix, lock_file::ReinstallPackages, prompt, + environment::get_update_lock_file_and_prefix, lock_file::{ReinstallPackages, UpdateMode}, prompt, workspace::get_activated_environment_variables, }; -use crate::cli::cli_config::{NoInstallConfig, RevalidateConfig, WorkspaceConfig}; +use crate::cli::cli_config::{NoInstallConfig, WorkspaceConfig}; #[cfg(target_family = "unix")] use pixi_pty::unix::PtySession; @@ -32,9 +32,6 @@ pub struct Args { #[clap(flatten)] pub no_install_config: NoInstallConfig, - #[clap(flatten)] - pub revalidate_config: RevalidateConfig, - #[clap(flatten)] pub lock_file_update_config: LockFileUpdateConfig, @@ -279,7 +276,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { #[allow(unused_variables)] let (lock_file_data, prefix) = get_update_lock_file_and_prefix( &environment, - args.revalidate_config.update_mode(), + UpdateMode::QuickValidate, UpdateLockFileOptions { lock_file_usage: args.lock_file_update_config.lock_file_usage()?, no_install: args.no_install_config.no_install, diff --git a/src/cli/shell_hook.rs b/src/cli/shell_hook.rs index 5485e44697..ef62815e2e 100644 --- a/src/cli/shell_hook.rs +++ b/src/cli/shell_hook.rs @@ -15,13 +15,13 @@ use pixi_core::{ UpdateLockFileOptions, Workspace, WorkspaceLocator, activation::{CurrentEnvVarBehavior, get_activator}, environment::get_update_lock_file_and_prefix, - lock_file::ReinstallPackages, + lock_file::{ReinstallPackages, UpdateMode}, prompt, workspace::{Environment, HasWorkspaceRef, get_activated_environment_variables}, }; use crate::cli::cli_config::{ - LockFileUpdateConfig, NoInstallConfig, RevalidateConfig, WorkspaceConfig, + LockFileUpdateConfig, NoInstallConfig, WorkspaceConfig, }; /// Print the pixi environment activation script. @@ -40,9 +40,6 @@ pub struct Args { #[clap(flatten)] pub no_install_config: NoInstallConfig, - #[clap(flatten)] - pub revalidate_config: RevalidateConfig, - #[clap(flatten)] pub lock_file_update_config: LockFileUpdateConfig, @@ -163,7 +160,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { let (lock_file_data, _prefix) = get_update_lock_file_and_prefix( &environment, - args.revalidate_config.update_mode(), + UpdateMode::QuickValidate, UpdateLockFileOptions { lock_file_usage: args.lock_file_update_config.lock_file_usage()?, no_install: args.no_install_config.no_install, diff --git a/src/cli/upgrade.rs b/src/cli/upgrade.rs index 73c41cb66e..b4f3e90c68 100644 --- a/src/cli/upgrade.rs +++ b/src/cli/upgrade.rs @@ -18,7 +18,7 @@ use pixi_spec::PixiSpec; use rattler_conda_types::{MatchSpec, StringMatcher}; use crate::cli::cli_config::{ - LockFileUpdateConfig, NoInstallConfig, RevalidateConfig, WorkspaceConfig, + LockFileUpdateConfig, NoInstallConfig, WorkspaceConfig, }; /// Checks if there are newer versions of the dependencies and upgrades them in the lockfile and manifest file. @@ -31,9 +31,6 @@ pub struct Args { #[clap(flatten)] pub no_install_config: NoInstallConfig, - #[clap(flatten)] - pub revalidate_config: RevalidateConfig, - #[clap(flatten)] pub lock_file_update_config: LockFileUpdateConfig, diff --git a/src/cli/workspace/channel/add.rs b/src/cli/workspace/channel/add.rs index 2e9758c6c8..f5dbafa25a 100644 --- a/src/cli/workspace/channel/add.rs +++ b/src/cli/workspace/channel/add.rs @@ -2,7 +2,7 @@ use miette::IntoDiagnostic; use pixi_core::{ UpdateLockFileOptions, WorkspaceLocator, environment::{LockFileUsage, get_update_lock_file_and_prefix}, - lock_file::ReinstallPackages, + lock_file::{ReinstallPackages, UpdateMode}, }; use super::AddRemoveArgs; @@ -24,7 +24,7 @@ pub async fn execute(args: AddRemoveArgs) -> miette::Result<()> { // TODO: Update all environments touched by the features defined. get_update_lock_file_and_prefix( &workspace.workspace().default_environment(), - args.revalidate_config.update_mode(), + UpdateMode::QuickValidate, UpdateLockFileOptions { lock_file_usage: LockFileUsage::Update, no_install: args.no_install_config.no_install, diff --git a/src/cli/workspace/channel/mod.rs b/src/cli/workspace/channel/mod.rs index 6832221840..2fab5eea18 100644 --- a/src/cli/workspace/channel/mod.rs +++ b/src/cli/workspace/channel/mod.rs @@ -9,7 +9,7 @@ use pixi_manifest::{FeatureName, PrioritizedChannel}; use rattler_conda_types::{ChannelConfig, NamedChannelOrUrl}; use crate::cli::cli_config::{ - LockFileUpdateConfig, NoInstallConfig, RevalidateConfig, WorkspaceConfig, + LockFileUpdateConfig, NoInstallConfig, WorkspaceConfig, }; /// Commands to manage workspace channels. @@ -39,9 +39,6 @@ pub struct AddRemoveArgs { #[clap(flatten)] pub no_install_config: NoInstallConfig, - #[clap(flatten)] - pub revalidate_config: RevalidateConfig, - #[clap(flatten)] pub lock_file_update_config: LockFileUpdateConfig, diff --git a/src/cli/workspace/channel/remove.rs b/src/cli/workspace/channel/remove.rs index dacd94952b..36c523be9a 100644 --- a/src/cli/workspace/channel/remove.rs +++ b/src/cli/workspace/channel/remove.rs @@ -2,7 +2,7 @@ use miette::IntoDiagnostic; use pixi_core::{ UpdateLockFileOptions, WorkspaceLocator, environment::{LockFileUsage, get_update_lock_file_and_prefix}, - lock_file::ReinstallPackages, + lock_file::{ReinstallPackages, UpdateMode}, }; use super::AddRemoveArgs; @@ -22,7 +22,7 @@ pub async fn execute(args: AddRemoveArgs) -> miette::Result<()> { // Try to update the lock-file without the removed channels get_update_lock_file_and_prefix( &workspace.workspace().default_environment(), - args.revalidate_config.update_mode(), + UpdateMode::QuickValidate, UpdateLockFileOptions { lock_file_usage: LockFileUsage::Update, no_install: args.no_install_config.no_install, diff --git a/tests/integration_rust/common/builders.rs b/tests/integration_rust/common/builders.rs index 87058f6b5d..8efd1610d2 100644 --- a/tests/integration_rust/common/builders.rs +++ b/tests/integration_rust/common/builders.rs @@ -26,7 +26,7 @@ use pixi::cli::{ add, cli_config::{ - DependencyConfig, GitRev, LockFileUpdateConfig, NoInstallConfig, RevalidateConfig, + DependencyConfig, GitRev, LockFileUpdateConfig, NoInstallConfig, WorkspaceConfig, }, init, install, lock, remove, search, task, update, workspace, @@ -121,15 +121,6 @@ pub trait HasNoInstallConfig: Sized { } } -/// A trait used by builders to access RevalidateConfig -pub trait HasRevalidateConfig: Sized { - fn revalidate_config(&mut self) -> &mut RevalidateConfig; - /// Set whether to revalidate the environment (reinstall broken environment) - fn with_revalidate(mut self, revalidate: bool) -> Self { - self.revalidate_config().revalidate = revalidate; - self - } -} /// A trait used by AddBuilder and RemoveBuilder to set their inner /// DependencyConfig @@ -315,11 +306,6 @@ impl HasNoInstallConfig for RemoveBuilder { } } -impl HasRevalidateConfig for RemoveBuilder { - fn revalidate_config(&mut self) -> &mut RevalidateConfig { - &mut self.args.revalidate_config - } -} impl IntoFuture for RemoveBuilder { type Output = miette::Result<()>; diff --git a/tests/integration_rust/common/mod.rs b/tests/integration_rust/common/mod.rs index cd6dd91dc7..e3ed09f5b8 100644 --- a/tests/integration_rust/common/mod.rs +++ b/tests/integration_rust/common/mod.rs @@ -17,7 +17,7 @@ use indicatif::ProgressDrawTarget; use miette::{Context, Diagnostic, IntoDiagnostic}; use pixi::cli::LockFileUsageConfig; use pixi::cli::cli_config::{ - ChannelsConfig, LockFileUpdateConfig, NoInstallConfig, RevalidateConfig, WorkspaceConfig, + ChannelsConfig, LockFileUpdateConfig, NoInstallConfig, WorkspaceConfig, }; use pixi::cli::{ add, @@ -401,7 +401,6 @@ impl PixiControl { }, dependency_config: AddBuilder::dependency_config_with_specs(vec![spec]), no_install_config: NoInstallConfig { no_install: true }, - revalidate_config: RevalidateConfig { revalidate: false }, lock_file_update_config: LockFileUpdateConfig { no_lockfile_update: false, lock_file_usage: LockFileUsageConfig::default(), @@ -420,7 +419,6 @@ impl PixiControl { }, channel: vec![], no_install_config: NoInstallConfig { no_install: true }, - revalidate_config: RevalidateConfig { revalidate: false }, lock_file_update_config: LockFileUpdateConfig { no_lockfile_update: false, lock_file_usage: LockFileUsageConfig::default(), @@ -443,7 +441,6 @@ impl PixiControl { }, channel: vec![], no_install_config: NoInstallConfig { no_install: true }, - revalidate_config: RevalidateConfig { revalidate: false }, lock_file_update_config: LockFileUpdateConfig { no_lockfile_update: false, lock_file_usage: LockFileUsageConfig::default(), From 65e4bb876683e9c00856e90c4a12f698071c0ab8 Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Thu, 14 Aug 2025 10:28:27 +0200 Subject: [PATCH 07/29] fix: fmt --- src/cli/cli_config.rs | 1 - src/cli/import.rs | 4 +--- src/cli/remove.rs | 14 ++++---------- src/cli/run.rs | 4 +--- src/cli/shell.rs | 7 +++++-- src/cli/shell_hook.rs | 4 +--- src/cli/upgrade.rs | 4 +--- src/cli/workspace/channel/mod.rs | 4 +--- tests/integration_rust/common/builders.rs | 5 +---- 9 files changed, 15 insertions(+), 32 deletions(-) diff --git a/src/cli/cli_config.rs b/src/cli/cli_config.rs index 3795f2f8d7..e445e0429b 100644 --- a/src/cli/cli_config.rs +++ b/src/cli/cli_config.rs @@ -156,7 +156,6 @@ impl NoInstallConfig { } } - #[derive(Parser, Debug, Default, Clone)] pub struct GitRev { /// The git branch diff --git a/src/cli/import.rs b/src/cli/import.rs index eb0d4ccb2d..8534aa8cf8 100644 --- a/src/cli/import.rs +++ b/src/cli/import.rs @@ -16,9 +16,7 @@ use uv_requirements_txt::RequirementsTxt; use miette::{Diagnostic, IntoDiagnostic, Result}; use thiserror::Error; -use crate::cli::cli_config::{ - LockFileUpdateConfig, NoInstallConfig, WorkspaceConfig, -}; +use crate::cli::cli_config::{LockFileUpdateConfig, NoInstallConfig, WorkspaceConfig}; #[derive(Parser, Debug, Clone, PartialEq, ValueEnum)] pub enum ImportFileFormat { diff --git a/src/cli/remove.rs b/src/cli/remove.rs index ce56deb05f..196b825921 100644 --- a/src/cli/remove.rs +++ b/src/cli/remove.rs @@ -3,13 +3,12 @@ use miette::{Context, IntoDiagnostic}; use pixi_config::ConfigCli; use pixi_core::{ DependencyType, UpdateLockFileOptions, WorkspaceLocator, - environment::get_update_lock_file_and_prefix, lock_file::{ReinstallPackages, UpdateMode}, + environment::get_update_lock_file_and_prefix, + lock_file::{ReinstallPackages, UpdateMode}, }; use pixi_manifest::FeaturesExt; -use crate::cli::cli_config::{ - DependencyConfig, NoInstallConfig, WorkspaceConfig, -}; +use crate::cli::cli_config::{DependencyConfig, NoInstallConfig, WorkspaceConfig}; use crate::cli::{cli_config::LockFileUpdateConfig, has_specs::HasSpecs}; /// Removes dependencies from the workspace. @@ -41,12 +40,7 @@ pub struct Args { } pub async fn execute(args: Args) -> miette::Result<()> { - let ( - dependency_config, - no_install_config, - lock_file_update_config, - workspace_config, - ) = ( + let (dependency_config, no_install_config, lock_file_update_config, workspace_config) = ( args.dependency_config, args.no_install_config, args.lock_file_update_config, diff --git a/src/cli/run.rs b/src/cli/run.rs index 0f140bf666..0dec9c40c7 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -29,9 +29,7 @@ use pixi_core::{ workspace::{Environment, errors::UnsupportedPlatformError}, }; -use crate::cli::cli_config::{ - LockFileUpdateConfig, NoInstallConfig, WorkspaceConfig, -}; +use crate::cli::cli_config::{LockFileUpdateConfig, NoInstallConfig, WorkspaceConfig}; /// Runs task in the pixi environment. /// diff --git a/src/cli/shell.rs b/src/cli/shell.rs index f3ed435592..a24cb5c02a 100644 --- a/src/cli/shell.rs +++ b/src/cli/shell.rs @@ -10,8 +10,11 @@ use rattler_shell::{ use pixi_config::{ConfigCli, ConfigCliActivation, ConfigCliPrompt}; use pixi_core::{ - UpdateLockFileOptions, WorkspaceLocator, activation::CurrentEnvVarBehavior, - environment::get_update_lock_file_and_prefix, lock_file::{ReinstallPackages, UpdateMode}, prompt, + UpdateLockFileOptions, WorkspaceLocator, + activation::CurrentEnvVarBehavior, + environment::get_update_lock_file_and_prefix, + lock_file::{ReinstallPackages, UpdateMode}, + prompt, workspace::get_activated_environment_variables, }; diff --git a/src/cli/shell_hook.rs b/src/cli/shell_hook.rs index ef62815e2e..8e2efd55ab 100644 --- a/src/cli/shell_hook.rs +++ b/src/cli/shell_hook.rs @@ -20,9 +20,7 @@ use pixi_core::{ workspace::{Environment, HasWorkspaceRef, get_activated_environment_variables}, }; -use crate::cli::cli_config::{ - LockFileUpdateConfig, NoInstallConfig, WorkspaceConfig, -}; +use crate::cli::cli_config::{LockFileUpdateConfig, NoInstallConfig, WorkspaceConfig}; /// Print the pixi environment activation script. /// diff --git a/src/cli/upgrade.rs b/src/cli/upgrade.rs index b4f3e90c68..3012fa6707 100644 --- a/src/cli/upgrade.rs +++ b/src/cli/upgrade.rs @@ -17,9 +17,7 @@ use pixi_pypi_spec::PixiPypiSpec; use pixi_spec::PixiSpec; use rattler_conda_types::{MatchSpec, StringMatcher}; -use crate::cli::cli_config::{ - LockFileUpdateConfig, NoInstallConfig, WorkspaceConfig, -}; +use crate::cli::cli_config::{LockFileUpdateConfig, NoInstallConfig, WorkspaceConfig}; /// Checks if there are newer versions of the dependencies and upgrades them in the lockfile and manifest file. /// diff --git a/src/cli/workspace/channel/mod.rs b/src/cli/workspace/channel/mod.rs index 2fab5eea18..87117cbda3 100644 --- a/src/cli/workspace/channel/mod.rs +++ b/src/cli/workspace/channel/mod.rs @@ -8,9 +8,7 @@ use pixi_config::ConfigCli; use pixi_manifest::{FeatureName, PrioritizedChannel}; use rattler_conda_types::{ChannelConfig, NamedChannelOrUrl}; -use crate::cli::cli_config::{ - LockFileUpdateConfig, NoInstallConfig, WorkspaceConfig, -}; +use crate::cli::cli_config::{LockFileUpdateConfig, NoInstallConfig, WorkspaceConfig}; /// Commands to manage workspace channels. #[derive(Parser, Debug, Clone)] diff --git a/tests/integration_rust/common/builders.rs b/tests/integration_rust/common/builders.rs index 8efd1610d2..397d412f9c 100644 --- a/tests/integration_rust/common/builders.rs +++ b/tests/integration_rust/common/builders.rs @@ -26,8 +26,7 @@ use pixi::cli::{ add, cli_config::{ - DependencyConfig, GitRev, LockFileUpdateConfig, NoInstallConfig, - WorkspaceConfig, + DependencyConfig, GitRev, LockFileUpdateConfig, NoInstallConfig, WorkspaceConfig, }, init, install, lock, remove, search, task, update, workspace, }; @@ -121,7 +120,6 @@ pub trait HasNoInstallConfig: Sized { } } - /// A trait used by AddBuilder and RemoveBuilder to set their inner /// DependencyConfig pub trait HasLockFileUpdateConfig: Sized { @@ -306,7 +304,6 @@ impl HasNoInstallConfig for RemoveBuilder { } } - impl IntoFuture for RemoveBuilder { type Output = miette::Result<()>; type IntoFuture = Pin + 'static>>; From 0c20f7176ee6c45dc0f7ce518e5710f7b132df4c Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Thu, 14 Aug 2025 10:48:47 +0200 Subject: [PATCH 08/29] feat: remove --revalidation test --- tests/integration_python/test_run_cli.py | 44 ------------------------ 1 file changed, 44 deletions(-) diff --git a/tests/integration_python/test_run_cli.py b/tests/integration_python/test_run_cli.py index 3aae2c2dd4..87699988e1 100644 --- a/tests/integration_python/test_run_cli.py +++ b/tests/integration_python/test_run_cli.py @@ -186,50 +186,6 @@ def test_using_prefix_validation( assert Path(file).exists() -def test_prefix_revalidation(pixi: Path, tmp_pixi_workspace: Path, dummy_channel_1: str) -> None: - manifest = tmp_pixi_workspace.joinpath("pixi.toml") - toml = f""" - [project] - name = "test" - channels = ["{dummy_channel_1}"] - platforms = ["linux-64", "osx-64", "osx-arm64", "win-64"] - - [dependencies] - dummy-a = "*" - """ - manifest.write_text(toml) - - # Run the installation - verify_cli_command( - [pixi, "install", "--manifest-path", manifest], - ) - - # Validate creation of the pixi file with the hash - pixi_file = default_env_path(tmp_pixi_workspace).joinpath("conda-meta").joinpath("pixi") - assert pixi_file.exists() - assert "environment_lock_file_hash" in pixi_file.read_text() - - # Break environment on purpose - dummy_a_meta_files = ( - default_env_path(tmp_pixi_workspace).joinpath("conda-meta").glob("dummy-a*.json") - ) - - for file in dummy_a_meta_files: - path = Path(file) - if path.exists(): - path.unlink() # Removes the file - - # Run with revalidation to force reinstallation - verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "--revalidate", "echo", "hello"], - stdout_contains="hello", - ) - - # Validate that the dummy-a files are reinstalled - for file in dummy_a_meta_files: - assert Path(file).exists() - - def test_run_with_activation(pixi: Path, tmp_pixi_workspace: Path) -> None: manifest = tmp_pixi_workspace.joinpath("pixi.toml") toml = f""" From 31addfbcbd4af24126d8456ebe7b320091ed33d0 Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Thu, 14 Aug 2025 12:12:15 +0200 Subject: [PATCH 09/29] feat: update test --- docs/integration/editor/jetbrains.md | 2 +- src/cli/cli_config.rs | 10 +-- src/cli/run.rs | 23 ++++--- src/cli/workspace/channel/add.rs | 4 +- src/cli/workspace/channel/remove.rs | 4 +- tests/integration_python/test_main_cli.py | 78 +++++++++++++++++++++++ 6 files changed, 98 insertions(+), 23 deletions(-) diff --git a/docs/integration/editor/jetbrains.md b/docs/integration/editor/jetbrains.md index 0608e0437f..9545148f02 100644 --- a/docs/integration/editor/jetbrains.md +++ b/docs/integration/editor/jetbrains.md @@ -169,7 +169,7 @@ To configure an interpreter for a new project: 5. Once you have added and renamed the environments, select the desired interpreter to use in PyCharm from the list. If your project uses more than one environment, you can switch between them by selecting interpreter name in the -status bar at the bottom of the PyCharm window and selecting the interpreter for the desired interpeter from the list. +status bar at the bottom of the PyCharm window and selecting the interpreter for the desired interpreter from the list. Note that this will trigger PyCharm reindexing and might not be very fast. As with the pixi-pycharm shim, you should avoid using the PyCharm UI to attempt to add or remove packages from your environments and you should diff --git a/src/cli/cli_config.rs b/src/cli/cli_config.rs index e445e0429b..e6d390579b 100644 --- a/src/cli/cli_config.rs +++ b/src/cli/cli_config.rs @@ -145,14 +145,8 @@ impl NoInstallConfig { Self { no_install } } - /// Creates a NoInstallConfig that skips installation - pub fn skip_install() -> Self { - Self::new(true) - } - - /// Creates a NoInstallConfig that allows installation - pub fn allow_install() -> Self { - Self::new(false) + pub fn allow_installs(&self) -> bool { + !self.no_install } } diff --git a/src/cli/run.rs b/src/cli/run.rs index 0dec9c40c7..d619d9a477 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -126,8 +126,8 @@ pub async fn execute(args: Args) -> miette::Result<()> { let lock_file = workspace .update_lock_file(UpdateLockFileOptions { lock_file_usage: args.lock_file_update_config.lock_file_usage()?, + no_install: args.no_install_config.no_install, max_concurrent_solves: workspace.config().max_concurrent_solves(), - ..UpdateLockFileOptions::default() }) .await? .0; @@ -253,15 +253,18 @@ pub async fn execute(args: Args) -> miette::Result<()> { let task_env: &_ = match task_envs.entry(executable_task.run_environment.clone()) { Entry::Occupied(env) => env.into_mut(), Entry::Vacant(entry) => { - // Ensure there is a valid prefix - lock_file - .prefix( - &executable_task.run_environment, - UpdateMode::QuickValidate, - &ReinstallPackages::default(), - &[], - ) - .await?; + // Check if we allow installs + if args.no_install_config.allow_installs() { + // Ensure there is a valid prefix + lock_file + .prefix( + &executable_task.run_environment, + UpdateMode::QuickValidate, + &ReinstallPackages::default(), + &[], + ) + .await?; + } // Clear the current progress reports. lock_file.command_dispatcher.clear_reporter().await; diff --git a/src/cli/workspace/channel/add.rs b/src/cli/workspace/channel/add.rs index f5dbafa25a..b288d86c88 100644 --- a/src/cli/workspace/channel/add.rs +++ b/src/cli/workspace/channel/add.rs @@ -1,7 +1,7 @@ use miette::IntoDiagnostic; use pixi_core::{ UpdateLockFileOptions, WorkspaceLocator, - environment::{LockFileUsage, get_update_lock_file_and_prefix}, + environment::get_update_lock_file_and_prefix, lock_file::{ReinstallPackages, UpdateMode}, }; @@ -26,7 +26,7 @@ pub async fn execute(args: AddRemoveArgs) -> miette::Result<()> { &workspace.workspace().default_environment(), UpdateMode::QuickValidate, UpdateLockFileOptions { - lock_file_usage: LockFileUsage::Update, + lock_file_usage: args.lock_file_update_config.lock_file_usage()?, no_install: args.no_install_config.no_install, max_concurrent_solves: workspace.workspace().config().max_concurrent_solves(), }, diff --git a/src/cli/workspace/channel/remove.rs b/src/cli/workspace/channel/remove.rs index 36c523be9a..a0cbc72af9 100644 --- a/src/cli/workspace/channel/remove.rs +++ b/src/cli/workspace/channel/remove.rs @@ -1,7 +1,7 @@ use miette::IntoDiagnostic; use pixi_core::{ UpdateLockFileOptions, WorkspaceLocator, - environment::{LockFileUsage, get_update_lock_file_and_prefix}, + environment::get_update_lock_file_and_prefix, lock_file::{ReinstallPackages, UpdateMode}, }; @@ -24,7 +24,7 @@ pub async fn execute(args: AddRemoveArgs) -> miette::Result<()> { &workspace.workspace().default_environment(), UpdateMode::QuickValidate, UpdateLockFileOptions { - lock_file_usage: LockFileUsage::Update, + lock_file_usage: args.lock_file_update_config.lock_file_usage()?, no_install: args.no_install_config.no_install, max_concurrent_solves: workspace.workspace().config().max_concurrent_solves(), }, diff --git a/tests/integration_python/test_main_cli.py b/tests/integration_python/test_main_cli.py index 1d17e7142a..f637f6ebd4 100644 --- a/tests/integration_python/test_main_cli.py +++ b/tests/integration_python/test_main_cli.py @@ -1583,3 +1583,81 @@ def test_fish_completions(pixi: Path, tmp_pixi_workspace: Path) -> None: f"source {fish_completion_file}", ], ) + + +@pytest.mark.slow +def test_frozen_no_install_invariant(pixi: Path, tmp_pixi_workspace: Path) -> None: + """Test that --frozen --no-install maintains lockfile invariant and keeps conda-meta empty. + + This test verifies that when using --frozen --no-install flags together, the pixi.lock + file does not change and the conda-meta directory stays empty across all commands that + support these flags. + """ + manifest_path = tmp_pixi_workspace / "pixi.toml" + lock_file_path = tmp_pixi_workspace / "pixi.lock" + conda_meta_path = tmp_pixi_workspace / ".pixi" / "envs" / "default" / "conda-meta" + + # Common flags for frozen no-install operations + frozen_no_install_flags: list[str | Path] = ["--manifest-path", manifest_path, "--frozen", "--no-install"] + + # Create a new project with bzip2 (lightweight package) + verify_cli_command([pixi, "init", tmp_pixi_workspace], ExitCode.SUCCESS) + # Add bzip2 package to keep installation time low + verify_cli_command([pixi, "add", "--manifest-path", manifest_path, "bzip2"]) + # Store the original lockfile content + original_lock_content = lock_file_path.read_text() + + # Remove conda-meta folder to simulate missing environment + if conda_meta_path.exists(): + shutil.rmtree(conda_meta_path) + + # Helper function to check invariants + def check_invariants(command_name: str) -> None: + # Check that lockfile hasn't changed + current_lock_content = lock_file_path.read_text() + assert current_lock_content == original_lock_content, ( + f"Lockfile changed after {command_name} with --frozen --no-install" + ) + + # Check that conda-meta directory stays empty/non-existent + assert not conda_meta_path.exists() or not any(conda_meta_path.iterdir()), ( + f"conda-meta directory not empty after {command_name} with --frozen --no-install" + ) + + # Test commands that properly respect --frozen --no-install + # Define commands as (command_parts, additional_args, command_name_for_invariants) + commands_to_test: list[tuple[list[str], list[str], str]] = [ + # Let's start with adding a workspace channel because that would trigger a re-solve in most cases + ( + ["workspace", "channel", "add"], + ["https://prefix.dev/bioconda"], + "pixi workspace channel add", + ), + (["list"], [], "pixi list"), + (["tree"], [], "pixi tree"), + (["shell-hook"], [], "pixi shell-hook"), + # Special case: pixi shell uses --locked instead of --frozen and expects failure + (["shell"], [], "pixi shell"), + # Test manifest modifications with --frozen --no-install (these should work) + # Note: These modify manifest but not lockfile due to --frozen + (["add"], ["zlib"], "pixi add"), + (["remove"], ["zlib"], "pixi remove"), + (["run"], ["echo", "test"], "pixi run"), + ( + ["workspace", "channel", "remove"], + ["https://prefix.dev/bioconda"], + "pixi workspace channel remove", + ), + ] + + # Execute all commands and check invariants + for command_parts, additional_args, command_name in commands_to_test: + if command_name == "pixi shell": + # Special case: shell uses --locked instead of --frozen and expects failure + verify_cli_command( + [pixi, "shell", "--manifest-path", manifest_path, "--locked", "--no-install"], + expected_exit_code=ExitCode.FAILURE, + ) + else: + verify_cli_command([pixi, *command_parts, *frozen_no_install_flags, *additional_args]) + check_invariants(command_name) From 0259caba697b2b797d1248649fcacb5afaca2e4d Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Thu, 14 Aug 2025 12:12:34 +0200 Subject: [PATCH 10/29] fix: fmt --- tests/integration_python/test_main_cli.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/integration_python/test_main_cli.py b/tests/integration_python/test_main_cli.py index f637f6ebd4..a86ea258d9 100644 --- a/tests/integration_python/test_main_cli.py +++ b/tests/integration_python/test_main_cli.py @@ -1598,7 +1598,12 @@ def test_frozen_no_install_invariant(pixi: Path, tmp_pixi_workspace: Path) -> No conda_meta_path = tmp_pixi_workspace / ".pixi" / "envs" / "default" / "conda-meta" # Common flags for frozen no-install operations - frozen_no_install_flags: list[str | Path] = ["--manifest-path", manifest_path, "--frozen", "--no-install"] + frozen_no_install_flags: list[str | Path] = [ + "--manifest-path", + manifest_path, + "--frozen", + "--no-install", + ] # Create a new project with bzip2 (lightweight package) verify_cli_command([pixi, "init", tmp_pixi_workspace], ExitCode.SUCCESS) From 879526db5e69f4e960bdb2c0b84a8c3555562b62 Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Thu, 14 Aug 2025 13:15:33 +0200 Subject: [PATCH 11/29] fix: update cli docs --- docs/reference/cli/pixi/add.md | 4 +--- docs/reference/cli/pixi/import.md | 4 +--- docs/reference/cli/pixi/list.md | 4 +++- docs/reference/cli/pixi/lock.md | 4 ++++ docs/reference/cli/pixi/remove.md | 4 +--- docs/reference/cli/pixi/run.md | 4 +--- docs/reference/cli/pixi/shell-hook.md | 4 +--- docs/reference/cli/pixi/shell.md | 4 +--- docs/reference/cli/pixi/tree.md | 4 +++- docs/reference/cli/pixi/upgrade.md | 4 +--- docs/reference/cli/pixi/workspace/channel/add.md | 4 +--- docs/reference/cli/pixi/workspace/channel/remove.md | 4 +--- .../cli/pixi/workspace/export/conda-explicit-spec.md | 4 +++- 13 files changed, 22 insertions(+), 30 deletions(-) diff --git a/docs/reference/cli/pixi/add.md b/docs/reference/cli/pixi/add.md index b52b09c30c..60f9b28fbb 100644 --- a/docs/reference/cli/pixi/add.md +++ b/docs/reference/cli/pixi/add.md @@ -64,10 +64,8 @@ pixi add [OPTIONS] ... ## Update Options - `--no-install` : Don't modify the environment, only modify the lock-file -- `--revalidate` -: Run the complete environment validation. This will reinstall a broken environment - `--no-lockfile-update` -: Don't update lockfile, implies the no-install as well +: Legacy flag to skip lock-file updates, behavior was inconsistent across commands so it has been deprecated - `--frozen` : Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file
**env**: `PIXI_FROZEN` diff --git a/docs/reference/cli/pixi/import.md b/docs/reference/cli/pixi/import.md index 764598ccea..f5064c4ba9 100644 --- a/docs/reference/cli/pixi/import.md +++ b/docs/reference/cli/pixi/import.md @@ -51,10 +51,8 @@ pixi import [OPTIONS] ## Update Options - `--no-install` : Don't modify the environment, only modify the lock-file -- `--revalidate` -: Run the complete environment validation. This will reinstall a broken environment - `--no-lockfile-update` -: Don't update lockfile, implies the no-install as well +: Legacy flag to skip lock-file updates, behavior was inconsistent across commands so it has been deprecated - `--frozen` : Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file
**env**: `PIXI_FROZEN` diff --git a/docs/reference/cli/pixi/list.md b/docs/reference/cli/pixi/list.md index e814d64688..8f43e986d5 100644 --- a/docs/reference/cli/pixi/list.md +++ b/docs/reference/cli/pixi/list.md @@ -33,13 +33,15 @@ pixi list [OPTIONS] [REGEX] ## Update Options - `--no-lockfile-update` -: Don't update lockfile, implies the no-install as well +: Legacy flag to skip lock-file updates, behavior was inconsistent across commands so it has been deprecated - `--frozen` : Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file
**env**: `PIXI_FROZEN` - `--locked` : Check if lockfile is up-to-date before installing the environment, aborts when lockfile isn't up-to-date with the manifest file
**env**: `PIXI_LOCKED` +- `--no-install` +: Don't modify the environment, only modify the lock-file ## Global Options - `--manifest-path ` diff --git a/docs/reference/cli/pixi/lock.md b/docs/reference/cli/pixi/lock.md index 63599d0833..5729602e3b 100644 --- a/docs/reference/cli/pixi/lock.md +++ b/docs/reference/cli/pixi/lock.md @@ -17,6 +17,10 @@ pixi lock [OPTIONS] - `--check` : Check if any changes have been made to the lock file. If yes, exit with a non-zero code +## Update Options +- `--no-install` +: Don't modify the environment, only modify the lock-file + ## Global Options - `--manifest-path ` : The path to `pixi.toml`, `pyproject.toml`, or the workspace directory diff --git a/docs/reference/cli/pixi/remove.md b/docs/reference/cli/pixi/remove.md index f18efb887c..1e7ae39263 100644 --- a/docs/reference/cli/pixi/remove.md +++ b/docs/reference/cli/pixi/remove.md @@ -62,10 +62,8 @@ pixi remove [OPTIONS] ... ## Update Options - `--no-install` : Don't modify the environment, only modify the lock-file -- `--revalidate` -: Run the complete environment validation. This will reinstall a broken environment - `--no-lockfile-update` -: Don't update lockfile, implies the no-install as well +: Legacy flag to skip lock-file updates, behavior was inconsistent across commands so it has been deprecated - `--frozen` : Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file
**env**: `PIXI_FROZEN` diff --git a/docs/reference/cli/pixi/run.md b/docs/reference/cli/pixi/run.md index 4e89c155f4..f8742d2aac 100644 --- a/docs/reference/cli/pixi/run.md +++ b/docs/reference/cli/pixi/run.md @@ -55,10 +55,8 @@ pixi run [OPTIONS] [TASK]... ## Update Options - `--no-install` : Don't modify the environment, only modify the lock-file -- `--revalidate` -: Run the complete environment validation. This will reinstall a broken environment - `--no-lockfile-update` -: Don't update lockfile, implies the no-install as well +: Legacy flag to skip lock-file updates, behavior was inconsistent across commands so it has been deprecated - `--frozen` : Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file
**env**: `PIXI_FROZEN` diff --git a/docs/reference/cli/pixi/shell-hook.md b/docs/reference/cli/pixi/shell-hook.md index d25ae7ba6c..513d7c3fd4 100644 --- a/docs/reference/cli/pixi/shell-hook.md +++ b/docs/reference/cli/pixi/shell-hook.md @@ -50,10 +50,8 @@ pixi shell-hook [OPTIONS] ## Update Options - `--no-install` : Don't modify the environment, only modify the lock-file -- `--revalidate` -: Run the complete environment validation. This will reinstall a broken environment - `--no-lockfile-update` -: Don't update lockfile, implies the no-install as well +: Legacy flag to skip lock-file updates, behavior was inconsistent across commands so it has been deprecated - `--frozen` : Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file
**env**: `PIXI_FROZEN` diff --git a/docs/reference/cli/pixi/shell.md b/docs/reference/cli/pixi/shell.md index b849aa6b5c..28124203e8 100644 --- a/docs/reference/cli/pixi/shell.md +++ b/docs/reference/cli/pixi/shell.md @@ -45,10 +45,8 @@ pixi shell [OPTIONS] ## Update Options - `--no-install` : Don't modify the environment, only modify the lock-file -- `--revalidate` -: Run the complete environment validation. This will reinstall a broken environment - `--no-lockfile-update` -: Don't update lockfile, implies the no-install as well +: Legacy flag to skip lock-file updates, behavior was inconsistent across commands so it has been deprecated - `--frozen` : Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file
**env**: `PIXI_FROZEN` diff --git a/docs/reference/cli/pixi/tree.md b/docs/reference/cli/pixi/tree.md index fad0424641..85c75ce981 100644 --- a/docs/reference/cli/pixi/tree.md +++ b/docs/reference/cli/pixi/tree.md @@ -25,13 +25,15 @@ pixi tree [OPTIONS] [REGEX] ## Update Options - `--no-lockfile-update` -: Don't update lockfile, implies the no-install as well +: Legacy flag to skip lock-file updates, behavior was inconsistent across commands so it has been deprecated - `--frozen` : Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file
**env**: `PIXI_FROZEN` - `--locked` : Check if lockfile is up-to-date before installing the environment, aborts when lockfile isn't up-to-date with the manifest file
**env**: `PIXI_LOCKED` +- `--no-install` +: Don't modify the environment, only modify the lock-file ## Global Options - `--manifest-path ` diff --git a/docs/reference/cli/pixi/upgrade.md b/docs/reference/cli/pixi/upgrade.md index bceddbbdb2..3da08a11d7 100644 --- a/docs/reference/cli/pixi/upgrade.md +++ b/docs/reference/cli/pixi/upgrade.md @@ -51,10 +51,8 @@ pixi upgrade [OPTIONS] [PACKAGES]... ## Update Options - `--no-install` : Don't modify the environment, only modify the lock-file -- `--revalidate` -: Run the complete environment validation. This will reinstall a broken environment - `--no-lockfile-update` -: Don't update lockfile, implies the no-install as well +: Legacy flag to skip lock-file updates, behavior was inconsistent across commands so it has been deprecated - `--frozen` : Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file
**env**: `PIXI_FROZEN` diff --git a/docs/reference/cli/pixi/workspace/channel/add.md b/docs/reference/cli/pixi/workspace/channel/add.md index 080475e1f0..4c32a53bbc 100644 --- a/docs/reference/cli/pixi/workspace/channel/add.md +++ b/docs/reference/cli/pixi/workspace/channel/add.md @@ -48,10 +48,8 @@ pixi workspace channel add [OPTIONS] ... ## Update Options - `--no-install` : Don't modify the environment, only modify the lock-file -- `--revalidate` -: Run the complete environment validation. This will reinstall a broken environment - `--no-lockfile-update` -: Don't update lockfile, implies the no-install as well +: Legacy flag to skip lock-file updates, behavior was inconsistent across commands so it has been deprecated - `--frozen` : Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file
**env**: `PIXI_FROZEN` diff --git a/docs/reference/cli/pixi/workspace/channel/remove.md b/docs/reference/cli/pixi/workspace/channel/remove.md index 8cbc1615c4..3b74bbb80d 100644 --- a/docs/reference/cli/pixi/workspace/channel/remove.md +++ b/docs/reference/cli/pixi/workspace/channel/remove.md @@ -48,10 +48,8 @@ pixi workspace channel remove [OPTIONS] ... ## Update Options - `--no-install` : Don't modify the environment, only modify the lock-file -- `--revalidate` -: Run the complete environment validation. This will reinstall a broken environment - `--no-lockfile-update` -: Don't update lockfile, implies the no-install as well +: Legacy flag to skip lock-file updates, behavior was inconsistent across commands so it has been deprecated - `--frozen` : Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file
**env**: `PIXI_FROZEN` diff --git a/docs/reference/cli/pixi/workspace/export/conda-explicit-spec.md b/docs/reference/cli/pixi/workspace/export/conda-explicit-spec.md index 1890b09476..bcf279529e 100644 --- a/docs/reference/cli/pixi/workspace/export/conda-explicit-spec.md +++ b/docs/reference/cli/pixi/workspace/export/conda-explicit-spec.md @@ -52,13 +52,15 @@ pixi workspace export conda-explicit-spec [OPTIONS] ## Update Options - `--no-lockfile-update` -: Don't update lockfile, implies the no-install as well +: Legacy flag to skip lock-file updates, behavior was inconsistent across commands so it has been deprecated - `--frozen` : Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file
**env**: `PIXI_FROZEN` - `--locked` : Check if lockfile is up-to-date before installing the environment, aborts when lockfile isn't up-to-date with the manifest file
**env**: `PIXI_LOCKED` +- `--no-install` +: Don't modify the environment, only modify the lock-file ## Global Options - `--manifest-path ` From 236f3427fbf2017e01b6f50bb15cc0b23df26799 Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Thu, 14 Aug 2025 13:40:55 +0200 Subject: [PATCH 12/29] fmt --- docs/workspace/environment.md | 2 +- src/cli/completion.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/workspace/environment.md b/docs/workspace/environment.md index 9606eb3492..08fe446480 100644 --- a/docs/workspace/environment.md +++ b/docs/workspace/environment.md @@ -104,7 +104,7 @@ This file contains the following information: The `environment_lock_file_hash` is used to check if the environment is in sync with the `pixi.lock` file. If the hash of the `pixi.lock` file is different from the hash in the `pixi` file, Pixi will update the environment. -This is used to speedup activation, in order to trigger a full revalidation pass `--revalidate` to the `pixi run` or `pixi shell` command. +This is used to speedup activation, in order to trigger a full revalidation and installation use `pixi install` or `pixi reinstall`. A broken environment would typically not be found with a hash comparison, but a revalidation would reinstall the environment. By default, all lock file modifying commands will always use the revalidation and on `pixi install` it always revalidates. diff --git a/src/cli/completion.rs b/src/cli/completion.rs index 15e23e473f..665628cba4 100644 --- a/src/cli/completion.rs +++ b/src/cli/completion.rs @@ -306,7 +306,6 @@ _arguments "${_arguments_options[@]}" \ --pypi-keyring-provider: string@"nu-complete pixi run pypi_keyring_provider" # Specifies if we want to use uv keyring provider --concurrent-solves: string # Max concurrent solves, default is the number of CPUs --concurrent-downloads: string # Max concurrent network requests, default is 50 - --revalidate # Run the complete environment validation. This will reinstall a broken environment --force-activate # Do not use the environment activation cache. (default: true except in experimental mode) --environment(-e): string@"nu-complete pixi run environment" # The environment to run the task in --clean-env # Use a clean environment to run the task From c95aedb103af935ad6ea00c60978f182e4e5af2f Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Thu, 14 Aug 2025 13:42:18 +0200 Subject: [PATCH 13/29] fix: compile errors --- src/cli/workspace/channel/remove.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cli/workspace/channel/remove.rs b/src/cli/workspace/channel/remove.rs index fb08021544..a0cbc72af9 100644 --- a/src/cli/workspace/channel/remove.rs +++ b/src/cli/workspace/channel/remove.rs @@ -1,5 +1,4 @@ use miette::IntoDiagnostic; -use pixi_core::lock_file::{ReinstallPackages, UpdateMode}; use pixi_core::{ UpdateLockFileOptions, WorkspaceLocator, environment::get_update_lock_file_and_prefix, From e9467f074aedab9a51e4b931865095445b73d734 Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Thu, 14 Aug 2025 14:02:18 +0200 Subject: [PATCH 14/29] fix: update error message and improve docs --- src/cli/cli_config.rs | 5 ++--- .../pixi__cli__completion__tests__nushell_completion.snap | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/cli/cli_config.rs b/src/cli/cli_config.rs index e6d390579b..19c1825db2 100644 --- a/src/cli/cli_config.rs +++ b/src/cli/cli_config.rs @@ -116,9 +116,8 @@ impl LockFileUpdateConfig { // Error on deprecated flag usage if self.no_lockfile_update { return Err(miette::miette!( - help = "Use '--frozen' to skip lock-file updates.\n\nUse '--no-install' to skip installation.\n\n", - "The '--no-lockfile-update' flag has been deprecated due to inconsistent behavior across commands.\n\n\ - This flag will be removed in a future version." + help = "Use '--frozen' to skip lock-file updates.\nUse '--no-install' to skip installation.", + "The '--no-lockfile-update' flag has been deprecated due to inconsistent behavior across commands. This flag will be removed in a future version." )); } diff --git a/src/cli/snapshots/pixi__cli__completion__tests__nushell_completion.snap b/src/cli/snapshots/pixi__cli__completion__tests__nushell_completion.snap index bf2fffd47f..f8e3e542ac 100644 --- a/src/cli/snapshots/pixi__cli__completion__tests__nushell_completion.snap +++ b/src/cli/snapshots/pixi__cli__completion__tests__nushell_completion.snap @@ -15,7 +15,6 @@ expression: result --pypi-keyring-provider: string@"nu-complete pixi run pypi_keyring_provider" # Specifies if we want to use uv keyring provider --concurrent-solves: string # Max concurrent solves, default is the number of CPUs --concurrent-downloads: string # Max concurrent network requests, default is 50 - --revalidate # Run the complete environment validation. This will reinstall a broken environment --force-activate # Do not use the environment activation cache. (default: true except in experimental mode) --environment(-e): string@"nu-complete pixi run environment" # The environment to run the task in --clean-env # Use a clean environment to run the task From f468cae479410a119877a632a629850556e869ad Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Thu, 14 Aug 2025 15:41:13 +0200 Subject: [PATCH 15/29] feat: move to test folder and add missing tests --- tests/integration_python/common.py | 123 +++++++++++++++++++++- tests/integration_python/test_main_cli.py | 65 ++++++++++-- 2 files changed, 180 insertions(+), 8 deletions(-) diff --git a/tests/integration_python/common.py b/tests/integration_python/common.py index 601948848a..6996a9e513 100644 --- a/tests/integration_python/common.py +++ b/tests/integration_python/common.py @@ -5,7 +5,7 @@ from enum import IntEnum from pathlib import Path import sys -from typing import Generator, Optional, Sequence, Tuple +from typing import Generator, List, Optional, Sequence, Set, Tuple from rattler import Platform @@ -199,3 +199,124 @@ def run_and_get_env(pixi: Path, *args: str, env_var: str) -> Tuple[Optional[str] print(f"Error running command: {e}") print(f"Command: {' '.join(cmd)}") raise + + +# Command discovery utilities for testing CLI flag support + + +def discover_pixi_commands() -> set[str]: + """Discover all available pixi commands by walking the docs/reference/cli/pixi directory. + + Returns: + Set[str]: Set of command names in the format "pixi command subcommand ..." + + Examples: + {"pixi add", "pixi workspace channel add", "pixi shell", ...} + """ + docs_path = repo_root() / "docs" / "reference" / "cli" / "pixi" + commands: set[str] = set() + + if not docs_path.exists(): + return commands + + # Walk through all markdown files in the docs directory + for md_file in docs_path.rglob("*.md"): + # Get relative path from the pixi docs directory + relative_path = md_file.relative_to(docs_path) + + # Convert file path to command format + # e.g., "workspace/channel/add.md" -> "pixi workspace channel add" + command_parts = ["pixi"] + list(relative_path.parts[:-1]) + [relative_path.stem] + command = " ".join(command_parts) + commands.add(command) + + return commands + + +def check_command_supports_flags(command_parts: list[str], *flag_names: str) -> tuple[bool, ...]: + """Check if a command supports specific flags by examining its documentation. + + Args: + command_parts: List of command parts (e.g., ["workspace", "channel", "add"]) + *flag_names: Variable number of flag names to check for (e.g., "--frozen", "--no-install") + + Returns: + Tuple[bool, ...]: Tuple of booleans indicating support for each flag in order + + Examples: + check_command_supports_flags(["add"], "--frozen", "--no-install") + # Returns: (True, True) if both flags are supported + + check_command_supports_flags(["shell"], "--frozen", "--locked", "--no-install") + # Returns: (False, True, True) if only --locked and --no-install are supported + """ + # Build the documentation file path + docs_path = repo_root() / "docs" / "reference" / "cli" / "pixi" + doc_file = docs_path / Path(*command_parts).with_suffix(".md") + + if not doc_file.exists(): + return tuple(False for _ in flag_names) + + try: + doc_content = doc_file.read_text() + + # Check each flag + results = [] + for flag_name in flag_names: + results.append(flag_name in doc_content) + + return tuple(results) + + except (OSError, IOError): + return tuple(False for _ in flag_names) + + +def find_commands_supporting_flags(*flag_names: str) -> List[str]: + """Find all pixi commands that support ALL of the specified flags. + + Args: + *flag_names: Variable number of flag names that commands must support + + Returns: + List[str]: List of command names that support all specified flags + + Examples: + find_commands_supporting_flags("--frozen", "--no-install") + # Returns: ["pixi add", "pixi remove", "pixi run", ...] + + find_commands_supporting_flags("--locked", "--no-install") + # Returns: ["pixi shell"] (special case that uses --locked instead of --frozen) + """ + all_commands = discover_pixi_commands() + supported_commands = [] + + for command_str in all_commands: + # Skip the "pixi" prefix to get command parts + command_parts = ( + command_str.split()[1:] if command_str.startswith("pixi ") else command_str.split() + ) + + # Skip empty commands + if not command_parts: + continue + + # Check if the command supports all specified flags + flag_support = check_command_supports_flags(command_parts, *flag_names) + + # Only include if ALL flags are supported + if all(flag_support): + supported_commands.append(command_str) + + return sorted(supported_commands) + + +def find_commands_supporting_frozen_and_no_install() -> Set[str]: + """Convenience function to find commands supporting both --frozen and --no-install flags. + + This also includes commands that use --locked instead of --frozen (like pixi shell). + + Returns: + List[str]: List of command names supporting freeze/lock and no-install functionality + """ + # Find commands that support --frozen and --no-install + return set(find_commands_supporting_flags("--frozen", "--no-install")) diff --git a/tests/integration_python/test_main_cli.py b/tests/integration_python/test_main_cli.py index a86ea258d9..35112f78ed 100644 --- a/tests/integration_python/test_main_cli.py +++ b/tests/integration_python/test_main_cli.py @@ -18,6 +18,7 @@ cwd, verify_cli_command, CONDA_FORGE_CHANNEL, + find_commands_supporting_frozen_and_no_install, ) @@ -1588,10 +1589,14 @@ def test_fish_completions(pixi: Path, tmp_pixi_workspace: Path) -> None: @pytest.mark.slow def test_frozen_no_install_invariant(pixi: Path, tmp_pixi_workspace: Path) -> None: """Test that --frozen --no-install maintains lockfile invariant and keeps conda-meta empty. + This test is made up out of two parts: - This test verifies that when using --frozen --no-install flags together, the pixi.lock + 1. This test verifies that when using --frozen --no-install flags together, the pixi.lock file does not change and the conda-meta directory stays empty across all commands that support these flags. + 2. It discovers all documented commands that support both --frozen and --no-install flags + and checks that they are included in the test. If any command is missing, it fails + with a message to add it to the test list. """ manifest_path = tmp_pixi_workspace / "pixi.toml" lock_file_path = tmp_pixi_workspace / "pixi.lock" @@ -1609,14 +1614,24 @@ def test_frozen_no_install_invariant(pixi: Path, tmp_pixi_workspace: Path) -> No verify_cli_command([pixi, "init", tmp_pixi_workspace], ExitCode.SUCCESS) # Add bzip2 package to keep installation time low verify_cli_command([pixi, "add", "--manifest-path", manifest_path, "bzip2"]) + + # Create a simple environment.yml file for import testing + simple_env_yml = tmp_pixi_workspace / "simple_env.yml" + simple_env_yml.write_text("""name: simple-env +channels: + - conda-forge +dependencies: + - sdl2 +""") + # Store the original lockfile content original_lock_content = lock_file_path.read_text() - # Remove conda-meta folder to simulate missing environment + # Remove conda-meta folder to simulate something that would normally trigger an install if conda_meta_path.exists(): shutil.rmtree(conda_meta_path) - # Helper function to check invariants + # Helper function to check if the invariants hold after a command execution def check_invariants(command_name: str) -> None: # Check that lockfile hasn't changed current_lock_content = lock_file_path.read_text() @@ -1633,11 +1648,14 @@ def check_invariants(command_name: str) -> None: # Define commands as (command_parts, additional_args, command_name_for_invariants) commands_to_test: list[tuple[list[str], list[str], str]] = [ # Let's start with adding a workspace channel because that would trigger a re-solve in most cases + # Don't move this! ( ["workspace", "channel", "add"], ["https://prefix.dev/bioconda"], "pixi workspace channel add", ), + ] + commands_to_test += [ (["list"], [], "pixi list"), (["tree"], [], "pixi tree"), (["shell-hook"], [], "pixi shell-hook"), @@ -1645,15 +1663,29 @@ def check_invariants(command_name: str) -> None: (["shell"], [], "pixi shell"), # Test manifest modifications with --frozen --no-install (these should work) # Note: These modify manifest but not lockfile due to --frozen - (["add"], ["zlib"], "pixi add"), - (["remove"], ["zlib"], "pixi remove"), + (["add"], ["python"], "pixi add"), + (["remove"], ["python"], "pixi remove"), (["run"], ["echo", "test"], "pixi run"), + # Export commands - use temporary directory + ( + ["workspace", "export", "conda-explicit-spec"], + [str(tmp_pixi_workspace / "export_test")], + "pixi workspace export conda-explicit-spec", + ), + # Import commands - create a simple environment.yml file to import + (["import"], [str(tmp_pixi_workspace / "simple_env.yml")], "pixi import"), + # Upgrade commands + (["upgrade"], [], "pixi upgrade"), + ] + # This command needs to stay last so we always have something that requires a re-solve + # Dont move this! + commands_to_test.append( ( ["workspace", "channel", "remove"], ["https://prefix.dev/bioconda"], "pixi workspace channel remove", - ), - ] + ) + ) # Execute all commands and check invariants for command_parts, additional_args, command_name in commands_to_test: @@ -1666,3 +1698,22 @@ def check_invariants(command_name: str) -> None: else: verify_cli_command([pixi, *command_parts, *frozen_no_install_flags, *additional_args]) check_invariants(command_name) + + # Discover all commands that support --frozen and --no-install flags + supported_commands = find_commands_supporting_frozen_and_no_install() + + # Extract commands being tested from the test list + tested_commands = {command_name for _, _, command_name in commands_to_test} + + # Find commands that support the flags but aren't being tested + missing_commands = set(supported_commands) - tested_commands + + if missing_commands: + missing_list = "\n - ".join(sorted(missing_commands)) + pytest.fail( + f"Found {len(missing_commands)} command(s) that support --frozen --no-install " + f"but are not included in the test:\n - {missing_list}\n\n" + f"Please add these commands to the commands_to_test list in test_frozen_no_install_invariant " + f"to ensure comprehensive coverage.\n" + f"If you get here you know all commands that *are* supported correctly listen to --frozen and --no-install flags." + ) From a34581ea1625fb1317e3ac4156081df7d1f6da09 Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Thu, 14 Aug 2025 16:05:28 +0200 Subject: [PATCH 16/29] feat: common --- tests/integration_python/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration_python/common.py b/tests/integration_python/common.py index 6996a9e513..b64c5918b0 100644 --- a/tests/integration_python/common.py +++ b/tests/integration_python/common.py @@ -5,7 +5,7 @@ from enum import IntEnum from pathlib import Path import sys -from typing import Generator, List, Optional, Sequence, Set, Tuple +from typing import Generator, Optional, Sequence, Set, Tuple from rattler import Platform @@ -271,7 +271,7 @@ def check_command_supports_flags(command_parts: list[str], *flag_names: str) -> return tuple(False for _ in flag_names) -def find_commands_supporting_flags(*flag_names: str) -> List[str]: +def find_commands_supporting_flags(*flag_names: str) -> list[str]: """Find all pixi commands that support ALL of the specified flags. Args: From a1ba9ed4bdb7cbfc9fd13d37df8afd224e159685 Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Thu, 14 Aug 2025 16:17:39 +0200 Subject: [PATCH 17/29] feat: remove unused flags from import --- docs/reference/cli/pixi/import.md | 12 ------------ src/cli/import.rs | 7 +------ src/cli/upgrade.rs | 9 ++++++++- tests/integration_python/test_main_cli.py | 2 -- 4 files changed, 9 insertions(+), 21 deletions(-) diff --git a/docs/reference/cli/pixi/import.md b/docs/reference/cli/pixi/import.md index f5064c4ba9..90a1701017 100644 --- a/docs/reference/cli/pixi/import.md +++ b/docs/reference/cli/pixi/import.md @@ -48,18 +48,6 @@ pixi import [OPTIONS] - `--use-environment-activation-cache` : Use environment activation cache (experimental) -## Update Options -- `--no-install` -: Don't modify the environment, only modify the lock-file -- `--no-lockfile-update` -: Legacy flag to skip lock-file updates, behavior was inconsistent across commands so it has been deprecated -- `--frozen` -: Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file -
**env**: `PIXI_FROZEN` -- `--locked` -: Check if lockfile is up-to-date before installing the environment, aborts when lockfile isn't up-to-date with the manifest file -
**env**: `PIXI_LOCKED` - ## Global Options - `--manifest-path ` : The path to `pixi.toml`, `pyproject.toml`, or the workspace directory diff --git a/src/cli/import.rs b/src/cli/import.rs index 8534aa8cf8..509845ba5a 100644 --- a/src/cli/import.rs +++ b/src/cli/import.rs @@ -16,7 +16,7 @@ use uv_requirements_txt::RequirementsTxt; use miette::{Diagnostic, IntoDiagnostic, Result}; use thiserror::Error; -use crate::cli::cli_config::{LockFileUpdateConfig, NoInstallConfig, WorkspaceConfig}; +use crate::cli::cli_config::WorkspaceConfig; #[derive(Parser, Debug, Clone, PartialEq, ValueEnum)] pub enum ImportFileFormat { @@ -54,11 +54,6 @@ pub struct Args { #[clap(long, short)] pub feature: Option, - #[clap(flatten)] - pub no_install_config: NoInstallConfig, - #[clap(flatten)] - pub lock_file_update_config: LockFileUpdateConfig, - #[clap(flatten)] pub config: ConfigCli, } diff --git a/src/cli/upgrade.rs b/src/cli/upgrade.rs index 3012fa6707..a110b43cc6 100644 --- a/src/cli/upgrade.rs +++ b/src/cli/upgrade.rs @@ -83,8 +83,15 @@ pub async fn execute(args: Args) -> miette::Result<()> { ) }; - let (match_specs, pypi_deps) = parse_specs(feature, &args, &workspace)?; + if args.lock_file_update_config.lock_file_usage.frozen + || args.lock_file_update_config.lock_file_usage.locked + { + tracing::info!( + "using `--frozen` or `--locked` will not make any changes and does not display results. You probably meant: `--dry-run`" + ) + } + let (match_specs, pypi_deps) = parse_specs(feature, &args, &workspace)?; let (update_deps, workspace) = match workspace .update_dependencies( match_specs, diff --git a/tests/integration_python/test_main_cli.py b/tests/integration_python/test_main_cli.py index 35112f78ed..8fd18f0844 100644 --- a/tests/integration_python/test_main_cli.py +++ b/tests/integration_python/test_main_cli.py @@ -1672,8 +1672,6 @@ def check_invariants(command_name: str) -> None: [str(tmp_pixi_workspace / "export_test")], "pixi workspace export conda-explicit-spec", ), - # Import commands - create a simple environment.yml file to import - (["import"], [str(tmp_pixi_workspace / "simple_env.yml")], "pixi import"), # Upgrade commands (["upgrade"], [], "pixi upgrade"), ] From d3d8d4d3976063b55d4d4bc4704f87994c561543 Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Thu, 14 Aug 2025 16:33:06 +0200 Subject: [PATCH 18/29] feat: warning to upgrade --- src/cli/upgrade.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/cli/upgrade.rs b/src/cli/upgrade.rs index a110b43cc6..8c5fd1b45f 100644 --- a/src/cli/upgrade.rs +++ b/src/cli/upgrade.rs @@ -83,10 +83,11 @@ pub async fn execute(args: Args) -> miette::Result<()> { ) }; - if args.lock_file_update_config.lock_file_usage.frozen - || args.lock_file_update_config.lock_file_usage.locked + if !args.no_install_config.allow_installs() + && (args.lock_file_update_config.lock_file_usage.frozen + || args.lock_file_update_config.lock_file_usage.locked) { - tracing::info!( + tracing::warn!( "using `--frozen` or `--locked` will not make any changes and does not display results. You probably meant: `--dry-run`" ) } From d4ae702e982e6658d4d4cd68626d0b6916d0b0e0 Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Fri, 15 Aug 2025 11:33:34 +0200 Subject: [PATCH 19/29] Update src/cli/cli_config.rs Co-authored-by: Ruben Arts --- src/cli/cli_config.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cli/cli_config.rs b/src/cli/cli_config.rs index 19c1825db2..991368776f 100644 --- a/src/cli/cli_config.rs +++ b/src/cli/cli_config.rs @@ -101,8 +101,7 @@ impl ChannelsConfig { #[derive(Parser, Debug, Default, Clone)] pub struct LockFileUpdateConfig { - /// Legacy flag to skip lock-file updates, behavior was inconsistent across commands - /// so it has been deprecated. + /// [DEPRECATED]: use `--frozen` `--no-install`. Skips lock-file updates #[clap(long, help_heading = consts::CLAP_UPDATE_OPTIONS)] pub no_lockfile_update: bool, From e9c76ff06cd0befbd69e64ea6335ad3997d3f678 Mon Sep 17 00:00:00 2001 From: Ruben Arts Date: Fri, 15 Aug 2025 11:40:11 +0200 Subject: [PATCH 20/29] fix: hide deprecated cli command --- docs/reference/cli/pixi/add.md | 2 -- docs/reference/cli/pixi/list.md | 2 -- docs/reference/cli/pixi/remove.md | 2 -- docs/reference/cli/pixi/run.md | 2 -- docs/reference/cli/pixi/shell-hook.md | 2 -- docs/reference/cli/pixi/shell.md | 2 -- docs/reference/cli/pixi/tree.md | 2 -- docs/reference/cli/pixi/upgrade.md | 2 -- docs/reference/cli/pixi/workspace/channel/add.md | 2 -- docs/reference/cli/pixi/workspace/channel/remove.md | 2 -- docs/reference/cli/pixi/workspace/export/conda-explicit-spec.md | 2 -- src/cli/cli_config.rs | 2 +- 12 files changed, 1 insertion(+), 23 deletions(-) diff --git a/docs/reference/cli/pixi/add.md b/docs/reference/cli/pixi/add.md index 60f9b28fbb..a83f3d9009 100644 --- a/docs/reference/cli/pixi/add.md +++ b/docs/reference/cli/pixi/add.md @@ -64,8 +64,6 @@ pixi add [OPTIONS] ... ## Update Options - `--no-install` : Don't modify the environment, only modify the lock-file -- `--no-lockfile-update` -: Legacy flag to skip lock-file updates, behavior was inconsistent across commands so it has been deprecated - `--frozen` : Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file
**env**: `PIXI_FROZEN` diff --git a/docs/reference/cli/pixi/list.md b/docs/reference/cli/pixi/list.md index 8f43e986d5..9aeecae7cb 100644 --- a/docs/reference/cli/pixi/list.md +++ b/docs/reference/cli/pixi/list.md @@ -32,8 +32,6 @@ pixi list [OPTIONS] [REGEX] : Only list packages that are explicitly defined in the workspace ## Update Options -- `--no-lockfile-update` -: Legacy flag to skip lock-file updates, behavior was inconsistent across commands so it has been deprecated - `--frozen` : Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file
**env**: `PIXI_FROZEN` diff --git a/docs/reference/cli/pixi/remove.md b/docs/reference/cli/pixi/remove.md index 1e7ae39263..70efe086b9 100644 --- a/docs/reference/cli/pixi/remove.md +++ b/docs/reference/cli/pixi/remove.md @@ -62,8 +62,6 @@ pixi remove [OPTIONS] ... ## Update Options - `--no-install` : Don't modify the environment, only modify the lock-file -- `--no-lockfile-update` -: Legacy flag to skip lock-file updates, behavior was inconsistent across commands so it has been deprecated - `--frozen` : Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file
**env**: `PIXI_FROZEN` diff --git a/docs/reference/cli/pixi/run.md b/docs/reference/cli/pixi/run.md index f8742d2aac..e065a77925 100644 --- a/docs/reference/cli/pixi/run.md +++ b/docs/reference/cli/pixi/run.md @@ -55,8 +55,6 @@ pixi run [OPTIONS] [TASK]... ## Update Options - `--no-install` : Don't modify the environment, only modify the lock-file -- `--no-lockfile-update` -: Legacy flag to skip lock-file updates, behavior was inconsistent across commands so it has been deprecated - `--frozen` : Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file
**env**: `PIXI_FROZEN` diff --git a/docs/reference/cli/pixi/shell-hook.md b/docs/reference/cli/pixi/shell-hook.md index 513d7c3fd4..355543b3fb 100644 --- a/docs/reference/cli/pixi/shell-hook.md +++ b/docs/reference/cli/pixi/shell-hook.md @@ -50,8 +50,6 @@ pixi shell-hook [OPTIONS] ## Update Options - `--no-install` : Don't modify the environment, only modify the lock-file -- `--no-lockfile-update` -: Legacy flag to skip lock-file updates, behavior was inconsistent across commands so it has been deprecated - `--frozen` : Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file
**env**: `PIXI_FROZEN` diff --git a/docs/reference/cli/pixi/shell.md b/docs/reference/cli/pixi/shell.md index 28124203e8..2db4f4e375 100644 --- a/docs/reference/cli/pixi/shell.md +++ b/docs/reference/cli/pixi/shell.md @@ -45,8 +45,6 @@ pixi shell [OPTIONS] ## Update Options - `--no-install` : Don't modify the environment, only modify the lock-file -- `--no-lockfile-update` -: Legacy flag to skip lock-file updates, behavior was inconsistent across commands so it has been deprecated - `--frozen` : Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file
**env**: `PIXI_FROZEN` diff --git a/docs/reference/cli/pixi/tree.md b/docs/reference/cli/pixi/tree.md index 85c75ce981..0cdad2a4e3 100644 --- a/docs/reference/cli/pixi/tree.md +++ b/docs/reference/cli/pixi/tree.md @@ -24,8 +24,6 @@ pixi tree [OPTIONS] [REGEX] : Invert tree and show what depends on given package in the regex argument ## Update Options -- `--no-lockfile-update` -: Legacy flag to skip lock-file updates, behavior was inconsistent across commands so it has been deprecated - `--frozen` : Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file
**env**: `PIXI_FROZEN` diff --git a/docs/reference/cli/pixi/upgrade.md b/docs/reference/cli/pixi/upgrade.md index 3da08a11d7..a0674414c3 100644 --- a/docs/reference/cli/pixi/upgrade.md +++ b/docs/reference/cli/pixi/upgrade.md @@ -51,8 +51,6 @@ pixi upgrade [OPTIONS] [PACKAGES]... ## Update Options - `--no-install` : Don't modify the environment, only modify the lock-file -- `--no-lockfile-update` -: Legacy flag to skip lock-file updates, behavior was inconsistent across commands so it has been deprecated - `--frozen` : Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file
**env**: `PIXI_FROZEN` diff --git a/docs/reference/cli/pixi/workspace/channel/add.md b/docs/reference/cli/pixi/workspace/channel/add.md index 4c32a53bbc..52735a0a36 100644 --- a/docs/reference/cli/pixi/workspace/channel/add.md +++ b/docs/reference/cli/pixi/workspace/channel/add.md @@ -48,8 +48,6 @@ pixi workspace channel add [OPTIONS] ... ## Update Options - `--no-install` : Don't modify the environment, only modify the lock-file -- `--no-lockfile-update` -: Legacy flag to skip lock-file updates, behavior was inconsistent across commands so it has been deprecated - `--frozen` : Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file
**env**: `PIXI_FROZEN` diff --git a/docs/reference/cli/pixi/workspace/channel/remove.md b/docs/reference/cli/pixi/workspace/channel/remove.md index 3b74bbb80d..e7c98f45df 100644 --- a/docs/reference/cli/pixi/workspace/channel/remove.md +++ b/docs/reference/cli/pixi/workspace/channel/remove.md @@ -48,8 +48,6 @@ pixi workspace channel remove [OPTIONS] ... ## Update Options - `--no-install` : Don't modify the environment, only modify the lock-file -- `--no-lockfile-update` -: Legacy flag to skip lock-file updates, behavior was inconsistent across commands so it has been deprecated - `--frozen` : Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file
**env**: `PIXI_FROZEN` diff --git a/docs/reference/cli/pixi/workspace/export/conda-explicit-spec.md b/docs/reference/cli/pixi/workspace/export/conda-explicit-spec.md index bcf279529e..b8feba6781 100644 --- a/docs/reference/cli/pixi/workspace/export/conda-explicit-spec.md +++ b/docs/reference/cli/pixi/workspace/export/conda-explicit-spec.md @@ -51,8 +51,6 @@ pixi workspace export conda-explicit-spec [OPTIONS] : Use environment activation cache (experimental) ## Update Options -- `--no-lockfile-update` -: Legacy flag to skip lock-file updates, behavior was inconsistent across commands so it has been deprecated - `--frozen` : Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file
**env**: `PIXI_FROZEN` diff --git a/src/cli/cli_config.rs b/src/cli/cli_config.rs index 991368776f..defc93e5fd 100644 --- a/src/cli/cli_config.rs +++ b/src/cli/cli_config.rs @@ -102,7 +102,7 @@ impl ChannelsConfig { #[derive(Parser, Debug, Default, Clone)] pub struct LockFileUpdateConfig { /// [DEPRECATED]: use `--frozen` `--no-install`. Skips lock-file updates - #[clap(long, help_heading = consts::CLAP_UPDATE_OPTIONS)] + #[clap(hide = true, long, help_heading = consts::CLAP_UPDATE_OPTIONS)] pub no_lockfile_update: bool, /// Lock file usage from the CLI From 421d6dd98f2dc3989a8ac2ad0edcb366110e251c Mon Sep 17 00:00:00 2001 From: Ruben Arts Date: Thu, 14 Aug 2025 13:54:20 +0200 Subject: [PATCH 21/29] chore: version to 0.52.0 (#4347) Co-authored-by: Hofer-Julian <30049909+Hofer-Julian@users.noreply.github.com> --- CHANGELOG.md | 39 +++++++++++++++++++++++++++ CITATION.cff | 4 +-- Cargo.lock | 2 +- Cargo.toml | 2 +- crates/pixi_consts/src/consts.rs | 2 +- docs/integration/ci/github_actions.md | 2 +- docs/integration/editor/vscode.md | 2 +- install/install.ps1 | 2 +- install/install.sh | 2 +- schema/schema.json | 4 +-- tbump.toml | 2 +- tests/integration_python/common.py | 2 +- 12 files changed, 52 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebad084524..df19923e0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,45 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +### [0.52.0] - 2025-08-14 +#### ✨ Highlights + +You can now use `pixi global` to install source dependencies. +``` +pixi global install --path path/to/my-package my-package +``` +At the moment, you still have to specify the package name, which we will improve on later! + +#### ⚠️ Breaking Change + +In `v0.51.0` we changed the environment variable overwriting logic. +This has be reverted in this release, as there are some issues with it. + +#### Features + +- Include named source dependencies through `pixi global` by @tdejager in [#4165](https://github.com/prefix-dev/pixi/pull/4165) + +#### Documentation + +- Fix example package name by @henningkayser in [#4331](https://github.com/prefix-dev/pixi/pull/4331) +- Add keyring auth support doc and bump setup-pixi action version by @olivier-lacroix in [#4332](https://github.com/prefix-dev/pixi/pull/4332) +- Pycharm integration via conda environments.txt file by @analog-cbarber in [#4290](https://github.com/prefix-dev/pixi/pull/4290) + +#### Fixed + +- Fish completion script by @ruben-arts in [#4315](https://github.com/prefix-dev/pixi/pull/4315) +- Update named arg schema by @bollwyvl in [#4324](https://github.com/prefix-dev/pixi/pull/4324) +- Revert environment logic changes by @Hofer-Julian in [#4346](https://github.com/prefix-dev/pixi/pull/4346) + +#### Refactor + +- Move all non cli code into `pixi_core` crate by @haecker-felix in [#4337](https://github.com/prefix-dev/pixi/pull/4337) + +#### New Contributors +* @analog-cbarber made their first contribution in [#4290](https://github.com/prefix-dev/pixi/pull/4290) +* @haecker-felix made their first contribution in [#4337](https://github.com/prefix-dev/pixi/pull/4337) +* @henningkayser made their first contribution in [#4331](https://github.com/prefix-dev/pixi/pull/4331) + ### [0.51.0] - 2025-08-12 #### ✨ Highlights diff --git a/CITATION.cff b/CITATION.cff index bb0570ef21..1ee95078df 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -30,8 +30,8 @@ authors: - given-names: Julian family-names: Hofer email: julian.hofer@protonmail.com -repository-code: 'https://github.com/prefix-dev/pixi/releases/tag/v0.51.0' -url: 'https://pixi.sh/v0.51.0' +repository-code: 'https://github.com/prefix-dev/pixi/releases/tag/v0.52.0' +url: 'https://pixi.sh/v0.52.0' abstract: >- A cross-platform, language agnostic, package/project management tool for development in virtual environments. diff --git a/Cargo.lock b/Cargo.lock index 63b06596ab..c1766733ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4660,7 +4660,7 @@ dependencies = [ [[package]] name = "pixi" -version = "0.51.0" +version = "0.52.0" dependencies = [ "ahash", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index ef12b799f3..194066b20b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -205,7 +205,7 @@ license.workspace = true name = "pixi" readme.workspace = true repository.workspace = true -version = "0.51.0" +version = "0.52.0" [features] default = ["rustls-tls"] diff --git a/crates/pixi_consts/src/consts.rs b/crates/pixi_consts/src/consts.rs index 4eafa47533..3b00033aff 100644 --- a/crates/pixi_consts/src/consts.rs +++ b/crates/pixi_consts/src/consts.rs @@ -16,7 +16,7 @@ pub const PYPROJECT_MANIFEST: &str = "pyproject.toml"; pub const CONFIG_FILE: &str = "config.toml"; pub const PIXI_VERSION: &str = match option_env!("PIXI_VERSION") { Some(v) => v, - None => "0.51.0", + None => "0.52.0", }; pub const PREFIX_FILE_NAME: &str = "pixi_env_prefix"; pub const ENVIRONMENTS_DIR: &str = "envs"; diff --git a/docs/integration/ci/github_actions.md b/docs/integration/ci/github_actions.md index 055a3b1c74..c36b6d18b0 100644 --- a/docs/integration/ci/github_actions.md +++ b/docs/integration/ci/github_actions.md @@ -10,7 +10,7 @@ We created [prefix-dev/setup-pixi](https://github.com/prefix-dev/setup-pixi) to ```yaml - uses: prefix-dev/setup-pixi@v0.9.0 with: - pixi-version: v0.51.0 + pixi-version: v0.52.0 cache: true auth-host: prefix.dev auth-token: ${{ secrets.PREFIX_DEV_TOKEN }} diff --git a/docs/integration/editor/vscode.md b/docs/integration/editor/vscode.md index 56b89835b8..e3c13d9b30 100644 --- a/docs/integration/editor/vscode.md +++ b/docs/integration/editor/vscode.md @@ -28,7 +28,7 @@ Then, create the following two files in the `.devcontainer` directory: ```dockerfile title=".devcontainer/Dockerfile" FROM mcr.microsoft.com/devcontainers/base:jammy -ARG PIXI_VERSION=v0.51.0 +ARG PIXI_VERSION=v0.52.0 RUN curl -L -o /usr/local/bin/pixi -fsSL --compressed "https://github.com/prefix-dev/pixi/releases/download/${PIXI_VERSION}/pixi-$(uname -m)-unknown-linux-musl" \ && chmod +x /usr/local/bin/pixi \ diff --git a/install/install.ps1 b/install/install.ps1 index 85a019af30..1f23094f5f 100644 --- a/install/install.ps1 +++ b/install/install.ps1 @@ -22,7 +22,7 @@ .LINK https://github.com/prefix-dev/pixi .NOTES - Version: v0.51.0 + Version: v0.52.0 #> param ( [string] $PixiVersion = 'latest', diff --git a/install/install.sh b/install/install.sh index af3d3bd8b5..eaf021fe9c 100644 --- a/install/install.sh +++ b/install/install.sh @@ -1,6 +1,6 @@ #!/bin/sh set -eu -# Version: v0.51.0 +# Version: v0.52.0 __wrap__() { VERSION="${PIXI_VERSION:-latest}" diff --git a/schema/schema.json b/schema/schema.json index 5ff9a4cf96..fa3f65ce58 100644 --- a/schema/schema.json +++ b/schema/schema.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://pixi.sh/v0.51.0/schema/manifest/schema.json", + "$id": "https://pixi.sh/v0.52.0/schema/manifest/schema.json", "title": "`pixi.toml` manifest file", "description": "The configuration for a [`pixi`](https://pixi.sh) project.", "type": "object", @@ -10,7 +10,7 @@ "title": "Schema", "description": "The schema identifier for the project's configuration", "type": "string", - "default": "https://pixi.sh/v0.51.0/schema/manifest/schema.json", + "default": "https://pixi.sh/v0.52.0/schema/manifest/schema.json", "format": "uri-reference" }, "activation": { diff --git a/tbump.toml b/tbump.toml index e383aa312c..655113ba75 100644 --- a/tbump.toml +++ b/tbump.toml @@ -1,7 +1,7 @@ github_url = "https://github.com/prefix-dev/pixi" [version] -current = "0.51.0" +current = "0.52.0" # Example of a semver regexp. # Make sure this matches current_version before diff --git a/tests/integration_python/common.py b/tests/integration_python/common.py index b64c5918b0..eec6bbebf8 100644 --- a/tests/integration_python/common.py +++ b/tests/integration_python/common.py @@ -9,7 +9,7 @@ from rattler import Platform -PIXI_VERSION = "0.51.0" +PIXI_VERSION = "0.52.0" ALL_PLATFORMS = '["linux-64", "osx-64", "osx-arm64", "win-64", "linux-ppc64le", "linux-aarch64"]' From ad4e247fb54e4b8f936a985f7601182e7547732a Mon Sep 17 00:00:00 2001 From: Lucas Colley Date: Thu, 14 Aug 2025 14:29:06 +0100 Subject: [PATCH 22/29] ci: turn off trampoline workflow on push (#4348) --- .github/workflows/trampoline.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/trampoline.yaml b/.github/workflows/trampoline.yaml index ebdeeb8211..27d185175c 100644 --- a/.github/workflows/trampoline.yaml +++ b/.github/workflows/trampoline.yaml @@ -1,11 +1,6 @@ name: Update Trampoline Binaries on: - push: - paths: - - "trampoline/**" - - ".github/workflows/trampoline.yaml" - - "src/global/trampoline.rs" workflow_dispatch: pull_request: paths: From acc605082c6de8a74840ab3c905f4556c264d0b5 Mon Sep 17 00:00:00 2001 From: Hofer-Julian <30049909+Hofer-Julian@users.noreply.github.com> Date: Thu, 14 Aug 2025 16:28:52 +0200 Subject: [PATCH 23/29] feat: infer package name for source package with pixi global install (#4340) --- .../src/global/project/global_spec.rs | 87 ++---------- .../pixi_core/src/global/project/manifest.rs | 14 +- crates/pixi_core/src/global/project/mod.rs | 134 ++++++++++++++++-- src/cli/global/add.rs | 13 +- src/cli/global/global_specs.rs | 88 +++++++----- src/cli/global/install.rs | 16 +-- 6 files changed, 209 insertions(+), 143 deletions(-) diff --git a/crates/pixi_core/src/global/project/global_spec.rs b/crates/pixi_core/src/global/project/global_spec.rs index 860fa7cfe6..fe69a5aad1 100644 --- a/crates/pixi_core/src/global/project/global_spec.rs +++ b/crates/pixi_core/src/global/project/global_spec.rs @@ -5,24 +5,21 @@ use rattler_conda_types::{MatchSpec, NamelessMatchSpec, PackageName, ParseStrict /// The encapsulation of a package name and its associated /// Pixi specification. #[derive(Debug, Clone)] -pub enum GlobalSpec { - // TODO: this will be used later - #[allow(dead_code)] - /// A global specification without a package name. - /// can be a path or a URL. - Nameless(PixiSpec), - /// A global specification with a package name. - Named(NamedGlobalSpec), -} - -#[derive(Debug, Clone)] -pub struct NamedGlobalSpec { +pub struct GlobalSpec { pub name: PackageName, pub spec: PixiSpec, } -impl NamedGlobalSpec { - /// Creates a new `NamedGlobalSpec` with a package name and a Pixi specification. +#[derive(Debug, thiserror::Error, miette::Diagnostic)] +pub enum FromMatchSpecError { + #[error("package name is required, not found for {0}")] + NameRequired(Box), + #[error(transparent)] + ParseMatchSpec(#[from] rattler_conda_types::ParseMatchSpecError), +} + +impl GlobalSpec { + /// Creates a new `GlobalSpec` with a package name and a Pixi specification. pub fn new(name: PackageName, spec: PixiSpec) -> Self { Self { name, spec } } @@ -37,13 +34,13 @@ impl NamedGlobalSpec { &self.spec } - /// Convert from a &str and a ChannelConfig into a [`NamedGlobalSpec`]. + /// Convert from a &str and a ChannelConfig into a [`GlobalSpec`]. pub fn try_from_str( spec_str: &str, channel_config: &rattler_conda_types::ChannelConfig, ) -> Result { let match_spec = MatchSpec::from_str(spec_str, ParseStrictness::Lenient)?; - NamedGlobalSpec::try_from_matchspec_with_name(match_spec, channel_config) + GlobalSpec::try_from_matchspec_with_name(match_spec, channel_config) } /// Converts a [`MatchSpec`] into a [`GlobalSpec`]. @@ -56,65 +53,9 @@ impl NamedGlobalSpec { let (name, nameless_spec) = match_spec.into_nameless(); if let Some(name) = name { let pixi_spec = PixiSpec::from_nameless_matchspec(nameless_spec, channel_config); - Ok(NamedGlobalSpec::new(name, pixi_spec)) + Ok(GlobalSpec::new(name, pixi_spec)) } else { Err(FromMatchSpecError::NameRequired(Box::new(nameless_spec))) } } } - -#[derive(Debug, thiserror::Error, miette::Diagnostic)] -pub enum FromMatchSpecError { - #[error("package name is required, not found for {0}")] - NameRequired(Box), - #[error(transparent)] - ParseMatchSpec(#[from] rattler_conda_types::ParseMatchSpecError), -} - -impl GlobalSpec { - /// Creates a new `GlobalSpec` without a package name. - #[allow(dead_code)] - pub fn nameless(spec: PixiSpec) -> Self { - GlobalSpec::Nameless(spec) - } - - /// Creates a new `GlobalSpec` with a package name and a Pixi specification. - pub fn named(name: PackageName, spec: PixiSpec) -> Self { - GlobalSpec::Named(NamedGlobalSpec { name, spec }) - } - - /// Convert from a &str and a ChannelConfig into a [`GlobalSpec`]. - /// If the spec contains a package name, it will create a Named variant, - /// otherwise it will create a Nameless variant. - pub fn try_from_str( - spec_str: &str, - channel_config: &rattler_conda_types::ChannelConfig, - ) -> Result { - match NamedGlobalSpec::try_from_str(spec_str, channel_config) { - Ok(named_spec) => Ok(GlobalSpec::Named(named_spec)), - Err(FromMatchSpecError::NameRequired(nameless)) => Ok(GlobalSpec::Nameless( - PixiSpec::from_nameless_matchspec(*nameless, channel_config), - )), - Err(e) => Err(e), - } - } - - pub fn into_named(self) -> Option { - match self { - GlobalSpec::Named(named_spec) => Some(named_spec), - GlobalSpec::Nameless(_) => None, - } - } - - #[cfg(test)] - pub fn spec(&self) -> &PixiSpec { - match self { - GlobalSpec::Nameless(spec) => spec, - GlobalSpec::Named(named_spec) => &named_spec.spec, - } - } - - pub fn is_nameless(&self) -> bool { - matches!(self, GlobalSpec::Nameless(_)) - } -} diff --git a/crates/pixi_core/src/global/project/manifest.rs b/crates/pixi_core/src/global/project/manifest.rs index 171f7d4f7a..acf204355c 100644 --- a/crates/pixi_core/src/global/project/manifest.rs +++ b/crates/pixi_core/src/global/project/manifest.rs @@ -19,7 +19,7 @@ use toml_span::{DeserError, Value}; use super::{ EnvironmentName, ExposedName, - global_spec::NamedGlobalSpec, + global_spec::GlobalSpec, parsed_manifest::{ManifestParsingError, ManifestVersion, ParsedManifest}, }; use crate::global::project::ParsedEnvironment; @@ -138,7 +138,7 @@ impl Manifest { pub fn add_dependency( &mut self, env_name: &EnvironmentName, - named_spec: &NamedGlobalSpec, + named_spec: &GlobalSpec, ) -> miette::Result<()> { let name = named_spec.name(); let spec = named_spec.spec(); @@ -949,7 +949,7 @@ mod tests { let env_name = EnvironmentName::from_str("test-env").unwrap(); let named_global_spec = - NamedGlobalSpec::try_from_str("pythonic ==3.15.0", &channel_config).unwrap(); + GlobalSpec::try_from_str("pythonic ==3.15.0", &channel_config).unwrap(); // Add environment manifest @@ -999,7 +999,7 @@ mod tests { assert_eq!(actual_value, *named_global_spec.spec()); // Add another dependency - let build_match_spec = NamedGlobalSpec::try_from_str( + let build_match_spec = GlobalSpec::try_from_str( "python [version='==3.11.0', build=he550d4f_1_cpython]", &channel_config, ) @@ -1007,7 +1007,7 @@ mod tests { manifest .add_dependency(&env_name, &build_match_spec) .unwrap(); - let any_spec = NamedGlobalSpec::try_from_str("any-spec", &channel_config).unwrap(); + let any_spec = GlobalSpec::try_from_str("any-spec", &channel_config).unwrap(); manifest.add_dependency(&env_name, &any_spec).unwrap(); assert_snapshot!(manifest.document.to_string()); @@ -1019,7 +1019,7 @@ mod tests { let env_name = EnvironmentName::from_str("test-env").unwrap(); let channel_config = ChannelConfig::default_with_root_dir(std::env::current_dir().unwrap()); - let spec = NamedGlobalSpec::try_from_str("pythonic ==3.15.0", &channel_config).unwrap(); + let spec = GlobalSpec::try_from_str("pythonic ==3.15.0", &channel_config).unwrap(); // Add environment manifest.add_environment(&env_name, None).unwrap(); @@ -1028,7 +1028,7 @@ mod tests { manifest.add_dependency(&env_name, &spec).unwrap(); // Add the same dependency again, with a new match_spec - let new_spec = NamedGlobalSpec::try_from_str("pythonic==3.18.0", &channel_config).unwrap(); + let new_spec = GlobalSpec::try_from_str("pythonic==3.18.0", &channel_config).unwrap(); manifest.add_dependency(&env_name, &new_spec).unwrap(); // Check document diff --git a/crates/pixi_core/src/global/project/mod.rs b/crates/pixi_core/src/global/project/mod.rs index 846b6d590a..b5d007448d 100644 --- a/crates/pixi_core/src/global/project/mod.rs +++ b/crates/pixi_core/src/global/project/mod.rs @@ -16,18 +16,20 @@ use indicatif::ProgressBar; use is_executable::IsExecutable; use itertools::Itertools; pub use manifest::{ExposedType, Manifest, Mapping}; -use miette::{Context, IntoDiagnostic}; +use miette::{Context, Diagnostic, IntoDiagnostic}; use once_cell::sync::OnceCell; pub use parsed_manifest::ParsedManifest; pub use parsed_manifest::{ExposedName, ParsedEnvironment}; use pixi_build_discovery::EnabledProtocols; use pixi_command_dispatcher::{ - BuildEnvironment, CommandDispatcher, InstallPixiEnvironmentSpec, Limits, PixiEnvironmentSpec, + BuildBackendMetadataSpec, BuildEnvironment, CommandDispatcher, InstallPixiEnvironmentSpec, + Limits, PixiEnvironmentSpec, }; use pixi_config::{Config, RunPostLinkScripts, default_channel_config, pixi_home}; use pixi_consts::consts::{self}; use pixi_manifest::PrioritizedChannel; use pixi_progress::global_multi_progress; +use pixi_record::PinnedSourceSpec; use pixi_reporters::TopLevelProgress; use pixi_spec_containers::DependencyMap; use pixi_utils::prefix::{Executable, Prefix}; @@ -71,9 +73,41 @@ mod environment; mod global_spec; mod manifest; mod parsed_manifest; -pub use global_spec::{FromMatchSpecError, GlobalSpec, NamedGlobalSpec}; +pub use global_spec::{FromMatchSpecError, GlobalSpec}; use pixi_build_frontend::BackendOverride; +#[derive(Debug, thiserror::Error, miette::Diagnostic)] +pub enum CommandDispatcherError { + #[error("could not determine cache directory")] + CacheDirectory(#[source] Box), + #[error("failed to initialize repodata gateway")] + RepodataGateway(#[source] Box), + #[error("failed to initialize authenticated client")] + AuthenticatedClient(#[source] Box), + #[error("failed to parse backend override from environment")] + BackendOverride(#[source] Box), +} + +#[derive(Debug, thiserror::Error, miette::Diagnostic)] +pub enum InferPackageNameError { + #[error("only path and git specifications are supported for package name inference")] + UnsupportedSpecType, + #[error( + "git package name inference is not yet supported. Please specify the package name explicitly" + )] + GitNotSupported, + #[error("failed to get command dispatcher")] + CommandDispatcher(#[from] CommandDispatcherError), + #[error("failed to get build backend metadata for package name inference")] + BuildBackendMetadata(#[source] Box), + #[error("no package outputs found in the specified path/repository")] + NoPackageOutputs, + #[error( + "multiple package outputs found: {package_names}. Please specify the package name explicitly" + )] + MultiplePackageOutputs { package_names: String }, +} + pub(crate) const MANIFESTS_DIR: &str = "manifests"; /// The pixi global project, this main struct to interact with the pixi global @@ -1273,7 +1307,7 @@ impl Project { } /// Returns the command dispatcher for this project. - fn command_dispatcher(&self) -> miette::Result<&CommandDispatcher> { + fn command_dispatcher(&self) -> Result<&CommandDispatcher, CommandDispatcherError> { const BUILD_DIR: &str = "bld"; self.command_dispatcher.get_or_try_init(|| { @@ -1282,20 +1316,32 @@ impl Project { let cache_dirs = pixi_command_dispatcher::CacheDirs::new( pixi_config::get_cache_dir() .map(|cache_dir| cache_dir.join(BUILD_DIR)) - .map_err(|e| miette::miette!("Failed to get cache directory: {}", e))?, + .map_err(|e| CommandDispatcherError::CacheDirectory(e.into()))?, ); Ok(pixi_command_dispatcher::CommandDispatcher::builder() - .with_gateway(self.repodata_gateway()?.clone()) + .with_gateway( + self.repodata_gateway() + .map_err(|e| CommandDispatcherError::RepodataGateway(e.into()))? + .clone(), + ) .with_cache_dirs(cache_dirs) .with_root_dir(self.root.clone()) - .with_download_client(self.authenticated_client()?.clone()) + .with_download_client( + self.authenticated_client() + .map_err(|e| CommandDispatcherError::AuthenticatedClient(e.into()))? + .clone(), + ) .with_max_download_concurrency(self.concurrent_downloads_semaphore()) .with_limits(Limits { max_concurrent_solves: self.config().max_concurrent_solves().into(), ..Limits::default() }) - .with_backend_overrides(BackendOverride::from_env()?.unwrap_or_default()) + .with_backend_overrides( + BackendOverride::from_env() + .map_err(|e| CommandDispatcherError::BackendOverride(e.into()))? + .unwrap_or_default(), + ) .execute_link_scripts(match self.config.run_post_link_scripts() { RunPostLinkScripts::Insecure => true, RunPostLinkScripts::False => false, @@ -1304,6 +1350,78 @@ impl Project { .finish()) }) } + + /// Infer the package name from a PixiSpec (path or git) by examining build outputs + pub async fn infer_package_name_from_spec( + &self, + pixi_spec: &pixi_spec::PixiSpec, + ) -> Result { + // Convert PixiSpec to SourceSpec and resolve it to PinnedSourceSpec + let pinned_source_spec = match pixi_spec { + pixi_spec::PixiSpec::Path(path_spec) => { + PinnedSourceSpec::Path(pixi_record::PinnedPathSpec { + path: path_spec.path.clone(), + }) + } + pixi_spec::PixiSpec::Git(git_spec) => { + // Convert GitSpec to SourceSpec + let source_spec = pixi_spec::SourceSpec { + location: pixi_spec::SourceLocationSpec::Git(git_spec.clone()), + }; + + // Use the command dispatcher to resolve and checkout the git repository + let command_dispatcher = self.command_dispatcher()?; + let checkout = command_dispatcher + .pin_and_checkout(source_spec) + .await + .map_err(|e| InferPackageNameError::BuildBackendMetadata(Box::new(e)))?; + + checkout.pinned + } + _ => { + return Err(InferPackageNameError::UnsupportedSpecType); + } + }; + + // Create the metadata spec + let metadata_spec = BuildBackendMetadataSpec { + source: pinned_source_spec, + channel_config: self.global_channel_config().clone(), + channels: self + .config() + .default_channels() + .iter() + .filter_map(|c| c.clone().into_base_url(self.global_channel_config()).ok()) + .collect(), + build_environment: pixi_command_dispatcher::BuildEnvironment::default(), + variants: None, + enabled_protocols: Default::default(), + }; + + // Get the metadata using the command dispatcher + let command_dispatcher = self.command_dispatcher()?; + let metadata = command_dispatcher + .build_backend_metadata(metadata_spec) + .await + .map_err(|e| InferPackageNameError::BuildBackendMetadata(Box::new(e)))?; + + // Get the available outputs and use exactly_one to handle the single output case + let packages = metadata.metadata.outputs(); + + match packages.len() { + 0 => Err(InferPackageNameError::NoPackageOutputs), + 1 => { + let package = &packages[0]; + Ok(package.name.clone()) + } + _ => { + let package_names: Vec<_> = packages.iter().map(|p| p.name.as_source()).collect(); + Err(InferPackageNameError::MultiplePackageOutputs { + package_names: package_names.join(", "), + }) + } + } + } } impl Repodata for Project { diff --git a/src/cli/global/add.rs b/src/cli/global/add.rs index 1590ce4275..4ffca55f52 100644 --- a/src/cli/global/add.rs +++ b/src/cli/global/add.rs @@ -2,9 +2,8 @@ use crate::cli::global::global_specs::GlobalSpecs; use crate::cli::global::revert_environment_after_error; use clap::Parser; -use itertools::Itertools; use pixi_config::{Config, ConfigCli}; -use pixi_core::global::project::NamedGlobalSpec; +use pixi_core::global::project::GlobalSpec; use pixi_core::global::{EnvironmentName, Mapping, Project, StateChange, StateChanges}; /// Adds dependencies to an environment @@ -49,7 +48,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { async fn apply_changes( env_name: &EnvironmentName, - specs: &[NamedGlobalSpec], + specs: &[GlobalSpec], expose: &[Mapping], project: &mut Project, ) -> miette::Result { @@ -107,11 +106,9 @@ pub async fn execute(args: Args) -> miette::Result<()> { .to_global_specs( project_original.global_channel_config(), &project_original.root, - )? - .into_iter() - // TODO: will allow nameless specs later - .filter_map(|s| s.into_named()) - .collect_vec(); + &project_original, + ) + .await?; let mut project_modified = project_original.clone(); diff --git a/src/cli/global/global_specs.rs b/src/cli/global/global_specs.rs index 031d2c4501..66972738d5 100644 --- a/src/cli/global/global_specs.rs +++ b/src/cli/global/global_specs.rs @@ -6,7 +6,7 @@ use pixi_consts::consts; use typed_path::Utf8TypedPathBuf; use url::Url; -use pixi_core::global::project::{FromMatchSpecError, GlobalSpec}; +use pixi_core::global::project::FromMatchSpecError; use pixi_spec::PixiSpec; use rattler_conda_types::{ChannelConfig, MatchSpec, ParseMatchSpecError, ParseStrictness}; @@ -56,21 +56,19 @@ pub enum GlobalSpecsConversionError { RelativePath(String, String), #[error("could not absolutize path: {0}")] AbsolutizePath(String), - #[error("specs cannot be empty if git or path is not specified")] - SpecsMissing, + #[error("failed to infer package name")] + #[diagnostic(transparent)] + PackageNameInference(#[from] pixi_core::global::project::InferPackageNameError), } impl GlobalSpecs { /// Convert GlobalSpecs to a vector of GlobalSpec instances - pub fn to_global_specs( + pub async fn to_global_specs( &self, channel_config: &ChannelConfig, manifest_root: &Path, - ) -> Result, GlobalSpecsConversionError> { - if self.specs.is_empty() && self.git.is_none() && self.path.is_none() { - return Err(GlobalSpecsConversionError::SpecsMissing); - } - + project: &pixi_core::global::Project, + ) -> Result, GlobalSpecsConversionError> { let git_or_path_spec = if let Some(git_url) = &self.git { let git_spec = pixi_spec::GitSpec { git: git_url.clone(), @@ -97,7 +95,12 @@ impl GlobalSpecs { }; if let Some(pixi_spec) = git_or_path_spec { if self.specs.is_empty() { - return Ok(Vec::from([GlobalSpec::Nameless(pixi_spec)])); + // Infer the package name from the path/git spec + let inferred_name = project.infer_package_name_from_spec(&pixi_spec).await?; + return Ok(vec![pixi_core::global::project::GlobalSpec::new( + inferred_name, + pixi_spec, + )]); } self.specs @@ -106,20 +109,18 @@ impl GlobalSpecs { MatchSpec::from_str(spec_str, ParseStrictness::Lenient)? .name .ok_or(GlobalSpecsConversionError::NameRequired) - .map(|name| GlobalSpec::named(name, pixi_spec.clone())) + .map(|name| { + pixi_core::global::project::GlobalSpec::new(name, pixi_spec.clone()) + }) }) .collect() } else { self.specs .iter() .map(|spec_str| { - let global_spec = - GlobalSpec::try_from_str(spec_str, channel_config).map_err(Box::new)?; - if global_spec.is_nameless() { - Err(GlobalSpecsConversionError::NameRequired) - } else { - Ok(global_spec) - } + pixi_core::global::project::GlobalSpec::try_from_str(spec_str, channel_config) + .map_err(Box::new) + .map_err(GlobalSpecsConversionError::FromMatchSpec) }) .collect() } @@ -128,22 +129,13 @@ impl GlobalSpecs { #[cfg(test)] mod tests { - use pixi_core::global::project::GlobalSpec; use std::path::PathBuf; use super::*; use rattler_conda_types::ChannelConfig; - #[cfg(test)] - pub fn spec(global_spec: &GlobalSpec) -> &PixiSpec { - match global_spec { - GlobalSpec::Nameless(spec) => spec, - GlobalSpec::Named(named_spec) => &named_spec.spec, - } - } - - #[test] - fn test_to_global_specs_named() { + #[tokio::test] + async fn test_to_global_specs_named() { let specs = GlobalSpecs { specs: vec!["numpy==1.21.0".to_string(), "scipy>=1.7".to_string()], git: None, @@ -154,15 +146,22 @@ mod tests { let channel_config = ChannelConfig::default_with_root_dir(PathBuf::from(".")); let manifest_root = PathBuf::from("."); + + // Create a dummy project - this test doesn't use path/git specs so project won't be called + let project = pixi_core::global::Project::discover_or_create() + .await + .unwrap(); + let global_specs = specs - .to_global_specs(&channel_config, &manifest_root) + .to_global_specs(&channel_config, &manifest_root, &project) + .await .unwrap(); assert_eq!(global_specs.len(), 2); } - #[test] - fn test_to_global_specs_with_git() { + #[tokio::test] + async fn test_to_global_specs_with_git() { let specs = GlobalSpecs { specs: vec!["mypackage".to_string()], git: Some("https://github.com/user/repo.git".parse().unwrap()), @@ -173,19 +172,26 @@ mod tests { let channel_config = ChannelConfig::default_with_root_dir(PathBuf::from(".")); let manifest_root = PathBuf::from("."); + + // Create a dummy project - this test specifies a package name so inference won't be needed + let project = pixi_core::global::Project::discover_or_create() + .await + .unwrap(); + let global_specs = specs - .to_global_specs(&channel_config, &manifest_root) + .to_global_specs(&channel_config, &manifest_root, &project) + .await .unwrap(); assert_eq!(global_specs.len(), 1); assert!(matches!( - spec(global_specs.first().unwrap()), + &global_specs.first().unwrap().spec, &PixiSpec::Git(..) )) } - #[test] - fn test_to_global_specs_nameless() { + #[tokio::test] + async fn test_to_global_specs_nameless() { let specs = GlobalSpecs { specs: vec![">=1.0".to_string()], git: None, @@ -196,7 +202,15 @@ mod tests { let channel_config = ChannelConfig::default_with_root_dir(PathBuf::from(".")); let manifest_root = PathBuf::from("."); - let global_specs = specs.to_global_specs(&channel_config, &manifest_root); + + // Create a dummy project + let project = pixi_core::global::Project::discover_or_create() + .await + .unwrap(); + + let global_specs = specs + .to_global_specs(&channel_config, &manifest_root, &project) + .await; assert!(global_specs.is_err()); } } diff --git a/src/cli/global/install.rs b/src/cli/global/install.rs index 0ba3a72f3e..bbbc296d5a 100644 --- a/src/cli/global/install.rs +++ b/src/cli/global/install.rs @@ -14,7 +14,7 @@ use pixi_core::global::{ self, EnvChanges, EnvState, EnvironmentName, Mapping, Project, StateChange, StateChanges, common::{NotChangedReason, contains_menuinst_document}, list::list_all_global_environments, - project::{ExposedType, NamedGlobalSpec}, + project::{ExposedType, GlobalSpec}, }; /// Installs the defined packages in a globally accessible location and exposes their command line applications. @@ -93,13 +93,9 @@ pub async fn execute(args: Args) -> miette::Result<()> { let specs = args .packages - .to_global_specs(&channel_config, &project_original.root)? - .into_iter() - // TODO: will allow nameless specs later - .filter_map(|s| s.into_named()) - .collect_vec(); - - let env_to_specs: IndexMap> = match &args.environment { + .to_global_specs(&channel_config, &project_original.root, &project_original) + .await?; + let env_to_specs: IndexMap> = match &args.environment { Some(env_name) => IndexMap::from([(env_name.clone(), specs)]), None => specs .into_iter() @@ -172,7 +168,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { async fn setup_environment( env_name: &EnvironmentName, args: &Args, - specs: &[NamedGlobalSpec], + specs: &[GlobalSpec], project: &mut Project, ) -> miette::Result { let mut state_changes = StateChanges::new_with_env(env_name.clone()); @@ -201,7 +197,7 @@ async fn setup_environment( .with .iter() .map(|spec| { - NamedGlobalSpec::try_from_matchspec_with_name( + GlobalSpec::try_from_matchspec_with_name( spec.clone(), project.config().global_channel_config(), ) From dbbb6dc6e8a66fba8f231856f28a27a2ccce6596 Mon Sep 17 00:00:00 2001 From: Swastik Patel Date: Fri, 15 Aug 2025 13:53:41 +0530 Subject: [PATCH 24/29] feat: Add `pixi --list` to view all the commands (pixi-extensions + built-in commands) (#4307) Co-authored-by: nichmor --- docs/reference/cli/pixi.md | 4 +- src/cli/command_info.rs | 2 +- src/cli/mod.rs | 100 +++++++++++++++++++++++++++++++++++-- 3 files changed, 99 insertions(+), 7 deletions(-) diff --git a/docs/reference/cli/pixi.md b/docs/reference/cli/pixi.md index 28078f0fcc..931a48e653 100644 --- a/docs/reference/cli/pixi.md +++ b/docs/reference/cli/pixi.md @@ -5,7 +5,7 @@ ## Usage ``` -pixi [OPTIONS] +pixi [OPTIONS] [COMMAND] ``` ## Subcommands @@ -56,5 +56,7 @@ pixi [OPTIONS] : Hide all progress bars, always turned on if stderr is not a terminal
**env**: `PIXI_NO_PROGRESS`
**default**: `false` +- `--list` +: List all installed commands (built-in and extensions) --8<-- "docs/reference/cli/pixi_extender:example" diff --git a/src/cli/command_info.rs b/src/cli/command_info.rs index 788770faca..a78c582b0c 100644 --- a/src/cli/command_info.rs +++ b/src/cli/command_info.rs @@ -52,7 +52,7 @@ fn find_similar_commands(input: &str) -> Vec { } /// Find all external commands available in PATH -fn find_external_commands() -> HashMap { +pub(crate) fn find_external_commands() -> HashMap { let mut commands = HashMap::new(); if let Some(dirs) = search_directories() { diff --git a/src/cli/mod.rs b/src/cli/mod.rs index b152366506..623886f8d0 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,5 +1,5 @@ -use clap::Parser; use clap::builder::styling::{AnsiColor, Color, Style}; +use clap::{CommandFactory, Parser}; use indicatif::ProgressDrawTarget; use miette::{Diagnostic, IntoDiagnostic}; use pixi_consts::consts; @@ -69,16 +69,26 @@ For more information, see the documentation at: https://pixi.sh #[clap(arg_required_else_help = true, styles=get_styles(), disable_help_flag = true, allow_external_subcommands = true)] pub struct Args { #[command(subcommand)] - command: Command, + command: Option, #[clap(flatten)] global_options: GlobalOptions, + + /// List all installed commands (built-in and extensions) + #[clap(long = "list", help_heading = consts::CLAP_GLOBAL_OPTIONS)] + list: bool, } #[derive(Debug, Parser)] pub struct GlobalOptions { /// Display help information - #[clap(long, short, global = true, action = clap::ArgAction::Help, help_heading = consts::CLAP_GLOBAL_OPTIONS)] + #[clap( + long, + short, + global = true, + action = clap::ArgAction::Help, + help_heading = consts::CLAP_GLOBAL_OPTIONS + )] help: Option, /// Increase logging verbosity (-v for warnings, -vv for info, -vvv for debug, -vvvv for trace) @@ -295,7 +305,12 @@ impl LockFileUsageConfig { pub async fn execute() -> miette::Result<()> { let args = Args::parse(); + + // Extract values we need before moving args + let no_progress = args.no_progress(); + set_console_colors(&args); + let use_colors = console::colors_enabled_stderr(); let in_ci = matches!(env::var("CI").as_deref(), Ok("1" | "true")); let no_wrap = matches!(env::var("PIXI_NO_WRAP").as_deref(), Ok("1" | "true")); @@ -312,15 +327,26 @@ pub async fn execute() -> miette::Result<()> { }))?; // Hide all progress bars if the user requested it. - if args.no_progress() { + if no_progress { global_multi_progress().set_draw_target(ProgressDrawTarget::hidden()); } + // Handle `--list`: print installed commands and exit 0 + if args.list { + print_installed_commands(); + return Ok(()); + } + // Setup logging for the application. setup_logging(&args, use_colors)?; + let (Some(command), global_options) = (args.command, args.global_options) else { + // match CI expectations + std::process::exit(2); + }; + // Execute the command - execute_command(args.command, &args.global_options).await + execute_command(command, &global_options).await } #[cfg(feature = "console-subscriber")] @@ -488,6 +514,70 @@ pub fn get_styles() -> clap::builder::Styles { .placeholder(Style::new().fg_color(Some(Color::Ansi(AnsiColor::BrightCyan)))) } +/// Print all installed commands (built-in and external extensions) +fn print_installed_commands() { + use std::collections::BTreeMap; + + let use_colors = console::colors_enabled_stderr(); + + // Header + if use_colors { + println!( + "{}", + console::style("Installed Commands:") + .bold() + .underlined() + .bright() + .green() + ); + } else { + println!("Installed Commands:"); + } + + // Built-in commands + let mut builtins: BTreeMap> = BTreeMap::new(); + for sc in Args::command().get_subcommands() { + builtins.insert( + sc.get_name().to_string(), + sc.get_about().map(|s| s.to_string()), + ); + } + + for (name, about) in builtins { + let summary = about + .as_deref() + .and_then(|s| s.lines().next()) + .unwrap_or(""); + if use_colors { + println!( + " {} {}", + console::style(format!("{:<20}", name)).bright().cyan(), + summary + ); + } else { + println!(" {:<20} {}", name, summary); + } + } + + // External commands (pixi-*) + let mut external_names: Vec = + command_info::find_external_commands().into_keys().collect(); + external_names.sort(); + + for name in external_names { + let via = format!("(via pixi-{})", name); + if use_colors { + println!( + " {} {}", + console::style(format!("{:<20}", name)).bright().cyan(), + via + ); + } else { + println!(" {:<20} {}", name, via); + } + } +} + #[cfg(test)] mod tests { use super::*; From a5d63f69960eb6de4d6c67e61b2f1080bfd495f3 Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Fri, 15 Aug 2025 10:32:52 +0200 Subject: [PATCH 25/29] feat: Use log level info for build backends (#4354) --- .../src/command_dispatcher/instantiate_backend.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pixi_command_dispatcher/src/command_dispatcher/instantiate_backend.rs b/crates/pixi_command_dispatcher/src/command_dispatcher/instantiate_backend.rs index 26fd573c73..84b60e7978 100644 --- a/crates/pixi_command_dispatcher/src/command_dispatcher/instantiate_backend.rs +++ b/crates/pixi_command_dispatcher/src/command_dispatcher/instantiate_backend.rs @@ -133,7 +133,7 @@ impl CommandDispatcher { }; // Add debug information about what the backend supports. - tracing::debug!( + tracing::info!( "Instantiated backend {}{}, negotiated API version {}", tool.executable(), tool.version() From 9e63a1c643fce622d6b0b90ed725d3fca6f6a7b6 Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Fri, 15 Aug 2025 11:11:08 +0200 Subject: [PATCH 26/29] refactor: remove manual conflicts check for `--frozen` & `--locked` (#4359) --- src/cli/cli_config.rs | 12 +-- src/cli/install.rs | 2 +- src/cli/mod.rs | 137 +++------------------------ src/cli/reinstall.rs | 2 +- src/cli/remove.rs | 26 ++--- src/cli/run.rs | 2 +- tests/integration_rust/common/mod.rs | 2 +- 7 files changed, 38 insertions(+), 145 deletions(-) diff --git a/src/cli/cli_config.rs b/src/cli/cli_config.rs index defc93e5fd..7096d775e2 100644 --- a/src/cli/cli_config.rs +++ b/src/cli/cli_config.rs @@ -120,12 +120,12 @@ impl LockFileUpdateConfig { )); } - let usage: LockFileUsage = self - .lock_file_usage - .clone() - .try_into() - .map_err(|e: crate::cli::LockFileUsageError| miette::miette!(e))?; - Ok(usage) + let usage: LockFileUsage = self.lock_file_usage.clone().into(); + if self.no_lockfile_update { + Ok(LockFileUsage::Frozen) + } else { + Ok(usage) + } } } diff --git a/src/cli/install.rs b/src/cli/install.rs index 752877c430..acd0ba4031 100644 --- a/src/cli/install.rs +++ b/src/cli/install.rs @@ -89,7 +89,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { &environments, UpdateMode::Revalidate, UpdateLockFileOptions { - lock_file_usage: args.lock_file_usage.try_into()?, + lock_file_usage: args.lock_file_usage.into(), no_install: false, max_concurrent_solves: workspace.config().max_concurrent_solves(), }, diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 623886f8d0..7d451eaa1c 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -198,11 +198,11 @@ pub struct LockFileUsageArgs { struct LockFileUsageArgsRaw { /// Install the environment as defined in the lockfile, doesn't update /// lockfile if it isn't up-to-date with the manifest file. - #[clap(long, env = "PIXI_FROZEN", help_heading = consts::CLAP_UPDATE_OPTIONS)] + #[clap(long, env = "PIXI_FROZEN", help_heading = consts::CLAP_UPDATE_OPTIONS, conflicts_with = "locked")] frozen: bool, /// Check if lockfile is up-to-date before installing the environment, /// aborts when lockfile isn't up-to-date with the manifest file. - #[clap(long, env = "PIXI_LOCKED", help_heading = consts::CLAP_UPDATE_OPTIONS)] + #[clap(long, env = "PIXI_LOCKED", help_heading = consts::CLAP_UPDATE_OPTIONS, conflicts_with = "frozen")] locked: bool, } @@ -216,15 +216,10 @@ impl LockFileUsageArgs { } } -// Automatic validation when converting from raw args -impl TryFrom for LockFileUsageArgs { - type Error = LockFileUsageError; - - fn try_from(raw: LockFileUsageArgsRaw) -> Result { - if raw.frozen && raw.locked { - return Err(LockFileUsageError::FrozenAndLocked); - } - Ok(LockFileUsageArgs { inner: raw }) +// Automatic conversion from raw args (conflicts handled by clap) +impl From for LockFileUsageArgs { + fn from(raw: LockFileUsageArgsRaw) -> Self { + LockFileUsageArgs { inner: raw } } } @@ -232,9 +227,7 @@ impl TryFrom for LockFileUsageArgs { impl clap::FromArgMatches for LockFileUsageArgs { fn from_arg_matches(matches: &clap::ArgMatches) -> Result { let raw = LockFileUsageArgsRaw::from_arg_matches(matches)?; - raw.try_into().map_err(|e: LockFileUsageError| { - clap::Error::raw(clap::error::ErrorKind::ArgumentConflict, e.to_string()) - }) + Ok(raw.into()) } fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> Result<(), clap::Error> { @@ -265,17 +258,14 @@ impl From for pixi_core::environment::LockFileUsage { } } -impl TryFrom for pixi_core::environment::LockFileUsage { - type Error = LockFileUsageError; - - fn try_from(value: LockFileUsageConfig) -> Result { - value.validate()?; +impl From for pixi_core::environment::LockFileUsage { + fn from(value: LockFileUsageConfig) -> Self { if value.frozen { - Ok(Self::Frozen) + Self::Frozen } else if value.locked { - Ok(Self::Locked) + Self::Locked } else { - Ok(Self::Update) + Self::Update } } } @@ -285,24 +275,14 @@ impl TryFrom for pixi_core::environment::LockFileUsage { pub struct LockFileUsageConfig { /// Install the environment as defined in the lockfile, doesn't update /// lockfile if it isn't up-to-date with the manifest file. - #[clap(long, env = "PIXI_FROZEN", help_heading = consts::CLAP_UPDATE_OPTIONS)] + #[clap(long, env = "PIXI_FROZEN", help_heading = consts::CLAP_UPDATE_OPTIONS, conflicts_with = "locked")] pub frozen: bool, /// Check if lockfile is up-to-date before installing the environment, /// aborts when lockfile isn't up-to-date with the manifest file. - #[clap(long, env = "PIXI_LOCKED", help_heading = consts::CLAP_UPDATE_OPTIONS)] + #[clap(long, env = "PIXI_LOCKED", help_heading = consts::CLAP_UPDATE_OPTIONS, conflicts_with = "frozen")] pub locked: bool, } -impl LockFileUsageConfig { - /// Validate that the configuration is valid - pub fn validate(&self) -> Result<(), LockFileUsageError> { - if self.frozen && self.locked { - return Err(LockFileUsageError::FrozenAndLocked); - } - Ok(()) - } -} - pub async fn execute() -> miette::Result<()> { let args = Args::parse(); @@ -583,95 +563,6 @@ mod tests { use super::*; use temp_env; - #[test] - fn test_frozen_and_locked_conflict() { - // Test that --frozen and --locked conflict is caught by validation - let config_result = LockFileUsageConfig::try_parse_from(["test", "--frozen", "--locked"]); - assert!(config_result.is_ok(), "Parsing should succeed"); - let parsed = config_result.unwrap(); - assert!(parsed.frozen, "Expected frozen to be true"); - assert!(parsed.locked, "Expected locked to be true"); - assert!( - parsed.validate().is_err(), - "Expected validation to fail when both --frozen and --locked are provided" - ); - } - - #[test] - fn test_lockfile_usage_args_try_from_validation() { - // Valid case - let valid_raw = LockFileUsageArgsRaw { - frozen: true, - locked: false, - }; - let result = LockFileUsageArgs::try_from(valid_raw); - assert!(result.is_ok()); - - // Valid case - let valid_raw = LockFileUsageArgsRaw { - frozen: false, - locked: true, - }; - let result = LockFileUsageArgs::try_from(valid_raw); - assert!(result.is_ok()); - - // Valid case - let valid_raw = LockFileUsageArgsRaw { - frozen: false, - locked: false, - }; - let result = LockFileUsageArgs::try_from(valid_raw); - assert!(result.is_ok()); - - // Invalid case - let invalid_raw = LockFileUsageArgsRaw { - frozen: true, - locked: true, - }; - let result = LockFileUsageArgs::try_from(invalid_raw); - assert!(result.is_err()); - - let error = result.unwrap_err(); - assert!(error.to_string().contains("cannot be used together with")); - } - - #[test] - fn test_pixi_frozen_true_with_locked_flag_should_fail() { - // PIXI_FROZEN=true with --locked should fail validation - temp_env::with_var("PIXI_FROZEN", Some("true"), || { - let result = LockFileUsageConfig::try_parse_from(["test", "--locked"]); - - assert!( - result.is_ok(), - "Parsing should succeed, but validation should fail" - ); - let parsed = result.unwrap(); - assert!(parsed.frozen, "Expected frozen flag to be true"); - assert!(parsed.locked, "Expected locked flag to be true"); - assert!( - parsed.validate().is_err(), - "Expected validation to fail when both frozen and locked are true" - ); - }); - } - - #[test] - fn test_pixi_frozen_false_with_locked_flag_should_pass() { - // PIXI_FROZEN=false with --locked should pass validation - temp_env::with_var("PIXI_FROZEN", Some("false"), || { - let result = LockFileUsageConfig::try_parse_from(["test", "--locked"]); - - assert!( - result.is_ok(), - "Expected success when PIXI_FROZEN=false and --locked is used" - ); - let parsed = result.unwrap(); - assert!(parsed.locked, "Expected locked flag to be true"); - assert!(!parsed.frozen, "Expected frozen flag to be false"); - assert!(parsed.validate().is_ok(), "Expected validation to pass"); - }); - } - #[test] fn test_clap_boolean_env_var_behavior() { // Test PIXI_FROZEN=true diff --git a/src/cli/reinstall.rs b/src/cli/reinstall.rs index 56f767c127..73be5bea89 100644 --- a/src/cli/reinstall.rs +++ b/src/cli/reinstall.rs @@ -82,7 +82,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { &environment, UpdateMode::Revalidate, UpdateLockFileOptions { - lock_file_usage: args.lock_file_usage.clone().try_into()?, + lock_file_usage: args.lock_file_usage.clone().into(), no_install: false, max_concurrent_solves: workspace.config().max_concurrent_solves(), }, diff --git a/src/cli/remove.rs b/src/cli/remove.rs index 196b825921..f5c9a69c2f 100644 --- a/src/cli/remove.rs +++ b/src/cli/remove.rs @@ -115,18 +115,20 @@ pub async fn execute(args: Args) -> miette::Result<()> { // TODO: update all environments touched by this feature defined. // updating prefix after removing from toml - get_update_lock_file_and_prefix( - &workspace.default_environment(), - UpdateMode::QuickValidate, - UpdateLockFileOptions { - lock_file_usage: lock_file_update_config.lock_file_usage()?, - no_install: no_install_config.no_install, - max_concurrent_solves: workspace.config().max_concurrent_solves(), - }, - ReinstallPackages::default(), - &[], - ) - .await?; + if !lock_file_update_config.no_lockfile_update { + get_update_lock_file_and_prefix( + &workspace.default_environment(), + UpdateMode::Revalidate, + UpdateLockFileOptions { + lock_file_usage: lock_file_update_config.lock_file_usage()?, + no_install: no_install_config.no_install, + max_concurrent_solves: workspace.config().max_concurrent_solves(), + }, + ReinstallPackages::default(), + &[], + ) + .await?; + } dependency_config.display_success("Removed", Default::default()); diff --git a/src/cli/run.rs b/src/cli/run.rs index 502d7e4a78..3bb95b3e6d 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -125,8 +125,8 @@ pub async fn execute(args: Args) -> miette::Result<()> { // Ensure that the lock-file is up-to-date. let lock_file = workspace .update_lock_file(UpdateLockFileOptions { - lock_file_usage: args.lock_file_update_config.lock_file_usage()?, no_install: args.no_install_config.no_install, + lock_file_usage: args.lock_file_update_config.lock_file_usage()?, max_concurrent_solves: workspace.config().max_concurrent_solves(), }) .await? diff --git a/tests/integration_rust/common/mod.rs b/tests/integration_rust/common/mod.rs index e3ed09f5b8..f9157152db 100644 --- a/tests/integration_rust/common/mod.rs +++ b/tests/integration_rust/common/mod.rs @@ -491,7 +491,7 @@ impl PixiControl { // Ensure the lock-file is up-to-date let lock_file = project .update_lock_file(UpdateLockFileOptions { - lock_file_usage: args.lock_file_update_config.lock_file_usage()?, + lock_file_usage: args.lock_file_update_config.lock_file_usage(), ..UpdateLockFileOptions::default() }) .await? From 390f00189b69bfe41f108f2e02a60778421dc606 Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Fri, 15 Aug 2025 11:51:14 +0200 Subject: [PATCH 27/29] fix: test --- tests/integration_rust/common/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration_rust/common/mod.rs b/tests/integration_rust/common/mod.rs index f9157152db..8df58acc41 100644 --- a/tests/integration_rust/common/mod.rs +++ b/tests/integration_rust/common/mod.rs @@ -491,7 +491,7 @@ impl PixiControl { // Ensure the lock-file is up-to-date let lock_file = project .update_lock_file(UpdateLockFileOptions { - lock_file_usage: args.lock_file_update_config.lock_file_usage(), + lock_file_usage: args.lock_file_update_config.lock_file_usage().unwrap(), ..UpdateLockFileOptions::default() }) .await? From 95f2776eb574e5b4ee4393d0e3264049014d2ac3 Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Fri, 15 Aug 2025 12:00:38 +0200 Subject: [PATCH 28/29] feat: use fill revalidation for workspace stuff for now --- src/cli/workspace/channel/add.rs | 2 +- src/cli/workspace/channel/remove.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli/workspace/channel/add.rs b/src/cli/workspace/channel/add.rs index b288d86c88..37d5cddce0 100644 --- a/src/cli/workspace/channel/add.rs +++ b/src/cli/workspace/channel/add.rs @@ -24,7 +24,7 @@ pub async fn execute(args: AddRemoveArgs) -> miette::Result<()> { // TODO: Update all environments touched by the features defined. get_update_lock_file_and_prefix( &workspace.workspace().default_environment(), - UpdateMode::QuickValidate, + UpdateMode::Revalidate, UpdateLockFileOptions { lock_file_usage: args.lock_file_update_config.lock_file_usage()?, no_install: args.no_install_config.no_install, diff --git a/src/cli/workspace/channel/remove.rs b/src/cli/workspace/channel/remove.rs index a0cbc72af9..79331118fe 100644 --- a/src/cli/workspace/channel/remove.rs +++ b/src/cli/workspace/channel/remove.rs @@ -22,7 +22,7 @@ pub async fn execute(args: AddRemoveArgs) -> miette::Result<()> { // Try to update the lock-file without the removed channels get_update_lock_file_and_prefix( &workspace.workspace().default_environment(), - UpdateMode::QuickValidate, + UpdateMode::Revalidate, UpdateLockFileOptions { lock_file_usage: args.lock_file_update_config.lock_file_usage()?, no_install: args.no_install_config.no_install, From 26cae854f71e1bc01660b2790df697e536accf08 Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Fri, 15 Aug 2025 13:15:50 +0200 Subject: [PATCH 29/29] feat: change deprecated docs --- src/cli/cli_config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/cli_config.rs b/src/cli/cli_config.rs index 7096d775e2..fc4a8e4e9e 100644 --- a/src/cli/cli_config.rs +++ b/src/cli/cli_config.rs @@ -101,7 +101,7 @@ impl ChannelsConfig { #[derive(Parser, Debug, Default, Clone)] pub struct LockFileUpdateConfig { - /// [DEPRECATED]: use `--frozen` `--no-install`. Skips lock-file updates + /// DEPRECATED: use `--frozen` `--no-install`. Skips lock-file updates #[clap(hide = true, long, help_heading = consts::CLAP_UPDATE_OPTIONS)] pub no_lockfile_update: bool,