Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
1413928
#809: Enhance uninstall with --force to remove a tool from the softwa…
juliane-cap Apr 15, 2025
9664ef1
adjust exceptions to coding conventions
juliane-cap Apr 15, 2025
30344ec
Add Test for force uninstall
juliane-cap Apr 16, 2025
e837029
Merge branch 'main' into 809-uninstall-force-remove-from-repo
juliane-cap Apr 17, 2025
9847de3
Merge branch 'main' into 809-uninstall-force-remove-from-repo
jan-vcapgemini Apr 22, 2025
ee84131
Refactor minor path details
juliane-cap Apr 24, 2025
253f486
Merge branch 'main' into 809-uninstall-force-remove-from-repo
juliane-cap Apr 24, 2025
64d1c71
Merge branch 'main' into 809-uninstall-force-remove-from-repo
juliane-cap Apr 25, 2025
5ef1a3f
Merge branch 'main' into 809-uninstall-force-remove-from-repo
juliane-cap Apr 28, 2025
0125011
Merge branch 'main' into 809-uninstall-force-remove-from-repo
juliane-cap Apr 30, 2025
62ff9fe
add custom macOS workaround
juliane-cap Apr 30, 2025
d3d39f7
Merge branch 'main' into 809-uninstall-force-remove-from-repo
juliane-cap May 6, 2025
19c6d04
Take special cases into account
juliane-cap May 6, 2025
0c9ce4c
Fix logs causing errors
juliane-cap May 6, 2025
c655dc2
Merge branch 'main' into 809-uninstall-force-remove-from-repo
juliane-cap May 6, 2025
6b92bbe
Merge branch 'main' into 809-uninstall-force-remove-from-repo
juliane-cap May 14, 2025
069bc09
Merge branch 'main' into 809-uninstall-force-remove-from-repo
juliane-cap May 15, 2025
0acc93c
Merge branch 'main' into 809-uninstall-force-remove-from-repo
juliane-cap May 16, 2025
85c5786
Simplified changes by moving methods to implementation class
juliane-cap May 16, 2025
6e08263
Add JUnit Test for LocalToolCommandlet Method
juliane-cap May 16, 2025
c264ec0
Merge branch 'main' into 809-uninstall-force-remove-from-repo
hohwille May 20, 2025
f87f955
Update cli/src/test/java/com/devonfw/tools/ide/commandlet/UninstallCo…
hohwille May 20, 2025
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
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Release with new features and bugfixes:

