diff --git a/.gitignore b/.gitignore index ca9478ec9e..ce8c9a7ce5 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,9 @@ build/ conda-bld/ .conda-env-vars +# Allow conda files in test data +!tests/data/channels/channels/**/*.conda + # Python __pycache__/ *.py[cod] @@ -111,4 +114,3 @@ secrets.json # Generated files *_pb2.py *_pb2_grpc.py - diff --git a/crates/pixi_cli/src/global/update.rs b/crates/pixi_cli/src/global/update.rs index 5f3d85fcbb..f287c514c0 100644 --- a/crates/pixi_cli/src/global/update.rs +++ b/crates/pixi_cli/src/global/update.rs @@ -2,10 +2,10 @@ use crate::global::revert_environment_after_error; use clap::Parser; use fancy_display::FancyDisplay; use pixi_config::{Config, ConfigCli}; -use pixi_global::StateChanges; use pixi_global::common::check_all_exposed; use pixi_global::project::ExposedType; use pixi_global::{EnvironmentName, Project}; +use pixi_global::{StateChange, StateChanges}; /// Updates environments in the global environment. #[derive(Parser, Debug, Clone)] @@ -27,18 +27,10 @@ pub async fn execute(args: Args) -> miette::Result<()> { env_name: &EnvironmentName, project: &mut Project, ) -> miette::Result { - let mut state_changes = StateChanges::default(); // If the environment isn't up-to-date our executable detection afterwards will not work - let require_reinstall = if !project.environment_in_sync_internal(env_name, true).await? { - let environment_update = project.install_environment(env_name).await?; - state_changes.insert_change( - env_name, - pixi_global::StateChange::UpdatedEnvironment(environment_update), - ); - false - } else { - true - }; + if !project.environment_in_sync(env_name).await? { + let _ = project.install_environment(env_name).await?; + } // See what executables were installed prior to update let env_binaries = project.executables_of_direct_dependencies(env_name).await?; @@ -57,17 +49,20 @@ pub async fn execute(args: Args) -> miette::Result<()> { }; // Reinstall the environment - if require_reinstall { - let environment_update = project.install_environment(env_name).await?; + let environment_update = project.install_environment(env_name).await?; + + let mut state_changes = StateChanges::default(); + + state_changes.insert_change( + env_name, + StateChange::UpdatedEnvironment(environment_update), + ); - state_changes.insert_change( - env_name, - pixi_global::StateChange::UpdatedEnvironment(environment_update), - ); - } // Sync executables exposed names with the manifest project.sync_exposed_names(env_name, expose_type).await?; + state_changes |= project.sync_shortcuts(env_name).await?; + // Expose or prune executables of the new environment state_changes |= project .expose_executables_from_environment(env_name) diff --git a/crates/pixi_global/src/project/mod.rs b/crates/pixi_global/src/project/mod.rs index 20e4185e2c..e721d49c2f 100644 --- a/crates/pixi_global/src/project/mod.rs +++ b/crates/pixi_global/src/project/mod.rs @@ -1178,6 +1178,10 @@ impl Project { shortcuts_sync_status(shortcuts, prefix_records, prefix.root())?; for record in records_to_install { + tracing::debug!( + "Installing menuitems for record: {}", + record.name().as_normalized() + ); rattler_menuinst::install_menuitems_for_record( prefix.root(), &record, @@ -1200,6 +1204,10 @@ impl Project { } for record in records_to_uninstall { + tracing::debug!( + "Uninstalling menuitems for record: {}", + record.name().as_normalized() + ); rattler_menuinst::remove_menuitems_for_record(prefix.root(), record.clone()) .into_diagnostic()?; diff --git a/pixi.lock b/pixi.lock index 7022194398..3d57e49156 100644 --- a/pixi.lock +++ b/pixi.lock @@ -99,8 +99,9 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/mypy-1.16.0-py313h536fd9c_0.conda - conda: https://prefix.dev/conda-forge/noarch/mypy_extensions-1.1.0-pyha770c72_0.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.5.0-h7b32b05_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.5.2-h26f9b46_0.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/patchelf-0.18.0-h3f2d84a_2.conda - conda: https://prefix.dev/conda-forge/noarch/pathspec-0.12.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-64/pcre2-10.45-hc749103_0.conda - conda: https://prefix.dev/conda-forge/linux-64/perl-5.32.1-7_hd590300_perl5.conda @@ -125,6 +126,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.13-7_cp313.conda - conda: https://prefix.dev/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-64/pyyaml-6.0.2-py313h8060acc_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/rattler-build-0.36.0-hff40e2b_0.conda - conda: https://prefix.dev/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - conda: https://prefix.dev/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda @@ -240,8 +242,9 @@ environments: - conda: https://prefix.dev/conda-forge/linux-aarch64/mypy-1.16.0-py313h31d5739_0.conda - conda: https://prefix.dev/conda-forge/noarch/mypy_extensions-1.1.0-pyha770c72_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/ncurses-6.5-ha32ae93_3.conda - - conda: https://prefix.dev/conda-forge/linux-aarch64/openssl-3.5.0-hd08dc88_1.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/openssl-3.5.2-h8e36d6e_0.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/patchelf-0.18.0-h5ad3122_2.conda - conda: https://prefix.dev/conda-forge/noarch/pathspec-0.12.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/pcre2-10.45-hf4ec17f_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/perl-5.32.1-7_h31becfc_perl5.conda @@ -266,6 +269,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.13-7_cp313.conda - conda: https://prefix.dev/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/pyyaml-6.0.2-py313h857f82b_2.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/rattler-build-0.36.0-h33857bb_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/readline-8.2-h8382b9d_2.conda - conda: https://prefix.dev/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda @@ -411,6 +415,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.13-7_cp313.conda - conda: https://prefix.dev/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-64/pyyaml-6.0.2-py313h717bdf5_2.conda + - conda: https://prefix.dev/conda-forge/osx-64/rattler-build-0.36.0-h625f1b7_0.conda - conda: https://prefix.dev/conda-forge/osx-64/readline-8.2-h7cca4af_2.conda - conda: https://prefix.dev/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda @@ -559,6 +564,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.13-7_cp313.conda - conda: https://prefix.dev/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/pyyaml-6.0.2-py313ha9b7d5b_2.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/rattler-build-0.36.0-h3ab7716_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/readline-8.2-h1d1bf99_2.conda - conda: https://prefix.dev/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda @@ -678,6 +684,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.13-7_cp313.conda - conda: https://prefix.dev/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/win-64/pyyaml-6.0.2-py313hb4c8b1a_2.conda + - conda: https://prefix.dev/conda-forge/win-64/rattler-build-0.36.0-ha8cf89e_0.conda - conda: https://prefix.dev/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda - conda: https://prefix.dev/conda-forge/win-64/ruamel.yaml-0.18.12-py313ha7868ed_0.conda @@ -867,7 +874,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/mkdocs-redirects-1.2.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - conda: https://prefix.dev/conda-forge/linux-64/openjpeg-2.5.3-h5fbd93e_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.5.0-h7b32b05_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.5.2-h26f9b46_0.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - conda: https://prefix.dev/conda-forge/noarch/paginate-0.5.7-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pathspec-0.12.1-pyhd8ed1ab_1.conda @@ -996,7 +1003,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/mkdocs-redirects-1.2.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/ncurses-6.5-ha32ae93_3.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/openjpeg-2.5.3-h3f56577_0.conda - - conda: https://prefix.dev/conda-forge/linux-aarch64/openssl-3.5.0-hd08dc88_1.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/openssl-3.5.2-h8e36d6e_0.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - conda: https://prefix.dev/conda-forge/noarch/paginate-0.5.7-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pathspec-0.12.1-pyhd8ed1ab_1.conda @@ -1487,8 +1494,9 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/mypy-1.16.0-py313h536fd9c_0.conda - conda: https://prefix.dev/conda-forge/noarch/mypy_extensions-1.1.0-pyha770c72_0.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.5.0-h7b32b05_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.5.2-h26f9b46_0.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/patchelf-0.18.0-h3f2d84a_2.conda - conda: https://prefix.dev/conda-forge/noarch/pathspec-0.12.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-64/pcre2-10.45-hc749103_0.conda - conda: https://prefix.dev/conda-forge/linux-64/perl-5.32.1-7_hd590300_perl5.conda @@ -1506,6 +1514,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.13-7_cp313.conda - conda: https://prefix.dev/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-64/pyyaml-6.0.2-py313h8060acc_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/rattler-build-0.36.0-hff40e2b_0.conda - conda: https://prefix.dev/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - conda: https://prefix.dev/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda - conda: https://prefix.dev/conda-forge/linux-64/ruff-0.12.1-py313h7585d4e_1.conda @@ -1595,8 +1604,9 @@ environments: - conda: https://prefix.dev/conda-forge/linux-aarch64/mypy-1.16.0-py313h31d5739_0.conda - conda: https://prefix.dev/conda-forge/noarch/mypy_extensions-1.1.0-pyha770c72_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/ncurses-6.5-ha32ae93_3.conda - - conda: https://prefix.dev/conda-forge/linux-aarch64/openssl-3.5.0-hd08dc88_1.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/openssl-3.5.2-h8e36d6e_0.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/patchelf-0.18.0-h5ad3122_2.conda - conda: https://prefix.dev/conda-forge/noarch/pathspec-0.12.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/pcre2-10.45-hf4ec17f_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/perl-5.32.1-7_h31becfc_perl5.conda @@ -1614,6 +1624,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.13-7_cp313.conda - conda: https://prefix.dev/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/pyyaml-6.0.2-py313h857f82b_2.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/rattler-build-0.36.0-h33857bb_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/readline-8.2-h8382b9d_2.conda - conda: https://prefix.dev/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/ruff-0.12.1-py313h2eefac3_1.conda @@ -1726,6 +1737,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.13-7_cp313.conda - conda: https://prefix.dev/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-64/pyyaml-6.0.2-py313h717bdf5_2.conda + - conda: https://prefix.dev/conda-forge/osx-64/rattler-build-0.36.0-h625f1b7_0.conda - conda: https://prefix.dev/conda-forge/osx-64/readline-8.2-h7cca4af_2.conda - conda: https://prefix.dev/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda - conda: https://prefix.dev/conda-forge/osx-64/ruff-0.12.1-py313hcbad2e9_1.conda @@ -1841,6 +1853,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.13-7_cp313.conda - conda: https://prefix.dev/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/pyyaml-6.0.2-py313ha9b7d5b_2.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/rattler-build-0.36.0-h3ab7716_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/readline-8.2-h1d1bf99_2.conda - conda: https://prefix.dev/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/ruff-0.12.1-py313h2222209_1.conda @@ -1927,6 +1940,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.13-7_cp313.conda - conda: https://prefix.dev/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/win-64/pyyaml-6.0.2-py313hb4c8b1a_2.conda + - conda: https://prefix.dev/conda-forge/win-64/rattler-build-0.36.0-ha8cf89e_0.conda - conda: https://prefix.dev/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda - conda: https://prefix.dev/conda-forge/win-64/ruff-0.12.1-py313h564f793_1.conda - conda: https://prefix.dev/conda-forge/win-64/rust-1.86.0-hf8d6059_0.conda @@ -2180,7 +2194,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/libgcc-15.1.0-h767d61c_2.conda - conda: https://prefix.dev/conda-forge/linux-64/libgomp-15.1.0-h767d61c_2.conda - conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_2.conda - - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.5.0-h7b32b05_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.5.2-h26f9b46_0.conda - conda: https://prefix.dev/conda-forge/linux-64/patchelf-0.18.0-h3f2d84a_2.conda - conda: https://prefix.dev/conda-forge/linux-64/rattler-build-0.44.0-h2d22210_0.conda linux-aarch64: @@ -2189,7 +2203,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-aarch64/libgcc-15.1.0-he277a41_2.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libgomp-15.1.0-he277a41_2.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libstdcxx-15.1.0-h3f4de04_2.conda - - conda: https://prefix.dev/conda-forge/linux-aarch64/openssl-3.5.0-hd08dc88_1.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/openssl-3.5.2-h8e36d6e_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/patchelf-0.18.0-h5ad3122_2.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/rattler-build-0.44.0-h3618846_0.conda osx-64: @@ -2234,6 +2248,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_1.conda - conda: https://prefix.dev/conda-forge/linux-64/libmpdec-4.0.0-hb9d3cd8_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.50.0-hee588c1_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_2.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/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda @@ -2241,8 +2256,9 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/mypy-1.16.0-py313h536fd9c_0.conda - conda: https://prefix.dev/conda-forge/noarch/mypy_extensions-1.1.0-pyha770c72_0.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.5.0-h7b32b05_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.5.2-h26f9b46_0.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/patchelf-0.18.0-h3f2d84a_2.conda - conda: https://prefix.dev/conda-forge/noarch/pathspec-0.12.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-64/psutil-7.0.0-py313h536fd9c_0.conda @@ -2260,6 +2276,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.13-7_cp313.conda - conda: https://prefix.dev/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-64/pyyaml-6.0.2-py313h8060acc_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/rattler-build-0.36.0-hff40e2b_0.conda - conda: https://prefix.dev/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - conda: https://prefix.dev/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda - conda: https://prefix.dev/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda @@ -2302,6 +2319,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-aarch64/liblzma-5.8.1-h86ecc28_1.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libmpdec-4.0.0-h86ecc28_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libsqlite-3.50.0-h5eb1b54_0.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/libstdcxx-15.1.0-h3f4de04_2.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libuuid-2.38.1-hb4cce97_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libzlib-1.3.1-h86ecc28_2.conda - conda: https://prefix.dev/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda @@ -2309,8 +2327,9 @@ environments: - conda: https://prefix.dev/conda-forge/linux-aarch64/mypy-1.16.0-py313h31d5739_0.conda - conda: https://prefix.dev/conda-forge/noarch/mypy_extensions-1.1.0-pyha770c72_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/ncurses-6.5-ha32ae93_3.conda - - conda: https://prefix.dev/conda-forge/linux-aarch64/openssl-3.5.0-hd08dc88_1.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/openssl-3.5.2-h8e36d6e_0.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/patchelf-0.18.0-h5ad3122_2.conda - conda: https://prefix.dev/conda-forge/noarch/pathspec-0.12.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/psutil-7.0.0-py313h31d5739_0.conda @@ -2328,6 +2347,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.13-7_cp313.conda - conda: https://prefix.dev/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/pyyaml-6.0.2-py313h857f82b_2.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/rattler-build-0.36.0-h33857bb_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/readline-8.2-h8382b9d_2.conda - conda: https://prefix.dev/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda - conda: https://prefix.dev/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda @@ -2390,6 +2410,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.13-7_cp313.conda - conda: https://prefix.dev/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-64/pyyaml-6.0.2-py313h717bdf5_2.conda + - conda: https://prefix.dev/conda-forge/osx-64/rattler-build-0.36.0-h625f1b7_0.conda - conda: https://prefix.dev/conda-forge/osx-64/readline-8.2-h7cca4af_2.conda - conda: https://prefix.dev/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda - conda: https://prefix.dev/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda @@ -2452,6 +2473,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.13-7_cp313.conda - conda: https://prefix.dev/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/pyyaml-6.0.2-py313ha9b7d5b_2.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/rattler-build-0.36.0-h3ab7716_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/readline-8.2-h1d1bf99_2.conda - conda: https://prefix.dev/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda - conda: https://prefix.dev/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda @@ -2513,6 +2535,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.13-7_cp313.conda - conda: https://prefix.dev/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/win-64/pyyaml-6.0.2-py313hb4c8b1a_2.conda + - conda: https://prefix.dev/conda-forge/win-64/rattler-build-0.36.0-ha8cf89e_0.conda - conda: https://prefix.dev/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda - conda: https://prefix.dev/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhd8ed1ab_0.conda @@ -8625,6 +8648,17 @@ packages: purls: [] size: 3117410 timestamp: 1746223723843 +- conda: https://prefix.dev/conda-forge/linux-64/openssl-3.5.2-h26f9b46_0.conda + sha256: c9f54d4e8212f313be7b02eb962d0cb13a8dae015683a403d3accd4add3e520e + md5: ffffb341206dd0dab0c36053c048d621 + depends: + - __glibc >=2.17,<3.0.a0 + - ca-certificates + - libgcc >=14 + license: Apache-2.0 + license_family: Apache + size: 3128847 + timestamp: 1754465526100 - conda: https://prefix.dev/conda-forge/linux-aarch64/openssl-3.5.0-hd08dc88_1.conda sha256: 58120daf06a52ba203f94eccb43900213a9f2b3cc310bbaa868505ccd7afbdaa md5: ee68fdc3a8723e9c58bdd2f10544658f @@ -8636,6 +8670,16 @@ packages: purls: [] size: 3642633 timestamp: 1746225726804 +- conda: https://prefix.dev/conda-forge/linux-aarch64/openssl-3.5.2-h8e36d6e_0.conda + sha256: 07d96b672fc8ae796208628d4a996b5155ab14b69e4f26fe3eaf82bcd71d1d7f + md5: ed060dc5bd1dc09e8df358fbba05d27c + depends: + - ca-certificates + - libgcc >=14 + license: Apache-2.0 + license_family: Apache + size: 3655596 + timestamp: 1754467141632 - conda: https://prefix.dev/conda-forge/osx-64/openssl-3.5.0-hc426f3f_1.conda sha256: bcac94cb82a458b4e3164da8d9bced08cc8c3da2bc3bd7330711a3689c1464a5 md5: 919faa07b9647beb99a0e7404596a465 @@ -9907,6 +9951,19 @@ packages: - pkg:pypi/pyyaml-env-tag?source=hash-mapping size: 11137 timestamp: 1747237061448 +- conda: https://prefix.dev/conda-forge/linux-64/rattler-build-0.36.0-hff40e2b_0.conda + sha256: 82063bc8a3afd9611d97f07ec084f4c87f9c0d8a31ff60ee3300adcfeb9125b8 + md5: 79daab09276312e5ea5f9f4c3b00e4b2 + depends: + - patchelf + - __glibc >=2.17,<3.0.a0 + - openssl >=3.4.1,<4.0a0 + constrains: + - __glibc >=2.17 + license: BSD-3-Clause + license_family: BSD + size: 11127511 + timestamp: 1739904562938 - conda: https://prefix.dev/conda-forge/linux-64/rattler-build-0.44.0-h2d22210_0.conda sha256: f42b8bcab699f1eb312062cb0c1095066eeb5aabb9117574b3a91d2687ed4474 md5: fc7102e7482525f017a3cbe2750f35f9 @@ -9921,6 +9978,18 @@ packages: license_family: BSD size: 16357677 timestamp: 1750800802907 +- conda: https://prefix.dev/conda-forge/linux-aarch64/rattler-build-0.36.0-h33857bb_0.conda + sha256: e69019eb5e94fa5cc94aa96c9c24fdf2be05803335f9705c8df2b958bb149cf7 + md5: 2bf9f1141346e782a7b8779d2f4df26e + depends: + - patchelf + - openssl >=3.4.1,<4.0a0 + constrains: + - __glibc >=2.17 + license: BSD-3-Clause + license_family: BSD + size: 11334918 + timestamp: 1739904565295 - conda: https://prefix.dev/conda-forge/linux-aarch64/rattler-build-0.44.0-h3618846_0.conda sha256: 903324242bee99d7ca95546c65099a5649f11abbaf1bb3a35b6c35b755b89d8a md5: f3ecdbbb4908cffc959cac8d2e245157 @@ -9934,6 +10003,17 @@ packages: license_family: BSD size: 16372237 timestamp: 1750800821377 +- conda: https://prefix.dev/conda-forge/osx-64/rattler-build-0.36.0-h625f1b7_0.conda + sha256: 13f3b3fe72bd5027cb36946e98c38531d64e99f2af482dedcedd624223c065a4 + md5: d56b867eb6c7b761ae14ba3a5d4d3e17 + depends: + - __osx >=10.13 + constrains: + - __osx >=10.13 + license: BSD-3-Clause + license_family: BSD + size: 9527540 + timestamp: 1739904568428 - conda: https://prefix.dev/conda-forge/osx-64/rattler-build-0.44.0-h39668a9_0.conda sha256: 48ea4d40bf53db8489f8b2cbfa7d211b952c52f63aebdca07c34022caa6e687f md5: a463aa469bb1b8ea8236594646bb4667 @@ -9945,6 +10025,17 @@ packages: license_family: BSD size: 14756499 timestamp: 1750800866620 +- conda: https://prefix.dev/conda-forge/osx-arm64/rattler-build-0.36.0-h3ab7716_0.conda + sha256: f5668de07ee9ab552d1a19d38614b168016ba40eb002ae79b553f4a1bd1421fa + md5: f31c129f37baa3aa8cbc1e7956d607d2 + depends: + - __osx >=11.0 + constrains: + - __osx >=11.0 + license: BSD-3-Clause + license_family: BSD + size: 8983525 + timestamp: 1739904546535 - conda: https://prefix.dev/conda-forge/osx-arm64/rattler-build-0.44.0-hf783435_0.conda sha256: 9261c1ed8243ff73a9ae8fb70086e5ee1d348e46885243080ac66b9b233a8c19 md5: 83eaa51c3ac116c23dc77e96497cdfae @@ -9956,6 +10047,17 @@ packages: license_family: BSD size: 13803732 timestamp: 1750800856755 +- conda: https://prefix.dev/conda-forge/win-64/rattler-build-0.36.0-ha8cf89e_0.conda + sha256: fe62e8c41f466599a9635416de4b5ea9140d451102f59710d146c21019234312 + md5: 72e83b28335cc022e31795e18f912e39 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.42.34433 + - ucrt >=10.0.20348.0 + license: BSD-3-Clause + license_family: BSD + size: 9016531 + timestamp: 1739904611873 - conda: https://prefix.dev/conda-forge/win-64/rattler-build-0.44.0-h18a1a76_0.conda sha256: 90586fa59a640dfb69c8cd8178dc4922e4ba2e814e835bea45807ec346aa81c4 md5: 056857dc95ab25a90146912a98051380 diff --git a/pixi.toml b/pixi.toml index 30757b9379..410eb06627 100644 --- a/pixi.toml +++ b/pixi.toml @@ -54,6 +54,7 @@ pytest-rerunfailures = ">=15,<16" pytest-timeout = ">=2.3.1,<3" pytest-xdist = ">=3.6.1,<4" pyyaml = ">=6.0.2,<7" +rattler-build = "==0.36.0" rich = ">=14,<15" tomli-w = ">=1.0,<2" types-pyyaml = ">=6.0.12.20241230,<7" diff --git a/tests/data/channels/channels/shortcuts_channel_1/noarch/pixi-editor-0.1.3-h4616a5c_0.conda b/tests/data/channels/channels/shortcuts_channel_1/noarch/pixi-editor-1.0.0-h4616a5c_0.conda similarity index 98% rename from tests/data/channels/channels/shortcuts_channel_1/noarch/pixi-editor-0.1.3-h4616a5c_0.conda rename to tests/data/channels/channels/shortcuts_channel_1/noarch/pixi-editor-1.0.0-h4616a5c_0.conda index 7213c3f36f..ecb4279f3d 100644 Binary files a/tests/data/channels/channels/shortcuts_channel_1/noarch/pixi-editor-0.1.3-h4616a5c_0.conda and b/tests/data/channels/channels/shortcuts_channel_1/noarch/pixi-editor-1.0.0-h4616a5c_0.conda differ diff --git a/tests/data/channels/channels/shortcuts_channel_1/noarch/pixi-editor-2.0.0-h4616a5c_0.conda b/tests/data/channels/channels/shortcuts_channel_1/noarch/pixi-editor-2.0.0-h4616a5c_0.conda new file mode 100644 index 0000000000..e5ed18f820 Binary files /dev/null and b/tests/data/channels/channels/shortcuts_channel_1/noarch/pixi-editor-2.0.0-h4616a5c_0.conda differ diff --git a/tests/data/channels/channels/shortcuts_channel_1/noarch/repodata.json b/tests/data/channels/channels/shortcuts_channel_1/noarch/repodata.json index 2b325db167..783fcdb5fa 100644 --- a/tests/data/channels/channels/shortcuts_channel_1/noarch/repodata.json +++ b/tests/data/channels/channels/shortcuts_channel_1/noarch/repodata.json @@ -4,19 +4,32 @@ }, "packages": {}, "packages.conda": { - "pixi-editor-0.1.3-h4616a5c_0.conda": { + "pixi-editor-1.0.0-h4616a5c_0.conda": { "build": "h4616a5c_0", "build_number": 0, "depends": [], - "md5": "710f47b333ecfeeaf1fd8e1b02542283", + "md5": "5b8c04e66f6bcbdf6563e3a84c4a92bc", "name": "pixi-editor", "noarch": "generic", - "sha256": "aa4823f8f8c9d999a97f7f85e17f24495b45892a6619c7875a255462bcb5e7d4", - "size": 335330, + "sha256": "181254b2d6a343088c939f6e2c077a0a1b5dd75b48dedc375078093263d1d419", + "size": 335993, "subdir": "noarch", - "timestamp": 1740648548184, - "version": "0.1.3" + "timestamp": 1756486136456, + "version": "1.0.0" + }, + "pixi-editor-2.0.0-h4616a5c_0.conda": { + "build": "h4616a5c_0", + "build_number": 0, + "depends": [], + "md5": "d604f124886b285caf9a635f3e9ed8b5", + "name": "pixi-editor", + "noarch": "generic", + "sha256": "9fb8e7bcc3d7b62b0d75d160a08a980962eeb47450c591b18c0181b7fec95bcb", + "size": 337199, + "subdir": "noarch", + "timestamp": 1756486145266, + "version": "2.0.0" } }, "repodata_version": 2 -} +} \ No newline at end of file diff --git a/tests/data/channels/mappings.toml b/tests/data/channels/mappings.toml index f0d60f3bd8..7d7de8fd06 100644 --- a/tests/data/channels/mappings.toml +++ b/tests/data/channels/mappings.toml @@ -7,8 +7,9 @@ "multiple_versions_channel_1_020.yaml" = "multiple_versions_channel_1" "non_self_expose_channel_1.yaml" = "non_self_expose_channel_1" "non_self_expose_channel_2.yaml" = "non_self_expose_channel_2" -"pixi-editor/recipe.yaml" = "shortcuts_channel_1" "post_link_script_package/recipe.yaml" = "post_link_script_channel" +"shortcuts_channel_1/1/recipe.yaml" = "shortcuts_channel_1" +"shortcuts_channel_1/2/recipe.yaml" = "shortcuts_channel_1" "trampoline/trampoline_1.yaml" = "trampoline_channel" "trampoline/trampoline_2.yaml" = "trampoline_channel" "trampoline/trampoline_path.yaml" = "trampoline_path_channel" diff --git a/tests/data/channels/recipes/pixi-editor/Menu/menu.json b/tests/data/channels/recipes/pixi-editor/Menu/menu.json deleted file mode 100644 index bed1da30c0..0000000000 --- a/tests/data/channels/recipes/pixi-editor/Menu/menu.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft-07/schema", - "$id": "https://schemas.conda.io/menuinst-1.schema.json", - "menu_name": "pixi-editor", - "menu_items": [ - { - "name": { - "target_environment_is_base": "pixi-editor", - "target_environment_is_not_base": "pixi-editor ({{ ENV_NAME }})" - }, - "description": "Scientific Python Development Environment", - "icon": "{{ MENU_DIR }}/pixi-icon.{{ ICON_EXT }}", - "activate": false, - "terminal": false, - "command": [""], - "platforms": { - "win": { - "desktop": true, - "app_user_model_id": "dev.prefix.pixi-editor", - "command": ["notepad.exe", "%*"], - "file_extensions": [ - ".pixi" - ] - }, - "linux": { - "Categories": [ - "Development", - "Science" - ], - "command": ["gedit", "%F"], - "MimeType": [ - "text/x-pixi" - ] - }, - "osx": { - "command": ["open", "-a", "TextEdit"], - "CFBundleName": "Pixi Editor", - "CFBundleIdentifier": "dev.prefix.pixi-editor", - "CFBundleVersion": "0.1.0", - "CFBundleDocumentTypes": [ - { - "CFBundleTypeName": "text document", - "CFBundleTypeRole": "Editor", - "LSHandlerRank": "Default", - "CFBundleTypeIconFile": "pixi-icon.icns", - "LSItemContentTypes": [ - "public.pixi" - ] - } - ] - } - } - } - ] -} diff --git a/tests/data/channels/recipes/pixi-editor/recipe.yaml b/tests/data/channels/recipes/pixi-editor/recipe.yaml deleted file mode 100644 index 2a13dfe444..0000000000 --- a/tests/data/channels/recipes/pixi-editor/recipe.yaml +++ /dev/null @@ -1,31 +0,0 @@ -package: - name: pixi-editor - version: "0.1.3" - -requirements: - build: - - python - -build: - noarch: generic - script: - interpreter: python - content: | - import os - import shutil - from pathlib import Path - - prefix = os.environ.get("PREFIX", "") - recipe_dir = os.environ.get("RECIPE_DIR", "") - - menu_dir = Path(prefix) / "Menu" - menu_dir.mkdir(parents=True, exist_ok=True) - - # Copy menu.json - shutil.copy(Path(recipe_dir) / "Menu" / "menu.json", menu_dir / "pixi-editor.json") - - # Copy icon files - for icon_name in ["pixi-icon.ico", "pixi-icon.png", "pixi-icon.icns"]: - src = Path(recipe_dir) / "Menu" / icon_name - if src.exists(): - shutil.copy(src, menu_dir) diff --git a/tests/data/channels/recipes/pixi-editor/Menu/pixi-icon.icns b/tests/data/channels/recipes/shortcuts_channel_1/1/Menu/another-icon.icns similarity index 100% rename from tests/data/channels/recipes/pixi-editor/Menu/pixi-icon.icns rename to tests/data/channels/recipes/shortcuts_channel_1/1/Menu/another-icon.icns diff --git a/tests/data/channels/recipes/pixi-editor/Menu/pixi-icon.ico b/tests/data/channels/recipes/shortcuts_channel_1/1/Menu/another-icon.ico similarity index 100% rename from tests/data/channels/recipes/pixi-editor/Menu/pixi-icon.ico rename to tests/data/channels/recipes/shortcuts_channel_1/1/Menu/another-icon.ico diff --git a/tests/data/channels/recipes/pixi-editor/Menu/pixi-icon.png b/tests/data/channels/recipes/shortcuts_channel_1/1/Menu/another-icon.png similarity index 100% rename from tests/data/channels/recipes/pixi-editor/Menu/pixi-icon.png rename to tests/data/channels/recipes/shortcuts_channel_1/1/Menu/another-icon.png diff --git a/tests/data/channels/recipes/shortcuts_channel_1/1/Menu/menu.json b/tests/data/channels/recipes/shortcuts_channel_1/1/Menu/menu.json new file mode 100644 index 0000000000..bef25a9d2a --- /dev/null +++ b/tests/data/channels/recipes/shortcuts_channel_1/1/Menu/menu.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "$id": "https://schemas.conda.io/menuinst-1.schema.json", + "menu_name": "pixi-editor", + "menu_items": [ + { + "name": { + "target_environment_is_base": "pixi-editor", + "target_environment_is_not_base": "pixi-editor ({{ ENV_NAME }})" + }, + "description": "Pixi Editor 1", + "icon": "{{ MENU_DIR }}/another-icon.{{ ICON_EXT }}", + "activate": false, + "terminal": false, + "command": [""], + "platforms": { + "win": { + "desktop": true, + "app_user_model_id": "dev.prefix.pixi-editor", + "command": ["notepad.exe", "%*"], + "file_extensions": [".pixi"] + }, + "linux": { + "Categories": ["Development", "Science"], + "command": ["gedit", "%F"], + "MimeType": ["text/x-pixi"] + }, + "osx": { + "command": ["open", "-a", "TextEdit"], + "CFBundleName": "Pixi Editor", + "CFBundleIdentifier": "dev.prefix.pixi-editor", + "CFBundleVersion": "0.1.0", + "CFBundleDocumentTypes": [ + { + "CFBundleTypeName": "text document", + "CFBundleTypeRole": "Editor", + "LSHandlerRank": "Default", + "CFBundleTypeIconFile": "another-icon.icns", + "LSItemContentTypes": ["public.pixi"] + } + ] + } + } + } + ] +} diff --git a/tests/data/channels/recipes/shortcuts_channel_1/1/recipe.yaml b/tests/data/channels/recipes/shortcuts_channel_1/1/recipe.yaml new file mode 100644 index 0000000000..a969415222 --- /dev/null +++ b/tests/data/channels/recipes/shortcuts_channel_1/1/recipe.yaml @@ -0,0 +1,32 @@ +outputs: + - package: + name: pixi-editor + version: "1.0.0" + + requirements: + build: + - python + + build: + noarch: generic + script: + interpreter: python + content: | + import os + import shutil + from pathlib import Path + + prefix = os.environ.get("PREFIX", "") + recipe_dir = os.environ.get("RECIPE_DIR", "") + + menu_dir = Path(prefix) / "Menu" + menu_dir.mkdir(parents=True, exist_ok=True) + + # Copy menu.json + shutil.copy(Path(recipe_dir) / "Menu" / "menu.json", menu_dir / "pixi-editor.json") + + # Copy icon files + for icon_name in ["another-icon.ico", "another-icon.png", "another-icon.icns"]: + src = Path(recipe_dir) / "Menu" / icon_name + if src.exists(): + shutil.copy(src, menu_dir) diff --git a/tests/data/channels/recipes/shortcuts_channel_1/2/Menu/menu.json b/tests/data/channels/recipes/shortcuts_channel_1/2/Menu/menu.json new file mode 100644 index 0000000000..83327337ad --- /dev/null +++ b/tests/data/channels/recipes/shortcuts_channel_1/2/Menu/menu.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "$id": "https://schemas.conda.io/menuinst-1.schema.json", + "menu_name": "pixi-editor", + "menu_items": [ + { + "name": { + "target_environment_is_base": "pixi-editor", + "target_environment_is_not_base": "pixi-editor ({{ ENV_NAME }})" + }, + "description": "Pixi Editor 2", + "icon": "{{ MENU_DIR }}/pixi-icon.{{ ICON_EXT }}", + "activate": false, + "terminal": false, + "command": [""], + "platforms": { + "win": { + "desktop": true, + "app_user_model_id": "dev.prefix.pixi-editor", + "command": ["notepad.exe", "%*"], + "file_extensions": [".pixi"] + }, + "linux": { + "Categories": ["Development", "Science"], + "command": ["gedit", "%F"], + "MimeType": ["text/x-pixi"] + }, + "osx": { + "command": ["open", "-a", "TextEdit"], + "CFBundleName": "Pixi Editor", + "CFBundleIdentifier": "dev.prefix.pixi-editor", + "CFBundleVersion": "0.1.0", + "CFBundleDocumentTypes": [ + { + "CFBundleTypeName": "text document", + "CFBundleTypeRole": "Editor", + "LSHandlerRank": "Default", + "CFBundleTypeIconFile": "pixi-icon.icns", + "LSItemContentTypes": ["public.pixi"] + } + ] + } + } + } + ] +} diff --git a/tests/data/channels/recipes/shortcuts_channel_1/2/Menu/pixi-icon.icns b/tests/data/channels/recipes/shortcuts_channel_1/2/Menu/pixi-icon.icns new file mode 100644 index 0000000000..68f610243a Binary files /dev/null and b/tests/data/channels/recipes/shortcuts_channel_1/2/Menu/pixi-icon.icns differ diff --git a/tests/data/channels/recipes/shortcuts_channel_1/2/Menu/pixi-icon.ico b/tests/data/channels/recipes/shortcuts_channel_1/2/Menu/pixi-icon.ico new file mode 100644 index 0000000000..6d58d94fd3 Binary files /dev/null and b/tests/data/channels/recipes/shortcuts_channel_1/2/Menu/pixi-icon.ico differ diff --git a/tests/data/channels/recipes/shortcuts_channel_1/2/Menu/pixi-icon.png b/tests/data/channels/recipes/shortcuts_channel_1/2/Menu/pixi-icon.png new file mode 100644 index 0000000000..ef095240da Binary files /dev/null and b/tests/data/channels/recipes/shortcuts_channel_1/2/Menu/pixi-icon.png differ diff --git a/tests/data/channels/recipes/shortcuts_channel_1/2/recipe.yaml b/tests/data/channels/recipes/shortcuts_channel_1/2/recipe.yaml new file mode 100644 index 0000000000..96404472dc --- /dev/null +++ b/tests/data/channels/recipes/shortcuts_channel_1/2/recipe.yaml @@ -0,0 +1,32 @@ +outputs: + - package: + name: pixi-editor + version: "2.0.0" + + requirements: + build: + - python + + build: + noarch: generic + script: + interpreter: python + content: | + import os + import shutil + from pathlib import Path + + prefix = os.environ.get("PREFIX", "") + recipe_dir = os.environ.get("RECIPE_DIR", "") + + menu_dir = Path(prefix) / "Menu" + menu_dir.mkdir(parents=True, exist_ok=True) + + # Copy menu.json + shutil.copy(Path(recipe_dir) / "Menu" / "menu.json", menu_dir / "pixi-editor.json") + + # Copy icon files + for icon_name in ["pixi-icon.ico", "pixi-icon.png", "pixi-icon.icns"]: + src = Path(recipe_dir) / "Menu" / icon_name + if src.exists(): + shutil.copy(src, menu_dir) diff --git a/tests/integration_python/pixi_global/test_shortcuts.py b/tests/integration_python/pixi_global/test_shortcuts.py index 20debb0a67..bda0319123 100644 --- a/tests/integration_python/pixi_global/test_shortcuts.py +++ b/tests/integration_python/pixi_global/test_shortcuts.py @@ -1,12 +1,13 @@ -from pathlib import Path +import hashlib import tomllib -from typing import List - -import tomli_w -from ..common import verify_cli_command, ExitCode, CURRENT_PLATFORM from abc import ABC, abstractmethod -import pytest from dataclasses import dataclass +from pathlib import Path + +import pytest +import tomli_w + +from ..common import CURRENT_PLATFORM, ExitCode, verify_cli_command @dataclass @@ -30,7 +31,7 @@ def setup_data(tmp_path: Path) -> SetupData: class PlatformConfig(ABC): @abstractmethod - def _shortcut_paths(self, data_home: Path, name: str) -> List[Path]: + def shortcut_path(self, data_home: Path, name: str) -> Path: pass @abstractmethod @@ -38,33 +39,64 @@ def shortcut_exists(self, data_home: Path, name: str) -> bool: """Given the name of a shortcut, return whether it exists or not.""" pass + @abstractmethod + def get_shortcut_content_hash(self, data_home: Path, name: str) -> str: + """Get a hash of the shortcut content for comparison. Raises FileNotFoundError if shortcut doesn't exist.""" + pass + class LinuxConfig(PlatformConfig): - def _shortcut_paths(self, data_home: Path, name: str) -> List[Path]: - return [data_home / ".local" / "share" / "applications" / f"{name}_{name}.desktop"] + def shortcut_path(self, data_home: Path, name: str) -> Path: + return data_home / ".local" / "share" / "applications" / f"{name}_{name}.desktop" def shortcut_exists(self, data_home: Path, name: str) -> bool: - return self._shortcut_paths(data_home, name).pop().is_file() + return self.shortcut_path(data_home, name).is_file() + + def get_shortcut_content_hash(self, data_home: Path, name: str) -> str: + shortcut_file = self.shortcut_path(data_home, name) + if not shortcut_file.is_file(): + raise FileNotFoundError( + f"Shortcut file {shortcut_file} does not exist or is not a file" + ) + return hashlib.sha256(shortcut_file.read_bytes()).hexdigest() class MacOSConfig(PlatformConfig): - def _shortcut_paths(self, data_home: Path, name: str) -> List[Path]: - return [data_home / "Applications" / f"{name}.app"] + def shortcut_path(self, data_home: Path, name: str) -> Path: + return data_home / "Applications" / f"{name}.app" def shortcut_exists(self, data_home: Path, name: str) -> bool: - return self._shortcut_paths(data_home, name).pop().is_dir() + return self.shortcut_path(data_home, name).is_dir() + + def get_shortcut_content_hash(self, data_home: Path, name: str) -> str: + shortcut_dir = self.shortcut_path(data_home, name) + if not shortcut_dir.is_dir(): + raise FileNotFoundError( + f"Shortcut directory {shortcut_dir} does not exist or is not a directory" + ) + + # Hash all files in the .app directory recursively + hash_md5 = hashlib.sha256() + for file_path in sorted(shortcut_dir.rglob("*")): + if file_path.is_file(): + hash_md5.update(file_path.read_bytes()) + return hash_md5.hexdigest() class WindowsConfig(PlatformConfig): - def _shortcut_paths(self, data_home: Path, name: str) -> List[Path]: - return [data_home / "Desktop" / f"{name}.lnk"] + def shortcut_path(self, data_home: Path, name: str) -> Path: + return data_home / "Desktop" / f"{name}.lnk" def shortcut_exists(self, data_home: Path, name: str) -> bool: - for path in self._shortcut_paths(data_home, name): - print(path) - if not path.is_file(): - return False - return True + return self.shortcut_path(data_home, name).is_file() + + def get_shortcut_content_hash(self, data_home: Path, name: str) -> str: + shortcut_file = self.shortcut_path(data_home, name) + if not shortcut_file.is_file(): + raise FileNotFoundError( + f"Shortcut file {shortcut_file} does not exist or is not a file" + ) + return hashlib.sha256(shortcut_file.read_bytes()).hexdigest() def get_platform_config(platform: str) -> PlatformConfig: @@ -93,6 +125,13 @@ def verify_shortcuts_exist( ) +def get_shortcut_content_hash(data_home: Path, name: str) -> str: + """Get the hash of a shortcut's content for the current platform.""" + system = CURRENT_PLATFORM + config = get_platform_config(system) + return config.get_shortcut_content_hash(data_home, name) + + def test_sync_creation_and_removal( pixi: Path, setup_data: SetupData, @@ -372,3 +411,49 @@ def test_add_shortcut( # Verify shortcut exists verify_shortcuts_exist(setup_data.data_home, ["pixi-editor"], expected_exists=True) + + +def test_update_installs_new_shortcuts( + pixi: Path, + setup_data: SetupData, + shortcuts_channel_1: str, +) -> None: + # Verify shortcuts exist after sync + verify_cli_command( + [ + pixi, + "global", + "install", + "--channel", + shortcuts_channel_1, + "pixi-editor=1.0.0", + ], + env=setup_data.env, + ) + verify_shortcuts_exist(setup_data.data_home, ["pixi-editor"], expected_exists=True) + + # Change version requirement to '*' + manifests = setup_data.pixi_home.joinpath("manifests") + manifest = manifests.joinpath("pixi-global.toml") + manifest_dict = tomllib.loads(manifest.read_text()) + manifest_dict["envs"]["pixi-editor"]["dependencies"]["pixi-editor"] = "*" + manifest.write_text(tomli_w.dumps(manifest_dict)) + + # Get initial hash before sync + initial_hash = get_shortcut_content_hash(setup_data.data_home, "pixi-editor") + + # Run pixi sync (nothing should be updated here) + verify_cli_command([pixi, "global", "sync"], env=setup_data.env) + verify_shortcuts_exist(setup_data.data_home, ["pixi-editor"], expected_exists=True) + + # Verify shortcut content is unchanged + current_hash = get_shortcut_content_hash(setup_data.data_home, "pixi-editor") + assert current_hash == initial_hash, "Shortcut content should remain unchanged after sync" + + # Run pixi update + verify_cli_command([pixi, "global", "update"], env=setup_data.env) + verify_shortcuts_exist(setup_data.data_home, ["pixi-editor"], expected_exists=True) + + # Verify shortcut content has changed after update + updated_hash = get_shortcut_content_hash(setup_data.data_home, "pixi-editor") + assert updated_hash != initial_hash, "Shortcut content should be updated after update command"