• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

devonfw / IDEasy / 13547736881

26 Feb 2025 03:41PM UTC coverage: 68.227% (+0.2%) from 68.042%
13547736881

push

github

web-flow
#789: implement uninstall of IDEasy (#1071)

Co-authored-by: jan-vcapgemini <59438728+jan-vcapgemini@users.noreply.github.com>

3029 of 4889 branches covered (61.96%)

Branch coverage included in aggregate %.

7860 of 11071 relevant lines covered (71.0%)

3.09 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.24
cli/src/main/java/com/devonfw/tools/ide/tool/IdeasyCommandlet.java
1
package com.devonfw.tools.ide.tool;
2

3
import java.nio.file.Files;
4
import java.nio.file.Path;
5
import java.util.ArrayList;
6
import java.util.Iterator;
7
import java.util.List;
8
import java.util.Set;
9

10
import com.devonfw.tools.ide.cli.CliException;
11
import com.devonfw.tools.ide.commandlet.UpgradeMode;
12
import com.devonfw.tools.ide.common.SimpleSystemPath;
13
import com.devonfw.tools.ide.common.Tag;
14
import com.devonfw.tools.ide.context.IdeContext;
15
import com.devonfw.tools.ide.io.FileAccess;
16
import com.devonfw.tools.ide.os.WindowsHelper;
17
import com.devonfw.tools.ide.os.WindowsPathSyntax;
18
import com.devonfw.tools.ide.process.ProcessMode;
19
import com.devonfw.tools.ide.tool.mvn.MvnArtifact;
20
import com.devonfw.tools.ide.tool.mvn.MvnBasedLocalToolCommandlet;
21
import com.devonfw.tools.ide.tool.repository.MavenRepository;
22
import com.devonfw.tools.ide.variable.IdeVariables;
23
import com.devonfw.tools.ide.version.IdeVersion;
24
import com.devonfw.tools.ide.version.VersionIdentifier;
25

26
/**
27
 * {@link MvnBasedLocalToolCommandlet} for IDEasy (ide-cli).
28
 */
