Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Release with new features and bugfixes:
* https://github.com/devonfw/IDEasy/issues/1517[#1517]: Fix IDEasy MSI does not configure Windows Terminal for git-bash
* https://github.com/devonfw/IDEasy/issues/1227[#1227]: Added Setup instrcutions to the Windows installer
* https://github.com/devonfw/IDEasy/issues/865[#865]: az not working on Mac
* https://github.com/devonfw/IDEasy/issues/1659[#1659]: Abort runTool with warning when global tool installer runs in background


The full list of changes for this release can be found in https://github.com/devonfw/IDEasy/milestone/46?closed=1[milestone 2026.07.001].

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,7 @@ protected ToolInstallation doInstall(ToolInstallRequest request) {
}
installationPath = getInstallationPath(toolEdition.edition(), resolvedVersion);
if (installationPath == null) {
throw new CliException("The tool " + this.tool + " is about to be installed. Please complete the installation and if required "
+ "reboot your machine. Then rerun the command to start the tool.", 2);
return new ToolInstallation(null, null, null, resolvedVersion, true, true);
}
return createToolInstallation(installationPath, resolvedVersion, true, pc, false);
}
Expand Down
14 changes: 12 additions & 2 deletions cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.devonfw.tools.ide.process.ProcessErrorHandling;
import com.devonfw.tools.ide.process.ProcessMode;
import com.devonfw.tools.ide.process.ProcessResult;
import com.devonfw.tools.ide.process.ProcessResultImpl;
import com.devonfw.tools.ide.property.Property;
import com.devonfw.tools.ide.property.ToolArgumentsProperty;
import com.devonfw.tools.ide.security.ToolVersionChoice;
Expand Down Expand Up @@ -272,7 +273,10 @@ public ProcessResult runTool(ToolInstallRequest request, ProcessMode processMode
// we render this warning so the error gets detected and can be fixed but we do not block the user by skipping the installation.
LOG.warn("Preventing infinity loop during installation of {}", request.getRequested(), new RuntimeException());
} else {
install(request);
ToolInstallation installation = install(request);
if (installation != null && installation.installedAsynchronously()) {
Comment thread
Ali-Shariati-Najafabadi marked this conversation as resolved.
return new ProcessResultImpl(this.tool, this.tool, 0, List.of());
}
}
return runTool(request.getProcessContext(), processMode, args);
}
Expand Down Expand Up @@ -344,7 +348,13 @@ public ToolInstallation install(ToolInstallRequest request) {
if (request.isInstallLoop()) {
return toolAlreadyInstalled(request);
}
return doInstall(request);
ToolInstallation installation = doInstall(request);
if (installation != null && installation.installedAsynchronously()) {
LOG.warn(
"The installation of {} is currently running in the background!\nYou need to complete the installation, potentially reboot and rerun your 'ide' command in a new terminal session after the installation has completed.",
request.getRequested());
}
return installation;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,23 @@
* "bin").
* @param resolvedVersion the {@link VersionIdentifier} of the resolved tool version installed in {@code rootDir}.
* @param newInstallation {@code true} if the tool has been newly installed, {@code false} otherwise (the tool was already installed before).
* @param installedAsynchronously {@code true} if the installation was launched in the background and the tool is not yet available, {@code false} otherwise.
*/
public record ToolInstallation(Path rootDir, Path linkDir, Path binDir, VersionIdentifier resolvedVersion, boolean newInstallation) {
public record ToolInstallation(Path rootDir, Path linkDir, Path binDir, VersionIdentifier resolvedVersion, boolean newInstallation,
boolean installedAsynchronously) {

/**
* Creates a {@link ToolInstallation} with {@code installedAsynchronously} set to {@code false}.
*
* @param rootDir see {@link #rootDir()}.
* @param linkDir see {@link #linkDir()}.
* @param binDir see {@link #binDir()}.
* @param resolvedVersion see {@link #resolvedVersion()}.
* @param newInstallation see {@link #newInstallation()}.
*/
public ToolInstallation(Path rootDir, Path linkDir, Path binDir, VersionIdentifier resolvedVersion, boolean newInstallation) {

this(rootDir, linkDir, binDir, resolvedVersion, newInstallation, false);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package com.devonfw.tools.ide.tool;

import java.util.List;
import java.util.Set;

import org.junit.jupiter.api.Test;

import com.devonfw.tools.ide.common.Tag;
import com.devonfw.tools.ide.context.AbstractIdeContextTest;
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.context.IdeTestContext;
import com.devonfw.tools.ide.os.SystemInfoMock;
import com.devonfw.tools.ide.process.ProcessResult;
import com.devonfw.tools.ide.version.VersionIdentifier;
import com.devonfw.tools.ide.tool.ToolEdition;
import com.devonfw.tools.ide.tool.ToolEditionAndVersion;

/**
* Test of {@link GlobalToolCommandlet}.
*/
class GlobalToolCommandletTest extends AbstractIdeContextTest {

private static final String TOOL_NAME = "docker";

private static final String TOOL_VERSION = "1.21.0";

/**
* Dummy {@link GlobalToolCommandlet} that simulates a background GUI installer (e.g. Rancher Desktop on Windows).
* Only {@code doInstall} is overridden so the warning-check inside the real {@code install()} is exercised.
*/
static class AsyncInstallerToolCommandlet extends GlobalToolCommandlet {

AsyncInstallerToolCommandlet(IdeContext context) {

super(context, TOOL_NAME, Set.of(Tag.DOCKER));
}

@Override
protected void completeRequest(ToolInstallRequest request) {

VersionIdentifier version = VersionIdentifier.of(TOOL_VERSION);
ToolEdition edition = new ToolEdition(TOOL_NAME, "rancher");
ToolEditionAndVersion requested = new ToolEditionAndVersion(edition, version);
requested.setResolvedVersion(version);
request.setRequested(requested);
}

@Override
protected ToolInstallation doInstall(ToolInstallRequest request) {

VersionIdentifier version = VersionIdentifier.of(TOOL_VERSION);
return new ToolInstallation(null, null, null, version, true, true);
}
}

/**
* Verifies that when {@code doInstall} signals an asynchronous background installation, the real {@code install()} logs the warning and {@code runTool}
* returns exit code 0 without trying to execute the not-yet-available binary.
*/
@Test
void testInstallLogsWarningAndRunToolAbortsWhenInstallationIsAsynchronous() {

// arrange
IdeTestContext context = newContext(PROJECT_BASIC);
context.setSystemInfo(SystemInfoMock.WINDOWS_X64);
AsyncInstallerToolCommandlet commandlet = new AsyncInstallerToolCommandlet(context);

// act
ProcessResult result = commandlet.runTool(List.of("ps"));

// assert: runTool returns 0 without crashing with "command not found"
assertThat(result.getExitCode()).isEqualTo(0);
// assert: warning was emitted by install() covering both "ide install docker" and "ide docker ps" paths
assertThat(context).logAtWarning().hasMessageContaining("is currently running in the background!");
assertThat(context).logAtWarning()
.hasMessageContaining("rerun your 'ide' command in a new terminal session after the installation has completed.");
}

/**
* Verifies that calling {@code install()} directly (as done by {@code ide install docker}) also logs the background-installation warning.
*/
@Test
void testInstallDirectlyAlsoLogsWarningWhenInstallationIsAsynchronous() {

// arrange
IdeTestContext context = newContext(PROJECT_BASIC);
context.setSystemInfo(SystemInfoMock.WINDOWS_X64);
AsyncInstallerToolCommandlet commandlet = new AsyncInstallerToolCommandlet(context);

// act
ToolInstallation installation = commandlet.install();

// assert: the async flag is set
assertThat(installation.installedAsynchronously()).isTrue();
// assert: warning was logged even without runTool being called
assertThat(context).logAtWarning().hasMessageContaining("is currently running in the background!");
assertThat(context).logAtWarning()
.hasMessageContaining("rerun your 'ide' command in a new terminal session after the installation has completed.");
}
}
Loading