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

devonfw / IDEasy / 19481956452

18 Nov 2025 10:06PM UTC coverage: 69.044% (+0.1%) from 68.905%
19481956452

Pull #1593

github

web-flow
Merge b71c405c9 into 553958662
Pull Request #1593: #1144: #1145: CVE warnings and suggestions

3570 of 5669 branches covered (62.97%)

Branch coverage included in aggregate %.

9308 of 12983 relevant lines covered (71.69%)

3.15 hits per line

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

70.23
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.ArrayList;
7
import java.util.Collection;
8
import java.util.List;
9
import java.util.Set;
10

11
import com.devonfw.tools.ide.commandlet.Commandlet;
12
import com.devonfw.tools.ide.common.Tag;
13
import com.devonfw.tools.ide.common.Tags;
14
import com.devonfw.tools.ide.context.IdeContext;
15
import com.devonfw.tools.ide.environment.EnvironmentVariables;
16
import com.devonfw.tools.ide.environment.EnvironmentVariablesFiles;
17
import com.devonfw.tools.ide.log.IdeSubLogger;
18
import com.devonfw.tools.ide.nls.NlsBundle;
19
import com.devonfw.tools.ide.os.MacOsHelper;
20
import com.devonfw.tools.ide.process.EnvironmentContext;
21
import com.devonfw.tools.ide.process.ProcessContext;
22
import com.devonfw.tools.ide.process.ProcessErrorHandling;
23
import com.devonfw.tools.ide.process.ProcessMode;
24
import com.devonfw.tools.ide.process.ProcessResult;
25
import com.devonfw.tools.ide.property.StringProperty;
26
import com.devonfw.tools.ide.security.ToolVersionChoice;
27
import com.devonfw.tools.ide.step.Step;
28
import com.devonfw.tools.ide.tool.repository.ToolRepository;
29
import com.devonfw.tools.ide.url.model.file.json.Cve;
30
import com.devonfw.tools.ide.url.model.file.json.ToolSecurity;
31
import com.devonfw.tools.ide.variable.IdeVariables;
32
import com.devonfw.tools.ide.version.GenericVersionRange;
33
import com.devonfw.tools.ide.version.VersionIdentifier;
34

35
/**
36
 * {@link Commandlet} for a tool integrated into the IDE.
37
 */
