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

devonfw / IDEasy / 13772844222

10 Mar 2025 07:10PM UTC coverage: 68.619% (+0.1%) from 68.471%
13772844222

push

github

web-flow
#654: improved plugin suppport (#1085)

Co-authored-by: Jörg Hohwiller <hohwille@users.noreply.github.com>

3067 of 4915 branches covered (62.4%)

Branch coverage included in aggregate %.

7934 of 11117 relevant lines covered (71.37%)

3.11 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
   * Uninstalls the {@link #getName() tool}.
304
   */
305
  public abstract void uninstall();
306

307
  /**
308
   * @return the {@link ToolRepository}.
309
   */
310
  public ToolRepository getToolRepository() {
311

312
    return this.context.getDefaultToolRepository();
4✔
313
  }
314

315
  /**
316
   * List the available editions of this tool.
317
   */
318
  public void listEditions() {
319

320
    List<String> editions = getToolRepository().getSortedEditions(getName());
6✔
321
    for (String edition : editions) {
10✔
322
      this.context.info(edition);
4✔
323
    }
1✔
324
  }
1✔
325

326
  /**
327
   * List the available versions of this tool.
328
   */
329
  public void listVersions() {
330

331
    List<VersionIdentifier> versions = getToolRepository().getSortedVersions(getName(), getConfiguredEdition(), this);
9✔
332
    for (VersionIdentifier vi : versions) {
10✔
333
      this.context.info(vi.toString());
5✔
334
    }
1✔
335
  }
1✔
336

337
  /**
338
   * Sets the tool version in the environment variable configuration file.
339
   *
340
   * @param version the version (pattern) to set.
341
   */
342
  public void setVersion(String version) {
343

344
    if ((version == null) || version.isBlank()) {
×
345
      throw new IllegalStateException("Version has to be specified!");
×
346
    }
347
    VersionIdentifier configuredVersion = VersionIdentifier.of(version);
×
348
    if (!configuredVersion.isPattern() && !configuredVersion.isValid()) {
×
349
      this.context.warning("Version {} seems to be invalid", version);
×
350
    }
351
    setVersion(configuredVersion, true);
×
352
  }
×
353

354
  /**
355
   * Sets the tool version in the environment variable configuration file.
356
   *
357
   * @param version the version to set. May also be a {@link VersionIdentifier#isPattern() version pattern}.
358
   * @param hint - {@code true} to print the installation hint, {@code false} otherwise.
359
   */
360
  public void setVersion(VersionIdentifier version, boolean hint) {
361

362
    setVersion(version, hint, null);
5✔
363
  }
1✔
364

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

374
    String edition = getConfiguredEdition();
3✔
375
    ToolRepository toolRepository = getToolRepository();
3✔
376
    VersionIdentifier versionIdentifier = toolRepository.resolveVersion(this.tool, edition, version, this);
8✔
377
    Objects.requireNonNull(versionIdentifier);
3✔
378

379
    EnvironmentVariables variables = this.context.getVariables();
4✔
380
    if (destination == null) {
2✔
381
      //use default location
382
      destination = EnvironmentVariablesFiles.SETTINGS;
2✔
383
    }
384
    EnvironmentVariables settingsVariables = variables.getByType(destination.toType());
5✔
385
    String name = EnvironmentVariables.getToolVersionVariable(this.tool);
4✔
386

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

405
  /**
406
   * Sets the tool edition in the environment variable configuration file.
407
   *
408
   * @param edition the edition to set.
409
   */
410
  public void setEdition(String edition) {
411

412
    setEdition(edition, true);
4✔
413
  }
1✔
414

415
  /**
416
   * Sets the tool edition in the environment variable configuration file.
417
   *
418
   * @param edition the edition to set
419
   * @param hint - {@code true} to print the installation hint, {@code false} otherwise.
420
   */
421
  public void setEdition(String edition, boolean hint) {
422

423
    setEdition(edition, hint, null);
5✔
424
  }
1✔
425

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

435
    if ((edition == null) || edition.isBlank()) {
5!
436
      throw new IllegalStateException("Edition has to be specified!");
×
437
    }
438

439
    if (destination == null) {
2✔
440
      //use default location
441
      destination = EnvironmentVariablesFiles.SETTINGS;
2✔
442
    }
443

444
    if (!getToolRepository().getSortedEditions(this.tool).contains(edition)) {
8!
445
      this.context.warning("Edition {} seems to be invalid", edition);
10✔
446
    }
447
    EnvironmentVariables variables = this.context.getVariables();
4✔
448
    EnvironmentVariables settingsVariables = variables.getByType(destination.toType());
5✔
449
    String name = EnvironmentVariables.getToolEditionVariable(this.tool);
4✔
450
    settingsVariables.set(name, edition, false);
6✔
451
    settingsVariables.save();
2✔
452

453
    this.context.info("{}={} has been set in {}", name, edition, settingsVariables.getSource());
19✔
454
    EnvironmentVariables declaringVariables = variables.findVariable(name);
4✔
455
    if ((declaringVariables != null) && (declaringVariables != settingsVariables)) {
5!
456
      this.context.warning("The variable {} is overridden in {}. Please remove the overridden declaration in order to make the change affect.", name,
13✔
457
          declaringVariables.getSource());
2✔
458
    }
459
    if (hint) {
2!
460
      this.context.info("To install that edition call the following command:");
4✔
461
      this.context.info("ide install {}", this.tool);
11✔
462
    }
463
  }