29
public class IdeasyCommandlet extends MvnBasedLocalToolCommandlet {
30

31
  /** The {@link MvnArtifact} for IDEasy. */
32
  public static final MvnArtifact ARTIFACT = MvnArtifact.ofIdeasyCli("*!", "tar.gz", "${os}-${arch}");
6✔
33

34
  private static final String BASH_CODE_SOURCE_FUNCTIONS = "source \"$IDE_ROOT/_ide/installation/functions\"";
35

36
  /** The {@link #getName() tool name}. */
37
  public static final String TOOL_NAME = "ideasy";
38
  public static final String BASHRC = ".bashrc";
39
  public static final String ZSHRC = ".zshrc";
40
  public static final String IDE_BIN = "\\_ide\\bin";
41
  public static final String IDE_INSTALLATION_BIN = "\\_ide\\installation\\bin";
42

43
  private final UpgradeMode mode;
44

45
  /**
46
   * The constructor.
47
   *
48
   * @param context the {@link IdeContext}.
49
   */
50
  public IdeasyCommandlet(IdeContext context) {
51
    this(context, UpgradeMode.STABLE);
4✔
52
  }
1✔
53

54
  /**
55
   * The constructor.
56
   *
57
   * @param context the {@link IdeContext}.
58
   * @param mode the {@link UpgradeMode}.
59
   */
60
  public IdeasyCommandlet(IdeContext context, UpgradeMode mode) {
61

62
    super(context, TOOL_NAME, ARTIFACT, Set.of(Tag.PRODUCTIVITY, Tag.IDE));
8✔
63
    this.mode = mode;
3✔
64
  }
1✔
65

66
  @Override
67
  public VersionIdentifier getInstalledVersion() {
68

69
    return IdeVersion.getVersionIdentifier();
2✔
70
  }
71

72
  @Override
73
  public String getConfiguredEdition() {
74

75
    return this.tool;
×
76
  }
77

78
  @Override
79
  public VersionIdentifier getConfiguredVersion() {
80

81
    UpgradeMode upgradeMode = this.mode;
×
82
    if (upgradeMode == null) {
×
83
      if (IdeVersion.getVersionString().contains("SNAPSHOT")) {
×
84
        upgradeMode = UpgradeMode.SNAPSHOT;
×
85
      } else {
86
        if (IdeVersion.getVersionIdentifier().getDevelopmentPhase().isStable()) {
×
87
          upgradeMode = UpgradeMode.STABLE;
×
88
        } else {
89
          upgradeMode = UpgradeMode.UNSTABLE;
×
90
        }
91
      }
92
    }
93
    return upgradeMode.getVersion();
×
94
  }
95

96
  @Override
97
  public Path getToolPath() {
98

99
    return this.context.getIdeInstallationPath();
×
100
  }
101

102
  @Override
103
  public boolean install(boolean silent) {
104

105
    if (IdeVersion.isUndefined()) {
2!
106
      this.context.warning("You are using IDEasy version {} which indicates local development - skipping upgrade.", IdeVersion.getVersionString());
10✔
107
      return false;
2✔
108
    }
109
    return super.install(silent);
×
110
  }
111

112
  /**
113
   * @return the latest released {@link VersionIdentifier version} of IDEasy.
114
   */
115
  public VersionIdentifier getLatestVersion() {
116

117
    VersionIdentifier currentVersion = IdeVersion.getVersionIdentifier();
2✔
118
    if (IdeVersion.isUndefined()) {
2!
119
      return currentVersion;
2✔
120
    }
121
    VersionIdentifier configuredVersion = getConfiguredVersion();
×
122
    return getToolRepository().resolveVersion(this.tool, getConfiguredEdition(), configuredVersion, this);
×
123
  }
124

125
  /**
126
   * Checks if an update is available and logs according information.
127
   *
128
   * @return {@code true} if an update is available, {@code false} otherwise.
129
   */
130
  public boolean checkIfUpdateIsAvailable() {
131
    VersionIdentifier installedVersion = getInstalledVersion();
3✔
132
    VersionIdentifier latestVersion = getLatestVersion();
3✔
133
    if (installedVersion.equals(latestVersion)) {
4!
134
      this.context.success("Your version of IDEasy is {} which is the latest released version.", installedVersion);
10✔
135
      return false;
2✔
136
    } else {
137
      this.context.interaction("Your version of IDEasy is {} but version {} is available. Please run the following command to upgrade to the latest version:\n"
×
138
          + "ide upgrade", installedVersion, latestVersion);
139
      return true;
×
140
    }
141
  }
142

143
  /**
144
   * Initial installation of IDEasy.
145
   *
146
   * @param cwd the {@link Path} to the current working directory.
147
   * @see com.devonfw.tools.ide.commandlet.InstallCommandlet
148
   */
149
  public void installIdeasy(Path cwd) {
150
    Path ideRoot = determineIdeRoot(cwd);
4✔
151
    Path idePath = ideRoot.resolve(IdeContext.FOLDER_UNDERSCORE_IDE);
4✔
152
    Path installationPath = idePath.resolve(IdeContext.FOLDER_INSTALLATION);
4✔
153
    Path ideasySoftwarePath = idePath.resolve(IdeContext.FOLDER_SOFTWARE).resolve(MavenRepository.ID).resolve(IdeasyCommandlet.TOOL_NAME)
8✔
154
        .resolve(IdeasyCommandlet.TOOL_NAME);
2✔
155
    Path ideasyVersionPath = ideasySoftwarePath.resolve(IdeVersion.getVersionString());
4✔
156
    if (Files.isDirectory(ideasyVersionPath)) {
5!
157
      throw new CliException("IDEasy is already installed at " + ideasyVersionPath + " - if your installation is broken, delete it manually and rerun setup!");
×
158
    }
159
    FileAccess fileAccess = this.context.getFileAccess();
4✔
160
    List<Path> installationArtifacts = new ArrayList<>();
4✔
161
    boolean success = true;
2✔
162
    success &= addInstallationArtifact(cwd, "bin", true, installationArtifacts);
9✔
163
    success &= addInstallationArtifact(cwd, "functions", true, installationArtifacts);
9✔
164
    success &= addInstallationArtifact(cwd, "internal", true, installationArtifacts);
9✔
165
    success &= addInstallationArtifact(cwd, "system", true, installationArtifacts);
9✔
166
    success &= addInstallationArtifact(cwd, "IDEasy.pdf", true, installationArtifacts);
9✔
167
    success &= addInstallationArtifact(cwd, "setup", true, installationArtifacts);
9✔
168
    success &= addInstallationArtifact(cwd, "setup.bat", false, installationArtifacts);
9✔
169
    if (!success) {
2!
170
      throw new CliException("IDEasy release is inconsistent at " + cwd);
×
171
    }
172
    fileAccess.mkdirs(ideasyVersionPath);
3✔
173
    for (Path installationArtifact : installationArtifacts) {
10✔
174
      fileAccess.copy(installationArtifact, ideasyVersionPath);
4✔
175
    }
1✔
176
    fileAccess.symlink(ideasyVersionPath, installationPath);
4✔
177
    addToShellRc(BASHRC, ideRoot, null);
5✔
178
    addToShellRc(ZSHRC, ideRoot, "autoload -U +X bashcompinit && bashcompinit");
5✔
179
    installIdeasyWindowsEnv(ideRoot, installationPath);
4✔
180
    this.context.success("IDEasy has been installed successfully on your system.");
4✔
181
    this.context.warning("IDEasy has been setup for new shells but it cannot work in your current shell(s).\n"
4✔
182
        + "Reboot or open a new terminal to make it work.");
183
  }
1✔
184

185
  private void installIdeasyWindowsEnv(Path ideRoot, Path installationPath) {
186
    if (!this.context.getSystemInfo().isWindows()) {
5✔
187
      return;
1✔
188
    }
189
    WindowsHelper helper = WindowsHelper.get(this.context);
4✔
190
    helper.setUserEnvironmentValue(IdeVariables.IDE_ROOT.getName(), ideRoot.toString());
6✔
191
    String userPath = helper.getUserEnvironmentValue(IdeVariables.PATH.getName());
5✔
192
    if (userPath == null) {
2!
193
      this.context.error("Could not read user PATH from registry!");
×
194
    } else {
195
      this.context.info("Found user PATH={}", userPath);
10✔
196
      Path ideasyBinPath = installationPath.resolve("bin");
4✔
197
      SimpleSystemPath path = SimpleSystemPath.of(userPath, ';');
4✔
198
      if (path.getEntries().isEmpty()) {
4!
199
        this.context.warning("ATTENTION:\n"
×
200
            + "Your user specific PATH variable seems to be empty.\n"
201
            + "You can double check this by pressing [Windows][r] and launch the program SystemPropertiesAdvanced.\n"
202
            + "Then click on 'Environment variables' and check if 'PATH' is set in the 'user variables' from the upper list.\n"
203
            + "In case 'PATH' is defined there non-empty and you get this message, please abort and give us feedback:\n"
204
            + "https://github.com/devonfw/IDEasy/issues\n"
205
            + "Otherwise all is correct and you can continue.");
206
        this.context.askToContinue("Are you sure you want to override your PATH?");
×
207
      } else {
208
        path.removeEntries(s -> s.endsWith(IDE_BIN));
7✔
209
      }
210
      path.getEntries().add(ideasyBinPath.toString());
6✔
211
      helper.setUserEnvironmentValue(IdeVariables.PATH.getName(), path.toString());
6✔
212
    }
213
  }
1✔
214

215
  static String removeObsoleteEntryFromWindowsPath(String userPath) {
216
    return removeEntryFromWindowsPath(userPath, IDE_BIN);
4✔
217
  }
218

219
  static String removeEntryFromWindowsPath(String userPath, String suffix) {
220
    int len = userPath.length();
3✔
221
    int start = 0;
2✔
222
    while ((start >= 0) && (start < len)) {
5!
223
      int end = userPath.indexOf(';', start);
5✔
224
      if (end < 0) {
2✔
225
        end = len;
2✔
226
      }
227
      String entry = userPath.substring(start, end);
5✔
228
      if (entry.endsWith(suffix)) {
4✔
229
        String prefix = "";
2✔
230
        int offset = 1;
2✔
231
        if (start > 0) {
2✔
232
          prefix = userPath.substring(0, start - 1);
7✔
233
          offset = 0;
2✔
234
        }
235
        if (end == len) {
3✔
236
          return prefix;
2✔
237
        } else {
238
          return removeEntryFromWindowsPath(prefix + userPath.substring(end + offset), suffix);
10✔
239
        }
240
      }
241
      start = end + 1;
4✔
242
    }
1✔
243
    return userPath;
2✔
244
  }
245

246
  /**
247
   * Adds ourselves to the shell RC (run-commands) configuration file.
248
   *
249
   * @param filename the name of the RC file.
250
   * @param ideRoot the IDE_ROOT {@link Path}.
251
   */
252
  private void addToShellRc(String filename, Path ideRoot, String extraLine) {
253

254
    modifyShellRc(filename, ideRoot, true, extraLine);
6✔
255
  }
1✔
256

257
  private void removeFromShellRc(String filename, Path ideRoot) {
258

259
    modifyShellRc(filename, ideRoot, false, null);
6✔
260
  }
1✔
261

262
  /**
263
   * Adds ourselves to the shell RC (run-commands) configuration file.
264
   *
265
   * @param filename the name of the RC file.
266
   * @param ideRoot the IDE_ROOT {@link Path}.
267
   */
268
  private void modifyShellRc(String filename, Path ideRoot, boolean add, String extraLine) {
269

270
    if (add) {
2✔
271
      this.context.info("Configuring IDEasy in {}", filename);
11✔
272
    } else {
273
      this.context.info("Removing IDEasy from {}", filename);
10✔
274
    }
275
    Path rcFile = this.context.getUserHome().resolve(filename);
6✔
276
    FileAccess fileAccess = this.context.getFileAccess();
4✔
277
    List<String> lines = fileAccess.readFileLines(rcFile);
4✔
278
    if (lines == null) {
2✔
279
      if (!add) {
2!
280
        return;
×
281
      }
282
      lines = new ArrayList<>();
5✔
283
    } else {
284
      // since it is unspecified if the returned List may be immutable we want to get sure
285
      lines = new ArrayList<>(lines);
5✔
286
    }
287
    Iterator<String> iterator = lines.iterator();
3✔
288
    int removeCount = 0;
2✔
289
    while (iterator.hasNext()) {
3✔
290
      String line = iterator.next();
4✔
291
      line = line.trim();
3✔
292
      if (isObsoleteRcLine(line)) {
3✔
293
        this.context.info("Removing obsolete line from {}: {}", filename, line);
14✔
294
        iterator.remove();
2✔
295
        removeCount++;
2✔
296
      } else if (line.equals(extraLine)) {
4✔
297
        extraLine = null;
2✔
298
      }
299
    }
1✔
300
    if (add) {
2✔
301
      if (extraLine != null) {
2!
302
        lines.add(extraLine);
×
303
      }
304
      if (!this.context.getSystemInfo().isWindows()) {
5✔
305
        lines.add("export IDE_ROOT=\"" + WindowsPathSyntax.MSYS.format(ideRoot) + "\"");
7✔
306
      }
307
      lines.add(BASH_CODE_SOURCE_FUNCTIONS);
4✔
308
    }
309
    fileAccess.writeFileLines(lines, rcFile);
4✔
310
    this.context.debug("Successfully updated {}", filename);
10✔
311
  }
1✔
312

313
  private static boolean isObsoleteRcLine(String line) {
314
    if (line.startsWith("alias ide=")) {
4!
315
      return true;
×
316
    } else if (line.startsWith("export IDE_ROOT=")) {
4!
317
      return true;
×
318
    } else if (line.equals("ide")) {
4✔
319
      return true;
2✔
320
    } else if (line.equals("ide init")) {
4✔
321
      return true;
2✔
322
    } else if (line.startsWith("source \"$IDE_ROOT/_ide/")) {
4✔
323
      return true;
2✔
324
    }
325
    return false;
2✔
326
  }
327

328
  private boolean addInstallationArtifact(Path cwd, String artifactName, boolean required, List<Path> installationArtifacts) {
329

330
    Path artifactPath = cwd.resolve(artifactName);
4✔
331
    if (Files.exists(artifactPath)) {
5!
332
      installationArtifacts.add(artifactPath);
5✔
333
    } else if (required) {
×
334
      this.context.error("Missing required file {}", artifactName);
×
335
      return false;
×
336
    }
337
    return true;
2✔
338
  }
339

340
  private Path determineIdeRoot(Path cwd) {
341
    Path ideRoot = this.context.getIdeRoot();
4✔
342
    if (ideRoot == null) {
2!
343
      Path home = this.context.getUserHome();
4✔
344
      Path installRoot = home;
2✔
345
      if (this.context.getSystemInfo().isWindows()) {
5✔
346
        if (!cwd.startsWith(home)) {
4!
347
          installRoot = cwd.getRoot();
×
348
        }
349
      }
350
      ideRoot = installRoot.resolve(IdeContext.FOLDER_PROJECTS);
4✔
351
    } else {
1✔
352
      assert (Files.isDirectory(ideRoot)) : "IDE_ROOT directory does not exist!";
×
353
    }
354
    return ideRoot;
2✔
355
  }
356

357
  /**
358
   * Uninstalls IDEasy entirely from the system.
359
   */
360
  public void uninstallIdeasy() {
361

362
    Path ideRoot = this.context.getIdeRoot();
4✔
363
    removeFromShellRc(BASHRC, ideRoot);
4✔
364
    removeFromShellRc(ZSHRC, ideRoot);
4✔
365
    Path idePath = this.context.getIdePath();
4✔
366
    uninstallIdeasyWindowsEnv(ideRoot);
3✔
367
    uninstallIdeasyIdePath(idePath);
3✔
368
    this.context.success("IDEasy has been uninstalled from your system.");
4✔
369
    this.context.interaction("ATTENTION:\n"
10✔
370
        + "In order to prevent data-loss, we do not delete your projects and git repositories!\n"
371
        + "To entirely get rid of IDEasy, also check your IDE_ROOT folder at:\n"
372
        + "{}", ideRoot);
373
  }
1✔
374

375
  private void uninstallIdeasyIdePath(Path idePath) {
376
    if (this.context.getSystemInfo().isWindows()) {
5✔
377
      this.context.newProcess().executable("bash").addArgs("-c",
17✔
378
          "sleep 10 && rm -rf \"" + WindowsPathSyntax.MSYS.format(idePath) + "\"").run(ProcessMode.BACKGROUND);
5✔
379
      this.context.interaction("To prevent windows file locking errors, we perform an asynchronous deletion of {} in background now.\n"
11✔
380
          + "Please close all terminals and wait a minute for the deletion to complete before running other commands.", idePath);
381
    } else {
382
      this.context.info("Finally deleting {}", idePath);
10✔
383
      this.context.getFileAccess().delete(idePath);
5✔
384
    }
385
  }
1✔
386

387
  private void uninstallIdeasyWindowsEnv(Path ideRoot) {
388
    if (!this.context.getSystemInfo().isWindows()) {
5✔
389
      return;
1✔
390
    }
391
    WindowsHelper helper = WindowsHelper.get(this.context);
4✔
392
    helper.removeUserEnvironmentValue(IdeVariables.IDE_ROOT.getName());
4✔
393
    String userPath = helper.getUserEnvironmentValue(IdeVariables.PATH.getName());
5✔
394
    if (userPath == null) {
2!
395
      this.context.error("Could not read user PATH from registry!");
×
396
    } else {
397
      this.context.info("Found user PATH={}", userPath);
10✔
398
      String newUserPath = userPath;
2✔
399
      if (!userPath.isEmpty()) {
3!
400
        SimpleSystemPath path = SimpleSystemPath.of(userPath, ';');
4✔
401
        path.removeEntries(s -> s.endsWith(IDE_BIN) || s.endsWith(IDE_INSTALLATION_BIN));
15!
402
        newUserPath = path.toString();
3✔
403
      }
404
      if (newUserPath.equals(userPath)) {
4!
405
        this.context.error("Could not find IDEasy in PATH:\n{}", userPath);
×
406
      } else {
407
        helper.setUserEnvironmentValue(IdeVariables.PATH.getName(), newUserPath);
5✔
408
      }
409
    }
410
  }
1✔
411
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc