From d04661bdfcf2c4268c370ffaccf7146e053b6019 Mon Sep 17 00:00:00 2001 From: Glatzel <893016099@qq.com> Date: Tue, 18 Mar 2025 16:31:41 +0800 Subject: [PATCH 01/26] fix unexpected exposed removal --- src/global/project/mod.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/global/project/mod.rs b/src/global/project/mod.rs index d81cd651e8..2609042cf9 100644 --- a/src/global/project/mod.rs +++ b/src/global/project/mod.rs @@ -747,7 +747,12 @@ impl Project { .filter_map(|mapping| { // If the executable isn't requested, remove the mapping if execs_all.iter().all(|executable| { - executable_from_path(&executable.path) != mapping.executable_relname() + executable_from_path(&executable.path) + != PathBuf::from(mapping.executable_relname()) + .file_name() + .unwrap() + .to_string_lossy() + .to_string() }) { Some(mapping.exposed_name().clone()) } else { From 174982039689972916308320d48e4cff03b2be77 Mon Sep 17 00:00:00 2001 From: Glatzel <893016099@qq.com> Date: Tue, 18 Mar 2025 16:39:58 +0800 Subject: [PATCH 02/26] add comment --- src/global/project/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/global/project/mod.rs b/src/global/project/mod.rs index 2609042cf9..4efedbb0fb 100644 --- a/src/global/project/mod.rs +++ b/src/global/project/mod.rs @@ -746,6 +746,8 @@ impl Project { .iter() .filter_map(|mapping| { // If the executable isn't requested, remove the mapping + // Use file name of executable relname here for custom exposed path. + // `exposed = {dotnet = 'dotnet\dotnet' }`, file_name will be `dotnet`, eg. if execs_all.iter().all(|executable| { executable_from_path(&executable.path) != PathBuf::from(mapping.executable_relname()) From 03faefaf08b6cb8a2e9ddae1392223b0781f886a Mon Sep 17 00:00:00 2001 From: Glatzel <893016099@qq.com> Date: Tue, 18 Mar 2025 17:04:38 +0800 Subject: [PATCH 03/26] clippy --- src/global/project/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/global/project/mod.rs b/src/global/project/mod.rs index 4efedbb0fb..251ee2e5d9 100644 --- a/src/global/project/mod.rs +++ b/src/global/project/mod.rs @@ -752,9 +752,8 @@ impl Project { executable_from_path(&executable.path) != PathBuf::from(mapping.executable_relname()) .file_name() - .unwrap() + .unwrap_or(OsStr::new("")) .to_string_lossy() - .to_string() }) { Some(mapping.exposed_name().clone()) } else { From a64867f212ac17a910aee9cb611d88e7ebd99b0e Mon Sep 17 00:00:00 2001 From: Glatzel <893016099@qq.com> Date: Tue, 18 Mar 2025 19:42:19 +0800 Subject: [PATCH 04/26] fix unexpected error message if env is not existed --- src/cli/global/update.rs | 9 ++++++++- src/global/project/mod.rs | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/cli/global/update.rs b/src/cli/global/update.rs index e355c5c486..2a51827772 100644 --- a/src/cli/global/update.rs +++ b/src/cli/global/update.rs @@ -28,7 +28,14 @@ pub async fn execute(args: Args) -> miette::Result<()> { project: &mut Project, ) -> miette::Result { // See what executables were installed prior to update - let env_binaries = project.executables_of_direct_dependencies(env_name).await?; + let env_binaries = match project.executables_of_direct_dependencies(env_name).await { + Ok(env_binaries) => env_binaries, + // try to install env if env is not existed. + Err(_) => { + let _ = project.install_environment(env_name).await?; + project.executables_of_direct_dependencies(env_name).await? + } + }; // Get the exposed binaries from mapping let exposed_mapping_binaries = &project diff --git a/src/global/project/mod.rs b/src/global/project/mod.rs index 251ee2e5d9..e45d7188a5 100644 --- a/src/global/project/mod.rs +++ b/src/global/project/mod.rs @@ -754,6 +754,7 @@ impl Project { .file_name() .unwrap_or(OsStr::new("")) .to_string_lossy() + }) { Some(mapping.exposed_name().clone()) } else { From 282b188ff252a6bd46d7e6c274f19788567d485f Mon Sep 17 00:00:00 2001 From: Glatzel <893016099@qq.com> Date: Tue, 18 Mar 2025 19:48:53 +0800 Subject: [PATCH 05/26] fmt --- src/global/project/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/global/project/mod.rs b/src/global/project/mod.rs index e45d7188a5..251ee2e5d9 100644 --- a/src/global/project/mod.rs +++ b/src/global/project/mod.rs @@ -754,7 +754,6 @@ impl Project { .file_name() .unwrap_or(OsStr::new("")) .to_string_lossy() - }) { Some(mapping.exposed_name().clone()) } else { From d6a607409af3ee0a2710fd77f5dddfdd9e90b9f3 Mon Sep 17 00:00:00 2001 From: Glatzel <893016099@qq.com> Date: Tue, 18 Mar 2025 22:17:01 +0800 Subject: [PATCH 06/26] initial test --- .../pixi_global/test_global.py | 74 ++++++++++++++++++- 1 file changed, 71 insertions(+), 3 deletions(-) diff --git a/tests/integration_python/pixi_global/test_global.py b/tests/integration_python/pixi_global/test_global.py index 1e853bf296..15dbc9693c 100644 --- a/tests/integration_python/pixi_global/test_global.py +++ b/tests/integration_python/pixi_global/test_global.py @@ -1,10 +1,11 @@ -from pathlib import Path +import platform import tomllib +from pathlib import Path import pytest import tomli_w -from ..common import verify_cli_command, ExitCode, exec_extension, bat_extension -import platform + +from ..common import ExitCode, bat_extension, exec_extension, verify_cli_command MANIFEST_VERSION = 1 @@ -1958,3 +1959,70 @@ def test_remove_dependency(pixi: Path, tmp_pixi_workspace: Path, dummy_channel_1 env=env, stderr_contains="Environment dummy-a doesn't exist", ) + + +def test_update_env_not_installed(pixi: Path, tmp_pixi_workspace: Path, dummy_channel_1: str) -> None: + env = {"PIXI_HOME": str(tmp_pixi_workspace)} + manifests = tmp_pixi_workspace.joinpath("manifests") + manifests.mkdir() + manifest = manifests.joinpath("pixi-global.toml") + original_toml = f""" + version = {MANIFEST_VERSION} + [envs.test] + channels = ["{dummy_channel_1}"] + [envs.test.dependencies] + dummy-a = "*" + [envs.test.exposed] + dummy-a = "custom/dummy-a" + """ + manifest.write_text(original_toml) + dummy_a = tmp_pixi_workspace / "bin" / exec_extension("dummy-a") + + # Test install env when updating with no error + verify_cli_command( + [pixi, "global", "update"], + ExitCode.SUCCESS, + env=env, + ) + assert dummy_a.is_file() + print(manifest.read_text()) + # The tables in the manifest have been preserved + assert manifest.read_text() == original_toml + + +def test_update_custom_exposed(pixi: Path, tmp_pixi_workspace: Path, dummy_channel_1: str) -> None: + env = {"PIXI_HOME": str(tmp_pixi_workspace)} + manifests = tmp_pixi_workspace.joinpath("manifests") + manifests.mkdir() + manifest = manifests.joinpath("pixi-global.toml") + original_toml = f""" + version = {MANIFEST_VERSION} + [envs.test] + channels = ["{dummy_channel_1}"] + [envs.test.dependencies] + dummy-a = "*" + [envs.test.exposed] + dummy-a = "custom/dummy-a" + """ + manifest.write_text(original_toml) + dummy_a = tmp_pixi_workspace / "bin" / exec_extension("dummy-a") + + # Test basic commands + verify_cli_command( + [pixi, "global", "update"], + ExitCode.SUCCESS, + env=env, + ) + assert dummy_a.is_file() + print(manifest.read_text()) + assert manifest.read_text() == original_toml + + # Test Update with custom reposed package in manifest preserved + verify_cli_command( + [pixi, "global", "update"], + ExitCode.SUCCESS, + env=env, + ) + assert dummy_a.is_file() + print(manifest.read_text()) + assert manifest.read_text() == original_toml From 8ac466c26a7867e5e53d300cdc314277bf99004f Mon Sep 17 00:00:00 2001 From: Glatzel <893016099@qq.com> Date: Tue, 18 Mar 2025 22:30:19 +0800 Subject: [PATCH 07/26] fmt --- tests/integration_python/pixi_global/test_global.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/integration_python/pixi_global/test_global.py b/tests/integration_python/pixi_global/test_global.py index 15dbc9693c..249807d099 100644 --- a/tests/integration_python/pixi_global/test_global.py +++ b/tests/integration_python/pixi_global/test_global.py @@ -1961,7 +1961,9 @@ def test_remove_dependency(pixi: Path, tmp_pixi_workspace: Path, dummy_channel_1 ) -def test_update_env_not_installed(pixi: Path, tmp_pixi_workspace: Path, dummy_channel_1: str) -> None: +def test_update_env_not_installed( + pixi: Path, tmp_pixi_workspace: Path, dummy_channel_1: str +) -> None: env = {"PIXI_HOME": str(tmp_pixi_workspace)} manifests = tmp_pixi_workspace.joinpath("manifests") manifests.mkdir() From 0593125a41ec11dccaca8f250b98faeca83d637e Mon Sep 17 00:00:00 2001 From: Glatzel <893016099@qq.com> Date: Wed, 19 Mar 2025 01:44:37 +0800 Subject: [PATCH 08/26] fix test binary path --- tests/integration_python/pixi_global/test_global.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration_python/pixi_global/test_global.py b/tests/integration_python/pixi_global/test_global.py index 249807d099..380666bfe2 100644 --- a/tests/integration_python/pixi_global/test_global.py +++ b/tests/integration_python/pixi_global/test_global.py @@ -1978,7 +1978,7 @@ def test_update_env_not_installed( dummy-a = "custom/dummy-a" """ manifest.write_text(original_toml) - dummy_a = tmp_pixi_workspace / "bin" / exec_extension("dummy-a") + dummy_a = tmp_pixi_workspace / "custom" / exec_extension("dummy-a") # Test install env when updating with no error verify_cli_command( @@ -2007,7 +2007,7 @@ def test_update_custom_exposed(pixi: Path, tmp_pixi_workspace: Path, dummy_chann dummy-a = "custom/dummy-a" """ manifest.write_text(original_toml) - dummy_a = tmp_pixi_workspace / "bin" / exec_extension("dummy-a") + dummy_a = tmp_pixi_workspace / "custom" / exec_extension("dummy-a") # Test basic commands verify_cli_command( From 1726a95203630a0be40be8f44462cd545f079342 Mon Sep 17 00:00:00 2001 From: Glatzel <893016099@qq.com> Date: Wed, 19 Mar 2025 01:55:52 +0800 Subject: [PATCH 09/26] revert test binary path to bin --- tests/integration_python/pixi_global/test_global.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/integration_python/pixi_global/test_global.py b/tests/integration_python/pixi_global/test_global.py index 380666bfe2..726617730d 100644 --- a/tests/integration_python/pixi_global/test_global.py +++ b/tests/integration_python/pixi_global/test_global.py @@ -1975,10 +1975,10 @@ def test_update_env_not_installed( [envs.test.dependencies] dummy-a = "*" [envs.test.exposed] - dummy-a = "custom/dummy-a" + dummy-a = "bin/dummy-a" """ manifest.write_text(original_toml) - dummy_a = tmp_pixi_workspace / "custom" / exec_extension("dummy-a") + dummy_a = tmp_pixi_workspace / "bin" / exec_extension("dummy-a") # Test install env when updating with no error verify_cli_command( @@ -2004,10 +2004,10 @@ def test_update_custom_exposed(pixi: Path, tmp_pixi_workspace: Path, dummy_chann [envs.test.dependencies] dummy-a = "*" [envs.test.exposed] - dummy-a = "custom/dummy-a" + dummy-a = "bin/dummy-a" """ manifest.write_text(original_toml) - dummy_a = tmp_pixi_workspace / "custom" / exec_extension("dummy-a") + dummy_a = tmp_pixi_workspace / "bin" / exec_extension("dummy-a") # Test basic commands verify_cli_command( From fd2b6b3afbea1311e7da1ffb74e0b62e8b6634d6 Mon Sep 17 00:00:00 2001 From: Glatzel <893016099@qq.com> Date: Wed, 19 Mar 2025 10:16:51 +0800 Subject: [PATCH 10/26] add real-world dotnet test --- .../pixi_global/test_global.py | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tests/integration_python/pixi_global/test_global.py b/tests/integration_python/pixi_global/test_global.py index 726617730d..d7964fcbbc 100644 --- a/tests/integration_python/pixi_global/test_global.py +++ b/tests/integration_python/pixi_global/test_global.py @@ -1,4 +1,6 @@ +import os import platform +import shutil import tomllib from pathlib import Path @@ -2028,3 +2030,64 @@ def test_update_custom_exposed(pixi: Path, tmp_pixi_workspace: Path, dummy_chann assert dummy_a.is_file() print(manifest.read_text()) assert manifest.read_text() == original_toml + + +@pytest.mark.slow +@pytest.mark.parametrize( + ("delete_exposed_on_second", "delete_env_on_second"), + [(True, False), (False, True), (False, False)], +) +def test_update_custom_exposed_twice( + pixi: Path, + tmp_pixi_workspace: Path, + delete_exposed_on_second: bool, + delete_env_on_second: bool, +) -> None: + env = {"PIXI_HOME": str(tmp_pixi_workspace)} + manifests = tmp_pixi_workspace.joinpath("manifests") + manifests.mkdir() + manifest = manifests.joinpath("pixi-global.toml") + original_toml = """ + version = {MANIFEST_VERSION} + [envs.dotnet] + channels = ["conda-forge"] + dependencies = { dotnet = "*" } + exposed = {dotnet = 'dotnet/dotnet' } + """ + manifest.write_text(original_toml) + dotnet = tmp_pixi_workspace / "bin" / exec_extension("dotnet") + + # Test first update with on env installed + verify_cli_command( + [pixi, "global", "update"], + ExitCode.SUCCESS, + env=env, + ) + + assert dotnet.is_file() + print(manifest.read_text()) + assert manifest.read_text() == original_toml + verify_cli_command( + [dotnet, "help"], + ExitCode.SUCCESS, + env=env, + ) + + # Test second update + if delete_exposed_on_second: + os.remove(dotnet) + if delete_env_on_second: + shutil.rmtree(tmp_pixi_workspace / "env") + verify_cli_command( + [pixi, "global", "update"], + ExitCode.SUCCESS, + env=env, + ) + assert dotnet.is_file() + verify_cli_command( + [dotnet, "help"], + ExitCode.SUCCESS, + env=env, + ) + print(manifest.read_text()) + assert manifest.read_text() == original_toml From bfdbc68cd44f3fa756bdd4fc0e74ed8b31e3073d Mon Sep 17 00:00:00 2001 From: Glatzel <893016099@qq.com> Date: Wed, 19 Mar 2025 10:26:30 +0800 Subject: [PATCH 11/26] fix manifest --- tests/integration_python/pixi_global/test_global.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/integration_python/pixi_global/test_global.py b/tests/integration_python/pixi_global/test_global.py index d7964fcbbc..3140cdd42f 100644 --- a/tests/integration_python/pixi_global/test_global.py +++ b/tests/integration_python/pixi_global/test_global.py @@ -2047,12 +2047,14 @@ def test_update_custom_exposed_twice( manifests = tmp_pixi_workspace.joinpath("manifests") manifests.mkdir() manifest = manifests.joinpath("pixi-global.toml") - original_toml = """ + original_toml = f""" version = {MANIFEST_VERSION} - [envs.dotnet] + [envs.test] channels = ["conda-forge"] - dependencies = { dotnet = "*" } - exposed = {dotnet = 'dotnet/dotnet' } + [envs.test.dependencies] + dotnet = "*" + [envs.test.exposed] + dotnet = "dotnet/dotnet" """ manifest.write_text(original_toml) dotnet = tmp_pixi_workspace / "bin" / exec_extension("dotnet") From 777b582314917ebed144c33ccd5b894440e2f807 Mon Sep 17 00:00:00 2001 From: Glatzel <893016099@qq.com> Date: Wed, 19 Mar 2025 11:14:44 +0800 Subject: [PATCH 12/26] fix exposed_mapping_binaries when env is not existed but exposed exists. --- src/cli/global/update.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/cli/global/update.rs b/src/cli/global/update.rs index 2a51827772..459cd94732 100644 --- a/src/cli/global/update.rs +++ b/src/cli/global/update.rs @@ -30,7 +30,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { // See what executables were installed prior to update let env_binaries = match project.executables_of_direct_dependencies(env_name).await { Ok(env_binaries) => env_binaries, - // try to install env if env is not existed. + // try to install env when env is not existed. Err(_) => { let _ = project.install_environment(env_name).await?; project.executables_of_direct_dependencies(env_name).await? @@ -38,10 +38,19 @@ pub async fn execute(args: Args) -> miette::Result<()> { }; // Get the exposed binaries from mapping - let exposed_mapping_binaries = &project - .environment(env_name) - .ok_or_else(|| miette::miette!("Environment {} not found", env_name.fancy_display()))? - .exposed; + let exposed_mapping_binaries = match &project.environment(env_name) { + Some(env) => &env.exposed, + None => { + // try to install env when env is not existed but exposed exists. + let _ = project.install_environment(env_name).await?; + &project + .environment(env_name) + .ok_or_else(|| { + miette::miette!("Environment {} not found", env_name.fancy_display()) + })? + .exposed + } + }; // Check if they were all auto-exposed, or if the user manually exposed a subset of them let expose_type = if check_all_exposed(&env_binaries, exposed_mapping_binaries) { From 4d0902df1db2eddcba8b46c0ab3dd90d5503edc2 Mon Sep 17 00:00:00 2001 From: Glatzel <893016099@qq.com> Date: Wed, 19 Mar 2025 11:28:47 +0800 Subject: [PATCH 13/26] revert exposed_mapping_binaries --- src/cli/global/update.rs | 19 ++++---------- .../pixi_global/test_global.py | 26 +++++++++++++++---- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/cli/global/update.rs b/src/cli/global/update.rs index 459cd94732..2a51827772 100644 --- a/src/cli/global/update.rs +++ b/src/cli/global/update.rs @@ -30,7 +30,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { // See what executables were installed prior to update let env_binaries = match project.executables_of_direct_dependencies(env_name).await { Ok(env_binaries) => env_binaries, - // try to install env when env is not existed. + // try to install env if env is not existed. Err(_) => { let _ = project.install_environment(env_name).await?; project.executables_of_direct_dependencies(env_name).await? @@ -38,19 +38,10 @@ pub async fn execute(args: Args) -> miette::Result<()> { }; // Get the exposed binaries from mapping - let exposed_mapping_binaries = match &project.environment(env_name) { - Some(env) => &env.exposed, - None => { - // try to install env when env is not existed but exposed exists. - let _ = project.install_environment(env_name).await?; - &project - .environment(env_name) - .ok_or_else(|| { - miette::miette!("Environment {} not found", env_name.fancy_display()) - })? - .exposed - } - }; + let exposed_mapping_binaries = &project + .environment(env_name) + .ok_or_else(|| miette::miette!("Environment {} not found", env_name.fancy_display()))? + .exposed; // Check if they were all auto-exposed, or if the user manually exposed a subset of them let expose_type = if check_all_exposed(&env_binaries, exposed_mapping_binaries) { diff --git a/tests/integration_python/pixi_global/test_global.py b/tests/integration_python/pixi_global/test_global.py index 3140cdd42f..01c0925f0b 100644 --- a/tests/integration_python/pixi_global/test_global.py +++ b/tests/integration_python/pixi_global/test_global.py @@ -1994,7 +1994,17 @@ def test_update_env_not_installed( assert manifest.read_text() == original_toml -def test_update_custom_exposed(pixi: Path, tmp_pixi_workspace: Path, dummy_channel_1: str) -> None: +@pytest.mark.parametrize( + ("delete_exposed_on_second", "delete_env_on_second"), + [(True, False), (False, True), (False, False)], +) +def test_update_custom_exposed_twice( + pixi: Path, + tmp_pixi_workspace: Path, + dummy_channel_1: str, + delete_exposed_on_second: bool, + delete_env_on_second: bool, +) -> None: env = {"PIXI_HOME": str(tmp_pixi_workspace)} manifests = tmp_pixi_workspace.joinpath("manifests") manifests.mkdir() @@ -2011,7 +2021,8 @@ def test_update_custom_exposed(pixi: Path, tmp_pixi_workspace: Path, dummy_chann manifest.write_text(original_toml) dummy_a = tmp_pixi_workspace / "bin" / exec_extension("dummy-a") - # Test basic commands + + # Test first update verify_cli_command( [pixi, "global", "update"], ExitCode.SUCCESS, @@ -2021,7 +2032,12 @@ def test_update_custom_exposed(pixi: Path, tmp_pixi_workspace: Path, dummy_chann print(manifest.read_text()) assert manifest.read_text() == original_toml - # Test Update with custom reposed package in manifest preserved + # Test second update + if delete_exposed_on_second: + os.remove(dummy_a) + if delete_env_on_second: + shutil.rmtree(tmp_pixi_workspace / "env") + verify_cli_command( [pixi, "global", "update"], ExitCode.SUCCESS, @@ -2037,7 +2053,7 @@ def test_update_custom_exposed(pixi: Path, tmp_pixi_workspace: Path, dummy_chann ("delete_exposed_on_second", "delete_env_on_second"), [(True, False), (False, True), (False, False)], ) -def test_update_custom_exposed_twice( +def test_update_custom_exposed_twice_slow( pixi: Path, tmp_pixi_workspace: Path, delete_exposed_on_second: bool, @@ -2079,7 +2095,7 @@ def test_update_custom_exposed_twice( if delete_exposed_on_second: os.remove(dotnet) if delete_env_on_second: - shutil.rmtree(tmp_pixi_workspace / "env") + shutil.rmtree(tmp_pixi_workspace / "envs") verify_cli_command( [pixi, "global", "update"], ExitCode.SUCCESS, From eb930cc47d0ed0582c293312b210c6be73080f29 Mon Sep 17 00:00:00 2001 From: Glatzel <893016099@qq.com> Date: Wed, 19 Mar 2025 11:29:17 +0800 Subject: [PATCH 14/26] fix env path --- tests/integration_python/pixi_global/test_global.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration_python/pixi_global/test_global.py b/tests/integration_python/pixi_global/test_global.py index 01c0925f0b..3f9802d6e8 100644 --- a/tests/integration_python/pixi_global/test_global.py +++ b/tests/integration_python/pixi_global/test_global.py @@ -2036,7 +2036,7 @@ def test_update_custom_exposed_twice( if delete_exposed_on_second: os.remove(dummy_a) if delete_env_on_second: - shutil.rmtree(tmp_pixi_workspace / "env") + shutil.rmtree(tmp_pixi_workspace / "envs") verify_cli_command( [pixi, "global", "update"], From 14c245a97079a07a7866ce7957a910f3467d9187 Mon Sep 17 00:00:00 2001 From: Glatzel <893016099@qq.com> Date: Wed, 19 Mar 2025 11:36:33 +0800 Subject: [PATCH 15/26] fmt --- tests/integration_python/pixi_global/test_global.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration_python/pixi_global/test_global.py b/tests/integration_python/pixi_global/test_global.py index 3f9802d6e8..759e2545c5 100644 --- a/tests/integration_python/pixi_global/test_global.py +++ b/tests/integration_python/pixi_global/test_global.py @@ -2021,7 +2021,6 @@ def test_update_custom_exposed_twice( manifest.write_text(original_toml) dummy_a = tmp_pixi_workspace / "bin" / exec_extension("dummy-a") - # Test first update verify_cli_command( [pixi, "global", "update"], From 8a5669ebb41cae650641c75f0d19cc26af094087 Mon Sep 17 00:00:00 2001 From: Glatzel <893016099@qq.com> Date: Wed, 19 Mar 2025 11:53:16 +0800 Subject: [PATCH 16/26] skip test on win as it need admin permission --- tests/integration_python/pixi_global/test_global.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/integration_python/pixi_global/test_global.py b/tests/integration_python/pixi_global/test_global.py index 759e2545c5..7b3864cfb6 100644 --- a/tests/integration_python/pixi_global/test_global.py +++ b/tests/integration_python/pixi_global/test_global.py @@ -1998,6 +1998,10 @@ def test_update_env_not_installed( ("delete_exposed_on_second", "delete_env_on_second"), [(True, False), (False, True), (False, False)], ) +@pytest.mark.skipif( + platform.system() == "Windows", + reason="require admin permissions to delete env", +) def test_update_custom_exposed_twice( pixi: Path, tmp_pixi_workspace: Path, @@ -2052,6 +2056,10 @@ def test_update_custom_exposed_twice( ("delete_exposed_on_second", "delete_env_on_second"), [(True, False), (False, True), (False, False)], ) +@pytest.mark.skipif( + platform.system() == "Windows", + reason="require admin permissions to delete env", +) def test_update_custom_exposed_twice_slow( pixi: Path, tmp_pixi_workspace: Path, From b1f568c196de8b4881f19786d2afe8ac13034f54 Mon Sep 17 00:00:00 2001 From: Glatzel <893016099@qq.com> Date: Wed, 19 Mar 2025 15:19:57 +0800 Subject: [PATCH 17/26] initial update remove deprecated env --- src/cli/global/update.rs | 5 ++++- src/global/project/mod.rs | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/cli/global/update.rs b/src/cli/global/update.rs index 2a51827772..d9cdbc9fd5 100644 --- a/src/cli/global/update.rs +++ b/src/cli/global/update.rs @@ -74,7 +74,10 @@ pub async fn execute(args: Args) -> miette::Result<()> { // Update all environments if the user did not specify any let env_names = match args.environments { Some(env_names) => env_names, - None => project_original.environments().keys().cloned().collect(), + None => { + let _ = project_original.prune_old_environments().await?; + project_original.environments().keys().cloned().collect() + } }; // Apply changes to each environment, only revert changes if an error occurs diff --git a/src/global/project/mod.rs b/src/global/project/mod.rs index 3a4eafa795..5247180070 100644 --- a/src/global/project/mod.rs +++ b/src/global/project/mod.rs @@ -1061,6 +1061,7 @@ impl Project { let mut state_changes = StateChanges::default(); for env_path in self.env_root.directories().await? { + println!("{}", env_path.to_str().unwrap()); let Some(Ok(env_name)) = env_path .file_name() .and_then(|name| name.to_str()) From be1075db79a56e9f27cb0209faebb38032df0042 Mon Sep 17 00:00:00 2001 From: Glatzel <893016099@qq.com> Date: Wed, 19 Mar 2025 15:31:47 +0800 Subject: [PATCH 18/26] add error handling and fmt --- src/cli/global/update.rs | 6 +++++- src/global/project/mod.rs | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/cli/global/update.rs b/src/cli/global/update.rs index d9cdbc9fd5..3541231d49 100644 --- a/src/cli/global/update.rs +++ b/src/cli/global/update.rs @@ -75,7 +75,11 @@ pub async fn execute(args: Args) -> miette::Result<()> { let env_names = match args.environments { Some(env_names) => env_names, None => { - let _ = project_original.prune_old_environments().await?; + // prune old environments whose env is still existed but already removed from manifest + match project_original.prune_old_environments().await { + Ok(state_changes) => state_changes.report(), + Err(err) => return Err(err), + } project_original.environments().keys().cloned().collect() } }; diff --git a/src/global/project/mod.rs b/src/global/project/mod.rs index 5247180070..3a4eafa795 100644 --- a/src/global/project/mod.rs +++ b/src/global/project/mod.rs @@ -1061,7 +1061,6 @@ impl Project { let mut state_changes = StateChanges::default(); for env_path in self.env_root.directories().await? { - println!("{}", env_path.to_str().unwrap()); let Some(Ok(env_name)) = env_path .file_name() .and_then(|name| name.to_str()) From bafaffbbbf9aa3f89f6492653e002b7a2529a4e4 Mon Sep 17 00:00:00 2001 From: Glatzel <893016099@qq.com> Date: Wed, 19 Mar 2025 15:38:04 +0800 Subject: [PATCH 19/26] add test_update_remove_old_env --- .../pixi_global/test_global.py | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tests/integration_python/pixi_global/test_global.py b/tests/integration_python/pixi_global/test_global.py index 7b3864cfb6..0a2fa9b32f 100644 --- a/tests/integration_python/pixi_global/test_global.py +++ b/tests/integration_python/pixi_global/test_global.py @@ -2116,3 +2116,47 @@ def test_update_custom_exposed_twice_slow( ) print(manifest.read_text()) assert manifest.read_text() == original_toml + + +def test_update_remove_old_env( + pixi: Path, + tmp_pixi_workspace: Path, + dummy_channel_1: str, +) -> None: + env = {"PIXI_HOME": str(tmp_pixi_workspace)} + manifests = tmp_pixi_workspace.joinpath("manifests") + manifests.mkdir() + manifest = manifests.joinpath("pixi-global.toml") + original_toml = f""" + version = {MANIFEST_VERSION} + [envs.test] + channels = ["{dummy_channel_1}"] + [envs.test.dependencies] + dummy-a = "*" + [envs.test.exposed] + dummy-a = "bin/dummy-a" + """ + manifest.write_text(original_toml) + dummy_a = tmp_pixi_workspace / "bin" / exec_extension("dummy-a") + + # Test first update + verify_cli_command( + [pixi, "global", "update"], + ExitCode.SUCCESS, + env=env, + ) + assert dummy_a.is_file() + print(manifest.read_text()) + assert manifest.read_text() == original_toml + + # Test remove env and update + original_toml = f" version = {MANIFEST_VERSION}" + manifest.write_text(original_toml) + verify_cli_command( + [pixi, "global", "update"], + ExitCode.SUCCESS, + env=env, + ) + assert not dummy_a.is_file() + print(manifest.read_text()) + assert manifest.read_text() == original_toml From 81c9174a23efd9a62562d2bcb76aa52e521ffb78 Mon Sep 17 00:00:00 2001 From: Glatzel <893016099@qq.com> Date: Wed, 19 Mar 2025 15:55:36 +0800 Subject: [PATCH 20/26] fix test --- tests/integration_python/pixi_global/test_global.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/integration_python/pixi_global/test_global.py b/tests/integration_python/pixi_global/test_global.py index 0a2fa9b32f..8b555574f5 100644 --- a/tests/integration_python/pixi_global/test_global.py +++ b/tests/integration_python/pixi_global/test_global.py @@ -2150,7 +2150,9 @@ def test_update_remove_old_env( assert manifest.read_text() == original_toml # Test remove env and update - original_toml = f" version = {MANIFEST_VERSION}" + original_toml = f""" + version = {MANIFEST_VERSION} + """ manifest.write_text(original_toml) verify_cli_command( [pixi, "global", "update"], From 38f34dde0d7dd004ba67624cdda0a2bf6ed3317b Mon Sep 17 00:00:00 2001 From: Glatzel <893016099@qq.com> Date: Wed, 19 Mar 2025 15:58:34 +0800 Subject: [PATCH 21/26] fix comment --- tests/integration_python/pixi_global/test_global.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration_python/pixi_global/test_global.py b/tests/integration_python/pixi_global/test_global.py index 8b555574f5..4957487462 100644 --- a/tests/integration_python/pixi_global/test_global.py +++ b/tests/integration_python/pixi_global/test_global.py @@ -2149,7 +2149,7 @@ def test_update_remove_old_env( print(manifest.read_text()) assert manifest.read_text() == original_toml - # Test remove env and update + # Test remove env from manifest and then update original_toml = f""" version = {MANIFEST_VERSION} """ From 82a42b0d4a9d99651b82259530aefa6accb5a3f1 Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Wed, 19 Mar 2025 10:42:56 +0100 Subject: [PATCH 22/26] Check if environment is in sync, instead of installing on failure --- src/cli/global/update.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/cli/global/update.rs b/src/cli/global/update.rs index 3541231d49..afa0c69ba8 100644 --- a/src/cli/global/update.rs +++ b/src/cli/global/update.rs @@ -27,15 +27,13 @@ pub async fn execute(args: Args) -> miette::Result<()> { env_name: &EnvironmentName, project: &mut Project, ) -> miette::Result { + // If the environment ins't up-to-date our executable detection afterwards will not work + 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 = match project.executables_of_direct_dependencies(env_name).await { - Ok(env_binaries) => env_binaries, - // try to install env if env is not existed. - Err(_) => { - let _ = project.install_environment(env_name).await?; - project.executables_of_direct_dependencies(env_name).await? - } - }; + let env_binaries = project.executables_of_direct_dependencies(env_name).await?; // Get the exposed binaries from mapping let exposed_mapping_binaries = &project From 4206a04ce4ca47d1cc617690e9ed1c018250273b Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Wed, 19 Mar 2025 10:51:13 +0100 Subject: [PATCH 23/26] Simplify environment pruning --- src/cli/global/update.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/cli/global/update.rs b/src/cli/global/update.rs index afa0c69ba8..63359be4b7 100644 --- a/src/cli/global/update.rs +++ b/src/cli/global/update.rs @@ -73,11 +73,9 @@ pub async fn execute(args: Args) -> miette::Result<()> { let env_names = match args.environments { Some(env_names) => env_names, None => { - // prune old environments whose env is still existed but already removed from manifest - match project_original.prune_old_environments().await { - Ok(state_changes) => state_changes.report(), - Err(err) => return Err(err), - } + // prune old environments + let state_changes = project_original.prune_old_environments().await?; + state_changes.report(); project_original.environments().keys().cloned().collect() } }; From 39a2bcd8a3f827ba06e24e5b3b4d3b426b73b2e1 Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Wed, 19 Mar 2025 11:01:49 +0100 Subject: [PATCH 24/26] Improve logic and test code and also run on Windows --- src/cli/global/update.rs | 2 +- src/global/project/mod.rs | 10 ++++---- .../pixi_global/test_global.py | 24 ++++--------------- 3 files changed, 10 insertions(+), 26 deletions(-) diff --git a/src/cli/global/update.rs b/src/cli/global/update.rs index 63359be4b7..7824091bb2 100644 --- a/src/cli/global/update.rs +++ b/src/cli/global/update.rs @@ -27,7 +27,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { env_name: &EnvironmentName, project: &mut Project, ) -> miette::Result { - // If the environment ins't up-to-date our executable detection afterwards will not work + // If the environment isn't up-to-date our executable detection afterwards will not work if !project.environment_in_sync(env_name).await? { let _ = project.install_environment(env_name).await?; } diff --git a/src/global/project/mod.rs b/src/global/project/mod.rs index 3a4eafa795..5b79dd07ea 100644 --- a/src/global/project/mod.rs +++ b/src/global/project/mod.rs @@ -756,12 +756,12 @@ impl Project { // If the executable isn't requested, remove the mapping // Use file name of executable relname here for custom exposed path. // `exposed = {dotnet = 'dotnet\dotnet' }`, file_name will be `dotnet`, eg. + let executable_file_name = PathBuf::from(mapping.executable_relname()) + .file_name()? + .to_string_lossy() + .to_string(); if execs_all.iter().all(|executable| { - executable_from_path(&executable.path) - != PathBuf::from(mapping.executable_relname()) - .file_name() - .unwrap_or(OsStr::new("")) - .to_string_lossy() + executable_from_path(&executable.path) != executable_file_name }) { Some(mapping.exposed_name().clone()) } else { diff --git a/tests/integration_python/pixi_global/test_global.py b/tests/integration_python/pixi_global/test_global.py index 4957487462..69cce4db6e 100644 --- a/tests/integration_python/pixi_global/test_global.py +++ b/tests/integration_python/pixi_global/test_global.py @@ -1,4 +1,3 @@ -import os import platform import shutil import tomllib @@ -411,7 +410,6 @@ def test_expose_preserves_table_format( ExitCode.SUCCESS, env=env, ) - print(manifest.read_text()) # The tables in the manifest have been preserved assert manifest.read_text() == original_toml + 'dummy-aa = "dummy-a"\n' @@ -1982,14 +1980,14 @@ def test_update_env_not_installed( manifest.write_text(original_toml) dummy_a = tmp_pixi_workspace / "bin" / exec_extension("dummy-a") - # Test install env when updating with no error + # If the environment isn't installed already, + # `pixi global update` will install it first verify_cli_command( [pixi, "global", "update"], ExitCode.SUCCESS, env=env, ) assert dummy_a.is_file() - print(manifest.read_text()) # The tables in the manifest have been preserved assert manifest.read_text() == original_toml @@ -1998,10 +1996,6 @@ def test_update_env_not_installed( ("delete_exposed_on_second", "delete_env_on_second"), [(True, False), (False, True), (False, False)], ) -@pytest.mark.skipif( - platform.system() == "Windows", - reason="require admin permissions to delete env", -) def test_update_custom_exposed_twice( pixi: Path, tmp_pixi_workspace: Path, @@ -2032,12 +2026,11 @@ def test_update_custom_exposed_twice( env=env, ) assert dummy_a.is_file() - print(manifest.read_text()) assert manifest.read_text() == original_toml # Test second update if delete_exposed_on_second: - os.remove(dummy_a) + dummy_a.unlink() if delete_env_on_second: shutil.rmtree(tmp_pixi_workspace / "envs") @@ -2047,7 +2040,6 @@ def test_update_custom_exposed_twice( env=env, ) assert dummy_a.is_file() - print(manifest.read_text()) assert manifest.read_text() == original_toml @@ -2056,10 +2048,6 @@ def test_update_custom_exposed_twice( ("delete_exposed_on_second", "delete_env_on_second"), [(True, False), (False, True), (False, False)], ) -@pytest.mark.skipif( - platform.system() == "Windows", - reason="require admin permissions to delete env", -) def test_update_custom_exposed_twice_slow( pixi: Path, tmp_pixi_workspace: Path, @@ -2090,7 +2078,6 @@ def test_update_custom_exposed_twice_slow( ) assert dotnet.is_file() - print(manifest.read_text()) assert manifest.read_text() == original_toml verify_cli_command( [dotnet, "help"], @@ -2100,7 +2087,7 @@ def test_update_custom_exposed_twice_slow( # Test second update if delete_exposed_on_second: - os.remove(dotnet) + dotnet.unlink() if delete_env_on_second: shutil.rmtree(tmp_pixi_workspace / "envs") verify_cli_command( @@ -2114,7 +2101,6 @@ def test_update_custom_exposed_twice_slow( ExitCode.SUCCESS, env=env, ) - print(manifest.read_text()) assert manifest.read_text() == original_toml @@ -2146,7 +2132,6 @@ def test_update_remove_old_env( env=env, ) assert dummy_a.is_file() - print(manifest.read_text()) assert manifest.read_text() == original_toml # Test remove env from manifest and then update @@ -2160,5 +2145,4 @@ def test_update_remove_old_env( env=env, ) assert not dummy_a.is_file() - print(manifest.read_text()) assert manifest.read_text() == original_toml From c40ac5264ca9357c713af243f3c8112da97a7d5c Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Wed, 19 Mar 2025 11:04:13 +0100 Subject: [PATCH 25/26] Remove unneeded test --- .../pixi_global/test_global.py | 61 ------------------- 1 file changed, 61 deletions(-) diff --git a/tests/integration_python/pixi_global/test_global.py b/tests/integration_python/pixi_global/test_global.py index 69cce4db6e..58e5f7ce4b 100644 --- a/tests/integration_python/pixi_global/test_global.py +++ b/tests/integration_python/pixi_global/test_global.py @@ -2043,67 +2043,6 @@ def test_update_custom_exposed_twice( assert manifest.read_text() == original_toml -@pytest.mark.slow -@pytest.mark.parametrize( - ("delete_exposed_on_second", "delete_env_on_second"), - [(True, False), (False, True), (False, False)], -) -def test_update_custom_exposed_twice_slow( - pixi: Path, - tmp_pixi_workspace: Path, - delete_exposed_on_second: bool, - delete_env_on_second: bool, -) -> None: - env = {"PIXI_HOME": str(tmp_pixi_workspace)} - manifests = tmp_pixi_workspace.joinpath("manifests") - manifests.mkdir() - manifest = manifests.joinpath("pixi-global.toml") - original_toml = f""" - version = {MANIFEST_VERSION} - [envs.test] - channels = ["conda-forge"] - [envs.test.dependencies] - dotnet = "*" - [envs.test.exposed] - dotnet = "dotnet/dotnet" - """ - manifest.write_text(original_toml) - dotnet = tmp_pixi_workspace / "bin" / exec_extension("dotnet") - - # Test first update with on env installed - verify_cli_command( - [pixi, "global", "update"], - ExitCode.SUCCESS, - env=env, - ) - - assert dotnet.is_file() - assert manifest.read_text() == original_toml - verify_cli_command( - [dotnet, "help"], - ExitCode.SUCCESS, - env=env, - ) - - # Test second update - if delete_exposed_on_second: - dotnet.unlink() - if delete_env_on_second: - shutil.rmtree(tmp_pixi_workspace / "envs") - verify_cli_command( - [pixi, "global", "update"], - ExitCode.SUCCESS, - env=env, - ) - assert dotnet.is_file() - verify_cli_command( - [dotnet, "help"], - ExitCode.SUCCESS, - env=env, - ) - assert manifest.read_text() == original_toml - - def test_update_remove_old_env( pixi: Path, tmp_pixi_workspace: Path, From 7eb32e9c386f88c4d81d5818a4d9b5504b635f16 Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Wed, 19 Mar 2025 11:05:40 +0100 Subject: [PATCH 26/26] Remove exitcode success --- tests/integration_python/pixi_global/test_global.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/tests/integration_python/pixi_global/test_global.py b/tests/integration_python/pixi_global/test_global.py index 58e5f7ce4b..f7f71d544b 100644 --- a/tests/integration_python/pixi_global/test_global.py +++ b/tests/integration_python/pixi_global/test_global.py @@ -318,11 +318,7 @@ def test_expose_basic(pixi: Path, tmp_pixi_workspace: Path, dummy_channel_1: str nested_dummy = tmp_pixi_workspace / "bin" / exec_extension("dummy") # Add dummy-a with simple syntax - verify_cli_command( - [pixi, "global", "expose", "add", "--environment=test", "dummy-a"], - ExitCode.SUCCESS, - env=env, - ) + verify_cli_command([pixi, "global", "expose", "add", "--environment=test", "dummy-a"], env=env) assert dummy_a.is_file() # Add dummy1 and dummy3 and nested/dummy @@ -407,7 +403,6 @@ def test_expose_preserves_table_format( verify_cli_command( [pixi, "global", "expose", "add", "--environment=test", "dummy-aa=dummy-a"], - ExitCode.SUCCESS, env=env, ) # The tables in the manifest have been preserved @@ -1984,7 +1979,6 @@ def test_update_env_not_installed( # `pixi global update` will install it first verify_cli_command( [pixi, "global", "update"], - ExitCode.SUCCESS, env=env, ) assert dummy_a.is_file() @@ -2022,7 +2016,6 @@ def test_update_custom_exposed_twice( # Test first update verify_cli_command( [pixi, "global", "update"], - ExitCode.SUCCESS, env=env, ) assert dummy_a.is_file() @@ -2036,7 +2029,6 @@ def test_update_custom_exposed_twice( verify_cli_command( [pixi, "global", "update"], - ExitCode.SUCCESS, env=env, ) assert dummy_a.is_file() @@ -2067,7 +2059,6 @@ def test_update_remove_old_env( # Test first update verify_cli_command( [pixi, "global", "update"], - ExitCode.SUCCESS, env=env, ) assert dummy_a.is_file() @@ -2080,7 +2071,6 @@ def test_update_remove_old_env( manifest.write_text(original_toml) verify_cli_command( [pixi, "global", "update"], - ExitCode.SUCCESS, env=env, ) assert not dummy_a.is_file()