38
public abstract class ToolCommandlet extends Commandlet implements Tags {
1✔
39

40
  /** @see #getName() */
41
  protected final String tool;
42

43
  private final Set<Tag> tags;
44

45
  /** The commandline arguments to pass to the tool. */
46
  public final StringProperty arguments;
47

48
  private Path executionDirectory;
49

50
  private MacOsHelper macOsHelper;
51

52
  /**
53
   * The constructor.
54
   *
55
   * @param context the {@link IdeContext}.
56
   * @param tool the {@link #getName() tool name}.
57
   * @param tags the {@link #getTags() tags} classifying the tool. Should be created via {@link Set#of(Object) Set.of} method.
58
   */
59
  public ToolCommandlet(IdeContext context, String tool, Set<Tag> tags) {
60

61
    super(context);
3✔
62
    this.tool = tool;
3✔
63
    this.tags = tags;
3✔
64
    addKeyword(tool);
3✔
65
    this.arguments = new StringProperty("", false, true, "args");
9✔
66
    initProperties();
2✔
67
  }
1✔
68

69
  /**
70
   * Add initial Properties to the tool
71
   */
72
  protected void initProperties() {
73

74
    add(this.arguments);
5✔
75
  }
1✔
76

77
  /**
78
   * @return the name of the tool (e.g. "java", "mvn", "npm", "node").
79
   */
80
  @Override
81
  public final String getName() {
82

83
    return this.tool;
3✔
84
  }
85

86
  /**
87
   * @return the name of the binary executable for this tool.
88
   */
89
  protected String getBinaryName() {
90

91
    return this.tool;
3✔
92
  }
93

94
  @Override
95
  public final Set<Tag> getTags() {
96

97
    return this.tags;
3✔
98
  }
99

100
  /**
101
   * @return the execution directory where the tool will be executed. Will be {@code null} by default leading to execution in the users current working
102
   *     directory where IDEasy was called.
103
   * @see #setExecutionDirectory(Path)
104
   */
105
  public Path getExecutionDirectory() {
106
    return this.executionDirectory;
×
107
  }
108

109
  /**
110
   * @param executionDirectory the new value of {@link #getExecutionDirectory()}.
111
   */
112
  public void setExecutionDirectory(Path executionDirectory) {
113
    this.executionDirectory = executionDirectory;
×
114
  }
×
115

116
  /**
117
   * @return the {@link EnvironmentVariables#getToolVersion(String) tool version}.
118
   */
119
  public VersionIdentifier getConfiguredVersion() {
120

121
    return this.context.getVariables().getToolVersion(getName());
7✔
122
  }
123

124
  /**
125
   * @return the {@link EnvironmentVariables#getToolEdition(String) tool edition}.
126
   */
127
  public String getConfiguredEdition() {
128

129
    return this.context.getVariables().getToolEdition(getName());
7✔
130
  }
131

132
  /**
133
   * @return the {@link ToolEdition} with {@link #getName() tool} with its {@link #getConfiguredEdition() edition}.
134
   */
135
  protected final ToolEdition getToolWithEdition() {
136

137
    return new ToolEdition(this.tool, getConfiguredEdition());
8✔
138
  }
139

140
  @Override
141
  public void run() {
142

143
    runTool(this.arguments.asArray());
6✔
144
  }
1✔
145

146
  /**
147
   * @param args the command-line arguments to run the tool.
148
   * @return the {@link ProcessResult result}.
149
   * @see ToolCommandlet#runTool(ProcessMode, GenericVersionRange, String...)
150
   */
151
  public ProcessResult runTool(String... args) {
152

153
    return runTool(ProcessMode.DEFAULT, null, args);
6✔
154
  }
155

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

167
    return runTool(processMode, toolVersion, ProcessErrorHandling.THROW_CLI, args);
7✔
168
  }
169

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

182
    if (toolVersion != null) {
2!
183
      throw new UnsupportedOperationException("Not implemented yet");
×
184
    }
185
    ProcessContext pc = this.context.newProcess().errorHandling(errorHandling);
6✔
186
    install(true, pc, null);
6✔
187
    return runTool(processMode, errorHandling, pc, args);
7✔
188
  }
189

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

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

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

214
    pc.executable(Path.of(getBinaryName()));
8✔
215
  }
1✔
216

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

225
    pc.addArgs(args);
4✔
226
  }
1✔
227

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

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

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

247
    return install(true);
4✔
248
  }
249

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

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

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

277
    return true;
2✔
278
  }
279

280
  /**
281
   * Checks a version to be installed for {@link Cve}s. If at least one {@link Cve} is found, we try to find better/safer versions as alternative. If we find
282
   * something better, we will suggest this to the user and ask him to make his choice.
283
   *
284
   * @param toolEdition the {@link ToolEdition}.
285
   * @param resolvedVersion the resolved {@link #getConfiguredEdition() version}.
286
   * @param allowedVersions a {@link GenericVersionRange} that defines which versions are allowed to consider.
287
   * @param skipSuggestions {@code true} to skip suggestions, {@code false} otherwise (try to find alternative suggestions and ask the user).
288
   * @return the {@link VersionIdentifier} to install. If there were {@link Cve}s found and better versions available the user made this choice.
289
   */
