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

devonfw / IDEasy / 14863053870

06 May 2025 03:03PM UTC coverage: 67.572% (-0.1%) from 67.672%
14863053870

Pull #1242

github

web-flow
Merge 0c9ce4cfc into 90ccc5b21
Pull Request #1242: #809: Enhance uninstall with --force to remove from the software repo

3105 of 5004 branches covered (62.05%)

Branch coverage included in aggregate %.

7995 of 11423 relevant lines covered (69.99%)

3.06 hits per line

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

67.8
cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java
1
package com.devonfw.tools.ide.tool;
2

3
import java.io.IOException;
4
import java.nio.file.Files;
5
import java.nio.file.Path;
6
import java.util.List;
7
import java.util.Objects;
8
import java.util.Set;
9

10
import com.devonfw.tools.ide.commandlet.Commandlet;
11
import com.devonfw.tools.ide.common.Tag;
12
import com.devonfw.tools.ide.common.Tags;
13
import com.devonfw.tools.ide.context.IdeContext;
14
import com.devonfw.tools.ide.environment.EnvironmentVariables;
15
import com.devonfw.tools.ide.environment.EnvironmentVariablesFiles;
16
import com.devonfw.tools.ide.nls.NlsBundle;
17
import com.devonfw.tools.ide.os.MacOsHelper;
18
import com.devonfw.tools.ide.process.EnvironmentContext;
19
import com.devonfw.tools.ide.process.ProcessContext;
20
import com.devonfw.tools.ide.process.ProcessErrorHandling;
21
import com.devonfw.tools.ide.process.ProcessMode;
22
import com.devonfw.tools.ide.process.ProcessResult;
23
import com.devonfw.tools.ide.property.StringProperty;
24
import com.devonfw.tools.ide.tool.repository.ToolRepository;
25
import com.devonfw.tools.ide.version.GenericVersionRange;
26
import com.devonfw.tools.ide.version.VersionIdentifier;
27

28
/**
29
 * {@link Commandlet} for a tool integrated into the IDE.
30
 */
31
public abstract class ToolCommandlet extends Commandlet implements Tags {
1✔
32

33
  /** @see #getName() */
34
  protected final String tool;
35

36
  private final Set<Tag> tags;
37

38
  /** The commandline arguments to pass to the tool. */
39
  public final StringProperty arguments;
40

41
  private Path executionDirectory;
42

43
  private MacOsHelper macOsHelper;
44

45
  /**
46
   * The constructor.
47
   *
48
   * @param context the {@link IdeContext}.
49
   * @param tool the {@link #getName() tool name}.
50
   * @param tags the {@link #getTags() tags} classifying the tool. Should be created via {@link Set#of(Object) Set.of} method.
51
   */
52
  public ToolCommandlet(IdeContext context, String tool, Set<Tag> tags) {
53

54
    super(context);
3✔
55
    this.tool = tool;
3✔
56
    this.tags = tags;
3✔
57
    addKeyword(tool);
3✔
58
    this.arguments = new StringProperty("", false, true, "args");
9✔
59
    initProperties();
2✔
60
  }
1✔
61

62
  /**
63
   * Add initial Properties to the tool
64
   */
65
  protected void initProperties() {
66

67
    add(this.arguments);
5✔
68
  }
1✔
69

70
  /**
71
   * @return the name of the tool (e.g. "java", "mvn", "npm", "node").
72
   */
73
  @Override
74
  public final String getName() {
75

76
    return this.tool;
3✔
77
  }
78

79
  /**
80
   * @return the name of the binary executable for this tool.
81
   */
82
  protected String getBinaryName() {
83

84
    return this.tool;
3✔
85
  }
86

87
  @Override
88
  public final Set<Tag> getTags() {
89

90
    return this.tags;
3✔
91
  }
92

93
  /**
94
   * @return the execution directory where the tool will be executed. Will be {@code null} by default leading to execution in the users current working
95
   *     directory where IDEasy was called.
96
   * @see #setExecutionDirectory(Path)
97
   */
98
  public Path getExecutionDirectory() {
99
    return this.executionDirectory;
×
100
  }
101

102
  /**
103
   * @param executionDirectory the new value of {@link #getExecutionDirectory()}.
104
   */
105
  public void setExecutionDirectory(Path executionDirectory) {
106
    this.executionDirectory = executionDirectory;
×
107
  }
×
108

109
  /**
110
   * @return the {@link EnvironmentVariables#getToolVersion(String) tool version}.
111
   */
112
  public VersionIdentifier getConfiguredVersion() {
113

114
    return this.context.getVariables().getToolVersion(getName());
7✔
115
  }
116

117
  /**
118
   * @return the {@link EnvironmentVariables#getToolEdition(String) tool edition}.
119
   */
120
  public String getConfiguredEdition() {
121

122
    return this.context.getVariables().getToolEdition(getName());
7✔
123
  }
124

125
  /**
126
   * @return the {@link #getName() tool} with its {@link #getConfiguredEdition() edition}. The edition will be omitted if same as tool.
127
   * @see #getToolWithEdition(String, String)
128
   */
129
  protected final String getToolWithEdition() {
130

131
    return getToolWithEdition(getName(), getConfiguredEdition());
6✔
132
  }
133

134
  /**
135
   * @param tool the tool name.
136
   * @param edition the edition.
137
   * @return the {@link #getName() tool} with its {@link #getConfiguredEdition() edition}. The edition will be omitted if same as tool.
138
   */
139
  protected static String getToolWithEdition(String tool, String edition) {
140

141
    if (tool.equals(edition)) {
4✔
142
      return tool;
2✔
143
    }
144
    return tool + "/" + edition;
4✔
145
  }
146

147
  @Override
148
  public void run() {
149

150
    runTool(this.arguments.asArray());
5✔
151
  }
1✔
152

153
  /**
154
   * @param args the command-line arguments to run the tool.
155
   * @see ToolCommandlet#runTool(ProcessMode, GenericVersionRange, String...)
156
   */
157
  public void runTool(String... args) {
158

159
    runTool(ProcessMode.DEFAULT, null, args);
5✔
160
  }
1✔
161

162
  /**
163
   * Ensures the tool is installed and then runs this tool with the given arguments.
164
   *
165
   * @param processMode the {@link ProcessMode}. Should typically be {@link ProcessMode#DEFAULT} or {@link ProcessMode#BACKGROUND}.
166
   * @param toolVersion the explicit {@link GenericVersionRange version} to run. Typically {@code null} to run the
167
   *     {@link #getConfiguredVersion() configured version}. Otherwise, the specified version will be used (from the software repository, if not compatible).
168
   * @param args the command-line arguments to run the tool.
169
   */
170
  public final void runTool(ProcessMode processMode, GenericVersionRange toolVersion, String... args) {
171

172
    runTool(processMode, toolVersion, ProcessErrorHandling.THROW_CLI, args);
7✔
173
  }
1✔
174

175
  /**
176
   * Ensures the tool is installed and then runs this tool with the given arguments.
177
   *
178
   * @param processMode the {@link ProcessMode}. Should typically be {@link ProcessMode#DEFAULT} or {@link ProcessMode#BACKGROUND}.
179
   * @param toolVersion the explicit {@link GenericVersionRange version} to run. Typically {@code null} to run the
180
   *     {@link #getConfiguredVersion() configured version}. Otherwise, the specified version will be used (from the software repository, if not compatible).
181
   * @param errorHandling the {@link ProcessErrorHandling}.
182
   * @param args the command-line arguments to run the tool.
183
   * @return the {@link ProcessResult result}.
184
   */
185
  public ProcessResult runTool(ProcessMode processMode, GenericVersionRange toolVersion, ProcessErrorHandling errorHandling, String... args) {
186

187
    ProcessContext pc = this.context.newProcess().errorHandling(errorHandling);
6✔
188
    install(true, pc);
5✔
189
    return runTool(processMode, errorHandling, pc, args);
7✔
190
  }
191

192
  /**
193
   * @param processMode the {@link ProcessMode}. Should typically be {@link ProcessMode#DEFAULT} or {@link ProcessMode#BACKGROUND}.
194
   * @param errorHandling the {@link ProcessErrorHandling}.
195
   * @param pc the {@link ProcessContext}.
196
   * @param args the command-line arguments to run the tool.
197
   * @return the {@link ProcessResult result}.
198
   */
199
  public ProcessResult runTool(ProcessMode processMode, ProcessErrorHandling errorHandling, ProcessContext pc, String... args) {
200

201
    if (this.executionDirectory != null) {
3!
202
      pc.directory(this.executionDirectory);
×
203
    }
204
    configureToolBinary(pc, processMode, errorHandling);
5✔
205
    configureToolArgs(pc, processMode, errorHandling, args);
6✔
206
    return pc.run(processMode);
4✔
207
  }
208

209
  /**
210
   * @param pc the {@link ProcessContext}.
211
   * @param processMode the {@link ProcessMode}.
212
   * @param errorHandling the {@link ProcessErrorHandling}.
213
   */
214
  protected void configureToolBinary(ProcessContext pc, ProcessMode processMode, ProcessErrorHandling errorHandling) {
215

216
    pc.executable(Path.of(getBinaryName()));
8✔
217
  }
1✔
218

219
  /**
220
   * @param pc the {@link ProcessContext}.
221
   * @param processMode the {@link ProcessMode}.
222
   * @param errorHandling the {@link ProcessErrorHandling}.
223
   * @param args the command-line arguments to {@link ProcessContext#addArgs(Object...) add}.
224
   */
225
  protected void configureToolArgs(ProcessContext pc, ProcessMode processMode, ProcessErrorHandling errorHandling, String... args) {
226

227
    pc.addArgs(args);
4✔
228
  }
1✔
229

230
  /**
231
   * Creates a new {@link ProcessContext} from the given executable with the provided arguments attached.
232
   *
233
   * @param binaryPath path to the binary executable for this process
234
   * @param args the command-line arguments for this process
235
   * @return {@link ProcessContext}
236
   */
237
  protected ProcessContext createProcessContext(Path binaryPath, String... args) {
238

239
    return this.context.newProcess().errorHandling(ProcessErrorHandling.THROW_ERR).executable(binaryPath).addArgs(args);
×
240
  }
241

242
  /**
243
   * Installs or updates the managed {@link #getName() tool}.
244
   *
245
   * @return {@code true} if the tool was newly installed, {@code false} if the tool was already installed before and nothing has changed.
246
   */
247
  public boolean install() {
248

249
    return install(true);
4✔
250
  }
251

252
  /**
253
   * Performs the installation of the {@link #getName() tool} managed by this {@link com.devonfw.tools.ide.commandlet.Commandlet}.
254
   *
255
   * @param silent - {@code true} if called recursively to suppress verbose logging, {@code false} otherwise.
256
   * @return {@code true} if the tool was newly installed, {@code false} if the tool was already installed before and nothing has changed.
257
   */
258
  public boolean install(boolean silent) {
259
    ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.THROW_CLI);
6✔
260
    return install(silent, pc);
5✔
261
  }
262

263
  /**
264
   * Installs or updates the managed {@link #getName() tool}.
265
   *
266
   * @param silent - {@code true} if called recursively to suppress verbose logging, {@code false} otherwise.
267
   * @param processContext the {@link ProcessContext} used to
268
   *     {@link LocalToolCommandlet#setEnvironment(EnvironmentContext, ToolInstallation, boolean) configure environment variables}.
269
   * @return {@code true} if the tool was newly installed, {@code false} if the tool was already installed before and nothing has changed.
270
   */
271
  public abstract boolean install(boolean silent, ProcessContext processContext);
272

273
  /**
274
   * @return {@code true} to extract (unpack) the downloaded binary file, {@code false} otherwise.
275
   */
276
  protected boolean isExtract() {
277

278
    return true;
2✔
279
  }
280

281
  /**
282
   * @return the {@link MacOsHelper} instance.
283
   */
284
  protected MacOsHelper getMacOsHelper() {
285

286
    if (this.macOsHelper == null) {
3✔
287
      this.macOsHelper = new MacOsHelper(this.context);
7✔
288
    }
289
    return this.macOsHelper;
3✔
290
  }
291

292
  /**
293
   * @return the currently installed {@link VersionIdentifier version} of this tool or {@code null} if not installed.
294
   */
295
  public abstract VersionIdentifier getInstalledVersion();
296

297
  /**
298
   * @return the installed edition of this tool or {@code null} if not installed.
299
   */
300
  public abstract String getInstalledEdition();
301

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

307
  /**
308
   * Uninstalls the {@link #getName() tool}.
309
   */
310
  public abstract void uninstall();
311

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

317
  /**
318
   * @return the {@link ToolRepository}.
319
   */
320
  public ToolRepository getToolRepository() {
321

322
    return this.context.getDefaultToolRepository();
4✔
323
  }
324

325
  /**
326
   * List the available editions of this tool.
327
   */
328
  public void listEditions() {
329

330
    List<String> editions = getToolRepository().getSortedEditions(getName());
6✔
331
    for (String edition : editions) {
10✔
332
      this.context.info(edition);
4✔
333
    }
1✔
334
  }
1✔
335

336
  /**
337
   * List the available versions of this tool.
338
   */
339
  public void listVersions() {
340

341
    List<VersionIdentifier> versions = getToolRepository().getSortedVersions(getName(), getConfiguredEdition(), this);
9✔
342
    for (VersionIdentifier vi : versions) {
10✔
343
      this.context.info(vi.toString());
5✔
344
    }
1✔
345
  }