* https://github.com/devonfw/IDEasy/issues/1233[#1233]: Fix dropdown width in MSI installer for drive selection
* https://github.com/devonfw/IDEasy/issues/1229[#1229]: Installation fails if version not available for current OS
* https://github.com/devonfw/IDEasy/issues/809[#809#]: make uninstall with --force also remove from software repo

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,16 @@ public void run() {
for (int i = 0; i < valueCount; i++) {
ToolCommandlet toolCommandlet = this.tools.getValue(i);
if (toolCommandlet.getInstalledVersion() != null) {
toolCommandlet.uninstall();
if (this.context.isForceMode()) {
this.context.warning(
"Sub-command uninstall via force mode will physically delete the currently installed version of " + toolCommandlet.getName()
+ " from the machine.\n"
+ "This may cause issues with other projects, that use the same version of " + toolCommandlet.getName() + ".\n"
+ "Deleting " + toolCommandlet.getName() + " version " + toolCommandlet.getInstalledVersion() + " from your machine.");
toolCommandlet.forceUninstall();
} else {
toolCommandlet.uninstall();
}
} else {
this.context.warning("Couldn't uninstall " + toolCommandlet.getName() + " because we could not find an installation");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.devonfw.tools.ide.tool;

import java.nio.file.Path;
import java.util.Set;

import com.devonfw.tools.ide.common.Tag;
Expand Down Expand Up @@ -48,11 +49,21 @@ public String getInstalledEdition() {
return getDelegate().getInstalledEdition();
}

@Override
public Path getInstalledSoftwareRepoPath() {
return getDelegate().getInstalledSoftwareRepoPath();
}

@Override
public void uninstall() {
getDelegate().uninstall();
}

@Override
public void forceUninstall() {
getDelegate().forceUninstall();
}

@Override
public void listEditions() {
getDelegate().listEditions();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,22 @@ public String getInstalledEdition() {
return null;
}

@Override
public Path getInstalledSoftwareRepoPath() {
//TODO: handle "--force uninstall <globaltool>"
this.context.error("Couldn't get installed repository path of " + this.getName());
return null;
}

@Override
public void uninstall() {
//TODO: handle "uninstall <globaltool>"
this.context.error("Couldn't uninstall " + this.getName());
}

@Override
public void forceUninstall() {
//TODO: handle "uninstall <globaltool>"
this.context.error("Couldn't uninstall " + this.getName() + " from your computer.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,6 @@ public String getInstalledEdition() {
* @return the installed edition of this tool or {@code null} if not installed.
*/
private String getInstalledEdition(Path toolPath) {

if (!Files.isDirectory(toolPath)) {
this.context.debug("Tool {} not installed in {}", this.tool, toolPath);
return null;
Expand Down Expand Up @@ -368,6 +367,31 @@ private String getEdition(Path toolRepoFolder, Path toolInstallFolder) {
return null;
}

@Override
public Path getInstalledSoftwareRepoPath() {
if (this.context.getSoftwarePath() == null) {
return null;
}
return getInstalledSoftwareRepoPath(this.context.getSoftwarePath().resolve(this.tool));
}

private Path getInstalledSoftwareRepoPath(Path toolPath) {
if (!Files.isDirectory(toolPath)) {
this.context.debug("Tool {} not installed in {}", this.tool, toolPath);
return null;
}
Path realPath = this.context.getFileAccess().toRealPath(toolPath);
// if the realPath changed, a link has been resolved
if (realPath.equals(toolPath)) {
if (!isIgnoreSoftwareRepo()) {
this.context.warning("Tool {} is not installed via software repository (maybe from devonfw-ide). Please consider reinstalling it.", this.tool);
}
// I do not see any reliable way how we could determine the edition of a tool that does not use software repo or that was installed by devonfw-ide
return null;
}
return realPath;
}

@Override
public void uninstall() {

Expand All @@ -378,13 +402,35 @@ public void uninstall() {
this.context.getFileAccess().delete(softwarePath);
this.context.success("Successfully uninstalled " + this.tool);
} catch (Exception e) {
this.context.error("Couldn't uninstall " + this.tool);
this.context.error("Couldn't uninstall " + this.tool, e);
}
} else {
this.context.warning("An installed version of " + this.tool + " does not exist");
}
} catch (Exception e) {
this.context.error(e.getMessage(), e);
}
}

@Override
public void forceUninstall() {
try {
Path repoPath = getInstalledSoftwareRepoPath();
Path realPath = getMacOsHelper().findLinkDir(repoPath, getBinaryName());
if (Files.exists(realPath)) {
this.context.info("Physically deleting " + realPath + " as requested by the user via force mode.");
uninstall();
try {
this.context.getFileAccess().delete(realPath);
this.context.success("Successfully deleted " + realPath + " from your computer.");
} catch (Exception e) {
this.context.error("Couldn't uninstall " + this.tool + " from your computer.", e);
}
} else {
this.context.warning("An installed version of " + this.tool + " does not exist");
}
} catch (Exception e) {
this.context.error(e.getMessage());
this.context.error("Couldn't uninstall " + this.tool + " from your computer. Installed version does not exist inside the software repository. ", e);
}
}

Expand Down
10 changes: 10 additions & 0 deletions cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -299,11 +299,21 @@ protected MacOsHelper getMacOsHelper() {
*/
public abstract String getInstalledEdition();

/**
* @return the path of the installed tool inside the software repo folder or {@code null} if not installed.
*/
public abstract Path getInstalledSoftwareRepoPath();

/**
* Uninstalls the {@link #getName() tool}.
*/
public abstract void uninstall();

/**
* Uninstalls the {@link #getName() tool} and the real tool version inside the software repository.
*/
public abstract void forceUninstall();

/**
* @return the {@link ToolRepository}.
*/
Expand Down
2 changes: 1 addition & 1 deletion cli/src/main/resources/nls/Help.properties
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ cmd.tomcat.val.command=Action to perform ( START | STOP )
cmd.uninstall=Uninstall selected tool(s).
cmd.uninstall-plugin=Uninstall the selected plugin for the selected tool.
cmd.uninstall-plugin.detail=Plugins can be only installed or uninstalled for tools that support such. Using the command "ide install-plugin", an uninstalled plugin can be restored.
cmd.uninstall.detail=Can be used to uninstall selected tool(s). E.g. to uninstall java simply call 'ide uninstall java'. To uninstall IDEasy itself, run 'ide uninstall' without further arguments.
cmd.uninstall.detail=Can be used to uninstall selected tool(s). E.g. to uninstall java simply call 'ide uninstall java'. To uninstall IDEasy itself, run 'ide uninstall' without further arguments. To fully delete the selected tool from your machine, use force mode. E.g. 'ide --force uninstall java'.
cmd.update=Pull your settings and apply updates (software, configuration and repositories).
cmd.update.detail=To update your IDE (if instructed by your ide-admin), you only need to run the following command: 'ide update'.
cmd.upgrade=Upgrade the version of IDEasy to the latest version available.
Expand Down
2 changes: 1 addition & 1 deletion cli/src/main/resources/nls/Help_de.properties
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ cmd.tomcat.val.command=Auszuführende Aktion ( START | STOP )
cmd.uninstall=Deinstalliert ausgewählte Werkzeug(e).
cmd.uninstall-plugin=Deinstalliert die selektierte Erweiterung für das selektierte Werkzeug.
cmd.uninstall-plugin.detail=Erweiterung können nur für Werkzeuge installiert und deinstalliert werden die diese unterstützen. Mit dem Befehl "ide install-plugin" kann die Erweiterung wieder hergestellt werden.
cmd.uninstall.detail=Wird dazu verwendet um ausgewählte Werkzeuge zu deinstallieren. Um z.B. Java zu deinstallieren, dient der Befehl 'ide uninstall java'. Um IDEasy selbst zu installieren, dient der Befehl 'ide uninstall' ohne weitere Parameter.
cmd.uninstall.detail=Wird dazu verwendet um ausgewählte Werkzeuge zu deinstallieren. Um z.B. Java zu deinstallieren, dient der Befehl 'ide uninstall java'. Um IDEasy selbst zu installieren, dient der Befehl 'ide uninstall' ohne weitere Parameter. Um ein aktuelles Werzeug vollständig von der Maschine zu löschen, wird der Force-Modus verwendet. Z.B. 'ide --force uninstall java'.
cmd.update=Updatet die Settings, Software und Repositories.
cmd.update.detail=Um die IDE auf den neuesten Stand zu bringen (falls von Ihrem Admin angewiesen) geben Sie einfach 'ide update' in die Konsole ein.
cmd.upgrade=Aktualisiere IDEasy auf die neueste Version.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
package com.devonfw.tools.ide.commandlet;

import java.nio.file.Files;
import java.nio.file.Path;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

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.log.IdeLogEntry;
import com.devonfw.tools.ide.os.SystemInfo;
import com.devonfw.tools.ide.os.SystemInfoMock;
import com.devonfw.tools.ide.os.WindowsHelper;
import com.devonfw.tools.ide.property.ToolProperty;
import com.devonfw.tools.ide.tool.az.Azure;
import com.devonfw.tools.ide.tool.dotnet.DotNet;
import com.devonfw.tools.ide.tool.eclipse.Eclipse;
import com.devonfw.tools.ide.tool.npm.Npm;
Expand All @@ -23,6 +26,22 @@
*/
public class UninstallCommandletTest extends AbstractIdeContextTest {

private static final String PROJECT = "edition-version-get-uninstall";

/**
* Mocks the installation of a tool, since forceUninstall depends on symlinks which are not distributed with git
*
* @param context the {@link IdeContext} to use.
* @param tool the tool to mock installation of.
*/
private static void mockInstallTool(IdeTestContext context, String tool) {

Path pathToInstallationOfDummyTool = context.getSoftwareRepositoryPath()
.resolve(context.getDefaultToolRepository().getId()).resolve(tool).resolve(tool + "/testVersion");
Path pathToLinkedSoftware = context.getSoftwarePath().resolve(tool);
context.getFileAccess().symlink(pathToInstallationOfDummyTool, pathToLinkedSoftware);
}

/**
* Test of {@link UninstallCommandlet} run.
*/
Expand Down Expand Up @@ -84,6 +103,35 @@ public void testUninstallNpm() {
assertThat(context).log().hasEntries(IdeLogEntry.ofSuccess("Successfully uninstalled npm"));
}

/** Test of {@link UninstallCommandlet} run with --force. */
@ParameterizedTest
@ValueSource(strings = { "windows", "mac", "linux" })
public void testForceUninstallAzure(String os) {

// arrange
SystemInfo systemInfo = SystemInfoMock.of(os);
String tool = "az";
IdeTestContext context = newContext(PROJECT, null, true);
context.setSystemInfo(systemInfo);
mockInstallTool(context, tool);
CommandletManager commandletManager = context.getCommandletManager();
UninstallCommandlet uninstallCommandlet = commandletManager.getCommandlet(UninstallCommandlet.class);
Azure azureCommandlet = commandletManager.getCommandlet(Azure.class);
uninstallCommandlet.tools.addValue(azureCommandlet);
uninstallCommandlet.tools.setValueAsString(tool, context);
context.getStartContext().setForceMode(true);

// act
uninstallCommandlet.run();
// assert
assertThat(context).log()
.hasEntries(IdeLogEntry.ofSuccess("Successfully deleted " + context.getSoftwareRepositoryPath()
.resolve(context.getDefaultToolRepository().getId()).resolve(tool).resolve(tool + "/testVersion") + " from your computer."));
assertThat(context).log().hasEntries(IdeLogEntry.ofSuccess("Successfully uninstalled " + tool));
assertThat(context.getSoftwareRepositoryPath()
.resolve(context.getDefaultToolRepository().getId()).resolve(tool).resolve(tool + "/testVersion")).doesNotExist();
}

/** Test {@link UninstallCommandlet} without arguments uninstalls IDEasy. */
@ParameterizedTest
@ValueSource(strings = { "windows", "mac", "linux" })
Expand Down