diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 39632366c..0944989ec 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -7,6 +7,7 @@ This file documents all notable changes to https://github.com/devonfw/IDEasy[IDE Release with new features and bugfixes: * https://github.com/devonfw/IDEasy/issues/553[#554]: Missmatch of IDE_ROOT +* https://github.com/devonfw/IDEasy/issues/556[#556]: ProcessContext should compute PATH on run and not in constructor * https://github.com/devonfw/IDEasy/issues/557[#557]: Failed to update tomcat: Cannot find a (Map) Key deserializer for type VersionRange The full list of changes for this release can be found in https://github.com/devonfw/IDEasy/milestone/13?closed=1[milestone 2024.09.002]. diff --git a/cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java b/cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java index 53018396a..84a3ed2d4 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java +++ b/cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java @@ -5,6 +5,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -39,6 +40,8 @@ public class SystemPath { private final List paths; + private final List extraPathEntries; + private final IdeContext context; private static final List EXTENSION_PRIORITY = List.of(".exe", ".cmd", ".bat", ".msi", ".ps1", ""); @@ -74,7 +77,7 @@ public SystemPath(IdeContext context, String envPath) { */ public SystemPath(IdeContext context, String envPath, Path ideRoot, Path softwarePath) { - this(context, envPath, ideRoot, softwarePath, File.pathSeparatorChar); + this(context, envPath, ideRoot, softwarePath, File.pathSeparatorChar, Collections.emptyList()); } /** @@ -85,15 +88,11 @@ public SystemPath(IdeContext context, String envPath, Path ideRoot, Path softwar * @param ideRoot the {@link IdeContext#getIdeRoot() IDE_ROOT}. * @param softwarePath the {@link IdeContext#getSoftwarePath() software path}. * @param pathSeparator the path separator char (';' for Windows and ':' otherwise). + * @param extraPathEntries the {@link List} of additional {@link Path}s to prepend. */ - public SystemPath(IdeContext context, String envPath, Path ideRoot, Path softwarePath, char pathSeparator) { + public SystemPath(IdeContext context, String envPath, Path ideRoot, Path softwarePath, char pathSeparator, List extraPathEntries) { - super(); - this.context = context; - this.envPath = envPath; - this.pathSeparator = pathSeparator; - this.tool2pathMap = new HashMap<>(); - this.paths = new ArrayList<>(); + this(context, envPath, pathSeparator, extraPathEntries, new HashMap<>(), new ArrayList<>()); String[] envPaths = envPath.split(Character.toString(pathSeparator)); for (String segment : envPaths) { Path path = Path.of(segment); @@ -105,6 +104,17 @@ public SystemPath(IdeContext context, String envPath, Path ideRoot, Path softwar collectToolPath(softwarePath); } + private SystemPath(IdeContext context, String envPath, char pathSeparator, List extraPathEntries, Map tool2PathMap, List paths) { + + super(); + this.context = context; + this.envPath = envPath; + this.pathSeparator = pathSeparator; + this.extraPathEntries = extraPathEntries; + this.tool2pathMap = tool2PathMap; + this.paths = paths; + } + private void collectToolPath(Path softwarePath) { if (softwarePath == null) { @@ -237,6 +247,9 @@ public String toString(WindowsPathSyntax pathSyntax) { separator = this.pathSeparator; } StringBuilder sb = new StringBuilder(this.envPath.length() + 128); + for (Path path : this.extraPathEntries) { + appendPath(path, sb, separator, pathSyntax); + } for (Path path : this.tool2pathMap.values()) { appendPath(path, sb, separator, pathSyntax); } @@ -246,6 +259,22 @@ public String toString(WindowsPathSyntax pathSyntax) { return sb.toString(); } + /** + * Derive a new {@link SystemPath} from this instance with the given parameters. + * + * @param overriddenPath the entire PATH to override and replace the current one from this {@link SystemPath} or {@code null} to keep the current PATH. + * @param extraPathEntries the {@link List} of additional PATH entries to add to the beginning of the PATH. May be empty to add nothing. + * @return the new {@link SystemPath} derived from this instance with the given parameters applied. + */ + public SystemPath withPath(String overriddenPath, List extraPathEntries) { + + if (overriddenPath == null) { + return new SystemPath(this.context, this.envPath, this.pathSeparator, extraPathEntries, this.tool2pathMap, this.paths); + } else { + return new SystemPath(this.context, overriddenPath, null, null, this.pathSeparator, extraPathEntries); + } + } + private static void appendPath(Path path, StringBuilder sb, char separator, WindowsPathSyntax pathSyntax) { if (sb.length() > 0) { diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/EnvironmentContext.java b/cli/src/main/java/com/devonfw/tools/ide/process/EnvironmentContext.java index 8f9de3dd0..8f06b9cbe 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/EnvironmentContext.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/EnvironmentContext.java @@ -1,14 +1,16 @@ package com.devonfw.tools.ide.process; +import java.nio.file.Path; + /** * Interface for the environment context relatable in the case a tool needs to be run with an environment variable. */ public interface EnvironmentContext { /** - * Sets or overrides the specified environment variable only for the planned process execution. Please note that the environment variables are initialized - * when the {@link EnvironmentContext} is created. This method explicitly set an additional or overrides an existing environment and will have effect for each - * process execution invoked from this {@link EnvironmentContext} instance. + * Sets or overrides the specified environment variable only in this context. Please note that the environment variables are initialized when the + * {@link EnvironmentContext} is created. This method explicitly set an additional or overrides an existing environment and will have effect only within this + * context and not change the {@link com.devonfw.tools.ide.environment.EnvironmentVariables} or {@link com.devonfw.tools.ide.common.SystemPath}. * * @param key the name of the environment variable (E.g. "PATH"). * @param value the value of the environment variable. @@ -16,4 +18,13 @@ public interface EnvironmentContext { */ EnvironmentContext withEnvVar(String key, String value); + /** + * Extends the "PATH" variable with the given {@link Path} entry. The new entry will be added to the beginning of the "PATH" and potentially override other + * entries if it contains the same binary. + * + * @param path the {@link Path} pointing to the folder with the binaries to add to the "PATH" variable. + * @return this {@link EnvironmentContext} for fluent API calls. + */ + EnvironmentContext withPathEntry(Path path); + } diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java index 7a2679250..f6e9928a5 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java @@ -109,18 +109,12 @@ default ProcessContext addArgs(List... args) { return this; } - /** - * Sets or overrides the specified environment variable only for the planned {@link #run() process execution}. Please note that the environment variables are - * initialized when the {@link ProcessContext} is created. This method explicitly set an additional or overrides an existing environment and will have effect - * for each {@link #run() process execution} invoked from this {@link ProcessContext} instance. Be aware of such side-effects when reusing the same - * {@link ProcessContext} to {@link #run() run} multiple commands. - * - * @param key the name of the environment variable (E.g. "PATH"). - * @param value the value of the environment variable. - * @return this {@link ProcessContext} for fluent API calls. - */ + @Override ProcessContext withEnvVar(String key, String value); + @Override + ProcessContext withPathEntry(Path path); + /** * Runs the previously configured {@link #executable(Path) command} with the configured {@link #addArgs(String...) arguments}. Will reset the * {@link #addArgs(String...) arguments} but not the {@link #executable(Path) command} for sub-sequent calls. diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java index 7e5c8fe9b..2bdf415a2 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java @@ -23,6 +23,7 @@ import com.devonfw.tools.ide.os.SystemInfoImpl; import com.devonfw.tools.ide.os.WindowsPathSyntax; import com.devonfw.tools.ide.util.FilenameUtil; +import com.devonfw.tools.ide.variable.IdeVariables; /** * Implementation of {@link ProcessContext}. @@ -40,6 +41,10 @@ public class ProcessContextImpl implements ProcessContext { private Path executable; + private String overriddenPath; + + private final List extraPathEntries; + private ProcessErrorHandling errorHandling; /** @@ -60,6 +65,7 @@ public ProcessContextImpl(IdeContext context) { } } this.arguments = new ArrayList<>(); + this.extraPathEntries = new ArrayList<>(); } @Override @@ -90,7 +96,7 @@ public ProcessContext executable(Path command) { throw new IllegalStateException("Arguments already present - did you forget to call run for previous call?"); } - this.executable = this.context.getPath().findBinary(command); + this.executable = command; return this; } @@ -104,14 +110,25 @@ public ProcessContext addArg(String arg) { @Override public ProcessContext withEnvVar(String key, String value) { - this.processBuilder.environment().put(key, value); + if (IdeVariables.PATH.getName().equals(key)) { + this.overriddenPath = value; + } else { + this.context.trace("Setting process environment variable {}={}", key, value); + this.processBuilder.environment().put(key, value); + } + return this; + } + + @Override + public ProcessContext withPathEntry(Path path) { + + this.extraPathEntries.add(path); return this; } @Override public ProcessResult run(ProcessMode processMode) { - // TODO ProcessMode needs to be configurable for GUI if (processMode == ProcessMode.DEFAULT) { this.processBuilder.redirectOutput(Redirect.INHERIT).redirectError(Redirect.INHERIT); } @@ -123,6 +140,15 @@ public ProcessResult run(ProcessMode processMode) { if (this.executable == null) { throw new IllegalStateException("Missing executable to run process!"); } + + SystemPath systemPath = this.context.getPath(); + if ((this.overriddenPath != null) || !this.extraPathEntries.isEmpty()) { + systemPath = systemPath.withPath(this.overriddenPath, this.extraPathEntries); + } + String path = systemPath.toString(); + this.context.trace("Setting PATH for process execution of {} to {}", this.executable.getFileName(), path); + this.executable = systemPath.findBinary(this.executable); + this.processBuilder.environment().put(IdeVariables.PATH.getName(), path); List args = new ArrayList<>(this.arguments.size() + 4); String interpreter = addExecutable(this.executable.toString(), args); args.addAll(this.arguments); diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java index e10fa117d..475e37a89 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java @@ -103,14 +103,12 @@ public void runTool(ProcessMode processMode, VersionIdentifier toolVersion, bool Path binaryPath; binaryPath = Path.of(getBinaryName()); - ProcessContext pc; + ProcessContext pc = createProcessContext(binaryPath, args); if (existsEnvironmentContext) { - pc = createProcessContext(binaryPath, args); install(pc, true); } else { install(true); - pc = createProcessContext(binaryPath, args); } pc.run(processMode); diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/ProcessContextGitMock.java b/cli/src/test/java/com/devonfw/tools/ide/context/ProcessContextGitMock.java index d9deee317..6c3fac919 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/context/ProcessContextGitMock.java +++ b/cli/src/test/java/com/devonfw/tools/ide/context/ProcessContextGitMock.java @@ -112,6 +112,12 @@ public ProcessContext withEnvVar(String key, String value) { return this; } + @Override + public ProcessContext withPathEntry(Path path) { + + return this; + } + @Override public ProcessResult run(ProcessMode processMode) {