1✔
346

347
  /**
348
   * Sets the tool version in the environment variable configuration file.
349
   *
350
   * @param version the version (pattern) to set.
351
   */
352
  public void setVersion(String version) {
353

354
    if ((version == null) || version.isBlank()) {
×
355
      throw new IllegalStateException("Version has to be specified!");
×
356
    }
357
    VersionIdentifier configuredVersion = VersionIdentifier.of(version);
×
358
    if (!configuredVersion.isPattern() && !configuredVersion.isValid()) {
×
359
      this.context.warning("Version {} seems to be invalid", version);
×
360
    }
361
    setVersion(configuredVersion, true);
×
362
  }
×
363

364
  /**
365
   * Sets the tool version in the environment variable configuration file.
366
   *
367
   * @param version the version to set. May also be a {@link VersionIdentifier#isPattern() version pattern}.
368
   * @param hint - {@code true} to print the installation hint, {@code false} otherwise.
369
   */
370
  public void setVersion(VersionIdentifier version, boolean hint) {
371

372
    setVersion(version, hint, null);
5✔
373
  }
1✔
374

375
  /**
376
   * Sets the tool version in the environment variable configuration file.
377
   *
378
   * @param version the version to set. May also be a {@link VersionIdentifier#isPattern() version pattern}.
379
   * @param hint - {@code true} to print the installation hint, {@code false} otherwise.
380
   * @param destination - the destination for the property to be set
381
   */
382
  public void setVersion(VersionIdentifier version, boolean hint, EnvironmentVariablesFiles destination) {
383

384
    String edition = getConfiguredEdition();
3✔
385
    ToolRepository toolRepository = getToolRepository();
3✔
386
    VersionIdentifier versionIdentifier = toolRepository.resolveVersion(this.tool, edition, version, this);
8✔
387
    Objects.requireNonNull(versionIdentifier);
3✔
388

389
    EnvironmentVariables variables = this.context.getVariables();
4✔
390
    if (destination == null) {
2✔
391
      //use default location
392
      destination = EnvironmentVariablesFiles.SETTINGS;
2✔
393
    }
394
    EnvironmentVariables settingsVariables = variables.getByType(destination.toType());
5✔
395
    String name = EnvironmentVariables.getToolVersionVariable(this.tool);
4✔
396

397
    VersionIdentifier resolvedVersion = toolRepository.resolveVersion(this.tool, edition, version, this);
8✔
398
    if (version.isPattern()) {
3!
399
      this.context.debug("Resolved version {} to {} for tool {}/{}", version, resolvedVersion, this.tool, edition);
×
400
    }
401
    settingsVariables.set(name, resolvedVersion.toString(), false);
7✔
402
    settingsVariables.save();
2✔
403
    this.context.info("{}={} has been set in {}", name, version, settingsVariables.getSource());
19✔
404
    EnvironmentVariables declaringVariables = variables.findVariable(name);
4✔
405
    if ((declaringVariables != null) && (declaringVariables != settingsVariables)) {
5!
406
      this.context.warning("The variable {} is overridden in {}. Please remove the overridden declaration in order to make the change affect.", name,
13✔
407
          declaringVariables.getSource());
2✔
408
    }
409
    if (hint) {
2✔
410
      this.context.info("To install that version call the following command:");
4✔
411
      this.context.info("ide install {}", this.tool);
11✔
412
    }
413
  }
1✔
414

415
  /**
416
   * Sets the tool edition in the environment variable configuration file.
417
   *
418
   * @param edition the edition to set.
419
   */
420
  public void setEdition(String edition) {
421

422
    setEdition(edition, true);
4✔
423
  }
1✔
424

425
  /**
426
   * Sets the tool edition in the environment variable configuration file.
427
   *
428
   * @param edition the edition to set
429
   * @param hint - {@code true} to print the installation hint, {@code false} otherwise.
430
   */
431
  public void setEdition(String edition, boolean hint) {
432

433
    setEdition(edition, hint, null);
5✔
434
  }
1✔
435

436
  /**
437
   * Sets the tool edition in the environment variable configuration file.
438
   *
439
   * @param edition the edition to set
440
   * @param hint - {@code true} to print the installation hint, {@code false} otherwise.
441
   * @param destination - the destination for the property to be set
442
   */