290
  protected VersionIdentifier cveCheck(ToolEdition toolEdition, VersionIdentifier resolvedVersion, GenericVersionRange allowedVersions,
291
      boolean skipSuggestions) {
292

293
    ToolSecurity toolSecurity = this.context.getDefaultToolRepository().findSecurity(this.tool, toolEdition.edition());
9✔
294
    double minSeverity = IdeVariables.CVE_MIN_SEVERITY.get(context);
7✔
295
    Collection<Cve> issues = toolSecurity.findCves(resolvedVersion, minSeverity);
5✔
296
    ToolVersionChoice currentChoice = ToolVersionChoice.ofCurrent(resolvedVersion, issues);
4✔
297
    if (logCvesAndReturnTrueForNone(toolEdition, resolvedVersion, currentChoice.option(), issues)) {
8✔
298
      return resolvedVersion;
2✔
299
    }
300
    if (skipSuggestions) {
2!
301
      // currently for a transitive dependency it does not make sense to suggest alternative versions, since the choice is not stored anywhere,
302
      // and we then would ask the user again every time the tool having this dependency is started. So we only log the problem and the user needs to react
303
      // (e.g. upgrade the tool with the dependency that is causing this).
304
      return resolvedVersion;
×
305
    }
306
    double currentSeveritySum = Cve.severitySum(issues);
3✔
307
    ToolVersionChoice latest = null;
2✔
308
    ToolVersionChoice nearest = null;
2✔
309
    List<VersionIdentifier> toolVersions = getVersions();
3✔
310
    double latestSeveritySum = currentSeveritySum;
2✔
311
    double nearestSeveritySum = currentSeveritySum;
2✔
312
    for (VersionIdentifier version : toolVersions) {
10✔
313
      if (allowedVersions == null || allowedVersions.contains(version)) {
6!
314
        issues = toolSecurity.findCves(version, minSeverity);
5✔
315
        double newSeveritySum = Cve.severitySum(issues);
3✔
316
        if (newSeveritySum < latestSeveritySum) {
4✔
317
          // we found a better/safer version
318
          if (version.isGreater(resolvedVersion)) {
4!
319
            latest = ToolVersionChoice.ofLatest(version, issues);
4✔
320
            nearest = null;
2✔
321
            latestSeveritySum = newSeveritySum;
3✔
322
          } else {
323
            // latest = null;
324
            nearest = ToolVersionChoice.ofNearest(version, issues);
×
325
            nearestSeveritySum = newSeveritySum;
×
326
          }
327
        } else if (newSeveritySum < nearestSeveritySum) {
4✔
328
          if (version.isGreater(resolvedVersion)) {
4!
329
            nearest = ToolVersionChoice.ofNearest(version, issues);
×
330
          } else if (nearest == null) {
2!
331
            nearest = ToolVersionChoice.ofNearest(version, issues);
4✔
332
          }
333
          nearestSeveritySum = newSeveritySum;
2✔
334
        }
335
      }
336
    }
1✔
337
    if ((latest == null) && (nearest == null)) {
2!
338
      this.context.warning(
×
339
          "Could not find any other version resolving your CVEs.\nPlease keep attention to this tool and consider updating as soon as security fixes are available.");
340
      return resolvedVersion;
×
341
    }
342
    List<ToolVersionChoice> choices = new ArrayList<>();
4✔
343
    choices.add(currentChoice);
4✔
344
    if (nearest != null) {
2!
345
      choices.add(nearest);
4✔
346
      logCvesAndReturnTrueForNone(toolEdition, nearest.version(), nearest.option(), nearest.issues());
10✔
347
    }
348
    if (latest != null) {
2!
349
      choices.add(latest);
4✔
350
      logCvesAndReturnTrueForNone(toolEdition, latest.version(), latest.option(), latest.issues());
10✔
351
    }
352
    ToolVersionChoice[] choicesArray = choices.toArray(ToolVersionChoice[]::new);
8✔
353
    ToolVersionChoice answer = this.context.question(choicesArray, "Which version do you want to install?");
9✔
354
    return answer.version();
3✔
355
  }
356

357
  private boolean logCvesAndReturnTrueForNone(ToolEdition toolEdition, VersionIdentifier version, String option, Collection<Cve> issues) {
358
    if (issues.isEmpty()) {
3✔
359
      this.context.info("No CVEs found for {} version {} of tool {}.", option, version, toolEdition);
18✔
360
      return true;
2✔
361
    }
362
    this.context.warning("For {} version {} of tool {} we found {} CVE(s):", option, version, toolEdition, issues.size());
24✔
363
    for (Cve cve : issues) {
10✔
364
      logCve(cve);
3✔
365
    }
1✔
366
    return false;
2✔
367
  }