1✔
464

465
  /**
466
   * Runs the tool's help command to provide the user with usage information.
467
   */
468
  @Override
469
  public void printHelp(NlsBundle bundle) {
470

471
    super.printHelp(bundle);
3✔
472
    String toolHelpArgs = getToolHelpArguments();
3✔
473
    if (toolHelpArgs != null && getInstalledVersion() != null) {
5!
474
      ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.LOG_WARNING)
6✔
475
          .executable(Path.of(getBinaryName())).addArgs(toolHelpArgs);
13✔
476
      pc.run(ProcessMode.DEFAULT);
4✔
477
    }
478
  }
1✔
479

480
  /**
481
   * @return the tool's specific help command. Usually help, --help or -h. Return null if not applicable.
482
   */
483
  public String getToolHelpArguments() {
484

485
    return null;
×
486
  }
487

488
  /**
489
   * Creates a start script for the tool using the tool name.
490
   *
491
   * @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
492
   *     instead.
493
   * @param binary name of the binary to execute from the start script.
494
   */
495
  protected void createStartScript(Path targetDir, String binary) {
496

497
    createStartScript(targetDir, binary, false);
×
498
  }
×
499

500
  /**
501
   * Creates a start script for the tool using the tool name.
502
   *
503
   * @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
504
   *     instead.
505
   * @param binary name of the binary to execute from the start script.
506
   * @param background {@code true} to run the {@code binary} in background, {@code false} otherwise (foreground).
507
   */
508
  protected void createStartScript(Path targetDir, String binary, boolean background) {
509

510
    Path binFolder = targetDir.resolve("bin");
×
511
    if (!Files.exists(binFolder)) {
×
512
      if (this.context.getSystemInfo().isMac()) {
×
513
        MacOsHelper macOsHelper = getMacOsHelper();
×
514
        Path appDir = macOsHelper.findAppDir(targetDir);
×
515
        binFolder = macOsHelper.findLinkDir(appDir, binary);
×
516
      } else {
×
517
        binFolder = targetDir;
×
518
      }
519
      assert (Files.exists(binFolder));
×
520
    }
521
    Path bashFile = binFolder.resolve(getName());
×
522
    String bashFileContentStart = "#!/usr/bin/env bash\n\"$(dirname \"$0\")/";
×
523
    String bashFileContentEnd = "\" $@";
×
524
    if (background) {
×
525
      bashFileContentEnd += " &";
×
526
    }
527
    try {
528
      Files.writeString(bashFile, bashFileContentStart + binary + bashFileContentEnd);
×
529
    } catch (IOException e) {
×
530
      throw new RuntimeException(e);
×
531
    }
×
532
    assert (Files.exists(bashFile));
×
533
    context.getFileAccess().makeExecutable(bashFile);
×
534
  }
×
535

536
  @Override
537
  public void reset() {
538
    super.reset();
2✔
539
    this.executionDirectory = null;
3✔
540
  }
1✔
541
}
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

© 2025 Coveralls, Inc