From a95cb84eda273375bd66f18a08036fe0f37758c6 Mon Sep 17 00:00:00 2001 From: ducminh02 Date: Mon, 4 May 2026 12:52:38 +0200 Subject: [PATCH 1/4] feat: configure XDG_BIN_HOME environment variable for Python and Uv with corresponding unit tests --- .../devonfw/tools/ide/tool/python/Python.java | 1 + .../com/devonfw/tools/ide/tool/uv/Uv.java | 9 ++++ .../tools/ide/tool/python/PythonTest.java | 41 ++++++++++++++++++ .../com/devonfw/tools/ide/tool/uv/UvTest.java | 42 +++++++++++++++++++ 4 files changed, 93 insertions(+) create mode 100644 cli/src/test/java/com/devonfw/tools/ide/tool/python/PythonTest.java create mode 100644 cli/src/test/java/com/devonfw/tools/ide/tool/uv/UvTest.java diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/python/Python.java b/cli/src/main/java/com/devonfw/tools/ide/tool/python/Python.java index caa38b5a3e..bb39c131ad 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/python/Python.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/python/Python.java @@ -66,6 +66,7 @@ public void setEnvironment(EnvironmentContext environmentContext, ToolInstallati super.setEnvironment(environmentContext, toolInstallation, additionalInstallation); environmentContext.withEnvVar("VIRTUAL_ENV", toolInstallation.rootDir().toString()); + environmentContext.withEnvVar("XDG_BIN_HOME", toolInstallation.binDir().toString()); } @Override diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/uv/Uv.java b/cli/src/main/java/com/devonfw/tools/ide/tool/uv/Uv.java index 4efdac6c65..fa61b1516b 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/uv/Uv.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/uv/Uv.java @@ -6,11 +6,13 @@ import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.process.EnvironmentContext; import com.devonfw.tools.ide.process.ProcessContext; import com.devonfw.tools.ide.process.ProcessMode; import com.devonfw.tools.ide.process.ProcessResult; import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; +import com.devonfw.tools.ide.tool.ToolInstallation; import com.devonfw.tools.ide.version.VersionIdentifier; /** @@ -42,4 +44,11 @@ public void installPython(Path installationPath, VersionIdentifier resolvedVersi ProcessResult result = runTool(processContext, ProcessMode.DEFAULT_CAPTURE, List.of("venv", "--python", resolvedVersion.toString())); assert result.isSuccessful(); } + @Override + public void setEnvironment(EnvironmentContext environmentContext, ToolInstallation toolInstallation, boolean additionalInstallation) { + + super.setEnvironment(environmentContext, toolInstallation, additionalInstallation); + Path pythonBinPath = this.context.getSoftwarePath().resolve("python").resolve("bin"); + environmentContext.withEnvVar("XDG_BIN_HOME", pythonBinPath.toString()); + } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/python/PythonTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/python/PythonTest.java new file mode 100644 index 0000000000..2b6f25e22b --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/python/PythonTest.java @@ -0,0 +1,41 @@ +package com.devonfw.tools.ide.tool.python; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import com.devonfw.tools.ide.context.AbstractIdeContextTest; +import com.devonfw.tools.ide.context.IdeTestContext; +import com.devonfw.tools.ide.environment.EnvironmentVariablesType; +import com.devonfw.tools.ide.environment.VariableLine; +import com.devonfw.tools.ide.environment.VariableSource; +import com.devonfw.tools.ide.os.WindowsPathSyntax; +import com.devonfw.tools.ide.process.EnvironmentVariableCollectorContext; +import com.devonfw.tools.ide.tool.ToolInstallation; +import com.devonfw.tools.ide.version.VersionIdentifier; + +/** + * Test of {@link Python}. + */ +public class PythonTest extends AbstractIdeContextTest { + + @Test + public void testSetEnvironment() { + + // arrange + IdeTestContext context = newContext(PROJECT_BASIC); + Python python = new Python(context); + Path rootDir = context.getSoftwarePath().resolve("python"); + ToolInstallation toolInstallation = new ToolInstallation(rootDir, rootDir, rootDir.resolve("bin"), VersionIdentifier.of("3.12.0"), true); + Map variables = new HashMap<>(); + EnvironmentVariableCollectorContext environmentContext = new EnvironmentVariableCollectorContext(variables, new VariableSource(EnvironmentVariablesType.WORKSPACE, null), WindowsPathSyntax.MSYS); + + // act + python.setEnvironment(environmentContext, toolInstallation, false); + + // assert + assertThat(variables.get("XDG_BIN_HOME").getValue()).isEqualTo(toolInstallation.binDir().toString()); + } +} diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/uv/UvTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/uv/UvTest.java new file mode 100644 index 0000000000..391f6a923c --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/uv/UvTest.java @@ -0,0 +1,42 @@ +package com.devonfw.tools.ide.tool.uv; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import com.devonfw.tools.ide.context.AbstractIdeContextTest; +import com.devonfw.tools.ide.context.IdeTestContext; +import com.devonfw.tools.ide.environment.EnvironmentVariablesType; +import com.devonfw.tools.ide.environment.VariableLine; +import com.devonfw.tools.ide.environment.VariableSource; +import com.devonfw.tools.ide.os.WindowsPathSyntax; +import com.devonfw.tools.ide.process.EnvironmentVariableCollectorContext; +import com.devonfw.tools.ide.tool.ToolInstallation; +import com.devonfw.tools.ide.version.VersionIdentifier; + +/** + * Test of {@link Uv}. + */ +public class UvTest extends AbstractIdeContextTest { + + @Test + public void testSetEnvironment() { + + // arrange + IdeTestContext context = newContext(PROJECT_BASIC); + Uv uv = new Uv(context); + Path rootDir = context.getSoftwarePath().resolve("uv"); + ToolInstallation toolInstallation = new ToolInstallation(rootDir, rootDir, rootDir, VersionIdentifier.of("0.1.0"), true); + Map variables = new HashMap<>(); + EnvironmentVariableCollectorContext environmentContext = new EnvironmentVariableCollectorContext(variables, new VariableSource(EnvironmentVariablesType.WORKSPACE, null), WindowsPathSyntax.MSYS); + + // act + uv.setEnvironment(environmentContext, toolInstallation, false); + + // assert + Path expectedPythonBinPath = context.getSoftwarePath().resolve("python").resolve("bin"); + assertThat(variables.get("XDG_BIN_HOME").getValue()).isEqualTo(expectedPythonBinPath.toString()); + } +} From d3d978ecfcc04a859f58cbbd076263d3268afa64 Mon Sep 17 00:00:00 2001 From: ducminh02 Date: Mon, 4 May 2026 12:57:10 +0200 Subject: [PATCH 2/4] test: update XDG_BIN_HOME assertions to use Path objects instead of strings --- .../test/java/com/devonfw/tools/ide/tool/python/PythonTest.java | 2 +- cli/src/test/java/com/devonfw/tools/ide/tool/uv/UvTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/python/PythonTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/python/PythonTest.java index 2b6f25e22b..7c2240bbf1 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/tool/python/PythonTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/python/PythonTest.java @@ -36,6 +36,6 @@ public void testSetEnvironment() { python.setEnvironment(environmentContext, toolInstallation, false); // assert - assertThat(variables.get("XDG_BIN_HOME").getValue()).isEqualTo(toolInstallation.binDir().toString()); + assertThat(Path.of(variables.get("XDG_BIN_HOME").getValue())).isEqualTo(toolInstallation.binDir()); } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/uv/UvTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/uv/UvTest.java index 391f6a923c..2dbfa3a1f7 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/tool/uv/UvTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/uv/UvTest.java @@ -37,6 +37,6 @@ public void testSetEnvironment() { // assert Path expectedPythonBinPath = context.getSoftwarePath().resolve("python").resolve("bin"); - assertThat(variables.get("XDG_BIN_HOME").getValue()).isEqualTo(expectedPythonBinPath.toString()); + assertThat(Path.of(variables.get("XDG_BIN_HOME").getValue())).isEqualTo(expectedPythonBinPath); } } From 1a283373aba7f3d7fe1d60616bf4ad442a1f59b3 Mon Sep 17 00:00:00 2001 From: ducminh02 Date: Thu, 7 May 2026 18:17:31 +0200 Subject: [PATCH 3/4] refactor: replace XDG_BIN_HOME with UV_TOOL_DIR and UV_TOOL_BIN_DIR environment variables in Python and Uv tool configurations --- .../main/java/com/devonfw/tools/ide/tool/python/Python.java | 3 ++- cli/src/main/java/com/devonfw/tools/ide/tool/uv/Uv.java | 5 +++-- .../java/com/devonfw/tools/ide/tool/python/PythonTest.java | 3 ++- cli/src/test/java/com/devonfw/tools/ide/tool/uv/UvTest.java | 5 +++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/python/Python.java b/cli/src/main/java/com/devonfw/tools/ide/tool/python/Python.java index bb39c131ad..3af6dd2fa1 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/python/Python.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/python/Python.java @@ -66,7 +66,8 @@ public void setEnvironment(EnvironmentContext environmentContext, ToolInstallati super.setEnvironment(environmentContext, toolInstallation, additionalInstallation); environmentContext.withEnvVar("VIRTUAL_ENV", toolInstallation.rootDir().toString()); - environmentContext.withEnvVar("XDG_BIN_HOME", toolInstallation.binDir().toString()); + environmentContext.withEnvVar("UV_TOOL_DIR", toolInstallation.rootDir().resolve("tools").toString()); + environmentContext.withEnvVar("UV_TOOL_BIN_DIR", toolInstallation.binDir().toString()); } @Override diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/uv/Uv.java b/cli/src/main/java/com/devonfw/tools/ide/tool/uv/Uv.java index fa61b1516b..9665353f4d 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/uv/Uv.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/uv/Uv.java @@ -48,7 +48,8 @@ public void installPython(Path installationPath, VersionIdentifier resolvedVersi public void setEnvironment(EnvironmentContext environmentContext, ToolInstallation toolInstallation, boolean additionalInstallation) { super.setEnvironment(environmentContext, toolInstallation, additionalInstallation); - Path pythonBinPath = this.context.getSoftwarePath().resolve("python").resolve("bin"); - environmentContext.withEnvVar("XDG_BIN_HOME", pythonBinPath.toString()); + Path pythonPath = this.context.getSoftwarePath().resolve("python"); + environmentContext.withEnvVar("UV_TOOL_DIR", pythonPath.resolve("tools").toString()); + environmentContext.withEnvVar("UV_TOOL_BIN_DIR", pythonPath.resolve("bin").toString()); } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/python/PythonTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/python/PythonTest.java index 7c2240bbf1..5f0139d68e 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/tool/python/PythonTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/python/PythonTest.java @@ -36,6 +36,7 @@ public void testSetEnvironment() { python.setEnvironment(environmentContext, toolInstallation, false); // assert - assertThat(Path.of(variables.get("XDG_BIN_HOME").getValue())).isEqualTo(toolInstallation.binDir()); + assertThat(Path.of(variables.get("UV_TOOL_DIR").getValue())).isEqualTo(toolInstallation.rootDir().resolve("tools")); + assertThat(Path.of(variables.get("UV_TOOL_BIN_DIR").getValue())).isEqualTo(toolInstallation.binDir()); } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/uv/UvTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/uv/UvTest.java index 2dbfa3a1f7..984f7bd4b6 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/tool/uv/UvTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/uv/UvTest.java @@ -36,7 +36,8 @@ public void testSetEnvironment() { uv.setEnvironment(environmentContext, toolInstallation, false); // assert - Path expectedPythonBinPath = context.getSoftwarePath().resolve("python").resolve("bin"); - assertThat(Path.of(variables.get("XDG_BIN_HOME").getValue())).isEqualTo(expectedPythonBinPath); + Path expectedPythonPath = context.getSoftwarePath().resolve("python"); + assertThat(Path.of(variables.get("UV_TOOL_DIR").getValue())).isEqualTo(expectedPythonPath.resolve("tools")); + assertThat(Path.of(variables.get("UV_TOOL_BIN_DIR").getValue())).isEqualTo(expectedPythonPath.resolve("bin")); } } From 267dc54ea368bc1e93bd1d030748ac917cd949ab Mon Sep 17 00:00:00 2001 From: ducminh02 Date: Fri, 8 May 2026 20:32:07 +0200 Subject: [PATCH 4/4] feat: add per-project uv tool isolation via environment variables in CHANGELOG --- CHANGELOG.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 7c2e2ca190..cea7328077 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -20,6 +20,7 @@ Release with new features and bugfixes: * https://github.com/devonfw/IDEasy/issues/1853[#1853]: Add ARM releases for VSCode on Mac * https://github.com/devonfw/IDEasy/issues/797[#797]: Use system unzip on macOS to preserve symlinks in ZIP extraction * https://github.com/devonfw/IDEasy/issues/1723[#1723]: Add commandlet for GitHub Copilot CLI +* https://github.com/devonfw/IDEasy/pull/1885[#1885]: Add per-project uv tool isolation using UV_TOOL_DIR and UV_TOOL_BIN_DIR * https://github.com/devonfw/IDEasy/issues/1688[#1688]: Remove unnecessary message in the CLI when installing a new tool * https://github.com/devonfw/IDEasy/issues/1685[#1685]: Add Nest CLI to IDEasy commandlets