368

369
  private void logCve(Cve cve) {
370

371
    this.context.warning("{} with severity {} and affected versions: {} ", cve.id(), cve.severity(), cve.versions());
22✔
372
    this.context.warning("https://nvd.nist.gov/vuln/detail/" + cve.id());
6✔
373
    this.context.info("");
4✔
374
  }
1✔
375

376
  /**
377
   * @return the {@link MacOsHelper} instance.
378
   */
379
  protected MacOsHelper getMacOsHelper() {
380

381
    if (this.macOsHelper == null) {
3✔
382
      this.macOsHelper = new MacOsHelper(this.context);
7✔
383
    }
384
    return this.macOsHelper;
3✔
385
  }
386

387
  /**
388
   * @return the currently installed {@link VersionIdentifier version} of this tool or {@code null} if not installed.
389
   */
390
  public abstract VersionIdentifier getInstalledVersion();
391

392
  /**
393
   * @return {@code true} if this tool is installed, {@code false} otherwise.
394
   */
395
  public boolean isInstalled() {
396

397
    return getInstalledVersion() != null;
7✔
398
  }
399

400
  /**
401
   * @return the installed edition of this tool or {@code null} if not installed.
402
   */
403
  public abstract String getInstalledEdition();
404

405
  /**
406
   * Uninstalls the {@link #getName() tool}.
407
   */
408
  public abstract void uninstall();
409

410
  /**
411
   * @return the {@link ToolRepository}.
412
   */
413
  public ToolRepository getToolRepository() {
414

415
    return this.context.getDefaultToolRepository();
4✔
416
  }
417

418
  /**
419
   * List the available editions of this tool.
420
   */
421
  public void listEditions() {
422

423
    List<String> editions = getToolRepository().getSortedEditions(getName());
6✔
424
    for (String edition : editions) {
10✔
425
      this.context.info(edition);
4✔
426
    }
1✔
427
  }
1✔
428

429
  /**
430
   * List the available versions of this tool.
431
   */
432
  public void listVersions() {
433

434
    List<VersionIdentifier> versions = getToolRepository().getSortedVersions(getName(), getConfiguredEdition(), this);
9✔
435
    for (VersionIdentifier vi : versions) {
10✔
436
      this.context.info(vi.toString());
5✔
437
    }
1✔
438
  }
1✔
439

440
  /**
441
   * @return the {@link com.devonfw.tools.ide.tool.repository.DefaultToolRepository#getSortedVersions(String, String, ToolCommandlet) sorted versions} of this
442
   *     tool.
443
   */
444
  public List<VersionIdentifier> getVersions() {
445
    return getToolRepository().getSortedVersions(getName(), getConfiguredEdition(), this);
9✔
446
  }
447

448
  /**
449
   * Sets the tool version in the environment variable configuration file.
450
   *
451
   * @param version the version (pattern) to set.
452
   */
453
  public void setVersion(String version) {
454

455
    if ((version == null) || version.isBlank()) {
×
456
      throw new IllegalStateException("Version has to be specified!");
×
457
    }
458
    VersionIdentifier configuredVersion = VersionIdentifier.of(version);
×
459
    if (!configuredVersion.isPattern() && !configuredVersion.isValid()) {
×
460
      this.context.warning("Version {} seems to be invalid", version);
×
461
    }
462
    setVersion(configuredVersion, true);
×
463
  }
×
464

465
  /**
466
   * Sets the tool version in the environment variable configuration file.
467
   *
468
   * @param version the version to set. May also be a {@link VersionIdentifier#isPattern() version pattern}.
469
   * @param hint - {@code true} to print the installation hint, {@code false} otherwise.
470
   */
471
  public void setVersion(VersionIdentifier version, boolean hint) {
472

473
    setVersion(version, hint, null);
5✔
474
  }
1✔
475

