From 5baed6e7cdc04a11379880d1832c0fc2b4b341c7 Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Wed, 12 Mar 2025 17:05:45 +0100 Subject: [PATCH 01/24] feat: add `pixi reinstall` Fixes #1695 --- src/cli/install.rs | 2 +- src/cli/mod.rs | 3 ++ src/cli/reinstall.rs | 112 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 src/cli/reinstall.rs diff --git a/src/cli/install.rs b/src/cli/install.rs index 1e25e32b6f..ca5b28a126 100644 --- a/src/cli/install.rs +++ b/src/cli/install.rs @@ -20,7 +20,7 @@ use pixi_config::ConfigCli; /// Running `pixi install` is not required before running other commands like `pixi run` or `pixi shell`. /// These commands will automatically install the environment if it is not already installed. /// -/// You can use `pixi clean` to remove the installed environments and start fresh. +/// You can use `pixi reinstall` to reinstall all environments, one environment or just some packages of an environment. #[derive(Parser, Debug)] pub struct Args { #[clap(flatten)] diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 576b3e3c5f..37da66fb63 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -25,6 +25,7 @@ pub mod init; pub mod install; pub mod list; pub mod lock; +pub mod reinstall; pub mod remove; pub mod run; pub mod search; @@ -134,6 +135,7 @@ pub enum Command { Remove(remove::Args), #[clap(visible_alias = "i")] Install(install::Args), + Reinstall(install::Args), Update(update::Args), Upgrade(upgrade::Args), Lock(lock::Args), @@ -270,6 +272,7 @@ pub async fn execute_command(command: Command) -> miette::Result<()> { Command::Global(cmd) => global::execute(cmd).await, Command::Auth(cmd) => rattler::cli::auth::execute(cmd).await.into_diagnostic(), Command::Install(cmd) => install::execute(cmd).await, + Command::Reinstall(cmd) => install::execute(cmd).await, Command::Shell(cmd) => shell::execute(cmd).await, Command::ShellHook(cmd) => shell_hook::execute(cmd).await, Command::Task(cmd) => task::execute(cmd).await, diff --git a/src/cli/reinstall.rs b/src/cli/reinstall.rs new file mode 100644 index 0000000000..66910c7868 --- /dev/null +++ b/src/cli/reinstall.rs @@ -0,0 +1,112 @@ +use crate::cli::cli_config::WorkspaceConfig; +use crate::environment::get_update_lock_file_and_prefix; +use crate::lock_file::UpdateMode; +use crate::{UpdateLockFileOptions, WorkspaceLocator}; +use clap::Parser; +use fancy_display::FancyDisplay; +use itertools::Itertools; +use pixi_config::ConfigCli; + +/// Re-install an environment, both updating the lockfile and re-installing the environment. +/// +/// This command reinstalls an environment, if the lockfile is not up-to-date it will be updated. +/// If `--package` is given, only the specified package will be reinstalled. +/// Otherwise the whole environment will be reinstalled. +/// +/// `pixi reinstall` only re-installs one environment at a time, +/// if you have multiple environments you can select the right one with the `--environment` flag. +/// If you don't provide an environment, the `default` environment will be re-installed. +/// +/// If you want to re-install all environments, you can use the `--all` flag. +#[derive(Parser, Debug)] +pub struct Args { + #[clap(flatten)] + pub project_config: WorkspaceConfig, + + #[clap(flatten)] + pub lock_file_usage: super::LockFileUsageConfig, + + /// The environment to install + #[arg(long, short)] + pub environment: Option>, + + #[clap(flatten)] + pub config: ConfigCli, + + /// Install all environments + #[arg(long, short, conflicts_with = "environment")] + pub all: bool, + + /// Specifies the package that should be reinstalled. + #[arg(num_args = 1.., required = true, id = "PACKAGE")] + packages: Vec, +} + +pub async fn execute(args: Args) -> miette::Result<()> { + let workspace = WorkspaceLocator::for_cli() + .with_search_start(args.project_config.workspace_locator_start()) + .locate()? + .with_cli_config(args.config); + + // Install either: + // + // 1. specific environments + // 2. all environments + // 3. default environment (if no environments are specified) + let envs = if let Some(envs) = args.environment { + envs + } else if args.all { + workspace + .environments() + .iter() + .map(|env| env.name().to_string()) + .collect() + } else { + vec![workspace.default_environment().name().to_string()] + }; + + let mut installed_envs = Vec::with_capacity(envs.len()); + for env in envs { + let environment = workspace.environment_from_name_or_env_var(Some(env))?; + + // Update the prefix by installing all packages + get_update_lock_file_and_prefix( + &environment, + UpdateMode::Revalidate, + UpdateLockFileOptions { + lock_file_usage: args.lock_file_usage.into(), + no_install: false, + max_concurrent_solves: workspace.config().max_concurrent_solves(), + }, + ) + .await?; + + installed_envs.push(environment.name().clone()); + } + + // Message what's installed + let detached_envs_message = + if let Ok(Some(path)) = workspace.config().detached_environments().path() { + format!(" in '{}'", console::style(path.display()).bold()) + } else { + "".to_string() + }; + + if installed_envs.len() == 1 { + eprintln!( + "{}The {} environment has been installed{}.", + console::style(console::Emoji("✔ ", "")).green(), + installed_envs[0].fancy_display(), + detached_envs_message + ); + } else { + eprintln!( + "{}The following environments have been installed: {}\t{}", + console::style(console::Emoji("✔ ", "")).green(), + installed_envs.iter().map(|n| n.fancy_display()).join(", "), + detached_envs_message + ); + } + + Ok(()) +} From 06e95670d6ff4a89683503c3f2b7fe7609e6f7e9 Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Thu, 13 Mar 2025 11:16:59 +0100 Subject: [PATCH 02/24] Add first mostly acceptable test --- src/cli/reinstall.rs | 8 +- .../mock-projects/test-rebuild/.gitattributes | 2 + .../mock-projects/test-rebuild/.gitignore | 4 + .../data/mock-projects/test-rebuild/pixi.lock | 291 ++++++++++++++++++ .../data/mock-projects/test-rebuild/pixi.toml | 14 + .../test-rebuild/pypi_package/pyproject.toml | 11 + .../pypi_package/src/pypi_package/__init__.py | 2 + tests/integration_python/conftest.py | 5 + tests/integration_python/test_main_cli.py | 29 ++ 9 files changed, 362 insertions(+), 4 deletions(-) create mode 100644 tests/data/mock-projects/test-rebuild/.gitattributes create mode 100644 tests/data/mock-projects/test-rebuild/.gitignore create mode 100644 tests/data/mock-projects/test-rebuild/pixi.lock create mode 100644 tests/data/mock-projects/test-rebuild/pixi.toml create mode 100644 tests/data/mock-projects/test-rebuild/pypi_package/pyproject.toml create mode 100644 tests/data/mock-projects/test-rebuild/pypi_package/src/pypi_package/__init__.py diff --git a/src/cli/reinstall.rs b/src/cli/reinstall.rs index 66910c7868..4542f7d191 100644 --- a/src/cli/reinstall.rs +++ b/src/cli/reinstall.rs @@ -10,7 +10,7 @@ use pixi_config::ConfigCli; /// Re-install an environment, both updating the lockfile and re-installing the environment. /// /// This command reinstalls an environment, if the lockfile is not up-to-date it will be updated. -/// If `--package` is given, only the specified package will be reinstalled. +/// If packages are specified, only those packages will be reinstalled. /// Otherwise the whole environment will be reinstalled. /// /// `pixi reinstall` only re-installs one environment at a time, @@ -26,19 +26,19 @@ pub struct Args { #[clap(flatten)] pub lock_file_usage: super::LockFileUsageConfig, - /// The environment to install + /// The environment to install. #[arg(long, short)] pub environment: Option>, #[clap(flatten)] pub config: ConfigCli, - /// Install all environments + /// Install all environments. #[arg(long, short, conflicts_with = "environment")] pub all: bool, /// Specifies the package that should be reinstalled. - #[arg(num_args = 1.., required = true, id = "PACKAGE")] + #[arg(id = "PACKAGE")] packages: Vec, } diff --git a/tests/data/mock-projects/test-rebuild/.gitattributes b/tests/data/mock-projects/test-rebuild/.gitattributes new file mode 100644 index 0000000000..887a2c18f0 --- /dev/null +++ b/tests/data/mock-projects/test-rebuild/.gitattributes @@ -0,0 +1,2 @@ +# SCM syntax highlighting & preventing 3-way merges +pixi.lock merge=binary linguist-language=YAML linguist-generated=true diff --git a/tests/data/mock-projects/test-rebuild/.gitignore b/tests/data/mock-projects/test-rebuild/.gitignore new file mode 100644 index 0000000000..740bb7d1ae --- /dev/null +++ b/tests/data/mock-projects/test-rebuild/.gitignore @@ -0,0 +1,4 @@ + +# pixi environments +.pixi +*.egg-info diff --git a/tests/data/mock-projects/test-rebuild/pixi.lock b/tests/data/mock-projects/test-rebuild/pixi.lock new file mode 100644 index 0000000000..f44ceb8eea --- /dev/null +++ b/tests/data/mock-projects/test-rebuild/pixi.lock @@ -0,0 +1,291 @@ +version: 6 +environments: + default: + channels: + - url: https://prefix.dev/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + linux-64: + - conda: https://prefix.dev/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 + - conda: https://prefix.dev/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 + - conda: https://prefix.dev/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda + - conda: https://prefix.dev/conda-forge/linux-64/ca-certificates-2025.1.31-hbcca054_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda + - conda: https://prefix.dev/conda-forge/linux-64/libexpat-2.6.4-h5888daf_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libffi-3.4.6-h2dba641_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libgcc-14.2.0-h767d61c_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/libgomp-14.2.0-h767d61c_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/liblzma-5.6.4-hb9d3cd8_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libmpdec-4.0.0-h4bc722e_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.49.1-hee588c1_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.1-h7b32b05_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/python-3.13.2-hf636f53_101_cp313.conda + - conda: https://prefix.dev/conda-forge/linux-64/python_abi-3.13-5_cp313.conda + - conda: https://prefix.dev/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda + - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda + - pypi: . +packages: +- conda: https://prefix.dev/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 + sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 + md5: d7c89558ba9fa0495403155b64376d81 + license: None + purls: [] + size: 2562 + timestamp: 1578324546067 +- conda: https://prefix.dev/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 + build_number: 16 + sha256: fbe2c5e56a653bebb982eda4876a9178aedfc2b545f25d0ce9c4c0b508253d22 + md5: 73aaf86a425cc6e73fcf236a5a46396d + depends: + - _libgcc_mutex 0.1 conda_forge + - libgomp >=7.5.0 + constrains: + - openmp_impl 9999 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 23621 + timestamp: 1650670423406 +- conda: https://prefix.dev/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda + sha256: 5ced96500d945fb286c9c838e54fa759aa04a7129c59800f0846b4335cee770d + md5: 62ee74e96c5ebb0af99386de58cf9553 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc-ng >=12 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 252783 + timestamp: 1720974456583 +- conda: https://prefix.dev/conda-forge/linux-64/ca-certificates-2025.1.31-hbcca054_0.conda + sha256: bf832198976d559ab44d6cdb315642655547e26d826e34da67cbee6624cda189 + md5: 19f3a56f68d2fd06c516076bff482c52 + license: ISC + purls: [] + size: 158144 + timestamp: 1738298224464 +- conda: https://prefix.dev/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda + sha256: db73f38155d901a610b2320525b9dd3b31e4949215c870685fd92ea61b5ce472 + md5: 01f8d123c96816249efd255a31ad7712 + depends: + - __glibc >=2.17,<3.0.a0 + constrains: + - binutils_impl_linux-64 2.43 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 671240 + timestamp: 1740155456116 +- conda: https://prefix.dev/conda-forge/linux-64/libexpat-2.6.4-h5888daf_0.conda + sha256: 56541b98447b58e52d824bd59d6382d609e11de1f8adf20b23143e353d2b8d26 + md5: db833e03127376d461e1e13e76f09b6c + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + constrains: + - expat 2.6.4.* + license: MIT + license_family: MIT + purls: [] + size: 73304 + timestamp: 1730967041968 +- conda: https://prefix.dev/conda-forge/linux-64/libffi-3.4.6-h2dba641_0.conda + sha256: 67a6c95e33ebc763c1adc3455b9a9ecde901850eb2fceb8e646cc05ef3a663da + md5: e3eb7806380bc8bcecba6d749ad5f026 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + purls: [] + size: 53415 + timestamp: 1739260413716 +- conda: https://prefix.dev/conda-forge/linux-64/libgcc-14.2.0-h767d61c_2.conda + sha256: 3a572d031cb86deb541d15c1875aaa097baefc0c580b54dc61f5edab99215792 + md5: ef504d1acbd74b7cc6849ef8af47dd03 + depends: + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex >=4.5 + constrains: + - libgomp 14.2.0 h767d61c_2 + - libgcc-ng ==14.2.0=*_2 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 847885 + timestamp: 1740240653082 +- conda: https://prefix.dev/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_2.conda + sha256: fb7558c328b38b2f9d2e412c48da7890e7721ba018d733ebdfea57280df01904 + md5: a2222a6ada71fb478682efe483ce0f92 + depends: + - libgcc 14.2.0 h767d61c_2 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 53758 + timestamp: 1740240660904 +- conda: https://prefix.dev/conda-forge/linux-64/libgomp-14.2.0-h767d61c_2.conda + sha256: 1a3130e0b9267e781b89399580f3163632d59fe5b0142900d63052ab1a53490e + md5: 06d02030237f4d5b3d9a7e7d348fe3c6 + depends: + - __glibc >=2.17,<3.0.a0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 459862 + timestamp: 1740240588123 +- conda: https://prefix.dev/conda-forge/linux-64/liblzma-5.6.4-hb9d3cd8_0.conda + sha256: cad52e10319ca4585bc37f0bc7cce99ec7c15dc9168e42ccb96b741b0a27db3f + md5: 42d5b6a0f30d3c10cd88cb8584fda1cb + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: 0BSD + purls: [] + size: 111357 + timestamp: 1738525339684 +- conda: https://prefix.dev/conda-forge/linux-64/libmpdec-4.0.0-h4bc722e_0.conda + sha256: d02d1d3304ecaf5c728e515eb7416517a0b118200cd5eacbe829c432d1664070 + md5: aeb98fdeb2e8f25d43ef71fbacbeec80 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc-ng >=12 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 89991 + timestamp: 1723817448345 +- conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.49.1-hee588c1_1.conda + sha256: 7a09eef804ef7cf4d88215c2297eabb72af8ad0bd5b012060111c289f14bbe7d + md5: 73cea06049cc4174578b432320a003b8 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libzlib >=1.3.1,<2.0a0 + license: Unlicense + purls: [] + size: 915956 + timestamp: 1739953155793 +- conda: https://prefix.dev/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda + sha256: 787eb542f055a2b3de553614b25f09eefb0a0931b0c87dbcce6efdfd92f04f18 + md5: 40b61aab5c7ba9ff276c41cfffe6b80b + depends: + - libgcc-ng >=12 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 33601 + timestamp: 1680112270483 +- conda: https://prefix.dev/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + sha256: d4bfe88d7cb447768e31650f06257995601f89076080e76df55e3112d4e47dc4 + md5: edb0dca6bc32e4f4789199455a1dbeb8 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + constrains: + - zlib 1.3.1 *_2 + license: Zlib + license_family: Other + purls: [] + size: 60963 + timestamp: 1727963148474 +- conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + sha256: 3fde293232fa3fca98635e1167de6b7c7fda83caf24b9d6c91ec9eefb4f4d586 + md5: 47e340acb35de30501a76c7c799c41d7 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: X11 AND BSD-3-Clause + purls: [] + size: 891641 + timestamp: 1738195959188 +- conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.1-h7b32b05_0.conda + sha256: cbf62df3c79a5c2d113247ddea5658e9ff3697b6e741c210656e239ecaf1768f + md5: 41adf927e746dc75ecf0ef841c454e48 + depends: + - __glibc >=2.17,<3.0.a0 + - ca-certificates + - libgcc >=13 + license: Apache-2.0 + license_family: Apache + purls: [] + size: 2939306 + timestamp: 1739301879343 +- pypi: . + name: pypi + version: 0.1.0 + sha256: ae72568434c2de5207d8d1394a4934a67a6fa897172a1036308bf5366d39c144 + requires_python: '>=3.11' +- conda: https://prefix.dev/conda-forge/linux-64/python-3.13.2-hf636f53_101_cp313.conda + build_number: 101 + sha256: cc1984ee54261cee6a2db75c65fc7d2967bc8c6e912d332614df15244d7730ef + md5: a7902a3611fe773da3921cbbf7bc2c5c + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.6.4,<3.0a0 + - libffi >=3.4,<4.0a0 + - libgcc >=13 + - liblzma >=5.6.4,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.48.0,<4.0a0 + - libuuid >=2.38.1,<3.0a0 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.4.1,<4.0a0 + - python_abi 3.13.* *_cp313 + - readline >=8.2,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + license: Python-2.0 + purls: [] + size: 33233150 + timestamp: 1739803603242 + python_site_packages_path: lib/python3.13/site-packages +- conda: https://prefix.dev/conda-forge/linux-64/python_abi-3.13-5_cp313.conda + build_number: 5 + sha256: 438225b241c5f9bddae6f0178a97f5870a89ecf927dfca54753e689907331442 + md5: 381bbd2a92c863f640a55b6ff3c35161 + constrains: + - python 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 6217 + timestamp: 1723823393322 +- conda: https://prefix.dev/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda + sha256: 2d6d0c026902561ed77cd646b5021aef2d4db22e57a5b0178dfc669231e06d2c + md5: 283b96675859b20a825f8fa30f311446 + depends: + - libgcc >=13 + - ncurses >=6.5,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 282480 + timestamp: 1740379431762 +- conda: https://prefix.dev/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda + sha256: e0569c9caa68bf476bead1bed3d79650bb080b532c64a4af7d8ca286c08dea4e + md5: d453b98d9c83e71da0741bb0ff4d76bc + depends: + - libgcc-ng >=12 + - libzlib >=1.2.13,<2.0.0a0 + license: TCL + license_family: BSD + purls: [] + size: 3318875 + timestamp: 1699202167581 +- conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda + sha256: c4b1ae8a2931fe9b274c44af29c5475a85b37693999f8c792dad0f8c6734b1de + md5: dbcace4706afdfb7eb891f7b37d07c04 + license: LicenseRef-Public-Domain + purls: [] + size: 122921 + timestamp: 1737119101255 diff --git a/tests/data/mock-projects/test-rebuild/pixi.toml b/tests/data/mock-projects/test-rebuild/pixi.toml new file mode 100644 index 0000000000..3fec4125f8 --- /dev/null +++ b/tests/data/mock-projects/test-rebuild/pixi.toml @@ -0,0 +1,14 @@ +[workspace] +authors = ["Julian Hofer "] +channels = ["https://prefix.dev/conda-forge"] +name = "test-rebuild" +platforms = ["linux-64"] +version = "0.1.0" + +[tasks] + +[dependencies] +python = ">=3.13.2,<3.14" + +[pypi-dependencies] +pypi_package = { path = "pypi_package" } diff --git a/tests/data/mock-projects/test-rebuild/pypi_package/pyproject.toml b/tests/data/mock-projects/test-rebuild/pypi_package/pyproject.toml new file mode 100644 index 0000000000..ea25b4570c --- /dev/null +++ b/tests/data/mock-projects/test-rebuild/pypi_package/pyproject.toml @@ -0,0 +1,11 @@ +[project] +authors = [{ name = "Julian Hofer", email = "julianhofer@gnome.org" }] +dependencies = [] +name = "pypi_package" +requires-python = ">= 3.11" +scripts = { pypi-package-main = "pypi_package:main" } +version = "0.1.0" + +[build-system] +build-backend = "hatchling.build" +requires = ["hatchling"] diff --git a/tests/data/mock-projects/test-rebuild/pypi_package/src/pypi_package/__init__.py b/tests/data/mock-projects/test-rebuild/pypi_package/src/pypi_package/__init__.py new file mode 100644 index 0000000000..401b5ccaa8 --- /dev/null +++ b/tests/data/mock-projects/test-rebuild/pypi_package/src/pypi_package/__init__.py @@ -0,0 +1,2 @@ +def main() -> None: + print("Number 1") diff --git a/tests/integration_python/conftest.py b/tests/integration_python/conftest.py index 7420862977..5db4d48ee4 100644 --- a/tests/integration_python/conftest.py +++ b/tests/integration_python/conftest.py @@ -49,6 +49,11 @@ def test_data() -> Path: return Path(__file__).parents[1].joinpath("data").resolve() +@pytest.fixture +def mock_projects(test_data: Path) -> Path: + return test_data.joinpath("mock-projects") + + @pytest.fixture def channels(test_data: Path) -> Path: return test_data.joinpath("channels", "channels") diff --git a/tests/integration_python/test_main_cli.py b/tests/integration_python/test_main_cli.py index 8c9b99a6be..c2e82574b5 100644 --- a/tests/integration_python/test_main_cli.py +++ b/tests/integration_python/test_main_cli.py @@ -1212,3 +1212,32 @@ def test_pixi_info_tasks(pixi: Path, tmp_pixi_workspace: Path) -> None: """ manifest.write_text(toml) verify_cli_command([pixi, "info", "--manifest-path", manifest], stdout_contains="foo, bar") + + +@pytest.mark.slow +def test_pixi_reinstall(pixi: Path, tmp_pixi_workspace: Path, mock_projects: Path) -> None: + manifest = tmp_pixi_workspace.joinpath("pixi.toml") + test_rebuild_src = mock_projects / "test-rebuild" + shutil.copytree(test_rebuild_src, tmp_pixi_workspace, dirs_exist_ok=True) + + # Check that package returns "Number 1" + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], stdout_contains="Number 1" + ) + + # Modify the Python file + pypi_package_init = tmp_pixi_workspace.joinpath( + "pypi_package", "src", "pypi_package", "__init__.py" + ) + pypi_package_init.write_text(pypi_package_init.read_text().replace("1", "2")) + + # That shouldn't trigger a re-install, so running still returns "Number 1" + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], stdout_contains="Number 1" + ) + + # After re-installing, it should return "Number 2" + verify_cli_command([pixi, "reinstall", "--manifest-path", manifest]) + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], stdout_contains="Number 2" + ) From 673148e8c2ca901227196791da76ce5c33b420b5 Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Thu, 13 Mar 2025 14:09:40 +0100 Subject: [PATCH 03/24] Add proper test --- .../data/mock-projects/test-rebuild/pixi.toml | 2 + .../test-rebuild/pixi_build_package/pixi.toml | 6 +++ .../pixi_build_package/pyproject.toml | 11 ++++ .../pixi_build_package/recipe.yaml | 18 +++++++ .../src/pixi_build_package/__init__.py | 2 + .../pypi_package/src/pypi_package/__init__.py | 2 +- tests/integration_python/test_main_cli.py | 53 +++++++++++++++---- 7 files changed, 83 insertions(+), 11 deletions(-) create mode 100644 tests/data/mock-projects/test-rebuild/pixi_build_package/pixi.toml create mode 100644 tests/data/mock-projects/test-rebuild/pixi_build_package/pyproject.toml create mode 100644 tests/data/mock-projects/test-rebuild/pixi_build_package/recipe.yaml create mode 100644 tests/data/mock-projects/test-rebuild/pixi_build_package/src/pixi_build_package/__init__.py diff --git a/tests/data/mock-projects/test-rebuild/pixi.toml b/tests/data/mock-projects/test-rebuild/pixi.toml index 3fec4125f8..d3817bb6ee 100644 --- a/tests/data/mock-projects/test-rebuild/pixi.toml +++ b/tests/data/mock-projects/test-rebuild/pixi.toml @@ -3,11 +3,13 @@ authors = ["Julian Hofer "] channels = ["https://prefix.dev/conda-forge"] name = "test-rebuild" platforms = ["linux-64"] +preview = ["pixi-build"] version = "0.1.0" [tasks] [dependencies] +pixi_build_package = { path = "pixi_build_package" } python = ">=3.13.2,<3.14" [pypi-dependencies] diff --git a/tests/data/mock-projects/test-rebuild/pixi_build_package/pixi.toml b/tests/data/mock-projects/test-rebuild/pixi_build_package/pixi.toml new file mode 100644 index 0000000000..d0a2cb3e1a --- /dev/null +++ b/tests/data/mock-projects/test-rebuild/pixi_build_package/pixi.toml @@ -0,0 +1,6 @@ +[package] +name = "pixi_build_package" +version = "0.1.0" + +[package.build] +backend = { name = "pixi-build-rattler-build", version = "0.1.*" } diff --git a/tests/data/mock-projects/test-rebuild/pixi_build_package/pyproject.toml b/tests/data/mock-projects/test-rebuild/pixi_build_package/pyproject.toml new file mode 100644 index 0000000000..3250ee0dca --- /dev/null +++ b/tests/data/mock-projects/test-rebuild/pixi_build_package/pyproject.toml @@ -0,0 +1,11 @@ +[project] +authors = [{ name = "Julian Hofer", email = "julianhofer@gnome.org" }] +dependencies = [] +name = "pixi_build_package" +requires-python = ">= 3.11" +scripts = { pixi-build-package-main = "pixi_build_package:main" } +version = "0.1.0" + +[build-system] +build-backend = "hatchling.build" +requires = ["hatchling"] diff --git a/tests/data/mock-projects/test-rebuild/pixi_build_package/recipe.yaml b/tests/data/mock-projects/test-rebuild/pixi_build_package/recipe.yaml new file mode 100644 index 0000000000..3aee311c11 --- /dev/null +++ b/tests/data/mock-projects/test-rebuild/pixi_build_package/recipe.yaml @@ -0,0 +1,18 @@ +package: + name: pixi_build_package + version: 0.1.0 + +source: + path: . + use_gitignore: true + +build: + noarch: python + number: 0 + script: | + pip install . --no-deps -vv +requirements: + host: + - pip + - python + - hatchling diff --git a/tests/data/mock-projects/test-rebuild/pixi_build_package/src/pixi_build_package/__init__.py b/tests/data/mock-projects/test-rebuild/pixi_build_package/src/pixi_build_package/__init__.py new file mode 100644 index 0000000000..7157d8a41a --- /dev/null +++ b/tests/data/mock-projects/test-rebuild/pixi_build_package/src/pixi_build_package/__init__.py @@ -0,0 +1,2 @@ +def main() -> None: + print("Pixi Build is number 1") diff --git a/tests/data/mock-projects/test-rebuild/pypi_package/src/pypi_package/__init__.py b/tests/data/mock-projects/test-rebuild/pypi_package/src/pypi_package/__init__.py index 401b5ccaa8..a26a635c7b 100644 --- a/tests/data/mock-projects/test-rebuild/pypi_package/src/pypi_package/__init__.py +++ b/tests/data/mock-projects/test-rebuild/pypi_package/src/pypi_package/__init__.py @@ -1,2 +1,2 @@ def main() -> None: - print("Number 1") + print("PyPI is number 1") diff --git a/tests/integration_python/test_main_cli.py b/tests/integration_python/test_main_cli.py index c2e82574b5..a61ad5b90e 100644 --- a/tests/integration_python/test_main_cli.py +++ b/tests/integration_python/test_main_cli.py @@ -1220,24 +1220,57 @@ def test_pixi_reinstall(pixi: Path, tmp_pixi_workspace: Path, mock_projects: Pat test_rebuild_src = mock_projects / "test-rebuild" shutil.copytree(test_rebuild_src, tmp_pixi_workspace, dirs_exist_ok=True) - # Check that package returns "Number 1" - verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], stdout_contains="Number 1" - ) - - # Modify the Python file pypi_package_init = tmp_pixi_workspace.joinpath( "pypi_package", "src", "pypi_package", "__init__.py" ) + pixi_build_package_init = tmp_pixi_workspace.joinpath( + "pixi_build_package", "src", "pixi_build_package", "__init__.py" + ) + + # Check that packages return "number 1" + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], + stdout_contains="PyPI is number 1", + ) + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pixi-build-package-main"], + stdout_contains="Pixi Build is number 1", + ) + + # Modify the Python files pypi_package_init.write_text(pypi_package_init.read_text().replace("1", "2")) + pixi_build_package_init.write_text(pypi_package_init.read_text().replace("1", "2")) - # That shouldn't trigger a re-install, so running still returns "Number 1" + # That shouldn't trigger a re-install, so running still returns "number 1" verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], stdout_contains="Number 1" + [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], + stdout_contains="PyPI is number 1", + ) + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pixi-build-package-main"], + stdout_contains="Pixi Build is number 1", ) - # After re-installing, it should return "Number 2" + # After re-installing pypi-package, it should return "number 2" + # pixi-build-package, should still return "number 1" + verify_cli_command([pixi, "reinstall", "--manifest-path", manifest, "pypi-package"]) + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], + stdout_contains="PyPI is number 2", + ) + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pixi-build-package-main"], + stdout_contains="Pixi Build is number 1", + ) + + # After re-installing the whole default environment, + # both should return "number 2" verify_cli_command([pixi, "reinstall", "--manifest-path", manifest]) verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], stdout_contains="Number 2" + [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], + stdout_contains="PyPI is number 2", + ) + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pixi-build-package-main"], + stdout_contains="Pixi Build is number 2", ) From 46c41de31debad66b5031aa679353fbe43a05eb5 Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Thu, 13 Mar 2025 14:33:00 +0100 Subject: [PATCH 04/24] Fix CLI --- src/cli/mod.rs | 4 ++-- src/cli/reinstall.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 37da66fb63..df93857f72 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -135,7 +135,7 @@ pub enum Command { Remove(remove::Args), #[clap(visible_alias = "i")] Install(install::Args), - Reinstall(install::Args), + Reinstall(reinstall::Args), Update(update::Args), Upgrade(upgrade::Args), Lock(lock::Args), @@ -272,7 +272,7 @@ pub async fn execute_command(command: Command) -> miette::Result<()> { Command::Global(cmd) => global::execute(cmd).await, Command::Auth(cmd) => rattler::cli::auth::execute(cmd).await.into_diagnostic(), Command::Install(cmd) => install::execute(cmd).await, - Command::Reinstall(cmd) => install::execute(cmd).await, + Command::Reinstall(cmd) => reinstall::execute(cmd).await, Command::Shell(cmd) => shell::execute(cmd).await, Command::ShellHook(cmd) => shell_hook::execute(cmd).await, Command::Task(cmd) => task::execute(cmd).await, diff --git a/src/cli/reinstall.rs b/src/cli/reinstall.rs index 4542f7d191..d793151e89 100644 --- a/src/cli/reinstall.rs +++ b/src/cli/reinstall.rs @@ -20,6 +20,10 @@ use pixi_config::ConfigCli; /// If you want to re-install all environments, you can use the `--all` flag. #[derive(Parser, Debug)] pub struct Args { + /// Specifies the package that should be reinstalled. + #[arg(value_name = "PACKAGE")] + packages: Option>, + #[clap(flatten)] pub project_config: WorkspaceConfig, @@ -36,10 +40,6 @@ pub struct Args { /// Install all environments. #[arg(long, short, conflicts_with = "environment")] pub all: bool, - - /// Specifies the package that should be reinstalled. - #[arg(id = "PACKAGE")] - packages: Vec, } pub async fn execute(args: Args) -> miette::Result<()> { From 90df9b080fea4784b58f7d3d800cc119a3795d42 Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Thu, 13 Mar 2025 14:44:40 +0100 Subject: [PATCH 05/24] Test remaining scenarios --- .../data/mock-projects/test-rebuild/pixi.toml | 10 ++ tests/integration_python/test_main_cli.py | 117 +++++++++++++++++- 2 files changed, 126 insertions(+), 1 deletion(-) diff --git a/tests/data/mock-projects/test-rebuild/pixi.toml b/tests/data/mock-projects/test-rebuild/pixi.toml index d3817bb6ee..1c4ea935e6 100644 --- a/tests/data/mock-projects/test-rebuild/pixi.toml +++ b/tests/data/mock-projects/test-rebuild/pixi.toml @@ -14,3 +14,13 @@ python = ">=3.13.2,<3.14" [pypi-dependencies] pypi_package = { path = "pypi_package" } + +[feature.dev.dependencies] +pixi_build_package = { path = "pixi_build_package" } +python = ">=3.13.2,<3.14" + +[feature.dev.pypi-dependencies] +pypi_package = { path = "pypi_package" } + +[environments] +dev = { features = ["dev"], no-default-feature = true } diff --git a/tests/integration_python/test_main_cli.py b/tests/integration_python/test_main_cli.py index a61ad5b90e..28c23e5f72 100644 --- a/tests/integration_python/test_main_cli.py +++ b/tests/integration_python/test_main_cli.py @@ -1215,7 +1215,9 @@ def test_pixi_info_tasks(pixi: Path, tmp_pixi_workspace: Path) -> None: @pytest.mark.slow -def test_pixi_reinstall(pixi: Path, tmp_pixi_workspace: Path, mock_projects: Path) -> None: +def test_pixi_reinstall_default_env( + pixi: Path, tmp_pixi_workspace: Path, mock_projects: Path +) -> None: manifest = tmp_pixi_workspace.joinpath("pixi.toml") test_rebuild_src = mock_projects / "test-rebuild" shutil.copytree(test_rebuild_src, tmp_pixi_workspace, dirs_exist_ok=True) @@ -1274,3 +1276,116 @@ def test_pixi_reinstall(pixi: Path, tmp_pixi_workspace: Path, mock_projects: Pat [pixi, "run", "--manifest-path", manifest, "pixi-build-package-main"], stdout_contains="Pixi Build is number 2", ) + + +@pytest.mark.slow +def test_pixi_reinstall_multi_env( + pixi: Path, tmp_pixi_workspace: Path, mock_projects: Path +) -> None: + manifest = tmp_pixi_workspace.joinpath("pixi.toml") + test_rebuild_src = mock_projects / "test-rebuild" + shutil.copytree(test_rebuild_src, tmp_pixi_workspace, dirs_exist_ok=True) + + pypi_package_init = tmp_pixi_workspace.joinpath( + "pypi_package", "src", "pypi_package", "__init__.py" + ) + pixi_build_package_init = tmp_pixi_workspace.joinpath( + "pixi_build_package", "src", "pixi_build_package", "__init__.py" + ) + + # Check that packages return "number 1" + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], + stdout_contains="PyPI is number 1", + ) + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pixi-build-package-main"], + stdout_contains="Pixi Build is number 1", + ) + + # Modify the Python files + pypi_package_init.write_text(pypi_package_init.read_text().replace("1", "2")) + pixi_build_package_init.write_text(pypi_package_init.read_text().replace("1", "2")) + + # That shouldn't trigger a re-install, so running still returns "number 1" + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "--environment", "dev", "pypi-package-main"], + stdout_contains="PyPI is number 1", + ) + verify_cli_command( + [ + pixi, + "run", + "--manifest-path", + manifest, + "--environment", + "dev", + "pixi-build-package-main", + ], + stdout_contains="Pixi Build is number 1", + ) + + # After re-installing pypi-package, it should return "number 2" + # pixi-build-package, should still return "number 1" + verify_cli_command([pixi, "reinstall", "--manifest-path", manifest, "pypi-package"]) + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], + stdout_contains="PyPI is number 2", + ) + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pixi-build-package-main"], + stdout_contains="Pixi Build is number 1", + ) + + # After re-installing both packages in the "dev" environment, + # both should return "number 2" + verify_cli_command( + [ + pixi, + "reinstall", + "--manifest-path", + manifest, + "--environment", + "dev", + "pypi-package", + "pixi-build-package-main", + ] + ) + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "--environment", "dev", "pypi-package-main"], + stdout_contains="PyPI is number 2", + ) + verify_cli_command( + [ + pixi, + "run", + "--manifest-path", + manifest, + "--environment", + "dev", + "pixi-build-package-main", + ], + stdout_contains="Pixi Build is number 2", + ) + + # In the default environment, it should still be "number 1" + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], + stdout_contains="PyPI is number 1", + ) + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pixi-build-package-main"], + stdout_contains="Pixi Build is number 1", + ) + + # After reinstalling all environments, + # also the default environment should be "number 2" + verify_cli_command([pixi, "reinstall", "--manifest-path", manifest, "--all"]) + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], + stdout_contains="PyPI is number 2", + ) + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pixi-build-package-main"], + stdout_contains="Pixi Build is number 2", + ) From f26755ed980ef05f66c09e2e5525eb4e45459e88 Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Thu, 13 Mar 2025 15:06:51 +0100 Subject: [PATCH 06/24] Hook everything up --- src/cli/install.rs | 1 + src/cli/reinstall.rs | 4 +++- src/cli/remove.rs | 1 + src/cli/run.rs | 1 + src/cli/shell.rs | 1 + src/cli/shell_hook.rs | 1 + src/cli/workspace/channel/add.rs | 1 + src/cli/workspace/channel/remove.rs | 1 + src/cli/workspace/platform/add.rs | 1 + src/cli/workspace/platform/remove.rs | 1 + src/environment/conda_prefix.rs | 15 ++++++++++++--- src/environment/mod.rs | 9 ++++++--- src/lock_file/resolve/build_dispatch.rs | 2 +- src/lock_file/update.rs | 18 +++++++++++++----- src/workspace/workspace_mut.rs | 1 + 15 files changed, 45 insertions(+), 13 deletions(-) diff --git a/src/cli/install.rs b/src/cli/install.rs index ca5b28a126..29edbad63d 100644 --- a/src/cli/install.rs +++ b/src/cli/install.rs @@ -77,6 +77,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { no_install: false, max_concurrent_solves: workspace.config().max_concurrent_solves(), }, + None, ) .await?; diff --git a/src/cli/reinstall.rs b/src/cli/reinstall.rs index d793151e89..b5f3f4ad77 100644 --- a/src/cli/reinstall.rs +++ b/src/cli/reinstall.rs @@ -6,6 +6,7 @@ use clap::Parser; use fancy_display::FancyDisplay; use itertools::Itertools; use pixi_config::ConfigCli; +use rattler_conda_types::PackageName; /// Re-install an environment, both updating the lockfile and re-installing the environment. /// @@ -22,7 +23,7 @@ use pixi_config::ConfigCli; pub struct Args { /// Specifies the package that should be reinstalled. #[arg(value_name = "PACKAGE")] - packages: Option>, + packages: Option>, #[clap(flatten)] pub project_config: WorkspaceConfig, @@ -78,6 +79,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { no_install: false, max_concurrent_solves: workspace.config().max_concurrent_solves(), }, + todo!(), ) .await?; diff --git a/src/cli/remove.rs b/src/cli/remove.rs index 695845bb73..4973bc5fc3 100644 --- a/src/cli/remove.rs +++ b/src/cli/remove.rs @@ -124,6 +124,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { no_install: prefix_update_config.no_install, max_concurrent_solves: workspace.config().max_concurrent_solves(), }, + None, ) .await?; } diff --git a/src/cli/run.rs b/src/cli/run.rs index 2b27084ffb..5c206db3f2 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -250,6 +250,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { .prefix( &executable_task.run_environment, args.prefix_update_config.update_mode(), + None, ) .await?; diff --git a/src/cli/shell.rs b/src/cli/shell.rs index 3f7869d753..214b56e03b 100644 --- a/src/cli/shell.rs +++ b/src/cli/shell.rs @@ -284,6 +284,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { && args.lock_file_update_config.no_lockfile_update, max_concurrent_solves: workspace.config().max_concurrent_solves(), }, + None, ) .await?; diff --git a/src/cli/shell_hook.rs b/src/cli/shell_hook.rs index 8fd376cefd..f9e6cd3655 100644 --- a/src/cli/shell_hook.rs +++ b/src/cli/shell_hook.rs @@ -163,6 +163,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { && args.lock_file_update_config.no_lockfile_update, max_concurrent_solves: workspace.config().max_concurrent_solves(), }, + None, ) .await?; diff --git a/src/cli/workspace/channel/add.rs b/src/cli/workspace/channel/add.rs index 6ffe32ac77..45df48b3b9 100644 --- a/src/cli/workspace/channel/add.rs +++ b/src/cli/workspace/channel/add.rs @@ -31,6 +31,7 @@ pub async fn execute(args: AddRemoveArgs) -> miette::Result<()> { && args.lock_file_update_config.no_lockfile_update, max_concurrent_solves: workspace.workspace().config().max_concurrent_solves(), }, + None, ) .await?; diff --git a/src/cli/workspace/channel/remove.rs b/src/cli/workspace/channel/remove.rs index 0e0756015d..1c9040f395 100644 --- a/src/cli/workspace/channel/remove.rs +++ b/src/cli/workspace/channel/remove.rs @@ -29,6 +29,7 @@ pub async fn execute(args: AddRemoveArgs) -> miette::Result<()> { && args.lock_file_update_config.no_lockfile_update, max_concurrent_solves: workspace.workspace().config().max_concurrent_solves(), }, + None, ) .await?; let workspace = workspace.save().await.into_diagnostic()?; diff --git a/src/cli/workspace/platform/add.rs b/src/cli/workspace/platform/add.rs index 4a25d7ff61..d5e2845633 100644 --- a/src/cli/workspace/platform/add.rs +++ b/src/cli/workspace/platform/add.rs @@ -55,6 +55,7 @@ pub async fn execute(workspace: Workspace, args: Args) -> miette::Result<()> { no_install: args.no_install, max_concurrent_solves: workspace.workspace().config().max_concurrent_solves(), }, + None, ) .await?; workspace.save().await.into_diagnostic()?; diff --git a/src/cli/workspace/platform/remove.rs b/src/cli/workspace/platform/remove.rs index aca72def1a..64bf2fd6ce 100644 --- a/src/cli/workspace/platform/remove.rs +++ b/src/cli/workspace/platform/remove.rs @@ -45,6 +45,7 @@ pub async fn execute(workspace: Workspace, args: Args) -> miette::Result<()> { no_install: args.no_install, max_concurrent_solves: workspace.workspace().config().max_concurrent_solves(), }, + None, ) .await?; workspace.save().await.into_diagnostic()?; diff --git a/src/environment/conda_prefix.rs b/src/environment/conda_prefix.rs index f66a5c62f2..c631d78584 100644 --- a/src/environment/conda_prefix.rs +++ b/src/environment/conda_prefix.rs @@ -1,3 +1,4 @@ +use std::collections::HashSet; use std::sync::{Arc, LazyLock}; use crate::build::{BuildContext, SourceCheckoutReporter}; @@ -16,7 +17,7 @@ use pixi_record::PixiRecord; use rattler::install::{DefaultProgressFormatter, IndicatifReporter, Installer}; use rattler::package_cache::PackageCache; use rattler_conda_types::{ - ChannelUrl, GenericVirtualPackage, Platform, PrefixRecord, RepoDataRecord, + ChannelUrl, GenericVirtualPackage, PackageName, Platform, PrefixRecord, RepoDataRecord, }; use reqwest_middleware::ClientWithMiddleware; use tokio::sync::Semaphore; @@ -178,6 +179,7 @@ impl CondaPrefixUpdater { pub async fn update( &self, pixi_records: Vec, + reinstall_packages: Option>, ) -> miette::Result<&CondaPrefixUpdated> { self.inner .created @@ -224,6 +226,7 @@ impl CondaPrefixUpdater { " ", self.inner.io_concurrency_limit.clone().into(), self.inner.build_context.clone(), + reinstall_packages, ) .await?; @@ -256,6 +259,7 @@ pub async fn update_prefix_conda( progress_bar_prefix: &str, io_concurrency_limit: Arc, build_context: BuildContext, + reinstall_packages: Option>, ) -> miette::Result { // Try to increase the rlimit to a sensible value for installation. try_increase_rlimit_to_sensible(); @@ -336,7 +340,7 @@ pub async fn update_prefix_conda( let result = await_in_progress( format!("{progress_bar_prefix}{progress_bar_message}",), |pb| async { - Installer::new() + let mut installer = Installer::new() .with_download_client(authenticated_client) .with_io_concurrency_semaphore(io_concurrency_limit) .with_execute_link_scripts(false) @@ -353,7 +357,12 @@ pub async fn update_prefix_conda( ) .clear_when_done(true) .finish(), - ) + ); + if let Some(reinstall_packages) = reinstall_packages { + installer.set_reinstall_packages(reinstall_packages); + } + + installer .install(prefix.root(), repodata_records) .await .into_diagnostic() diff --git a/src/environment/mod.rs b/src/environment/mod.rs index 786b6cd1cc..cdb663ebff 100644 --- a/src/environment/mod.rs +++ b/src/environment/mod.rs @@ -6,7 +6,7 @@ mod python_status; mod reporters; use std::{ - collections::HashMap, + collections::{HashMap, HashSet}, hash::{Hash, Hasher}, io::ErrorKind, path::{Path, PathBuf}, @@ -19,7 +19,7 @@ use pixi_git::credentials::store_credentials_from_url; use pixi_manifest::{FeaturesExt, PyPiRequirement}; use pixi_progress::await_in_progress; use pixi_spec::{GitSpec, PixiSpec}; -use rattler_conda_types::Platform; +use rattler_conda_types::{PackageName, Platform}; use rattler_lock::LockedPackageRef; use serde::{Deserialize, Serialize}; use xxhash_rust::xxh3::Xxh3; @@ -392,6 +392,7 @@ pub async fn get_update_lock_file_and_prefix<'env>( environment: &Environment<'env>, update_mode: UpdateMode, update_lock_file_options: UpdateLockFileOptions, + reinstall_packages: Option>, ) -> miette::Result<(LockFileDerivedData<'env>, Prefix)> { let current_platform = environment.best_platform(); let project = environment.workspace(); @@ -423,7 +424,9 @@ pub async fn get_update_lock_file_and_prefix<'env>( let prefix = if no_install { Prefix::new(environment.dir()) } else { - lock_file.prefix(environment, update_mode).await? + lock_file + .prefix(environment, update_mode, reinstall_packages) + .await? }; Ok((lock_file, prefix)) diff --git a/src/lock_file/resolve/build_dispatch.rs b/src/lock_file/resolve/build_dispatch.rs index fe2960d6ab..14317a3a44 100644 --- a/src/lock_file/resolve/build_dispatch.rs +++ b/src/lock_file/resolve/build_dispatch.rs @@ -265,7 +265,7 @@ impl<'a> LazyBuildDispatch<'a> { ); let prefix = self .prefix_updater - .update(self.repodata_records.clone()) + .update(self.repodata_records.clone(), None) .await .map_err(|err| { LazyBuildDispatchError::InitializationError(format!( diff --git a/src/lock_file/update.rs b/src/lock_file/update.rs index 710090778a..a406d7caf1 100644 --- a/src/lock_file/update.rs +++ b/src/lock_file/update.rs @@ -31,7 +31,7 @@ use pixi_uv_conversions::{ use pypi_mapping::{self}; use pypi_modifiers::pypi_marker_env::determine_marker_environment; use rattler::package_cache::PackageCache; -use rattler_conda_types::{Arch, MatchSpec, ParseStrictness, Platform}; +use rattler_conda_types::{Arch, MatchSpec, PackageName, ParseStrictness, Platform}; use rattler_lock::{ LockFile, ParseCondaLockError, PypiIndexes, PypiPackageData, PypiPackageEnvironmentData, }; @@ -286,6 +286,7 @@ impl<'p> LockFileDerivedData<'p> { &mut self, environment: &Environment<'p>, update_mode: UpdateMode, + reinstall_packages: Option>, ) -> miette::Result { // Check if the prefix is already up-to-date by validating the hash with the // environment file @@ -297,7 +298,7 @@ impl<'p> LockFileDerivedData<'p> { } // Get the up-to-date prefix - let prefix = self.update_prefix(environment).await?; + let prefix = self.update_prefix(environment, reinstall_packages).await?; // Save an environment file to the environment directory after the update. // Avoiding writing the cache away before the update is done. @@ -361,7 +362,11 @@ impl<'p> LockFileDerivedData<'p> { } /// Returns the up-to-date prefix for the given environment. - async fn update_prefix(&mut self, environment: &Environment<'p>) -> miette::Result { + async fn update_prefix( + &mut self, + environment: &Environment<'p>, + reinstall_packages: Option>, + ) -> miette::Result { // If we previously updated this environment, early out. if let Some(prefix) = self.updated_pypi_prefixes.get(environment.name()) { return Ok(prefix.clone()); @@ -382,7 +387,7 @@ impl<'p> LockFileDerivedData<'p> { tracing::info!("Updating prefix: '{}'", environment.dir().display()); // Get the prefix with the conda packages installed. let platform = environment.best_platform(); - let (prefix, python_status) = self.conda_prefix(environment).await?; + let (prefix, python_status) = self.conda_prefix(environment, reinstall_packages).await?; let pixi_records = self .pixi_records(environment, platform) .into_diagnostic()? @@ -507,6 +512,7 @@ impl<'p> LockFileDerivedData<'p> { async fn conda_prefix( &mut self, environment: &Environment<'p>, + reinstall_packages: Option>, ) -> miette::Result<(Prefix, PythonStatus)> { // If we previously updated this environment, early out. if let Some((prefix, python_status)) = self.updated_conda_prefixes.get(environment.name()) { @@ -536,7 +542,9 @@ impl<'p> LockFileDerivedData<'p> { prefix, python_status, .. - } = conda_prefix_updater.update(records).await?; + } = conda_prefix_updater + .update(records, reinstall_packages) + .await?; // Store that we updated the environment, so we won't have to do it again. self.updated_conda_prefixes.insert( diff --git a/src/workspace/workspace_mut.rs b/src/workspace/workspace_mut.rs index ef258aead2..4b0e811565 100644 --- a/src/workspace/workspace_mut.rs +++ b/src/workspace/workspace_mut.rs @@ -422,6 +422,7 @@ impl WorkspaceMut { .prefix( &self.workspace().default_environment(), UpdateMode::Revalidate, + None, ) .await?; } From 50b8b662007f99746a4411efad1f7b031b2359ed Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Thu, 13 Mar 2025 15:39:17 +0100 Subject: [PATCH 07/24] Get everything to compile --- src/cli/reinstall.rs | 7 ++++--- src/environment/mod.rs | 4 ++-- src/lock_file/update.rs | 14 +++++++++++--- tests/integration_rust/common/mod.rs | 2 +- tests/integration_rust/install_tests.rs | 3 ++- 5 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/cli/reinstall.rs b/src/cli/reinstall.rs index b5f3f4ad77..31f150a947 100644 --- a/src/cli/reinstall.rs +++ b/src/cli/reinstall.rs @@ -6,7 +6,6 @@ use clap::Parser; use fancy_display::FancyDisplay; use itertools::Itertools; use pixi_config::ConfigCli; -use rattler_conda_types::PackageName; /// Re-install an environment, both updating the lockfile and re-installing the environment. /// @@ -23,7 +22,7 @@ use rattler_conda_types::PackageName; pub struct Args { /// Specifies the package that should be reinstalled. #[arg(value_name = "PACKAGE")] - packages: Option>, + packages: Option>, #[clap(flatten)] pub project_config: WorkspaceConfig, @@ -66,6 +65,8 @@ pub async fn execute(args: Args) -> miette::Result<()> { vec![workspace.default_environment().name().to_string()] }; + let reinstall_packages = args.packages.map(|p| p.into_iter().collect()); + let mut installed_envs = Vec::with_capacity(envs.len()); for env in envs { let environment = workspace.environment_from_name_or_env_var(Some(env))?; @@ -79,7 +80,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { no_install: false, max_concurrent_solves: workspace.config().max_concurrent_solves(), }, - todo!(), + reinstall_packages.clone(), ) .await?; diff --git a/src/environment/mod.rs b/src/environment/mod.rs index cdb663ebff..e13bb4bf8d 100644 --- a/src/environment/mod.rs +++ b/src/environment/mod.rs @@ -19,7 +19,7 @@ use pixi_git::credentials::store_credentials_from_url; use pixi_manifest::{FeaturesExt, PyPiRequirement}; use pixi_progress::await_in_progress; use pixi_spec::{GitSpec, PixiSpec}; -use rattler_conda_types::{PackageName, Platform}; +use rattler_conda_types::Platform; use rattler_lock::LockedPackageRef; use serde::{Deserialize, Serialize}; use xxhash_rust::xxh3::Xxh3; @@ -392,7 +392,7 @@ pub async fn get_update_lock_file_and_prefix<'env>( environment: &Environment<'env>, update_mode: UpdateMode, update_lock_file_options: UpdateLockFileOptions, - reinstall_packages: Option>, + reinstall_packages: Option>, ) -> miette::Result<(LockFileDerivedData<'env>, Prefix)> { let current_platform = environment.best_platform(); let project = environment.workspace(); diff --git a/src/lock_file/update.rs b/src/lock_file/update.rs index a406d7caf1..22a5a90fae 100644 --- a/src/lock_file/update.rs +++ b/src/lock_file/update.rs @@ -4,6 +4,7 @@ use std::{ future::{ready, Future}, iter, path::PathBuf, + str::FromStr, sync::Arc, time::{Duration, Instant}, }; @@ -286,7 +287,7 @@ impl<'p> LockFileDerivedData<'p> { &mut self, environment: &Environment<'p>, update_mode: UpdateMode, - reinstall_packages: Option>, + reinstall_packages: Option>, ) -> miette::Result { // Check if the prefix is already up-to-date by validating the hash with the // environment file @@ -365,7 +366,7 @@ impl<'p> LockFileDerivedData<'p> { async fn update_prefix( &mut self, environment: &Environment<'p>, - reinstall_packages: Option>, + reinstall_packages: Option>, ) -> miette::Result { // If we previously updated this environment, early out. if let Some(prefix) = self.updated_pypi_prefixes.get(environment.name()) { @@ -387,7 +388,14 @@ impl<'p> LockFileDerivedData<'p> { tracing::info!("Updating prefix: '{}'", environment.dir().display()); // Get the prefix with the conda packages installed. let platform = environment.best_platform(); - let (prefix, python_status) = self.conda_prefix(environment, reinstall_packages).await?; + let reinstall_packages_conda = reinstall_packages.clone().map(|p| { + p.into_iter() + .filter_map(|p| PackageName::from_str(&p).ok()) + .collect() + }); + let (prefix, python_status) = self + .conda_prefix(environment, reinstall_packages_conda) + .await?; let pixi_records = self .pixi_records(environment, platform) .into_diagnostic()? diff --git a/tests/integration_rust/common/mod.rs b/tests/integration_rust/common/mod.rs index b90e082775..c70739cbee 100644 --- a/tests/integration_rust/common/mod.rs +++ b/tests/integration_rust/common/mod.rs @@ -513,7 +513,7 @@ impl PixiControl { let task_env = match task_env.as_ref() { None => { lock_file - .prefix(&task.run_environment, UpdateMode::Revalidate) + .prefix(&task.run_environment, UpdateMode::Revalidate, None) .await?; let env = get_task_env(&task.run_environment, args.clean_env, None, false, false) diff --git a/tests/integration_rust/install_tests.rs b/tests/integration_rust/install_tests.rs index d66bbb5edc..293081b6e5 100644 --- a/tests/integration_rust/install_tests.rs +++ b/tests/integration_rust/install_tests.rs @@ -598,6 +598,7 @@ async fn test_old_lock_install() { no_install: false, ..Default::default() }, + None, ) .await .unwrap(); @@ -969,7 +970,7 @@ async fn test_multiple_prefix_update() { let pixi_records = pixi_records.clone(); // tasks.push(conda_prefix_updater.update(pixi_records)); let updater = conda_prefix_updater.clone(); - sets.spawn(async move { updater.update(pixi_records).await.cloned() }); + sets.spawn(async move { updater.update(pixi_records, None).await.cloned() }); } let mut first_modified = None; From 4a02d5581dc4143507a1999db2dc028d17acb530 Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Thu, 13 Mar 2025 16:41:09 +0100 Subject: [PATCH 08/24] Get it to compile again --- src/cli/install.rs | 4 ++-- src/cli/reinstall.rs | 9 +++++++-- src/cli/remove.rs | 4 ++-- src/cli/run.rs | 4 ++-- src/cli/shell.rs | 7 +++++-- src/cli/shell_hook.rs | 3 ++- src/cli/workspace/channel/add.rs | 4 ++-- src/cli/workspace/channel/remove.rs | 4 ++-- src/cli/workspace/platform/add.rs | 4 ++-- src/cli/workspace/platform/remove.rs | 4 ++-- src/environment/mod.rs | 6 +++--- src/lock_file/mod.rs | 2 +- src/lock_file/update.rs | 22 +++++++++++----------- src/workspace/workspace_mut.rs | 4 ++-- tests/integration_rust/common/mod.rs | 8 ++++++-- tests/integration_rust/install_tests.rs | 4 ++-- 16 files changed, 53 insertions(+), 40 deletions(-) diff --git a/src/cli/install.rs b/src/cli/install.rs index 29edbad63d..1ab0a48d76 100644 --- a/src/cli/install.rs +++ b/src/cli/install.rs @@ -1,6 +1,6 @@ use crate::cli::cli_config::WorkspaceConfig; use crate::environment::get_update_lock_file_and_prefix; -use crate::lock_file::UpdateMode; +use crate::lock_file::{ReinstallPackages, UpdateMode}; use crate::{UpdateLockFileOptions, WorkspaceLocator}; use clap::Parser; use fancy_display::FancyDisplay; @@ -77,7 +77,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { no_install: false, max_concurrent_solves: workspace.config().max_concurrent_solves(), }, - None, + ReinstallPackages::default(), ) .await?; diff --git a/src/cli/reinstall.rs b/src/cli/reinstall.rs index 31f150a947..cf109e140a 100644 --- a/src/cli/reinstall.rs +++ b/src/cli/reinstall.rs @@ -1,6 +1,6 @@ use crate::cli::cli_config::WorkspaceConfig; use crate::environment::get_update_lock_file_and_prefix; -use crate::lock_file::UpdateMode; +use crate::lock_file::{ReinstallPackages, UpdateMode}; use crate::{UpdateLockFileOptions, WorkspaceLocator}; use clap::Parser; use fancy_display::FancyDisplay; @@ -21,6 +21,7 @@ use pixi_config::ConfigCli; #[derive(Parser, Debug)] pub struct Args { /// Specifies the package that should be reinstalled. + /// If no package is given, the whole environment will be reinstalled. #[arg(value_name = "PACKAGE")] packages: Option>, @@ -65,7 +66,11 @@ pub async fn execute(args: Args) -> miette::Result<()> { vec![workspace.default_environment().name().to_string()] }; - let reinstall_packages = args.packages.map(|p| p.into_iter().collect()); + let reinstall_packages = args + .packages + .map(|p| p.into_iter().collect()) + .map(ReinstallPackages::Some) + .unwrap_or(ReinstallPackages::All); let mut installed_envs = Vec::with_capacity(envs.len()); for env in envs { diff --git a/src/cli/remove.rs b/src/cli/remove.rs index 4973bc5fc3..68a1786342 100644 --- a/src/cli/remove.rs +++ b/src/cli/remove.rs @@ -2,7 +2,7 @@ use super::{cli_config::LockFileUpdateConfig, has_specs::HasSpecs}; use crate::{ cli::cli_config::{DependencyConfig, PrefixUpdateConfig, WorkspaceConfig}, environment::get_update_lock_file_and_prefix, - lock_file::UpdateMode, + lock_file::{ReinstallPackages, UpdateMode}, DependencyType, UpdateLockFileOptions, WorkspaceLocator, }; use clap::Parser; @@ -124,7 +124,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { no_install: prefix_update_config.no_install, max_concurrent_solves: workspace.config().max_concurrent_solves(), }, - None, + ReinstallPackages::default(), ) .await?; } diff --git a/src/cli/run.rs b/src/cli/run.rs index 5c206db3f2..6845e8f7b5 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -21,7 +21,7 @@ use tracing::Level; use crate::{ cli::cli_config::{PrefixUpdateConfig, WorkspaceConfig}, environment::sanity_check_project, - lock_file::UpdateLockFileOptions, + lock_file::{ReinstallPackages, UpdateLockFileOptions}, task::{ get_task_env, AmbiguousTask, CanSkip, ExecutableTask, FailedToParseShellScript, InvalidWorkingDirectory, SearchEnvironments, TaskAndEnvironment, TaskGraph, @@ -250,7 +250,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { .prefix( &executable_task.run_environment, args.prefix_update_config.update_mode(), - None, + ReinstallPackages::default(), ) .await?; diff --git a/src/cli/shell.rs b/src/cli/shell.rs index 214b56e03b..02dcfd6b3b 100644 --- a/src/cli/shell.rs +++ b/src/cli/shell.rs @@ -8,13 +8,16 @@ use rattler_shell::{ shell::{CmdExe, PowerShell, Shell, ShellEnum, ShellScript}, }; -use crate::cli::cli_config::{PrefixUpdateConfig, WorkspaceConfig}; use crate::lock_file::UpdateMode; use crate::workspace::get_activated_environment_variables; use crate::{ activation::CurrentEnvVarBehavior, environment::get_update_lock_file_and_prefix, prompt, UpdateLockFileOptions, WorkspaceLocator, }; +use crate::{ + cli::cli_config::{PrefixUpdateConfig, WorkspaceConfig}, + lock_file::ReinstallPackages, +}; use pixi_config::{ConfigCli, ConfigCliActivation, ConfigCliPrompt}; #[cfg(target_family = "unix")] use pixi_pty::unix::PtySession; @@ -284,7 +287,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { && args.lock_file_update_config.no_lockfile_update, max_concurrent_solves: workspace.config().max_concurrent_solves(), }, - None, + ReinstallPackages::default(), ) .await?; diff --git a/src/cli/shell_hook.rs b/src/cli/shell_hook.rs index f9e6cd3655..eb4979e70a 100644 --- a/src/cli/shell_hook.rs +++ b/src/cli/shell_hook.rs @@ -15,6 +15,7 @@ use crate::{ activation::{get_activator, CurrentEnvVarBehavior}, cli::cli_config::{PrefixUpdateConfig, WorkspaceConfig}, environment::get_update_lock_file_and_prefix, + lock_file::ReinstallPackages, prompt, workspace::{get_activated_environment_variables, Environment, HasWorkspaceRef}, UpdateLockFileOptions, Workspace, WorkspaceLocator, @@ -163,7 +164,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { && args.lock_file_update_config.no_lockfile_update, max_concurrent_solves: workspace.config().max_concurrent_solves(), }, - None, + ReinstallPackages::default(), ) .await?; diff --git a/src/cli/workspace/channel/add.rs b/src/cli/workspace/channel/add.rs index 45df48b3b9..a54a722170 100644 --- a/src/cli/workspace/channel/add.rs +++ b/src/cli/workspace/channel/add.rs @@ -1,6 +1,6 @@ use crate::{ environment::{get_update_lock_file_and_prefix, LockFileUsage}, - lock_file::UpdateMode, + lock_file::{ReinstallPackages, UpdateMode}, UpdateLockFileOptions, WorkspaceLocator, }; use miette::IntoDiagnostic; @@ -31,7 +31,7 @@ pub async fn execute(args: AddRemoveArgs) -> miette::Result<()> { && args.lock_file_update_config.no_lockfile_update, max_concurrent_solves: workspace.workspace().config().max_concurrent_solves(), }, - None, + ReinstallPackages::default(), ) .await?; diff --git a/src/cli/workspace/channel/remove.rs b/src/cli/workspace/channel/remove.rs index 1c9040f395..a1051f88dc 100644 --- a/src/cli/workspace/channel/remove.rs +++ b/src/cli/workspace/channel/remove.rs @@ -1,4 +1,4 @@ -use crate::lock_file::UpdateMode; +use crate::lock_file::{ReinstallPackages, UpdateMode}; use crate::{ environment::{get_update_lock_file_and_prefix, LockFileUsage}, UpdateLockFileOptions, WorkspaceLocator, @@ -29,7 +29,7 @@ pub async fn execute(args: AddRemoveArgs) -> miette::Result<()> { && args.lock_file_update_config.no_lockfile_update, max_concurrent_solves: workspace.workspace().config().max_concurrent_solves(), }, - None, + ReinstallPackages::default(), ) .await?; let workspace = workspace.save().await.into_diagnostic()?; diff --git a/src/cli/workspace/platform/add.rs b/src/cli/workspace/platform/add.rs index d5e2845633..75254e47a1 100644 --- a/src/cli/workspace/platform/add.rs +++ b/src/cli/workspace/platform/add.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use crate::{ environment::{get_update_lock_file_and_prefix, LockFileUsage}, - lock_file::UpdateMode, + lock_file::{ReinstallPackages, UpdateMode}, UpdateLockFileOptions, Workspace, }; use clap::Parser; @@ -55,7 +55,7 @@ pub async fn execute(workspace: Workspace, args: Args) -> miette::Result<()> { no_install: args.no_install, max_concurrent_solves: workspace.workspace().config().max_concurrent_solves(), }, - None, + ReinstallPackages::default(), ) .await?; workspace.save().await.into_diagnostic()?; diff --git a/src/cli/workspace/platform/remove.rs b/src/cli/workspace/platform/remove.rs index 64bf2fd6ce..3df8ab2f3f 100644 --- a/src/cli/workspace/platform/remove.rs +++ b/src/cli/workspace/platform/remove.rs @@ -5,7 +5,7 @@ use rattler_conda_types::Platform; use crate::{ environment::{get_update_lock_file_and_prefix, LockFileUsage}, - lock_file::UpdateMode, + lock_file::{ReinstallPackages, UpdateMode}, UpdateLockFileOptions, Workspace, }; @@ -45,7 +45,7 @@ pub async fn execute(workspace: Workspace, args: Args) -> miette::Result<()> { no_install: args.no_install, max_concurrent_solves: workspace.workspace().config().max_concurrent_solves(), }, - None, + ReinstallPackages::default(), ) .await?; workspace.save().await.into_diagnostic()?; diff --git a/src/environment/mod.rs b/src/environment/mod.rs index e13bb4bf8d..a78ee270f4 100644 --- a/src/environment/mod.rs +++ b/src/environment/mod.rs @@ -6,7 +6,7 @@ mod python_status; mod reporters; use std::{ - collections::{HashMap, HashSet}, + collections::HashMap, hash::{Hash, Hasher}, io::ErrorKind, path::{Path, PathBuf}, @@ -25,7 +25,7 @@ use serde::{Deserialize, Serialize}; use xxhash_rust::xxh3::Xxh3; use crate::{ - lock_file::{LockFileDerivedData, UpdateLockFileOptions, UpdateMode}, + lock_file::{LockFileDerivedData, ReinstallPackages, UpdateLockFileOptions, UpdateMode}, prefix::Prefix, rlimit::try_increase_rlimit_to_sensible, workspace::{grouped_environment::GroupedEnvironment, Environment, HasWorkspaceRef}, @@ -392,7 +392,7 @@ pub async fn get_update_lock_file_and_prefix<'env>( environment: &Environment<'env>, update_mode: UpdateMode, update_lock_file_options: UpdateLockFileOptions, - reinstall_packages: Option>, + reinstall_packages: ReinstallPackages, ) -> miette::Result<(LockFileDerivedData<'env>, Prefix)> { let current_platform = environment.best_platform(); let project = environment.workspace(); diff --git a/src/lock_file/mod.rs b/src/lock_file/mod.rs index d8f93640c7..78d5feac43 100644 --- a/src/lock_file/mod.rs +++ b/src/lock_file/mod.rs @@ -20,7 +20,7 @@ pub use satisfiability::{ verify_environment_satisfiability, verify_platform_satisfiability, EnvironmentUnsat, PlatformUnsat, }; -pub(crate) use update::{LockFileDerivedData, UpdateContext}; +pub use update::{LockFileDerivedData, ReinstallPackages, UpdateContext}; pub use update::{UpdateLockFileOptions, UpdateMode}; pub(crate) use utils::filter_lock_file; diff --git a/src/lock_file/update.rs b/src/lock_file/update.rs index 22a5a90fae..eb73eb4676 100644 --- a/src/lock_file/update.rs +++ b/src/lock_file/update.rs @@ -4,7 +4,6 @@ use std::{ future::{ready, Future}, iter, path::PathBuf, - str::FromStr, sync::Arc, time::{Duration, Instant}, }; @@ -213,6 +212,14 @@ pub struct UpdateLockFileOptions { pub max_concurrent_solves: usize, } +#[derive(Debug, Clone, Default)] +pub enum ReinstallPackages { + #[default] + None, + All, + Some(HashSet), +} + /// A struct that holds the lock-file and any potential derived data that was /// computed when calling `update_lock_file`. pub struct LockFileDerivedData<'p> { @@ -287,7 +294,7 @@ impl<'p> LockFileDerivedData<'p> { &mut self, environment: &Environment<'p>, update_mode: UpdateMode, - reinstall_packages: Option>, + reinstall_packages: ReinstallPackages, ) -> miette::Result { // Check if the prefix is already up-to-date by validating the hash with the // environment file @@ -366,7 +373,7 @@ impl<'p> LockFileDerivedData<'p> { async fn update_prefix( &mut self, environment: &Environment<'p>, - reinstall_packages: Option>, + reinstall_packages: ReinstallPackages, ) -> miette::Result { // If we previously updated this environment, early out. if let Some(prefix) = self.updated_pypi_prefixes.get(environment.name()) { @@ -388,14 +395,7 @@ impl<'p> LockFileDerivedData<'p> { tracing::info!("Updating prefix: '{}'", environment.dir().display()); // Get the prefix with the conda packages installed. let platform = environment.best_platform(); - let reinstall_packages_conda = reinstall_packages.clone().map(|p| { - p.into_iter() - .filter_map(|p| PackageName::from_str(&p).ok()) - .collect() - }); - let (prefix, python_status) = self - .conda_prefix(environment, reinstall_packages_conda) - .await?; + let (prefix, python_status) = self.conda_prefix(environment, todo!()).await?; let pixi_records = self .pixi_records(environment, platform) .into_diagnostic()? diff --git a/src/workspace/workspace_mut.rs b/src/workspace/workspace_mut.rs index 4b0e811565..84e75107c3 100644 --- a/src/workspace/workspace_mut.rs +++ b/src/workspace/workspace_mut.rs @@ -25,7 +25,7 @@ use crate::{ cli::cli_config::{LockFileUpdateConfig, PrefixUpdateConfig}, diff::LockFileDiff, environment::LockFileUsage, - lock_file::{LockFileDerivedData, UpdateContext, UpdateMode}, + lock_file::{LockFileDerivedData, ReinstallPackages, UpdateContext, UpdateMode}, workspace::{ grouped_environment::GroupedEnvironment, MatchSpecs, PypiDeps, SourceSpecs, UpdateDeps, NON_SEMVER_PACKAGES, @@ -422,7 +422,7 @@ impl WorkspaceMut { .prefix( &self.workspace().default_environment(), UpdateMode::Revalidate, - None, + ReinstallPackages::default(), ) .await?; } diff --git a/tests/integration_rust/common/mod.rs b/tests/integration_rust/common/mod.rs index c70739cbee..96d37be987 100644 --- a/tests/integration_rust/common/mod.rs +++ b/tests/integration_rust/common/mod.rs @@ -23,7 +23,7 @@ use pixi::{ task::{self, AddArgs, AliasArgs}, update, workspace, LockFileUsageConfig, }, - lock_file::UpdateMode, + lock_file::{ReinstallPackages, UpdateMode}, task::{ get_task_env, ExecutableTask, RunOutput, SearchEnvironments, TaskExecutionError, TaskGraph, TaskGraphError, TaskName, @@ -513,7 +513,11 @@ impl PixiControl { let task_env = match task_env.as_ref() { None => { lock_file - .prefix(&task.run_environment, UpdateMode::Revalidate, None) + .prefix( + &task.run_environment, + UpdateMode::Revalidate, + ReinstallPackages::default(), + ) .await?; let env = get_task_env(&task.run_environment, args.clean_env, None, false, false) diff --git a/tests/integration_rust/install_tests.rs b/tests/integration_rust/install_tests.rs index 293081b6e5..1bc681dd67 100644 --- a/tests/integration_rust/install_tests.rs +++ b/tests/integration_rust/install_tests.rs @@ -6,7 +6,7 @@ use crate::common::{ }; use crate::common::{LockFileExt, PixiControl}; use fs_err::tokio as tokio_fs; -use pixi::lock_file::UpdateMode; +use pixi::lock_file::{ReinstallPackages, UpdateMode}; use pixi::{ build::BuildContext, cli::{ @@ -598,7 +598,7 @@ async fn test_old_lock_install() { no_install: false, ..Default::default() }, - None, + ReinstallPackages::default(), ) .await .unwrap(); From 8dadeb69ca304b763ec9bcdc6110dc949b194e0a Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Thu, 13 Mar 2025 16:44:59 +0100 Subject: [PATCH 09/24] feat: set refresh strategy for the context --- src/lock_file/resolve/uv_resolution_context.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/lock_file/resolve/uv_resolution_context.rs b/src/lock_file/resolve/uv_resolution_context.rs index eb88a87182..43250775c4 100644 --- a/src/lock_file/resolve/uv_resolution_context.rs +++ b/src/lock_file/resolve/uv_resolution_context.rs @@ -75,4 +75,14 @@ impl UvResolutionContext { shared_state: SharedState::default(), }) } + + /// Set the cache refresh strategy. + pub fn set_cache_refresh( + mut self, + all: Option, + specific_packages: Option>, + ) { + let policy = uv_cache::Refresh::from_args(all, specific_packages.unwrap_or_default()); + self.cache = self.cache.with_refresh(policy); + } } From 00922b59ad1e6a65c6e01c32661884ae1dd0f166 Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Thu, 13 Mar 2025 16:49:58 +0100 Subject: [PATCH 10/24] Adapt for conda --- src/lock_file/update.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/lock_file/update.rs b/src/lock_file/update.rs index eb73eb4676..50d7aceb94 100644 --- a/src/lock_file/update.rs +++ b/src/lock_file/update.rs @@ -4,6 +4,7 @@ use std::{ future::{ready, Future}, iter, path::PathBuf, + str::FromStr, sync::Arc, time::{Duration, Instant}, }; @@ -393,13 +394,28 @@ impl<'p> LockFileDerivedData<'p> { ))?; tracing::info!("Updating prefix: '{}'", environment.dir().display()); - // Get the prefix with the conda packages installed. + let platform = environment.best_platform(); - let (prefix, python_status) = self.conda_prefix(environment, todo!()).await?; let pixi_records = self .pixi_records(environment, platform) .into_diagnostic()? .unwrap_or_default(); + + let conda_reinstall_packages = match reinstall_packages { + ReinstallPackages::None => None, + ReinstallPackages::Some(p) => Some( + p.into_iter() + .filter_map(|p| PackageName::from_str(&p).ok()) + .collect(), + ), + ReinstallPackages::All => Some(pixi_records.iter().map(|r| r.name().clone()).collect()), + }; + + // Get the prefix with the conda packages installed. + let (prefix, python_status) = self + .conda_prefix(environment, conda_reinstall_packages) + .await?; + let pypi_records = self .pypi_records(environment, platform) .into_diagnostic()? From 8c59bf68f1f358bca6c3a770082f5b7d402a7948 Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Thu, 13 Mar 2025 16:54:56 +0100 Subject: [PATCH 11/24] Adapt tests --- tests/integration_python/test_main_cli.py | 28 +++++++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/tests/integration_python/test_main_cli.py b/tests/integration_python/test_main_cli.py index 28c23e5f72..a75bcf0df2 100644 --- a/tests/integration_python/test_main_cli.py +++ b/tests/integration_python/test_main_cli.py @@ -1295,11 +1295,19 @@ def test_pixi_reinstall_multi_env( # Check that packages return "number 1" verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], + [pixi, "run", "--manifest-path", manifest, "--environment", "dev", "pypi-package-main"], stdout_contains="PyPI is number 1", ) verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "pixi-build-package-main"], + [ + pixi, + "run", + "--manifest-path", + manifest, + "--environment", + "dev", + "pixi-build-package-main", + ], stdout_contains="Pixi Build is number 1", ) @@ -1327,13 +1335,23 @@ def test_pixi_reinstall_multi_env( # After re-installing pypi-package, it should return "number 2" # pixi-build-package, should still return "number 1" - verify_cli_command([pixi, "reinstall", "--manifest-path", manifest, "pypi-package"]) verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], + [pixi, "reinstall", "--manifest-path", manifest, "--environment", "dev", "pypi-package"] + ) + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "--environment", "dev", "pypi-package-main"], stdout_contains="PyPI is number 2", ) verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "pixi-build-package-main"], + [ + pixi, + "run", + "--manifest-path", + manifest, + "--environment", + "dev", + "pixi-build-package-main", + ], stdout_contains="Pixi Build is number 1", ) From 6d90eaa2fb8e5520c0a810f0bf022952973a3a91 Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Thu, 13 Mar 2025 16:55:50 +0100 Subject: [PATCH 12/24] Reorder test a bit --- tests/integration_python/test_main_cli.py | 24 +++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/tests/integration_python/test_main_cli.py b/tests/integration_python/test_main_cli.py index a75bcf0df2..23fdefba36 100644 --- a/tests/integration_python/test_main_cli.py +++ b/tests/integration_python/test_main_cli.py @@ -1333,14 +1333,18 @@ def test_pixi_reinstall_multi_env( stdout_contains="Pixi Build is number 1", ) - # After re-installing pypi-package, it should return "number 2" - # pixi-build-package, should still return "number 1" + # After re-installing pixi-build-package, it should return "number 2" + # pypi-package, should still return "number 1" verify_cli_command( - [pixi, "reinstall", "--manifest-path", manifest, "--environment", "dev", "pypi-package"] - ) - verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "--environment", "dev", "pypi-package-main"], - stdout_contains="PyPI is number 2", + [ + pixi, + "reinstall", + "--manifest-path", + manifest, + "--environment", + "dev", + "pixi-build-package", + ] ) verify_cli_command( [ @@ -1352,7 +1356,11 @@ def test_pixi_reinstall_multi_env( "dev", "pixi-build-package-main", ], - stdout_contains="Pixi Build is number 1", + stdout_contains="Pixi Build is number 2", + ) + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "--environment", "dev", "pypi-package-main"], + stdout_contains="PyPI is number 1", ) # After re-installing both packages in the "dev" environment, From 80ccaa6e39cd6a31fadbaac2cdcf3b1085c3e82a Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Fri, 14 Mar 2025 09:19:03 +0100 Subject: [PATCH 13/24] Only use package names that are actually in our records --- src/lock_file/update.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lock_file/update.rs b/src/lock_file/update.rs index 50d7aceb94..b86390ecde 100644 --- a/src/lock_file/update.rs +++ b/src/lock_file/update.rs @@ -406,6 +406,7 @@ impl<'p> LockFileDerivedData<'p> { ReinstallPackages::Some(p) => Some( p.into_iter() .filter_map(|p| PackageName::from_str(&p).ok()) + .filter(|n| pixi_records.iter().any(|r| r.name() == n)) .collect(), ), ReinstallPackages::All => Some(pixi_records.iter().map(|r| r.name().clone()).collect()), From 597547dc52ca966825646944840fe20fb4f5fb73 Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Fri, 14 Mar 2025 09:19:05 +0100 Subject: [PATCH 14/24] feat: set refresh strategy for the cache --- .../resolve/uv_resolution_context.rs | 3 +- src/lock_file/update.rs | 31 ++++++++++++++++--- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/lock_file/resolve/uv_resolution_context.rs b/src/lock_file/resolve/uv_resolution_context.rs index 43250775c4..48afa492f7 100644 --- a/src/lock_file/resolve/uv_resolution_context.rs +++ b/src/lock_file/resolve/uv_resolution_context.rs @@ -81,8 +81,9 @@ impl UvResolutionContext { mut self, all: Option, specific_packages: Option>, - ) { + ) -> Self { let policy = uv_cache::Refresh::from_args(all, specific_packages.unwrap_or_default()); self.cache = self.cache.with_refresh(policy); + self } } diff --git a/src/lock_file/update.rs b/src/lock_file/update.rs index b86390ecde..29dfe93cfe 100644 --- a/src/lock_file/update.rs +++ b/src/lock_file/update.rs @@ -403,10 +403,10 @@ impl<'p> LockFileDerivedData<'p> { let conda_reinstall_packages = match reinstall_packages { ReinstallPackages::None => None, - ReinstallPackages::Some(p) => Some( - p.into_iter() - .filter_map(|p| PackageName::from_str(&p).ok()) - .filter(|n| pixi_records.iter().any(|r| r.name() == n)) + ReinstallPackages::Some(ref p) => Some( + p.iter() + .filter_map(|p| PackageName::from_str(p).ok()) + .filter(|name| pixi_records.iter().any(|r| r.name() == name)) .collect(), ), ReinstallPackages::All => Some(pixi_records.iter().map(|r| r.name().clone()).collect()), @@ -427,9 +427,30 @@ impl<'p> LockFileDerivedData<'p> { return Ok(prefix); } + let pypi_lock_file_names = pypi_records + .iter() + .filter_map(|(data, _)| to_uv_normalize(&data.name).ok()) + .collect::>(); + + // Figure out uv reinstall + let (uv_reinstall, uv_packages) = match reinstall_packages { + ReinstallPackages::None => (Some(false), None), + ReinstallPackages::All => (Some(true), None), + ReinstallPackages::Some(pkgs) => ( + None, + Some( + pkgs.into_iter() + .filter_map(|pkg| uv_pep508::PackageName::new(pkg).ok()) + .filter(|name| pypi_lock_file_names.contains(name)) + .collect(), + ), + ), + }; + let uv_context = match &self.uv_context { None => { - let context = UvResolutionContext::from_workspace(self.workspace)?; + let context = UvResolutionContext::from_workspace(self.workspace)? + .set_cache_refresh(uv_reinstall, uv_packages); self.uv_context = Some(context.clone()); context } From 5f1881c71479ffdae87a2927cd59eb9a5f6d379e Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Fri, 14 Mar 2025 09:46:33 +0100 Subject: [PATCH 15/24] fix: test for osx-arm64 --- tests/data/mock-projects/test-rebuild/pixi.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/data/mock-projects/test-rebuild/pixi.toml b/tests/data/mock-projects/test-rebuild/pixi.toml index 1c4ea935e6..a7aa202740 100644 --- a/tests/data/mock-projects/test-rebuild/pixi.toml +++ b/tests/data/mock-projects/test-rebuild/pixi.toml @@ -2,7 +2,7 @@ authors = ["Julian Hofer "] channels = ["https://prefix.dev/conda-forge"] name = "test-rebuild" -platforms = ["linux-64"] +platforms = ["linux-64", "osx-arm64", "win-64"] preview = ["pixi-build"] version = "0.1.0" From 4c551d110446541d136c29e6c4dd0587edbc1da2 Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Fri, 14 Mar 2025 10:13:02 +0100 Subject: [PATCH 16/24] Fix test --- tests/integration_python/test_main_cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration_python/test_main_cli.py b/tests/integration_python/test_main_cli.py index 23fdefba36..df2106898a 100644 --- a/tests/integration_python/test_main_cli.py +++ b/tests/integration_python/test_main_cli.py @@ -1241,7 +1241,7 @@ def test_pixi_reinstall_default_env( # Modify the Python files pypi_package_init.write_text(pypi_package_init.read_text().replace("1", "2")) - pixi_build_package_init.write_text(pypi_package_init.read_text().replace("1", "2")) + pixi_build_package_init.write_text(pixi_build_package_init.read_text().replace("1", "2")) # That shouldn't trigger a re-install, so running still returns "number 1" verify_cli_command( @@ -1313,7 +1313,7 @@ def test_pixi_reinstall_multi_env( # Modify the Python files pypi_package_init.write_text(pypi_package_init.read_text().replace("1", "2")) - pixi_build_package_init.write_text(pypi_package_init.read_text().replace("1", "2")) + pixi_build_package_init.write_text(pixi_build_package_init.read_text().replace("1", "2")) # That shouldn't trigger a re-install, so running still returns "number 1" verify_cli_command( From a6f0c0bfb8412fe7c504ba5fdc39e5a071143645 Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Fri, 14 Mar 2025 10:15:11 +0100 Subject: [PATCH 17/24] pixi build rebuild --- src/build/mod.rs | 11 +++++++---- src/environment/conda_prefix.rs | 6 ++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/build/mod.rs b/src/build/mod.rs index 07a39d3e6a..c0bc9bdcef 100644 --- a/src/build/mod.rs +++ b/src/build/mod.rs @@ -257,6 +257,7 @@ impl BuildContext { build_reporter: Arc, source_reporter: Option>, build_id: usize, + rebuild: bool, ) -> Result { let source_checkout = SourceCheckout { path: self @@ -287,10 +288,12 @@ impl BuildContext { .await?; // Check if there are already cached builds - if let Some(build) = cached_build { - if let Some(record) = Self::cached_build_source_record(build, &source_checkout)? { - build_reporter.on_build_cached(build_id); - return Ok(record); + if !rebuild { + if let Some(build) = cached_build { + if let Some(record) = Self::cached_build_source_record(build, &source_checkout)? { + build_reporter.on_build_cached(build_id); + return Ok(record); + } } } diff --git a/src/environment/conda_prefix.rs b/src/environment/conda_prefix.rs index c631d78584..6a53cca29d 100644 --- a/src/environment/conda_prefix.rs +++ b/src/environment/conda_prefix.rs @@ -315,6 +315,11 @@ pub async fn update_prefix_conda( let build_context = &build_context; let channels = &channels; let virtual_packages = &virtual_packages; + let rebuild = reinstall_packages + .as_ref() + .map(|packages| packages.iter().any(|p| p == &record.package_record.name)) + .unwrap_or(true); + async move { build_context .build_source_record( @@ -326,6 +331,7 @@ pub async fn update_prefix_conda( progress_reporter.clone(), Some(source_reporter), build_id, + rebuild, ) .await } From 7cbc7aca17ca3d506b41d07f127af127156c9ccd Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Fri, 14 Mar 2025 11:03:35 +0100 Subject: [PATCH 18/24] Fix pixi toml --- .../mock-projects/test-rebuild/pixi_build_package/pixi.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/data/mock-projects/test-rebuild/pixi_build_package/pixi.toml b/tests/data/mock-projects/test-rebuild/pixi_build_package/pixi.toml index d0a2cb3e1a..21583b94f8 100644 --- a/tests/data/mock-projects/test-rebuild/pixi_build_package/pixi.toml +++ b/tests/data/mock-projects/test-rebuild/pixi_build_package/pixi.toml @@ -4,3 +4,7 @@ version = "0.1.0" [package.build] backend = { name = "pixi-build-rattler-build", version = "0.1.*" } +channels = [ + "https://prefix.dev/pixi-build-backends", + "https://prefix.dev/conda-forge", +] From 61b7b0fb29529acc9ecb3eb6291179e0d840a1ba Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Fri, 14 Mar 2025 13:39:00 +0100 Subject: [PATCH 19/24] Fix tests (mostly) --- src/environment/conda_prefix.rs | 2 +- tests/integration_python/test_main_cli.py | 203 --------------- tests/integration_python/test_reinstall.py | 276 +++++++++++++++++++++ 3 files changed, 277 insertions(+), 204 deletions(-) create mode 100644 tests/integration_python/test_reinstall.py diff --git a/src/environment/conda_prefix.rs b/src/environment/conda_prefix.rs index 6a53cca29d..293980c3fa 100644 --- a/src/environment/conda_prefix.rs +++ b/src/environment/conda_prefix.rs @@ -318,7 +318,7 @@ pub async fn update_prefix_conda( let rebuild = reinstall_packages .as_ref() .map(|packages| packages.iter().any(|p| p == &record.package_record.name)) - .unwrap_or(true); + .unwrap_or(false); async move { build_context diff --git a/tests/integration_python/test_main_cli.py b/tests/integration_python/test_main_cli.py index df2106898a..8c9b99a6be 100644 --- a/tests/integration_python/test_main_cli.py +++ b/tests/integration_python/test_main_cli.py @@ -1212,206 +1212,3 @@ def test_pixi_info_tasks(pixi: Path, tmp_pixi_workspace: Path) -> None: """ manifest.write_text(toml) verify_cli_command([pixi, "info", "--manifest-path", manifest], stdout_contains="foo, bar") - - -@pytest.mark.slow -def test_pixi_reinstall_default_env( - pixi: Path, tmp_pixi_workspace: Path, mock_projects: Path -) -> None: - manifest = tmp_pixi_workspace.joinpath("pixi.toml") - test_rebuild_src = mock_projects / "test-rebuild" - shutil.copytree(test_rebuild_src, tmp_pixi_workspace, dirs_exist_ok=True) - - pypi_package_init = tmp_pixi_workspace.joinpath( - "pypi_package", "src", "pypi_package", "__init__.py" - ) - pixi_build_package_init = tmp_pixi_workspace.joinpath( - "pixi_build_package", "src", "pixi_build_package", "__init__.py" - ) - - # Check that packages return "number 1" - verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], - stdout_contains="PyPI is number 1", - ) - verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "pixi-build-package-main"], - stdout_contains="Pixi Build is number 1", - ) - - # Modify the Python files - pypi_package_init.write_text(pypi_package_init.read_text().replace("1", "2")) - pixi_build_package_init.write_text(pixi_build_package_init.read_text().replace("1", "2")) - - # That shouldn't trigger a re-install, so running still returns "number 1" - verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], - stdout_contains="PyPI is number 1", - ) - verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "pixi-build-package-main"], - stdout_contains="Pixi Build is number 1", - ) - - # After re-installing pypi-package, it should return "number 2" - # pixi-build-package, should still return "number 1" - verify_cli_command([pixi, "reinstall", "--manifest-path", manifest, "pypi-package"]) - verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], - stdout_contains="PyPI is number 2", - ) - verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "pixi-build-package-main"], - stdout_contains="Pixi Build is number 1", - ) - - # After re-installing the whole default environment, - # both should return "number 2" - verify_cli_command([pixi, "reinstall", "--manifest-path", manifest]) - verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], - stdout_contains="PyPI is number 2", - ) - verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "pixi-build-package-main"], - stdout_contains="Pixi Build is number 2", - ) - - -@pytest.mark.slow -def test_pixi_reinstall_multi_env( - pixi: Path, tmp_pixi_workspace: Path, mock_projects: Path -) -> None: - manifest = tmp_pixi_workspace.joinpath("pixi.toml") - test_rebuild_src = mock_projects / "test-rebuild" - shutil.copytree(test_rebuild_src, tmp_pixi_workspace, dirs_exist_ok=True) - - pypi_package_init = tmp_pixi_workspace.joinpath( - "pypi_package", "src", "pypi_package", "__init__.py" - ) - pixi_build_package_init = tmp_pixi_workspace.joinpath( - "pixi_build_package", "src", "pixi_build_package", "__init__.py" - ) - - # Check that packages return "number 1" - verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "--environment", "dev", "pypi-package-main"], - stdout_contains="PyPI is number 1", - ) - verify_cli_command( - [ - pixi, - "run", - "--manifest-path", - manifest, - "--environment", - "dev", - "pixi-build-package-main", - ], - stdout_contains="Pixi Build is number 1", - ) - - # Modify the Python files - pypi_package_init.write_text(pypi_package_init.read_text().replace("1", "2")) - pixi_build_package_init.write_text(pixi_build_package_init.read_text().replace("1", "2")) - - # That shouldn't trigger a re-install, so running still returns "number 1" - verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "--environment", "dev", "pypi-package-main"], - stdout_contains="PyPI is number 1", - ) - verify_cli_command( - [ - pixi, - "run", - "--manifest-path", - manifest, - "--environment", - "dev", - "pixi-build-package-main", - ], - stdout_contains="Pixi Build is number 1", - ) - - # After re-installing pixi-build-package, it should return "number 2" - # pypi-package, should still return "number 1" - verify_cli_command( - [ - pixi, - "reinstall", - "--manifest-path", - manifest, - "--environment", - "dev", - "pixi-build-package", - ] - ) - verify_cli_command( - [ - pixi, - "run", - "--manifest-path", - manifest, - "--environment", - "dev", - "pixi-build-package-main", - ], - stdout_contains="Pixi Build is number 2", - ) - verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "--environment", "dev", "pypi-package-main"], - stdout_contains="PyPI is number 1", - ) - - # After re-installing both packages in the "dev" environment, - # both should return "number 2" - verify_cli_command( - [ - pixi, - "reinstall", - "--manifest-path", - manifest, - "--environment", - "dev", - "pypi-package", - "pixi-build-package-main", - ] - ) - verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "--environment", "dev", "pypi-package-main"], - stdout_contains="PyPI is number 2", - ) - verify_cli_command( - [ - pixi, - "run", - "--manifest-path", - manifest, - "--environment", - "dev", - "pixi-build-package-main", - ], - stdout_contains="Pixi Build is number 2", - ) - - # In the default environment, it should still be "number 1" - verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], - stdout_contains="PyPI is number 1", - ) - verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "pixi-build-package-main"], - stdout_contains="Pixi Build is number 1", - ) - - # After reinstalling all environments, - # also the default environment should be "number 2" - verify_cli_command([pixi, "reinstall", "--manifest-path", manifest, "--all"]) - verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], - stdout_contains="PyPI is number 2", - ) - verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "pixi-build-package-main"], - stdout_contains="Pixi Build is number 2", - ) diff --git a/tests/integration_python/test_reinstall.py b/tests/integration_python/test_reinstall.py new file mode 100644 index 0000000000..d51f8881f2 --- /dev/null +++ b/tests/integration_python/test_reinstall.py @@ -0,0 +1,276 @@ +from .common import verify_cli_command +import pytest +from pathlib import Path +import shutil +import tomllib +import tomli_w + + +@pytest.fixture +def reinstall_workspace(tmp_pixi_workspace: Path, mock_projects: Path) -> Path: + test_rebuild_src = mock_projects / "test-rebuild" + shutil.rmtree(test_rebuild_src.joinpath(".pixi"), ignore_errors=True) + shutil.copytree(test_rebuild_src, tmp_pixi_workspace, dirs_exist_ok=True) + + # Enable debug logging + package_manifest = tmp_pixi_workspace.joinpath("pixi_build_package", "pixi.toml") + manifest_dict = tomllib.loads(package_manifest.read_text()) + manifest_dict["package"]["build"]["configuration"] = {"debug-dir": str(tmp_pixi_workspace)} + package_manifest.write_text(tomli_w.dumps(manifest_dict)) + + return tmp_pixi_workspace + + +@pytest.mark.slow +def test_pixi_reinstall_default_env(pixi: Path, reinstall_workspace: Path) -> None: + env = { + "PIXI_CACHE_DIR": str(reinstall_workspace.joinpath("pixi_cache")), + } + manifest = reinstall_workspace.joinpath("pixi.toml") + pypi_package_init = reinstall_workspace.joinpath( + "pypi_package", "src", "pypi_package", "__init__.py" + ) + pixi_build_package_init = reinstall_workspace.joinpath( + "pixi_build_package", "src", "pixi_build_package", "__init__.py" + ) + conda_metadata_params = reinstall_workspace.joinpath("conda_metadata_params.json") + conda_build_params = reinstall_workspace.joinpath("conda_build_params.json") + + # Check that packages return "number 1" + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], + stdout_contains="PyPI is number 1", + env=env, + ) + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pixi-build-package-main"], + stdout_contains="Pixi Build is number 1", + env=env, + ) + + # In order to build pixi-build-package-main, getMetadata and build has been called + assert conda_metadata_params.is_file() + assert conda_build_params.is_file() + + # Delete the files to get a clean state + conda_metadata_params.unlink() + conda_build_params.unlink() + + # Modify the Python files + pypi_package_init.write_text(pypi_package_init.read_text().replace("1", "2")) + pixi_build_package_init.write_text(pixi_build_package_init.read_text().replace("1", "2")) + + # That shouldn't trigger a re-install, so running still returns "number 1" + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], + stdout_contains="PyPI is number 1", + env=env, + ) + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pixi-build-package-main"], + stdout_contains="Pixi Build is number 1", + env=env, + ) + + # Everything pixi-build related is cached, no remote procedure was called + assert not conda_metadata_params.is_file() + assert not conda_build_params.is_file() + + # After re-installing pypi-package, it should return "number 2" + # pixi-build-package, should not be rebuild and therefore still return "number 1" + verify_cli_command([pixi, "reinstall", "--manifest-path", manifest, "pypi-package"], env=env) + assert not conda_metadata_params.is_file() + assert not conda_build_params.is_file() + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], + stdout_contains="PyPI is number 2", + env=env, + ) + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pixi-build-package-main"], + stdout_contains="Pixi Build is number 1", + env=env, + ) + + # After re-installing the whole default environment, + # both should return "number 2" + verify_cli_command([pixi, "reinstall", "--manifest-path", manifest], env=env) + assert not conda_metadata_params.is_file() + assert conda_build_params.is_file() + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], + stdout_contains="PyPI is number 2", + env=env, + ) + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pixi-build-package-main"], + stdout_contains="Pixi Build is number 2", + env=env, + ) + + +@pytest.mark.slow +def test_pixi_reinstall_multi_env(pixi: Path, reinstall_workspace: Path) -> None: + env = { + "PIXI_CACHE_DIR": str(reinstall_workspace.joinpath("pixi_cache")), + } + manifest = reinstall_workspace.joinpath("pixi.toml") + pypi_package_init = reinstall_workspace.joinpath( + "pypi_package", "src", "pypi_package", "__init__.py" + ) + pixi_build_package_init = reinstall_workspace.joinpath( + "pixi_build_package", "src", "pixi_build_package", "__init__.py" + ) + conda_metadata_params = reinstall_workspace.joinpath("conda_metadata_params.json") + conda_build_params = reinstall_workspace.joinpath("conda_build_params.json") + + # Check that packages return "number 1" + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "--environment", "dev", "pypi-package-main"], + stdout_contains="PyPI is number 1", + env=env, + ) + verify_cli_command( + [ + pixi, + "run", + "--manifest-path", + manifest, + "--environment", + "dev", + "pixi-build-package-main", + ], + stdout_contains="Pixi Build is number 1", + env=env, + ) + + # In order to build pixi-build-package-main, getMetadata and build has been called + assert conda_metadata_params.is_file() + assert conda_build_params.is_file() + + # Delete the files to get a clean state + conda_metadata_params.unlink() + conda_build_params.unlink() + + # Modify the Python files + pypi_package_init.write_text(pypi_package_init.read_text().replace("1", "2")) + pixi_build_package_init.write_text(pixi_build_package_init.read_text().replace("1", "2")) + + # That shouldn't trigger a re-install, so running still returns "number 1" + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "--environment", "dev", "pypi-package-main"], + stdout_contains="PyPI is number 1", + env=env, + ) + verify_cli_command( + [ + pixi, + "run", + "--manifest-path", + manifest, + "--environment", + "dev", + "pixi-build-package-main", + ], + stdout_contains="Pixi Build is number 1", + env=env, + ) + + # Everything pixi-build related is cached, no remote procedure was called + assert not conda_metadata_params.is_file() + assert not conda_build_params.is_file() + + # After re-building pixi-build-package, it should return "number 2" + # pypi-package, should still return "number 1" + verify_cli_command( + [ + pixi, + "reinstall", + "--manifest-path", + manifest, + "--environment", + "dev", + "pixi-build-package", + ], + env=env, + ) + assert not conda_metadata_params.is_file() + assert conda_build_params.is_file() + verify_cli_command( + [ + pixi, + "run", + "--manifest-path", + manifest, + "--environment", + "dev", + "pixi-build-package-main", + ], + stdout_contains="Pixi Build is number 2", + env=env, + ) + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "--environment", "dev", "pypi-package-main"], + stdout_contains="PyPI is number 1", + env=env, + ) + + # After re-installing both packages in the "dev" environment, + # both should return "number 2" + verify_cli_command( + [ + pixi, + "reinstall", + "--manifest-path", + manifest, + "--environment", + "dev", + "pypi-package", + "pixi-build-package-main", + ], + env=env, + ) + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "--environment", "dev", "pypi-package-main"], + stdout_contains="PyPI is number 2", + env=env, + ) + verify_cli_command( + [ + pixi, + "run", + "--manifest-path", + manifest, + "--environment", + "dev", + "pixi-build-package-main", + ], + stdout_contains="Pixi Build is number 2", + env=env, + ) + + # In the default environment, it should still be "number 1" + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], + stdout_contains="PyPI is number 1", + env=env, + ) + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pixi-build-package-main"], + stdout_contains="Pixi Build is number 1", + env=env, + ) + + # After reinstalling all environments, + # also the default environment should be "number 2" + verify_cli_command([pixi, "reinstall", "--manifest-path", manifest, "--all"], env=env) + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], + stdout_contains="PyPI is number 2", + env=env, + ) + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "pixi-build-package-main"], + stdout_contains="Pixi Build is number 2", + env=env, + ) From 9b13afd18c0accb762bd71192b44d5cba21d5cb9 Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Fri, 14 Mar 2025 14:40:15 +0100 Subject: [PATCH 20/24] Finalize tests --- .../data/mock-projects/test-rebuild/pixi.toml | 4 +- .../pixi_build_package_dev/pixi.toml | 10 ++ .../pixi_build_package_dev/pyproject.toml | 11 ++ .../pixi_build_package_dev/recipe.yaml | 18 +++ .../src/pixi_build_package_dev/__init__.py | 2 + .../pypi_package_dev/pyproject.toml | 11 ++ .../src/pypi_package_dev/__init__.py | 2 + tests/integration_python/test_reinstall.py | 136 +++++++++++------- 8 files changed, 142 insertions(+), 52 deletions(-) create mode 100644 tests/data/mock-projects/test-rebuild/pixi_build_package_dev/pixi.toml create mode 100644 tests/data/mock-projects/test-rebuild/pixi_build_package_dev/pyproject.toml create mode 100644 tests/data/mock-projects/test-rebuild/pixi_build_package_dev/recipe.yaml create mode 100644 tests/data/mock-projects/test-rebuild/pixi_build_package_dev/src/pixi_build_package_dev/__init__.py create mode 100644 tests/data/mock-projects/test-rebuild/pypi_package_dev/pyproject.toml create mode 100644 tests/data/mock-projects/test-rebuild/pypi_package_dev/src/pypi_package_dev/__init__.py diff --git a/tests/data/mock-projects/test-rebuild/pixi.toml b/tests/data/mock-projects/test-rebuild/pixi.toml index a7aa202740..a36ca54be0 100644 --- a/tests/data/mock-projects/test-rebuild/pixi.toml +++ b/tests/data/mock-projects/test-rebuild/pixi.toml @@ -16,11 +16,11 @@ python = ">=3.13.2,<3.14" pypi_package = { path = "pypi_package" } [feature.dev.dependencies] -pixi_build_package = { path = "pixi_build_package" } +pixi_build_package_dev = { path = "pixi_build_package_dev" } python = ">=3.13.2,<3.14" [feature.dev.pypi-dependencies] -pypi_package = { path = "pypi_package" } +pypi_package_dev = { path = "pypi_package_dev" } [environments] dev = { features = ["dev"], no-default-feature = true } diff --git a/tests/data/mock-projects/test-rebuild/pixi_build_package_dev/pixi.toml b/tests/data/mock-projects/test-rebuild/pixi_build_package_dev/pixi.toml new file mode 100644 index 0000000000..07f1a034a4 --- /dev/null +++ b/tests/data/mock-projects/test-rebuild/pixi_build_package_dev/pixi.toml @@ -0,0 +1,10 @@ +[package] +name = "pixi_build_package_dev" +version = "0.1.0" + +[package.build] +backend = { name = "pixi-build-rattler-build", version = "0.1.*" } +channels = [ + "https://prefix.dev/pixi-build-backends", + "https://prefix.dev/conda-forge", +] diff --git a/tests/data/mock-projects/test-rebuild/pixi_build_package_dev/pyproject.toml b/tests/data/mock-projects/test-rebuild/pixi_build_package_dev/pyproject.toml new file mode 100644 index 0000000000..f1e58bb856 --- /dev/null +++ b/tests/data/mock-projects/test-rebuild/pixi_build_package_dev/pyproject.toml @@ -0,0 +1,11 @@ +[project] +authors = [{ name = "Julian Hofer", email = "julianhofer@gnome.org" }] +dependencies = [] +name = "pixi_build_package_dev" +requires-python = ">= 3.11" +scripts = { pixi-build-package-dev-main = "pixi_build_package_dev:main" } +version = "0.1.0" + +[build-system] +build-backend = "hatchling.build" +requires = ["hatchling"] diff --git a/tests/data/mock-projects/test-rebuild/pixi_build_package_dev/recipe.yaml b/tests/data/mock-projects/test-rebuild/pixi_build_package_dev/recipe.yaml new file mode 100644 index 0000000000..094da528f7 --- /dev/null +++ b/tests/data/mock-projects/test-rebuild/pixi_build_package_dev/recipe.yaml @@ -0,0 +1,18 @@ +package: + name: pixi_build_package_dev + version: 0.1.0 + +source: + path: . + use_gitignore: true + +build: + noarch: python + number: 0 + script: | + pip install . --no-deps -vv +requirements: + host: + - pip + - python + - hatchling diff --git a/tests/data/mock-projects/test-rebuild/pixi_build_package_dev/src/pixi_build_package_dev/__init__.py b/tests/data/mock-projects/test-rebuild/pixi_build_package_dev/src/pixi_build_package_dev/__init__.py new file mode 100644 index 0000000000..66e8814e70 --- /dev/null +++ b/tests/data/mock-projects/test-rebuild/pixi_build_package_dev/src/pixi_build_package_dev/__init__.py @@ -0,0 +1,2 @@ +def main() -> None: + print("Pixi Build dev is number 1") diff --git a/tests/data/mock-projects/test-rebuild/pypi_package_dev/pyproject.toml b/tests/data/mock-projects/test-rebuild/pypi_package_dev/pyproject.toml new file mode 100644 index 0000000000..8ebbb4f304 --- /dev/null +++ b/tests/data/mock-projects/test-rebuild/pypi_package_dev/pyproject.toml @@ -0,0 +1,11 @@ +[project] +authors = [{ name = "Julian Hofer", email = "julianhofer@gnome.org" }] +dependencies = [] +name = "pypi_package_dev" +requires-python = ">= 3.11" +scripts = { pypi-package-dev-main = "pypi_package_dev:main" } +version = "0.1.0" + +[build-system] +build-backend = "hatchling.build" +requires = ["hatchling"] diff --git a/tests/data/mock-projects/test-rebuild/pypi_package_dev/src/pypi_package_dev/__init__.py b/tests/data/mock-projects/test-rebuild/pypi_package_dev/src/pypi_package_dev/__init__.py new file mode 100644 index 0000000000..88fb4d118d --- /dev/null +++ b/tests/data/mock-projects/test-rebuild/pypi_package_dev/src/pypi_package_dev/__init__.py @@ -0,0 +1,2 @@ +def main() -> None: + print("PyPI dev is number 1") diff --git a/tests/integration_python/test_reinstall.py b/tests/integration_python/test_reinstall.py index d51f8881f2..8abea0c3c9 100644 --- a/tests/integration_python/test_reinstall.py +++ b/tests/integration_python/test_reinstall.py @@ -13,10 +13,13 @@ def reinstall_workspace(tmp_pixi_workspace: Path, mock_projects: Path) -> Path: shutil.copytree(test_rebuild_src, tmp_pixi_workspace, dirs_exist_ok=True) # Enable debug logging - package_manifest = tmp_pixi_workspace.joinpath("pixi_build_package", "pixi.toml") - manifest_dict = tomllib.loads(package_manifest.read_text()) - manifest_dict["package"]["build"]["configuration"] = {"debug-dir": str(tmp_pixi_workspace)} - package_manifest.write_text(tomli_w.dumps(manifest_dict)) + packages = ["pixi_build_package", "pixi_build_package_dev"] + for package in packages: + package_dir = tmp_pixi_workspace / package + package_manifest = package_dir / "pixi.toml" + manifest_dict = tomllib.loads(package_manifest.read_text()) + manifest_dict["package"]["build"]["configuration"] = {"debug-dir": str(package_dir)} + package_manifest.write_text(tomli_w.dumps(manifest_dict)) return tmp_pixi_workspace @@ -27,14 +30,12 @@ def test_pixi_reinstall_default_env(pixi: Path, reinstall_workspace: Path) -> No "PIXI_CACHE_DIR": str(reinstall_workspace.joinpath("pixi_cache")), } manifest = reinstall_workspace.joinpath("pixi.toml") - pypi_package_init = reinstall_workspace.joinpath( - "pypi_package", "src", "pypi_package", "__init__.py" + conda_metadata_params = reinstall_workspace.joinpath( + "pixi_build_package", "conda_metadata_params.json" ) - pixi_build_package_init = reinstall_workspace.joinpath( - "pixi_build_package", "src", "pixi_build_package", "__init__.py" + conda_build_params = reinstall_workspace.joinpath( + "pixi_build_package", "conda_build_params.json" ) - conda_metadata_params = reinstall_workspace.joinpath("conda_metadata_params.json") - conda_build_params = reinstall_workspace.joinpath("conda_build_params.json") # Check that packages return "number 1" verify_cli_command( @@ -57,8 +58,9 @@ def test_pixi_reinstall_default_env(pixi: Path, reinstall_workspace: Path) -> No conda_build_params.unlink() # Modify the Python files - pypi_package_init.write_text(pypi_package_init.read_text().replace("1", "2")) - pixi_build_package_init.write_text(pixi_build_package_init.read_text().replace("1", "2")) + for package in ["pypi_package", "pixi_build_package"]: + init_py = reinstall_workspace.joinpath(package, "src", package, "__init__.py") + init_py.write_text(init_py.read_text().replace("1", "2")) # That shouldn't trigger a re-install, so running still returns "number 1" verify_cli_command( @@ -78,7 +80,7 @@ def test_pixi_reinstall_default_env(pixi: Path, reinstall_workspace: Path) -> No # After re-installing pypi-package, it should return "number 2" # pixi-build-package, should not be rebuild and therefore still return "number 1" - verify_cli_command([pixi, "reinstall", "--manifest-path", manifest, "pypi-package"], env=env) + verify_cli_command([pixi, "reinstall", "--manifest-path", manifest, "pypi_package"], env=env) assert not conda_metadata_params.is_file() assert not conda_build_params.is_file() verify_cli_command( @@ -115,18 +117,22 @@ def test_pixi_reinstall_multi_env(pixi: Path, reinstall_workspace: Path) -> None "PIXI_CACHE_DIR": str(reinstall_workspace.joinpath("pixi_cache")), } manifest = reinstall_workspace.joinpath("pixi.toml") - pypi_package_init = reinstall_workspace.joinpath( - "pypi_package", "src", "pypi_package", "__init__.py" + conda_metadata_params = reinstall_workspace.joinpath( + "pixi_build_package", "conda_metadata_params.json" ) - pixi_build_package_init = reinstall_workspace.joinpath( - "pixi_build_package", "src", "pixi_build_package", "__init__.py" + conda_build_params = reinstall_workspace.joinpath( + "pixi_build_package", "conda_build_params.json" + ) + conda_metadata_params_dev = reinstall_workspace.joinpath( + "pixi_build_package_dev", "conda_metadata_params.json" + ) + conda_build_params_dev = reinstall_workspace.joinpath( + "pixi_build_package_dev", "conda_build_params.json" ) - conda_metadata_params = reinstall_workspace.joinpath("conda_metadata_params.json") - conda_build_params = reinstall_workspace.joinpath("conda_build_params.json") - # Check that packages return "number 1" + # Check that packages return "number 1" in default environment verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "--environment", "dev", "pypi-package-main"], + [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], stdout_contains="PyPI is number 1", env=env, ) @@ -136,30 +142,54 @@ def test_pixi_reinstall_multi_env(pixi: Path, reinstall_workspace: Path) -> None "run", "--manifest-path", manifest, - "--environment", - "dev", "pixi-build-package-main", ], stdout_contains="Pixi Build is number 1", env=env, ) + # Check that packages return "number 1" in dev environment + verify_cli_command( + [pixi, "run", "--manifest-path", manifest, "--environment", "dev", "pypi-package-dev-main"], + stdout_contains="PyPI dev is number 1", + env=env, + ) + verify_cli_command( + [ + pixi, + "run", + "--manifest-path", + manifest, + "--environment", + "dev", + "pixi-build-package-dev-main", + ], + stdout_contains="Pixi Build dev is number 1", + env=env, + ) + # In order to build pixi-build-package-main, getMetadata and build has been called - assert conda_metadata_params.is_file() - assert conda_build_params.is_file() + assert conda_metadata_params_dev.is_file() + assert conda_build_params_dev.is_file() # Delete the files to get a clean state - conda_metadata_params.unlink() - conda_build_params.unlink() + conda_metadata_params_dev.unlink() + conda_build_params_dev.unlink() # Modify the Python files - pypi_package_init.write_text(pypi_package_init.read_text().replace("1", "2")) - pixi_build_package_init.write_text(pixi_build_package_init.read_text().replace("1", "2")) + for package in [ + "pypi_package", + "pixi_build_package", + "pypi_package_dev", + "pixi_build_package_dev", + ]: + init_py = reinstall_workspace.joinpath(package, "src", package, "__init__.py") + init_py.write_text(init_py.read_text().replace("1", "2")) # That shouldn't trigger a re-install, so running still returns "number 1" verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "--environment", "dev", "pypi-package-main"], - stdout_contains="PyPI is number 1", + [pixi, "run", "--manifest-path", manifest, "--environment", "dev", "pypi-package-dev-main"], + stdout_contains="PyPI dev is number 1", env=env, ) verify_cli_command( @@ -170,18 +200,18 @@ def test_pixi_reinstall_multi_env(pixi: Path, reinstall_workspace: Path) -> None manifest, "--environment", "dev", - "pixi-build-package-main", + "pixi-build-package-dev-main", ], - stdout_contains="Pixi Build is number 1", + stdout_contains="Pixi Build dev is number 1", env=env, ) # Everything pixi-build related is cached, no remote procedure was called - assert not conda_metadata_params.is_file() - assert not conda_build_params.is_file() + assert not conda_metadata_params_dev.is_file() + assert not conda_build_params_dev.is_file() - # After re-building pixi-build-package, it should return "number 2" - # pypi-package, should still return "number 1" + # After re-building pixi_build_package_dev, it should return "number 2" + # pypi_package_dev, should still return "number 1" verify_cli_command( [ pixi, @@ -190,12 +220,12 @@ def test_pixi_reinstall_multi_env(pixi: Path, reinstall_workspace: Path) -> None manifest, "--environment", "dev", - "pixi-build-package", + "pixi_build_package_dev", ], env=env, ) - assert not conda_metadata_params.is_file() - assert conda_build_params.is_file() + assert not conda_metadata_params_dev.is_file() + assert conda_build_params_dev.is_file() verify_cli_command( [ pixi, @@ -204,14 +234,14 @@ def test_pixi_reinstall_multi_env(pixi: Path, reinstall_workspace: Path) -> None manifest, "--environment", "dev", - "pixi-build-package-main", + "pixi-build-package-dev-main", ], - stdout_contains="Pixi Build is number 2", + stdout_contains="Pixi Build dev is number 2", env=env, ) verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "--environment", "dev", "pypi-package-main"], - stdout_contains="PyPI is number 1", + [pixi, "run", "--manifest-path", manifest, "--environment", "dev", "pypi-package-dev-main"], + stdout_contains="PyPI dev is number 1", env=env, ) @@ -225,14 +255,14 @@ def test_pixi_reinstall_multi_env(pixi: Path, reinstall_workspace: Path) -> None manifest, "--environment", "dev", - "pypi-package", - "pixi-build-package-main", + "pypi_package_dev", + "pixi_build_package_dev", ], env=env, ) verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "--environment", "dev", "pypi-package-main"], - stdout_contains="PyPI is number 2", + [pixi, "run", "--manifest-path", manifest, "--environment", "dev", "pypi-package-dev-main"], + stdout_contains="PyPI dev is number 2", env=env, ) verify_cli_command( @@ -243,9 +273,9 @@ def test_pixi_reinstall_multi_env(pixi: Path, reinstall_workspace: Path) -> None manifest, "--environment", "dev", - "pixi-build-package-main", + "pixi-build-package-dev-main", ], - stdout_contains="Pixi Build is number 2", + stdout_contains="Pixi Build dev is number 2", env=env, ) @@ -261,9 +291,15 @@ def test_pixi_reinstall_multi_env(pixi: Path, reinstall_workspace: Path) -> None env=env, ) + # Delete the files to get a clean state + conda_metadata_params.unlink() + conda_build_params.unlink() + # After reinstalling all environments, # also the default environment should be "number 2" verify_cli_command([pixi, "reinstall", "--manifest-path", manifest, "--all"], env=env) + assert not conda_metadata_params.is_file() + assert conda_build_params.is_file() verify_cli_command( [pixi, "run", "--manifest-path", manifest, "pypi-package-main"], stdout_contains="PyPI is number 2", From e284fcaaec5b2383be2165641f78cf762b831dda Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Fri, 14 Mar 2025 14:44:23 +0100 Subject: [PATCH 21/24] Adapt CLI docs --- docs/reference/cli/pixi.md | 1 + docs/reference/cli/pixi/install.md | 2 +- docs/reference/cli/pixi/reinstall.md | 57 ++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 docs/reference/cli/pixi/reinstall.md diff --git a/docs/reference/cli/pixi.md b/docs/reference/cli/pixi.md index 2c7a4f5169..12bacefe94 100644 --- a/docs/reference/cli/pixi.md +++ b/docs/reference/cli/pixi.md @@ -15,6 +15,7 @@ pixi [OPTIONS] | [`add`](pixi/add.md) | Adds dependencies to the workspace | | [`remove`](pixi/remove.md) | Removes dependencies from the workspace | | [`install`](pixi/install.md) | Install an environment, both updating the lockfile and installing the environment | +| [`reinstall`](pixi/reinstall.md) | Re-install an environment, both updating the lockfile and re-installing the environment | | [`update`](pixi/update.md) | The `update` command checks if there are newer versions of the dependencies and updates the `pixi.lock` file and environments accordingly | | [`upgrade`](pixi/upgrade.md) | Checks if there are newer versions of the dependencies and upgrades them in the lockfile and manifest file | | [`lock`](pixi/lock.md) | Solve environment and update the lock file without installing the environments | diff --git a/docs/reference/cli/pixi/install.md b/docs/reference/cli/pixi/install.md index 39fcf382be..93125679fb 100644 --- a/docs/reference/cli/pixi/install.md +++ b/docs/reference/cli/pixi/install.md @@ -51,7 +51,7 @@ If you want to install all environments, you can use the `--all` flag. Running `pixi install` is not required before running other commands like `pixi run` or `pixi shell`. These commands will automatically install the environment if it is not already installed. -You can use `pixi clean` to remove the installed environments and start fresh. +You can use `pixi reinstall` to reinstall all environments, one environment or just some packages of an environment. --8<-- "docs/reference/cli/pixi/install_extender.md:example" diff --git a/docs/reference/cli/pixi/reinstall.md b/docs/reference/cli/pixi/reinstall.md new file mode 100644 index 0000000000..e6eb5d58d9 --- /dev/null +++ b/docs/reference/cli/pixi/reinstall.md @@ -0,0 +1,57 @@ + +# [pixi](../pixi.md) reinstall + +## About +Re-install an environment, both updating the lockfile and re-installing the environment + +--8<-- "docs/reference/cli/pixi/reinstall_extender.md:description" + +## Usage +``` +pixi reinstall [OPTIONS] [PACKAGE]... +``` + +## Arguments +- `` +: Specifies the package that should be reinstalled. If no package is given, the whole environment will be reinstalled + +## Config Options +- `--tls-no-verify` +: Do not verify the TLS certificate of the server +- `--auth-file ` +: Path to the file containing the authentication token +- `--pypi-keyring-provider ` +: Specifies whether to use the keyring to look up credentials for PyPI +
**options**: `disabled`, `subprocess` +- `--concurrent-solves ` +: Max concurrent solves, default is the number of CPUs +- `--concurrent-downloads ` +: Max concurrent network requests, default is `50` + +## Update Options +- `--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` +- `--environment (-e) ` +: The environment to install +- `--all (-a)` +: Install all environments + +## Global Options +- `--manifest-path ` +: The path to `pixi.toml`, `pyproject.toml`, or the workspace directory + +## Description +Re-install an environment, both updating the lockfile and re-installing the environment. + +This command reinstalls an environment, if the lockfile is not up-to-date it will be updated. If packages are specified, only those packages will be reinstalled. Otherwise the whole environment will be reinstalled. + +`pixi reinstall` only re-installs one environment at a time, if you have multiple environments you can select the right one with the `--environment` flag. If you don't provide an environment, the `default` environment will be re-installed. + +If you want to re-install all environments, you can use the `--all` flag. + + +--8<-- "docs/reference/cli/pixi/reinstall_extender.md:example" From f9da01b722b846221bd95c3110de1f436b542257 Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Fri, 14 Mar 2025 14:50:38 +0100 Subject: [PATCH 22/24] Adapt string --- src/cli/install.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli/install.rs b/src/cli/install.rs index 1ab0a48d76..506d937dd9 100644 --- a/src/cli/install.rs +++ b/src/cli/install.rs @@ -94,14 +94,14 @@ pub async fn execute(args: Args) -> miette::Result<()> { if installed_envs.len() == 1 { eprintln!( - "{}The {} environment has been installed{}.", + "{}The {} environment has been re-installed{}.", console::style(console::Emoji("✔ ", "")).green(), installed_envs[0].fancy_display(), detached_envs_message ); } else { eprintln!( - "{}The following environments have been installed: {}\t{}", + "{}The following environments have been re-installed: {}\t{}", console::style(console::Emoji("✔ ", "")).green(), installed_envs.iter().map(|n| n.fancy_display()).join(", "), detached_envs_message From 736c30332de29025384366823afbd312b48be372 Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Fri, 14 Mar 2025 15:30:04 +0100 Subject: [PATCH 23/24] Fix string changes --- src/cli/install.rs | 4 ++-- src/cli/reinstall.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cli/install.rs b/src/cli/install.rs index 506d937dd9..1ab0a48d76 100644 --- a/src/cli/install.rs +++ b/src/cli/install.rs @@ -94,14 +94,14 @@ pub async fn execute(args: Args) -> miette::Result<()> { if installed_envs.len() == 1 { eprintln!( - "{}The {} environment has been re-installed{}.", + "{}The {} environment has been installed{}.", console::style(console::Emoji("✔ ", "")).green(), installed_envs[0].fancy_display(), detached_envs_message ); } else { eprintln!( - "{}The following environments have been re-installed: {}\t{}", + "{}The following environments have been installed: {}\t{}", console::style(console::Emoji("✔ ", "")).green(), installed_envs.iter().map(|n| n.fancy_display()).join(", "), detached_envs_message diff --git a/src/cli/reinstall.rs b/src/cli/reinstall.rs index cf109e140a..4bd4baf97a 100644 --- a/src/cli/reinstall.rs +++ b/src/cli/reinstall.rs @@ -102,14 +102,14 @@ pub async fn execute(args: Args) -> miette::Result<()> { if installed_envs.len() == 1 { eprintln!( - "{}The {} environment has been installed{}.", + "{}The {} environment has been re-installed{}.", console::style(console::Emoji("✔ ", "")).green(), installed_envs[0].fancy_display(), detached_envs_message ); } else { eprintln!( - "{}The following environments have been installed: {}\t{}", + "{}The following environments have been re-installed: {}\t{}", console::style(console::Emoji("✔ ", "")).green(), installed_envs.iter().map(|n| n.fancy_display()).join(", "), detached_envs_message From 9c9200f96ce25662064109835e3128614394c066 Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Fri, 14 Mar 2025 16:13:09 +0100 Subject: [PATCH 24/24] Fix CLI docs --- docs/reference/cli/pixi/reinstall.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/reference/cli/pixi/reinstall.md b/docs/reference/cli/pixi/reinstall.md index e6eb5d58d9..bb3e442ef6 100644 --- a/docs/reference/cli/pixi/reinstall.md +++ b/docs/reference/cli/pixi/reinstall.md @@ -14,6 +14,7 @@ pixi reinstall [OPTIONS] [PACKAGE]... ## Arguments - `` : Specifies the package that should be reinstalled. If no package is given, the whole environment will be reinstalled +
May be provided more than once. ## Config Options - `--tls-no-verify` @@ -37,6 +38,7 @@ pixi reinstall [OPTIONS] [PACKAGE]...
**env**: `PIXI_LOCKED` - `--environment (-e) ` : The environment to install +
May be provided more than once. - `--all (-a)` : Install all environments