From 7967bdc13d2283dc09e0df0ea1f1eba89e572f53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Hohwiller?= Date: Mon, 24 Feb 2025 16:35:51 +0100 Subject: [PATCH 1/6] #789: implement uninstall of IDEasy --- .../ide/commandlet/UninstallCommandlet.java | 16 +- .../tools/ide/common/SimpleSystemPath.java | 85 ++++++++++ .../devonfw/tools/ide/os/WindowsHelper.java | 5 + .../tools/ide/os/WindowsHelperImpl.java | 11 ++ .../tools/ide/tool/IdeasyCommandlet.java | 147 ++++++++++++++---- .../commandlet/UninstallCommandletTest.java | 54 ++++++- .../tools/ide/os/WindowsHelperMock.java | 6 + .../uninstall/_ide/installation/.gitkeep | 0 .../uninstall/_ide/software/.gitkeep | 0 .../ide-projects/uninstall/_ide/urls/.gitkeep | 0 .../uninstall/project/home/.ide/.gitkeep | 0 .../uninstall/project/settings/ide.properties | 1 + .../project/workspaces/main/.gitkeep | 0 13 files changed, 281 insertions(+), 44 deletions(-) create mode 100644 cli/src/main/java/com/devonfw/tools/ide/common/SimpleSystemPath.java create mode 100644 cli/src/test/resources/ide-projects/uninstall/_ide/installation/.gitkeep create mode 100644 cli/src/test/resources/ide-projects/uninstall/_ide/software/.gitkeep create mode 100644 cli/src/test/resources/ide-projects/uninstall/_ide/urls/.gitkeep create mode 100644 cli/src/test/resources/ide-projects/uninstall/project/home/.ide/.gitkeep create mode 100644 cli/src/test/resources/ide-projects/uninstall/project/settings/ide.properties create mode 100644 cli/src/test/resources/ide-projects/uninstall/project/workspaces/main/.gitkeep diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/UninstallCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/UninstallCommandlet.java index c5fc77c0d..640a37c2b 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/UninstallCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/UninstallCommandlet.java @@ -2,6 +2,7 @@ import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.property.ToolProperty; +import com.devonfw.tools.ide.tool.IdeasyCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; /** @@ -21,7 +22,7 @@ public UninstallCommandlet(IdeContext context) { super(context); addKeyword(getName()); - this.tools = add(new ToolProperty("", true, true, "tool")); + this.tools = add(new ToolProperty("", false, true, "tool")); } @Override @@ -33,7 +34,18 @@ public String getName() { @Override public void run() { - for (int i = 0; i < this.tools.getValueCount(); i++) { + int valueCount = this.tools.getValueCount(); + if (valueCount == 0) { + if (!this.context.isForceMode()) { + this.context.askToContinue("Sub-command uninstall without any further arguments will perform the entire uninstallation of IDEasy.\n" + + "Since this is typically not to be called manually, you may have forgotten to specify the tool to install as extra argument.\n" + + "The current command will uninstall IDEasy from your computer. Are you sure?"); + } + IdeasyCommandlet ideasy = new IdeasyCommandlet(this.context); + ideasy.uninstallIdeasy(); + return; + } + for (int i = 0; i < valueCount; i++) { ToolCommandlet toolCommandlet = this.tools.getValue(i); if (toolCommandlet.getInstalledVersion() != null) { toolCommandlet.uninstall(); diff --git a/cli/src/main/java/com/devonfw/tools/ide/common/SimpleSystemPath.java b/cli/src/main/java/com/devonfw/tools/ide/common/SimpleSystemPath.java new file mode 100644 index 000000000..ee66f436b --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/common/SimpleSystemPath.java @@ -0,0 +1,85 @@ +package com.devonfw.tools.ide.common; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.function.Predicate; + +/** + * Represents the PATH variable in a structured way. Similar to {@link SystemPath} but much simper: It just tokenizes the PATH into a {@link java.util.List} of + * {@link String}s. + */ +public class SimpleSystemPath { + + private final char separator; + + private final List entries; + + private SimpleSystemPath(char separator, List entries) { + + super(); + this.separator = separator; + this.entries = entries; + } + + /** + * @return the entries of this PATH as a mutable {@link List}. + */ + public List getEntries() { + + return this.entries; + } + + /** + * Remove all entries from this PATH that match the given {@link Predicate}. + * + * @param filter the {@link Predicate} {@link Predicate#test(Object) deciding} what to filter and remove. + */ + public void removeEntries(Predicate filter) { + + Iterator iterator = this.entries.iterator(); + while (iterator.hasNext()) { + String entry = iterator.next(); + if (filter.test(entry)) { + iterator.remove(); + } + } + } + + @Override + public String toString() { + + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (String entry : this.entries) { + if (first) { + first = false; + } else { + sb.append(this.separator); + } + sb.append(entry); + } + return sb.toString(); + } + + /** + * @param path the entire PATH as {@link String}, + * @param separator the separator character. + * @return the {@link SimpleSystemPath}. + */ + public static SimpleSystemPath of(String path, char separator) { + + List entries = new ArrayList<>(); + int start = 0; + int len = path.length(); + while (start < len) { + int end = path.indexOf(separator, start); + if (end < 0) { + end = len; + } + entries.add(path.substring(start, end)); + start = end + 1; + } + return new SimpleSystemPath(separator, entries); + } +} diff --git a/cli/src/main/java/com/devonfw/tools/ide/os/WindowsHelper.java b/cli/src/main/java/com/devonfw/tools/ide/os/WindowsHelper.java index 8500b3bce..a0a9a6eb7 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/os/WindowsHelper.java +++ b/cli/src/main/java/com/devonfw/tools/ide/os/WindowsHelper.java @@ -14,6 +14,11 @@ public interface WindowsHelper { */ void setUserEnvironmentValue(String key, String value); + /** + * @param key the name of the environment variable to remove. + */ + void removeUserEnvironmentValue(String key); + /** * @param key the name of the environment variable. * @return the value of the environment variable in the users context. diff --git a/cli/src/main/java/com/devonfw/tools/ide/os/WindowsHelperImpl.java b/cli/src/main/java/com/devonfw/tools/ide/os/WindowsHelperImpl.java index b57a975ce..d22a162ce 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/os/WindowsHelperImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/os/WindowsHelperImpl.java @@ -3,6 +3,7 @@ import java.util.List; import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.log.IdeLogLevel; import com.devonfw.tools.ide.process.ProcessMode; import com.devonfw.tools.ide.process.ProcessResult; @@ -33,6 +34,16 @@ public void setUserEnvironmentValue(String key, String value) { assert (result.isSuccessful()); } + @Override + public void removeUserEnvironmentValue(String key) { + ProcessResult result = this.context.newProcess().executable("reg").addArgs("delete", HKCU_ENVIRONMENT, "/v", key, "/f").run(ProcessMode.DEFAULT_CAPTURE); + if (result.isSuccessful()) { + this.context.debug("Removed environment variable {}", key); + } else { + result.log(IdeLogLevel.WARNING, this.context); + } + } + @Override public String getUserEnvironmentValue(String key) { diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/IdeasyCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/IdeasyCommandlet.java index 76e91673e..2501cbb22 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/IdeasyCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/IdeasyCommandlet.java @@ -9,6 +9,7 @@ import com.devonfw.tools.ide.cli.CliException; import com.devonfw.tools.ide.commandlet.UpgradeMode; +import com.devonfw.tools.ide.common.SimpleSystemPath; import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.io.FileAccess; @@ -33,6 +34,10 @@ public class IdeasyCommandlet extends MvnBasedLocalToolCommandlet { /** The {@link #getName() tool name}. */ public static final String TOOL_NAME = "ideasy"; + public static final String BASHRC = ".bashrc"; + public static final String ZSHRC = ".zshrc"; + public static final String IDE_BIN = "\\_ide\\bin"; + public static final String IDE_INSTALLATION_BIN = "\\_ide\\installation\\bin"; private final UpgradeMode mode; @@ -168,8 +173,8 @@ public void installIdeasy(Path cwd) { fileAccess.copy(installationArtifact, ideasyVersionPath); } fileAccess.symlink(ideasyVersionPath, installationPath); - addToShellRc(".bashrc", ideRoot, null); - addToShellRc(".zshrc", ideRoot, "autoload -U +X bashcompinit && bashcompinit"); + addToShellRc(BASHRC, ideRoot, null); + addToShellRc(ZSHRC, ideRoot, "autoload -U +X bashcompinit && bashcompinit"); installIdeasyWindowsEnv(ideRoot, installationPath); this.context.success("IDEasy has been installed successfully on your system."); this.context.warning("IDEasy has been setup for new shells but it cannot work in your current shell(s).\n" @@ -177,35 +182,40 @@ public void installIdeasy(Path cwd) { } private void installIdeasyWindowsEnv(Path ideRoot, Path installationPath) { - if (this.context.getSystemInfo().isWindows()) { - WindowsHelper helper = WindowsHelper.get(this.context); - helper.setUserEnvironmentValue(IdeVariables.IDE_ROOT.getName(), ideRoot.toString()); - String userPath = helper.getUserEnvironmentValue(IdeVariables.PATH.getName()); - if (userPath == null) { - this.context.error("Could not read user PATH from registry!"); + if (!this.context.getSystemInfo().isWindows()) { + return; + } + WindowsHelper helper = WindowsHelper.get(this.context); + helper.setUserEnvironmentValue(IdeVariables.IDE_ROOT.getName(), ideRoot.toString()); + String userPath = helper.getUserEnvironmentValue(IdeVariables.PATH.getName()); + if (userPath == null) { + this.context.error("Could not read user PATH from registry!"); + } else { + this.context.info("Found user PATH={}", userPath); + Path ideasyBinPath = installationPath.resolve("bin"); + SimpleSystemPath path = SimpleSystemPath.of(userPath, ';'); + if (path.getEntries().isEmpty()) { + this.context.warning("ATTENTION:\n" + + "Your user specific PATH variable seems to be empty.\n" + + "You can double check this by pressing [Windows][r] and launch the program SystemPropertiesAdvanced.\n" + + "Then click on 'Environment variables' and check if 'PATH' is set in the 'user variables' from the upper list.\n" + + "In case 'PATH' is defined there non-empty and you get this message, please abort and give us feedback:\n" + + "https://github.com/devonfw/IDEasy/issues\n" + + "Otherwise all is correct and you can continue."); + this.context.askToContinue("Are you sure you want to override your PATH?"); } else { - this.context.info("Found user PATH={}", userPath); - Path ideasyBinPath = installationPath.resolve("bin"); - userPath = removeObsoleteEntryFromWindowsPath(userPath); - if (userPath.isEmpty()) { - this.context.warning("ATTENTION:\n" - + "Your user specific PATH variable seems to be empty.\n" - + "You can double check this by pressing [Windows][r] and launch the program SystemPropertiesAdvanced.\n" - + "Then click on 'Environment variables' and check if 'PATH' is set in the 'user variables' from the upper list.\n" - + "In case 'PATH' is defined there non-empty and you get this message, please abort and give us feedback:\n" - + "https://github.com/devonfw/IDEasy/issues\n" - + "Otherwise all is correct and you can continue."); - this.context.askToContinue("Are you sure you want to override your PATH?"); - userPath = ideasyBinPath.toString(); - } else { - userPath = userPath + ";" + ideasyBinPath; - } - helper.setUserEnvironmentValue(IdeVariables.PATH.getName(), userPath); + path.removeEntries(s -> s.endsWith(IDE_BIN)); } + path.getEntries().add(ideasyBinPath.toString()); + helper.setUserEnvironmentValue(IdeVariables.PATH.getName(), path.toString()); } } static String removeObsoleteEntryFromWindowsPath(String userPath) { + return removeEntryFromWindowsPath(userPath, IDE_BIN); + } + + static String removeEntryFromWindowsPath(String userPath, String suffix) { int len = userPath.length(); int start = 0; while ((start >= 0) && (start < len)) { @@ -214,7 +224,7 @@ static String removeObsoleteEntryFromWindowsPath(String userPath) { end = len; } String entry = userPath.substring(start, end); - if (entry.endsWith("\\_ide\\bin")) { + if (entry.endsWith(suffix)) { String prefix = ""; int offset = 1; if (start > 0) { @@ -224,7 +234,7 @@ static String removeObsoleteEntryFromWindowsPath(String userPath) { if (end == len) { return prefix; } else { - return prefix + userPath.substring(end + offset); + return removeEntryFromWindowsPath(prefix + userPath.substring(end + offset), suffix); } } start = end + 1; @@ -240,11 +250,34 @@ static String removeObsoleteEntryFromWindowsPath(String userPath) { */ private void addToShellRc(String filename, Path ideRoot, String extraLine) { - this.context.info("Configuring IDEasy in {}", filename); + modifyShellRc(filename, ideRoot, true, extraLine); + } + + private void removeFromShellRc(String filename, Path ideRoot) { + + modifyShellRc(filename, ideRoot, false, null); + } + + /** + * Adds ourselves to the shell RC (run-commands) configuration file. + * + * @param filename the name of the RC file. + * @param ideRoot the IDE_ROOT {@link Path}. + */ + private void modifyShellRc(String filename, Path ideRoot, boolean add, String extraLine) { + + if (add) { + this.context.info("Configuring IDEasy in {}", filename); + } else { + this.context.info("Removing IDEasy from {}", filename); + } Path rcFile = this.context.getUserHome().resolve(filename); FileAccess fileAccess = this.context.getFileAccess(); List lines = fileAccess.readFileLines(rcFile); if (lines == null) { + if (!add) { + return; + } lines = new ArrayList<>(); } else { // since it is unspecified if the returned List may be immutable we want to get sure @@ -263,14 +296,17 @@ private void addToShellRc(String filename, Path ideRoot, String extraLine) { extraLine = null; } } - if (extraLine != null) { - lines.add(extraLine); - } - if (!this.context.getSystemInfo().isWindows()) { - lines.add("export IDE_ROOT=\"" + WindowsPathSyntax.MSYS.format(ideRoot) + "\""); + if (add) { + if (extraLine != null) { + lines.add(extraLine); + } + if (!this.context.getSystemInfo().isWindows()) { + lines.add("export IDE_ROOT=\"" + WindowsPathSyntax.MSYS.format(ideRoot) + "\""); + } + lines.add(BASH_CODE_SOURCE_FUNCTIONS); } - lines.add(BASH_CODE_SOURCE_FUNCTIONS); fileAccess.writeFileLines(lines, rcFile); + this.context.debug("Successfully updated {}", filename); } private static boolean isObsoleteRcLine(String line) { @@ -317,4 +353,47 @@ private Path determineIdeRoot(Path cwd) { return ideRoot; } + /** + * Uninstalls IDEasy entirely from the system. + */ + public void uninstallIdeasy() { + + Path ideRoot = this.context.getIdeRoot(); + removeFromShellRc(BASHRC, ideRoot); + removeFromShellRc(ZSHRC, ideRoot); + Path idePath = this.context.getIdePath(); + uninstallIdeasyWindowsEnv(ideRoot); + this.context.info("Finally deleting {}", idePath); + this.context.getFileAccess().delete(idePath); // TODO prevent Windows file locks + this.context.success("IDEasy has been uninstalled from your system."); + this.context.interaction("ATTENTION:\n" + + "In order to prevent data-loss, we do not delete your projects and git repositories!\n" + + "To entirely get rid of IDEasy, also check your IDE_ROOT folder at:\n" + + "{}", ideRoot); + } + + private void uninstallIdeasyWindowsEnv(Path ideRoot) { + if (!this.context.getSystemInfo().isWindows()) { + return; + } + WindowsHelper helper = WindowsHelper.get(this.context); + helper.removeUserEnvironmentValue(IdeVariables.IDE_ROOT.getName()); + String userPath = helper.getUserEnvironmentValue(IdeVariables.PATH.getName()); + if (userPath == null) { + this.context.error("Could not read user PATH from registry!"); + } else { + this.context.info("Found user PATH={}", userPath); + String newUserPath = userPath; + if (!userPath.isEmpty()) { + SimpleSystemPath path = SimpleSystemPath.of(userPath, ';'); + path.removeEntries(s -> s.endsWith(IDE_BIN) || s.endsWith(IDE_INSTALLATION_BIN)); + newUserPath = path.toString(); + } + if (newUserPath.equals(userPath)) { + this.context.error("Could not find IDEasy in PATH:\n{}", userPath); + } else { + helper.setUserEnvironmentValue(IdeVariables.PATH.getName(), newUserPath); + } + } + } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/UninstallCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/UninstallCommandletTest.java index 7e147b302..98ac5b284 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/UninstallCommandletTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/UninstallCommandletTest.java @@ -3,14 +3,20 @@ import java.nio.file.Files; 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.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.dotnet.DotNet; import com.devonfw.tools.ide.tool.eclipse.Eclipse; import com.devonfw.tools.ide.tool.npm.Npm; +import com.devonfw.tools.ide.variable.IdeVariables; /** * Integration test of {@link UninstallCommandlet}. @@ -21,13 +27,13 @@ public class UninstallCommandletTest extends AbstractIdeContextTest { * Test of {@link UninstallCommandlet} run. */ @Test - public void testUninstallCommandletRun_WithExistingCommandlet() { + public void testUninstallNpmAndDontButDotNetNotInstalled() { // arrange String npm = "npm"; String dotnet = "dotnet"; IdeTestContext context = newContext(PROJECT_BASIC); - CommandletManager commandletManager = getCommandletManager(context); + CommandletManager commandletManager = context.getCommandletManager(); UninstallCommandlet uninstallCommandlet = commandletManager.getCommandlet(UninstallCommandlet.class); Npm npmCommandlet = commandletManager.getCommandlet(Npm.class); DotNet dotnetCommandlet = commandletManager.getCommandlet(DotNet.class); @@ -45,12 +51,12 @@ public void testUninstallCommandletRun_WithExistingCommandlet() { } @Test - public void testUninstallCommandletRun_WithNonExistingCommandlet() { + public void testUninstallEclipseFailsWhenNotInstalled() { // arrange String eclipse = "eclipse"; IdeTestContext context = newContext(PROJECT_BASIC); - CommandletManager commandletManager = getCommandletManager(context); + CommandletManager commandletManager = context.getCommandletManager(); UninstallCommandlet uninstallCommandlet = commandletManager.getCommandlet(UninstallCommandlet.class); Eclipse eclipseCommandlet = commandletManager.getCommandlet(Eclipse.class); uninstallCommandlet.tools.addValue(eclipseCommandlet); @@ -62,12 +68,12 @@ public void testUninstallCommandletRun_WithNonExistingCommandlet() { } @Test - public void testUninstallCommandletRun() { + public void testUninstallNpm() { // arrange IdeTestContext context = newContext(PROJECT_BASIC); - CommandletManager commandletManager = getCommandletManager(context); + CommandletManager commandletManager = context.getCommandletManager(); UninstallCommandlet uninstallCommandlet = commandletManager.getCommandlet(UninstallCommandlet.class); Npm npmCommandlet = commandletManager.getCommandlet(Npm.class); uninstallCommandlet.tools.addValue(npmCommandlet); @@ -78,8 +84,40 @@ public void testUninstallCommandletRun() { assertThat(context).log().hasEntries(IdeLogEntry.ofSuccess("Successfully uninstalled npm")); } - private CommandletManager getCommandletManager(IdeTestContext context) { + /** Test {@link UninstallCommandlet} without arguments uninstalls IDEasy. */ + @ParameterizedTest + @ValueSource(strings = { "windows", "mac", "linux" }) + public void testUninstallIdeasy(String os) { - return context.getCommandletManager(); + // arrange + SystemInfo systemInfo = SystemInfoMock.of(os); + IdeTestContext context = newContext("uninstall"); + context.setSystemInfo(systemInfo); + CommandletManager commandletManager = context.getCommandletManager(); + UninstallCommandlet uninstallCommandlet = commandletManager.getCommandlet(UninstallCommandlet.class); + context.getStartContext().setForceMode(true); + WindowsHelper helper = context.getWindowsHelper(); + String originalPath = helper.getUserEnvironmentValue(IdeVariables.PATH.getName()); + if (systemInfo.isWindows()) { + helper.setUserEnvironmentValue(IdeVariables.PATH.getName(), "C:\\projects\\_ide\\installation\\bin;" + originalPath); + } + // act + uninstallCommandlet.run(); + // assert + assertThat(context.getIdePath()).doesNotExist(); + assertThat(context.getUserHome().resolve(".bashrc")).hasContent("#already exists\n" + + "alias devon=\"source ~/.devon/devon\"\n" + + "devon\n" + + "source ~/.devon/autocomplete\n"); + assertThat(context.getUserHome().resolve(".zshrc")).hasContent("#already exists\n" + + "autoload -U +X bashcompinit && bashcompinit\n" + + "alias devon=\"source ~/.devon/devon\"\n" + + "devon\n" + + "source ~/.devon/autocomplete\n"); + if (systemInfo.isWindows()) { + assertThat(helper.getUserEnvironmentValue("IDE_ROOT")).isNull(); + assertThat(helper.getUserEnvironmentValue("PATH")).isEqualTo( + "C:\\Users\\testuser\\AppData\\Local\\Microsoft\\WindowsApps;C:\\Users\\testuser\\scoop\\apps\\python\\current\\Scripts;C:\\Users\\testuser\\scoop\\apps\\python\\current;C:\\Users\\testuser\\scoop\\shims"); + } } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/os/WindowsHelperMock.java b/cli/src/test/java/com/devonfw/tools/ide/os/WindowsHelperMock.java index 1e90a8a78..cacd679b7 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/os/WindowsHelperMock.java +++ b/cli/src/test/java/com/devonfw/tools/ide/os/WindowsHelperMock.java @@ -27,6 +27,12 @@ public void setUserEnvironmentValue(String key, String value) { this.env.setProperty(key, value); } + @Override + public void removeUserEnvironmentValue(String key) { + + this.env.remove(key); + } + @Override public String getUserEnvironmentValue(String key) { diff --git a/cli/src/test/resources/ide-projects/uninstall/_ide/installation/.gitkeep b/cli/src/test/resources/ide-projects/uninstall/_ide/installation/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/cli/src/test/resources/ide-projects/uninstall/_ide/software/.gitkeep b/cli/src/test/resources/ide-projects/uninstall/_ide/software/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/cli/src/test/resources/ide-projects/uninstall/_ide/urls/.gitkeep b/cli/src/test/resources/ide-projects/uninstall/_ide/urls/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/cli/src/test/resources/ide-projects/uninstall/project/home/.ide/.gitkeep b/cli/src/test/resources/ide-projects/uninstall/project/home/.ide/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/cli/src/test/resources/ide-projects/uninstall/project/settings/ide.properties b/cli/src/test/resources/ide-projects/uninstall/project/settings/ide.properties new file mode 100644 index 000000000..ea30561d8 --- /dev/null +++ b/cli/src/test/resources/ide-projects/uninstall/project/settings/ide.properties @@ -0,0 +1 @@ +#empty diff --git a/cli/src/test/resources/ide-projects/uninstall/project/workspaces/main/.gitkeep b/cli/src/test/resources/ide-projects/uninstall/project/workspaces/main/.gitkeep new file mode 100644 index 000000000..e69de29bb From b6b69cd5033ef870c389570628a95f2298cd0911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Hohwiller?= Date: Mon, 24 Feb 2025 17:45:41 +0100 Subject: [PATCH 2/6] #789: added missing files (.gitignore) --- .../ide-projects/uninstall/project/home/.bashrc | 8 ++++++++ .../resources/ide-projects/uninstall/project/home/.zshrc | 9 +++++++++ 2 files changed, 17 insertions(+) create mode 100644 cli/src/test/resources/ide-projects/uninstall/project/home/.bashrc create mode 100644 cli/src/test/resources/ide-projects/uninstall/project/home/.zshrc diff --git a/cli/src/test/resources/ide-projects/uninstall/project/home/.bashrc b/cli/src/test/resources/ide-projects/uninstall/project/home/.bashrc new file mode 100644 index 000000000..dc2f5d69f --- /dev/null +++ b/cli/src/test/resources/ide-projects/uninstall/project/home/.bashrc @@ -0,0 +1,8 @@ +#already exists +alias devon="source ~/.devon/devon" +devon +source ~/.devon/autocomplete +source "$IDE_ROOT/_ide/functions" +ide +ide init +source "$IDE_ROOT/_ide/installation/functions" diff --git a/cli/src/test/resources/ide-projects/uninstall/project/home/.zshrc b/cli/src/test/resources/ide-projects/uninstall/project/home/.zshrc new file mode 100644 index 000000000..54e329359 --- /dev/null +++ b/cli/src/test/resources/ide-projects/uninstall/project/home/.zshrc @@ -0,0 +1,9 @@ +#already exists +autoload -U +X bashcompinit && bashcompinit +source "$IDE_ROOT/_ide/functions" +ide +ide init +alias devon="source ~/.devon/devon" +devon +source ~/.devon/autocomplete +source "$IDE_ROOT/_ide/installation/functions" From 2ac262465f259f0c1b39480b77ae9f4e203b7c25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Hohwiller?= Date: Wed, 26 Feb 2025 11:06:46 +0100 Subject: [PATCH 3/6] #789: prevent Windows file locks and improved help --- .../devonfw/tools/ide/tool/IdeasyCommandlet.java | 16 ++++++++++++++-- cli/src/main/resources/nls/Help.properties | 4 ++-- cli/src/main/resources/nls/Help_de.properties | 4 ++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/IdeasyCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/IdeasyCommandlet.java index 2501cbb22..046c96cd1 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/IdeasyCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/IdeasyCommandlet.java @@ -15,6 +15,7 @@ import com.devonfw.tools.ide.io.FileAccess; import com.devonfw.tools.ide.os.WindowsHelper; import com.devonfw.tools.ide.os.WindowsPathSyntax; +import com.devonfw.tools.ide.process.ProcessMode; import com.devonfw.tools.ide.tool.mvn.MvnArtifact; import com.devonfw.tools.ide.tool.mvn.MvnBasedLocalToolCommandlet; import com.devonfw.tools.ide.tool.repository.MavenRepository; @@ -363,8 +364,7 @@ public void uninstallIdeasy() { removeFromShellRc(ZSHRC, ideRoot); Path idePath = this.context.getIdePath(); uninstallIdeasyWindowsEnv(ideRoot); - this.context.info("Finally deleting {}", idePath); - this.context.getFileAccess().delete(idePath); // TODO prevent Windows file locks + uninstallIdeasyIdePath(idePath); this.context.success("IDEasy has been uninstalled from your system."); this.context.interaction("ATTENTION:\n" + "In order to prevent data-loss, we do not delete your projects and git repositories!\n" @@ -372,6 +372,18 @@ public void uninstallIdeasy() { + "{}", ideRoot); } + private void uninstallIdeasyIdePath(Path idePath) { + if (this.context.getSystemInfo().isWindows()) { + this.context.newProcess().executable("bash").addArgs("-c", + "'sleep 10;rm -rf \"" + WindowsPathSyntax.MSYS.format(idePath) + "\"").run(ProcessMode.BACKGROUND_SILENT); + this.context.interaction("To prevent windows file locking errors, we perform an asynchronous deletion of {} in background now.\n" + + "Please close all terminals and wait a minute for the deletion to complete before running other commands.", idePath); + } else { + this.context.info("Finally deleting {}", idePath); + this.context.getFileAccess().delete(idePath); + } + } + private void uninstallIdeasyWindowsEnv(Path ideRoot) { if (!this.context.getSystemInfo().isWindows()) { return; diff --git a/cli/src/main/resources/nls/Help.properties b/cli/src/main/resources/nls/Help.properties index 3c7ac3fa8..d8654be8c 100644 --- a/cli/src/main/resources/nls/Help.properties +++ b/cli/src/main/resources/nls/Help.properties @@ -102,10 +102,10 @@ cmd.terraform.detail=Terraform is an infrastructure as code tool for managing cl cmd.tomcat=Tool commandlet for Tomcat cmd.tomcat.detail=Tomcat is an open-source web server and Servlet container for Java code. cmd.tomcat.val.command=Action to perform ( START | STOP ) -cmd.uninstall=Uninstall the selected tool. +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 any tool e.g. to uninstall java simply type: 'uninstall java'. +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.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. diff --git a/cli/src/main/resources/nls/Help_de.properties b/cli/src/main/resources/nls/Help_de.properties index 1522aa9c1..6154168e0 100644 --- a/cli/src/main/resources/nls/Help_de.properties +++ b/cli/src/main/resources/nls/Help_de.properties @@ -102,10 +102,10 @@ cmd.terraform.detail=Terraform ist ein Tool für Infrastructure as Code zur Verw cmd.tomcat=Werkzeug Kommando für Tomcat cmd.tomcat.detail=Tomcat ist ein leistungsstarker Open-Source-Webserver und Servlet-Container für Java-Anwendungen. cmd.tomcat.val.command=Auszuführende Aktion ( START | STOP ) -cmd.uninstall=Deinstalliert das ausgewählte Werkzeug. +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 jedwedes Werkzeug zu deinstallieren. Um z.B. Java zu deinstallieren geben Sie einfach 'uninstall java' in die Konsole ein. +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 Befehlt 'ide uninstall' ohne weitere Parameter. 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. From f18157f008d64c0651191cd4fa19472686c45b1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Hohwiller?= Date: Wed, 26 Feb 2025 11:10:52 +0100 Subject: [PATCH 4/6] #789: allow uninstall of IDEasy outside project (IDE_HOME) --- .../devonfw/tools/ide/commandlet/UninstallCommandlet.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/UninstallCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/UninstallCommandlet.java index 640a37c2b..0514833e0 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/UninstallCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/UninstallCommandlet.java @@ -31,6 +31,12 @@ public String getName() { return "uninstall"; } + @Override + public boolean isIdeRootRequired() { + + return this.tools.getValueCount() > 0; + } + @Override public void run() { From c1178da1a495899992014ac5668b9fcbb66cd214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Hohwiller?= Date: Wed, 26 Feb 2025 16:12:39 +0100 Subject: [PATCH 5/6] #789: fix prevent Windows file locks --- .../main/java/com/devonfw/tools/ide/tool/IdeasyCommandlet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/IdeasyCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/IdeasyCommandlet.java index 046c96cd1..c7d742c4d 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/IdeasyCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/IdeasyCommandlet.java @@ -375,7 +375,7 @@ public void uninstallIdeasy() { private void uninstallIdeasyIdePath(Path idePath) { if (this.context.getSystemInfo().isWindows()) { this.context.newProcess().executable("bash").addArgs("-c", - "'sleep 10;rm -rf \"" + WindowsPathSyntax.MSYS.format(idePath) + "\"").run(ProcessMode.BACKGROUND_SILENT); + "sleep 10 && rm -rf \"" + WindowsPathSyntax.MSYS.format(idePath) + "\"").run(ProcessMode.BACKGROUND); this.context.interaction("To prevent windows file locking errors, we perform an asynchronous deletion of {} in background now.\n" + "Please close all terminals and wait a minute for the deletion to complete before running other commands.", idePath); } else { From 5ec87d50d42febc609df919a701160c1a2ba243b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Hohwiller?= Date: Wed, 26 Feb 2025 16:38:42 +0100 Subject: [PATCH 6/6] Update cli/src/main/resources/nls/Help_de.properties Co-authored-by: jan-vcapgemini <59438728+jan-vcapgemini@users.noreply.github.com> --- cli/src/main/resources/nls/Help_de.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/main/resources/nls/Help_de.properties b/cli/src/main/resources/nls/Help_de.properties index 6154168e0..c0ab49d81 100644 --- a/cli/src/main/resources/nls/Help_de.properties +++ b/cli/src/main/resources/nls/Help_de.properties @@ -105,7 +105,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 Befehlt '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. 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.