476
  /**
477
   * Sets the tool version in the environment variable configuration file.
478
   *
479
   * @param version the version to set. May also be a {@link VersionIdentifier#isPattern() version pattern}.
480
   * @param hint - {@code true} to print the installation hint, {@code false} otherwise.
481
   * @param destination - the destination for the property to be set
482
   */
483
  public void setVersion(VersionIdentifier version, boolean hint, EnvironmentVariablesFiles destination) {
484

485
    String edition = getConfiguredEdition();
3✔
486
    ToolRepository toolRepository = getToolRepository();
3✔
487

488
    EnvironmentVariables variables = this.context.getVariables();
4✔
489
    if (destination == null) {
2✔
490
      //use default location
491
      destination = EnvironmentVariablesFiles.SETTINGS;
2✔
492
    }
493
    EnvironmentVariables settingsVariables = variables.getByType(destination.toType());
5✔
494
    String name = EnvironmentVariables.getToolVersionVariable(this.tool);
4✔
495

496
    toolRepository.resolveVersion(this.tool, edition, version, this); // verify that the version actually exists
8✔
497
    settingsVariables.set(name, version.toString(), false);
7✔
498
    settingsVariables.save();
2✔
499
    EnvironmentVariables declaringVariables = variables.findVariable(name);
4✔
500
    if ((declaringVariables != null) && (declaringVariables != settingsVariables)) {
5!
501
      this.context.warning("The variable {} is overridden in {}. Please remove the overridden declaration in order to make the change affect.", name,
13✔
502
          declaringVariables.getSource());
2✔
503
    }
504
    if (hint) {
2✔
505
      this.context.info("To install that version call the following command:");
4✔
506
      this.context.info("ide install {}", this.tool);
11✔
507
    }
508
  }
1✔
509

510
  /**
511
   * Sets the tool edition in the environment variable configuration file.
512
   *
513
   * @param edition the edition to set.
514
   */
515
  public void setEdition(String edition) {
516

517
    setEdition(edition, true);
4✔
518
  }
1✔
519

520
  /**
521
   * Sets the tool edition in the environment variable configuration file.
522
   *
523
   * @param edition the edition to set
524
   * @param hint - {@code true} to print the installation hint, {@code false} otherwise.
525
   */
526
  public void setEdition(String edition, boolean hint) {
527

528
    setEdition(edition, hint, null);
5✔
529
  }
1✔
530

531
  /**
532
   * Sets the tool edition in the environment variable configuration file.
533
   *
534
   * @param edition the edition to set
535
   * @param hint - {@code true} to print the installation hint, {@code false} otherwise.
536
   * @param destination - the destination for the property to be set
537
   */
538
  public void setEdition(String edition, boolean hint, EnvironmentVariablesFiles destination) {
539

540
    if ((edition == null) || edition.isBlank()) {
5!
541
      throw new IllegalStateException("Edition has to be specified!");
×
542
    }
543

544
    if (destination == null) {
2✔
545
      //use default location
546
      destination = EnvironmentVariablesFiles.SETTINGS;
2✔
547
    }
548

549
    if (!getToolRepository().getSortedEditions(this.tool).contains(edition)) {
8✔
550
      this.context.warning("Edition {} seems to be invalid", edition);
10✔
551
    }
552
    EnvironmentVariables variables = this.context.getVariables();
4✔
553
    EnvironmentVariables settingsVariables = variables.getByType(destination.toType());
5✔
554
    String name = EnvironmentVariables.getToolEditionVariable(this.tool);
4✔
555
    settingsVariables.set(name, edition, false);
6✔
556
    settingsVariables.save();
2✔
557

558
    this.context.info("{}={} has been set in {}", name, edition, settingsVariables.getSource());
19✔
559
    EnvironmentVariables declaringVariables = variables.findVariable(name);
4✔
560
    if ((declaringVariables != null) && (declaringVariables != settingsVariables)) {
5!
561
      this.context.warning("The variable {} is overridden in {}. Please remove the overridden declaration in order to make the change affect.", name,
13✔
562
          declaringVariables.getSource());
2✔
563
    }
564
    if (hint) {
2!
565
      this.context.info("To install that edition call the following command:");
4✔
566
      this.context.info("ide install {}", this.tool);
11✔
567
    }
568
  }