443
  public void setEdition(String edition, boolean hint, EnvironmentVariablesFiles destination) {
444

445
    if ((edition == null) || edition.isBlank()) {
5!
446
      throw new IllegalStateException("Edition has to be specified!");
×
447
    }
448

449
    if (destination == null) {
2✔
450
      //use default location
451
      destination = EnvironmentVariablesFiles.SETTINGS;
2✔
452
    }
453

454
    if (!getToolRepository().getSortedEditions(this.tool).contains(edition)) {
8!
455
      this.context.warning("Edition {} seems to be invalid", edition);
10✔
456
    }
457
    EnvironmentVariables variables = this.context.getVariables();
4✔
458
    EnvironmentVariables settingsVariables = variables.getByType(destination.toType());
5✔
459
    String name = EnvironmentVariables.getToolEditionVariable(this.tool);
4✔
460
    settingsVariables.set(name, edition, false);
6✔
461
    settingsVariables.save();
2✔
462

463
    this.context.info("{}={} has been set in {}", name, edition, settingsVariables.getSource());
19✔
464
    EnvironmentVariables declaringVariables = variables.findVariable(name);
4✔
465
    if ((declaringVariables != null) && (declaringVariables != settingsVariables)) {
5!
466
      this.context.warning("The variable {} is overridden in {}. Please remove the overridden declaration in order to make the change affect.", name,
13✔
467
          declaringVariables.getSource());
2✔
468
    }
469
    if (hint) {
2!
470
      this.context.info("To install that edition call the following command:");
4✔
471
      this.context.info("ide install {}", this.tool);
11✔
472
    }
473
  }
1✔
474

475
  /**
476
   * Runs the tool's help command to provide the user with usage information.
477
   */
478
  @Override
479
  public void printHelp(NlsBundle bundle) {
480

481
    super.printHelp(bundle);
3✔
482
    String toolHelpArgs = getToolHelpArguments();
3✔
483
    if (toolHelpArgs != null && getInstalledVersion() != null) {
5!
484
      ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.LOG_WARNING)
6✔
485
          .executable(Path.of(getBinaryName())).addArgs(toolHelpArgs);
13✔
486
      pc.run(ProcessMode.DEFAULT);
4✔
487
    }
488
  }
1✔
489

490
  /**
491
   * @return the tool's specific help command. Usually help, --help or -h. Return null if not applicable.
492
   */
493
  public String getToolHelpArguments() {
494

495
    return null;
×
496
  }
497

498
  /**
499
   * Creates a start script for the tool using the tool name.
500
   *
501
   * @param targetDir the {@link Path} of the installation where to create the script. If a "bin" sub-folder is present, the script will be created there
502
   *     instead.
503
   * @param binary name of the binary to execute from the start script.
504
   */
505
  protected void createStartScript(Path targetDir, String binary) {
506

507
    createStartScript(targetDir, binary, false);
×
508
  }
×
509

510
  /**
511
   * Creates a start script for the tool using the tool name.
512
   *
513
   * @param targetDir the {@link Path} of the installation where to create the script. If a "bin" sub-folder is present, the script will be created there
514
   *     instead.
515
   * @param binary name of the binary to execute from the start script.
516
   * @param background {@code true} to run the {@code binary} in background, {@code false} otherwise (foreground).
517
   */
518
  protected void createStartScript(Path targetDir, String binary, boolean background) {
519

520
    Path binFolder = targetDir.resolve("bin");
×
521
    if (!Files.exists(binFolder)) {
×
522
      if (this.context.getSystemInfo().isMac()) {
×
523
        MacOsHelper macOsHelper = getMacOsHelper();
×
524
        Path appDir = macOsHelper.findAppDir(targetDir);
×
525
        binFolder = macOsHelper.findLinkDir(appDir, binary);
×
526
      } else {
×
527
        binFolder = targetDir;
×
528
      }
529
      assert (Files.exists(binFolder));
×
530
    }
531
    Path bashFile = binFolder.resolve(getName());
×
532
    String bashFileContentStart = "#!/usr/bin/env bash\n\"$(dirname \"$0\")/";
×
533
    String bashFileContentEnd = "\" $@";
×
534
    if (background) {
×
535
      bashFileContentEnd += " &";
×
536
    }
537
    try {
538
      Files.writeString(bashFile, bashFileContentStart + binary + bashFileContentEnd);
×
539
    } catch (IOException e) {
×
540
      throw new RuntimeException(e);
×
541
    }
×
542
    assert (Files.exists(bashFile));
×
543
    context.getFileAccess().makeExecutable(bashFile);
×
544
  }
×
545

546
  @Override
547
  public void reset() {
548
    super.reset();
2✔
549
    this.executionDirectory = null;
3✔
550
  }
1✔
551
}
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