1✔
569

570
  /**
571
   * Runs the tool's help command to provide the user with usage information.
572
   */
573
  @Override
574
  public void printHelp(NlsBundle bundle) {
575

576
    super.printHelp(bundle);
3✔
577
    String toolHelpArgs = getToolHelpArguments();
3✔
578
    if (toolHelpArgs != null && getInstalledVersion() != null) {
5!
579
      ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.LOG_WARNING)
6✔
580
          .executable(Path.of(getBinaryName())).addArgs(toolHelpArgs);
13✔
581
      pc.run(ProcessMode.DEFAULT);
4✔
582
    }
583
  }
1✔
584

585
  /**
586
   * @return the tool's specific help command. Usually help, --help or -h. Return null if not applicable.
587
   */
588
  public String getToolHelpArguments() {
589

590
    return null;
×
591
  }
592

593
  /**
594
   * Creates a start script for the tool using the tool name.
595
   *
596
   * @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
597
   *     instead.
598
   * @param binary name of the binary to execute from the start script.
599
   */
600
  protected void createStartScript(Path targetDir, String binary) {
601

602
    createStartScript(targetDir, binary, false);
×
603
  }
×
604

605
  /**
606
   * Creates a start script for the tool using the tool name.
607
   *
608
   * @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
609
   *     instead.
610
   * @param binary name of the binary to execute from the start script.
611
   * @param background {@code true} to run the {@code binary} in background, {@code false} otherwise (foreground).
612
   */
613
  protected void createStartScript(Path targetDir, String binary, boolean background) {
614

615
    Path binFolder = targetDir.resolve("bin");
×
616
    if (!Files.exists(binFolder)) {
×
617
      if (this.context.getSystemInfo().isMac()) {
×
618
        MacOsHelper macOsHelper = getMacOsHelper();
×
619
        Path appDir = macOsHelper.findAppDir(targetDir);
×
620
        binFolder = macOsHelper.findLinkDir(appDir, binary);
×
621
      } else {
×
622
        binFolder = targetDir;
×
623
      }
624
      assert (Files.exists(binFolder));
×
625
    }
626
    Path bashFile = binFolder.resolve(getName());
×
627
    String bashFileContentStart = "#!/usr/bin/env bash\n\"$(dirname \"$0\")/";
×
628
    String bashFileContentEnd = "\" $@";
×
629
    if (background) {
×
630
      bashFileContentEnd += " &";
×
631
    }
632
    try {
633
      Files.writeString(bashFile, bashFileContentStart + binary + bashFileContentEnd);
×
634
    } catch (IOException e) {
×
635
      throw new RuntimeException(e);
×
636
    }
×
637
    assert (Files.exists(bashFile));
×
638
    context.getFileAccess().makeExecutable(bashFile);
×
639
  }
×
640

641
  @Override
642
  public void reset() {
643
    super.reset();
2✔
644
    this.executionDirectory = null;
3✔
645
  }
1✔
646

647
  /**
648
   * @param step the {@link Step} to get {@link Step#asSuccess() success logger} from. May be {@code null}.
649
   * @return the {@link IdeSubLogger} from {@link Step#asSuccess()} or {@link IdeContext#success()} as fallback.
650
   */
651
  protected IdeSubLogger asSuccess(Step step) {
652

653
    if (step == null) {
2!
654
      return this.context.success();
4✔
655
    } else {
656
      return step.asSuccess();
×
657
    }
658
  }
659

660

661
  /**
662
   * @param step the {@link Step} to get {@link Step#asError() error logger} from. May be {@code null}.
663
   * @return the {@link IdeSubLogger} from {@link Step#asError()} or {@link IdeContext#error()} as fallback.
664
   */
665
  protected IdeSubLogger asError(Step step) {
666

667
    if (step == null) {
×
668
      return this.context.error();
×
669
    } else {
670
      return step.asError();
×
671
    }
672
  }
673
}
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