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

devonfw / IDEasy / 13210157275

08 Feb 2025 12:10AM UTC coverage: 68.25% (-0.1%) from 68.379%
13210157275

Pull #1021

github

web-flow
Merge 93d542ee7 into 9c2006bd8
Pull Request #1021: #786: support ide upgrade to automatically update to the latest version of IDEasy

2910 of 4683 branches covered (62.14%)

Branch coverage included in aggregate %.

7563 of 10662 relevant lines covered (70.93%)

3.09 hits per line

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

64.04
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;
×
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;
×
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 args the command-line arguments to run the tool.
182
   */
183
  public ProcessResult runTool(ProcessMode processMode, GenericVersionRange toolVersion, ProcessErrorHandling errorHandling, String... args) {
184

185
    ProcessContext pc = this.context.newProcess().errorHandling(errorHandling);
6✔
186
    install(true, pc);
5✔
187
    if (this.executionDirectory != null) {
3!
188
      pc.directory(this.executionDirectory);
×
189
    }
190
    configureToolBinary(pc, processMode, errorHandling);
5✔
191
    configureToolArgs(pc, processMode, errorHandling, args);
6✔
192
    return pc.run(processMode);
4✔
193
  }
194

195
  /**
196
   * @param pc the {@link ProcessContext}.
197
   * @param processMode the {@link ProcessMode}.
198
   * @param errorHandling the {@link ProcessErrorHandling}.
199
   */
200
  protected void configureToolBinary(ProcessContext pc, ProcessMode processMode, ProcessErrorHandling errorHandling) {
201

202
    pc.executable(Path.of(getBinaryName()));
8✔
203
  }
1✔
204

205
  /**
206
   * @param pc the {@link ProcessContext}.
207
   * @param processMode the {@link ProcessMode}.
208
   * @param errorHandling the {@link ProcessErrorHandling}.
209
   * @param args the command-line arguments to {@link ProcessContext#addArgs(Object...) add}.
210
   */
211
  protected void configureToolArgs(ProcessContext pc, ProcessMode processMode, ProcessErrorHandling errorHandling, String... args) {
212

213
    pc.addArgs(args);
4✔
214
  }
1✔
215

216
  /**
217
   * Creates a new {@link ProcessContext} from the given executable with the provided arguments attached.
218
   *
219
   * @param binaryPath path to the binary executable for this process
220
   * @param args the command-line arguments for this process
221
   * @return {@link ProcessContext}
222
   */
223
  protected ProcessContext createProcessContext(Path binaryPath, String... args) {
224

225
    return this.context.newProcess().errorHandling(ProcessErrorHandling.THROW_ERR).executable(binaryPath).addArgs(args);
×
226
  }
227

228
  /**
229
   * Installs or updates the managed {@link #getName() tool}.
230
   *
231
   * @return {@code true} if the tool was newly installed, {@code false} if the tool was already installed before and nothing has changed.
232
   */
233
  public boolean install() {
234

235
    return install(true);
4✔
236
  }
237

238
  /**
239
   * Performs the installation of the {@link #getName() tool} managed by this {@link com.devonfw.tools.ide.commandlet.Commandlet}.
240
   *
241
   * @param silent - {@code true} if called recursively to suppress verbose logging, {@code false} otherwise.
242
   * @return {@code true} if the tool was newly installed, {@code false} if the tool was already installed before and nothing has changed.
243
   */
244
  public boolean install(boolean silent) {
245

246
    return install(silent, EnvironmentContext.getEmpty());
5✔
247
  }
248

249
  /**
250
   * Installs or updates the managed {@link #getName() tool}.
251
   *
252
   * @param silent - {@code true} if called recursively to suppress verbose logging, {@code false} otherwise.
253
   * @param environmentContext the {@link EnvironmentContext} used to
254
   *     {@link LocalToolCommandlet#setEnvironment(EnvironmentContext, ToolInstallation, boolean) configure environment variables}.
255
   * @return {@code true} if the tool was newly installed, {@code false} if the tool was already installed before and nothing has changed.
256
   */
257
  public abstract boolean install(boolean silent, EnvironmentContext environmentContext);
258

259
  /**
260
   * @return {@code true} to extract (unpack) the downloaded binary file, {@code false} otherwise.
261
   */
262
  protected boolean isExtract() {
263

264
    return true;
2✔
265
  }
266

267
  /**
268
   * @return the {@link MacOsHelper} instance.
269
   */
270
  protected MacOsHelper getMacOsHelper() {
271

272
    if (this.macOsHelper == null) {
3✔
273
      this.macOsHelper = new MacOsHelper(this.context);
7✔
274
    }
275
    return this.macOsHelper;
3✔
276
  }
277

278
  /**
279
   * @return the currently installed {@link VersionIdentifier version} of this tool or {@code null} if not installed.
280
   */
281
  public abstract VersionIdentifier getInstalledVersion();
282

283
  /**
284
   * @return the installed edition of this tool or {@code null} if not installed.
285
   */
286
  public abstract String getInstalledEdition();
287

288
  /**
289
   * Uninstalls the {@link #getName() tool}.
290
   */
291
  public abstract void uninstall();
292

293
  /**
294
   * @return the {@link ToolRepository}.
295
   */
296
  public ToolRepository getToolRepository() {
297

298
    return this.context.getDefaultToolRepository();
4✔
299
  }
300

301
  /**
302
   * List the available editions of this tool.
303
   */
304
  public void listEditions() {
305

306
    List<String> editions = getToolRepository().getSortedEditions(getName());
6✔
307
    for (String edition : editions) {
10✔
308
      this.context.info(edition);
4✔
309
    }
1✔
310
  }
1✔
311

312
  /**
313
   * List the available versions of this tool.
314
   */
315
  public void listVersions() {
316

317
    List<VersionIdentifier> versions = getToolRepository().getSortedVersions(getName(), getConfiguredEdition(), this);
9✔
318
    for (VersionIdentifier vi : versions) {
10✔
319
      this.context.info(vi.toString());
5✔
320
    }
1✔
321
  }
1✔
322

323
  /**
324
   * Sets the tool version in the environment variable configuration file.
325
   *
326
   * @param version the version (pattern) to set.
327
   */
328
  public void setVersion(String version) {
329

330
    if ((version == null) || version.isBlank()) {
×
331
      throw new IllegalStateException("Version has to be specified!");
×
332
    }
333
    VersionIdentifier configuredVersion = VersionIdentifier.of(version);
×
334
    if (!configuredVersion.isPattern() && !configuredVersion.isValid()) {
×
335
      this.context.warning("Version {} seems to be invalid", version);
×
336
    }
337
    setVersion(configuredVersion, true);
×
338
  }
×
339

340
  /**
341
   * Sets the tool version in the environment variable configuration file.
342
   *
343
   * @param version the version to set. May also be a {@link VersionIdentifier#isPattern() version pattern}.
344
   * @param hint - {@code true} to print the installation hint, {@code false} otherwise.
345
   */
346
  public void setVersion(VersionIdentifier version, boolean hint) {
347

348
    setVersion(version, hint, null);
5✔
349
  }
1✔
350

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

360
    String edition = getConfiguredEdition();
3✔
361
    ToolRepository toolRepository = getToolRepository();
3✔
362
    VersionIdentifier versionIdentifier = toolRepository.resolveVersion(this.tool, edition, version, this);
8✔
363
    Objects.requireNonNull(versionIdentifier);
3✔
364

365
    EnvironmentVariables variables = this.context.getVariables();
4✔
366
    if (destination == null) {
2✔
367
      //use default location
368
      destination = EnvironmentVariablesFiles.SETTINGS;
2✔
369
    }
370
    EnvironmentVariables settingsVariables = variables.getByType(destination.toType());
5✔
371
    String name = EnvironmentVariables.getToolVersionVariable(this.tool);
4✔
372

373
    VersionIdentifier resolvedVersion = toolRepository.resolveVersion(this.tool, edition, version, this);
8✔
374
    if (version.isPattern()) {
3!
375
      this.context.debug("Resolved version {} to {} for tool {}/{}", version, resolvedVersion, this.tool, edition);
×
376
    }
377
    settingsVariables.set(name, resolvedVersion.toString(), false);
7✔
378
    settingsVariables.save();
2✔
379
    this.context.info("{}={} has been set in {}", name, version, settingsVariables.getSource());
19✔
380
    EnvironmentVariables declaringVariables = variables.findVariable(name);
4✔
381
    if ((declaringVariables != null) && (declaringVariables != settingsVariables)) {
5!
382
      this.context.warning("The variable {} is overridden in {}. Please remove the overridden declaration in order to make the change affect.", name,
13✔
383
          declaringVariables.getSource());
2✔
384
    }
385
    if (hint) {
2✔
386
      this.context.info("To install that version call the following command:");
4✔
387
      this.context.info("ide install {}", this.tool);
11✔
388
    }
389
  }
1✔
390

391
  /**
392
   * Sets the tool edition in the environment variable configuration file.
393
   *
394
   * @param edition the edition to set.
395
   */
396
  public void setEdition(String edition) {
397

398
    setEdition(edition, true);
×
399
  }
×
400

401
  /**
402
   * Sets the tool edition in the environment variable configuration file.
403
   *
404
   * @param edition the edition to set
405
   * @param hint - {@code true} to print the installation hint, {@code false} otherwise.
406
   */
407
  public void setEdition(String edition, boolean hint) {
408

409
    setEdition(edition, hint, null);
×
410
  }
×
411

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

421
    if ((edition == null) || edition.isBlank()) {
5!
422
      throw new IllegalStateException("Edition has to be specified!");
×
423
    }
424

425
    if (destination == null) {
2✔
426
      //use default location
427
      destination = EnvironmentVariablesFiles.SETTINGS;
2✔
428
    }
429

430
    if (!getToolRepository().getSortedEditions(this.tool).contains(edition)) {
8!
431
      this.context.warning("Edition {} seems to be invalid", edition);
10✔
432
    }
433
    EnvironmentVariables variables = this.context.getVariables();
4✔
434
    EnvironmentVariables settingsVariables = variables.getByType(destination.toType());
5✔
435
    String name = EnvironmentVariables.getToolEditionVariable(this.tool);
4✔
436
    settingsVariables.set(name, edition, false);
6✔
437
    settingsVariables.save();
2✔
438

439
    this.context.info("{}={} has been set in {}", name, edition, settingsVariables.getSource());
19✔
440
    EnvironmentVariables declaringVariables = variables.findVariable(name);
4✔
441
    if ((declaringVariables != null) && (declaringVariables != settingsVariables)) {
5!
442
      this.context.warning("The variable {} is overridden in {}. Please remove the overridden declaration in order to make the change affect.", name,
13✔
443
          declaringVariables.getSource());
2✔
444
    }
445
    if (hint) {
2!
446
      this.context.info("To install that edition call the following command:");
4✔
447
      this.context.info("ide install {}", this.tool);
11✔
448
    }
449
  }
1✔
450

451
  /**
452
   * Runs the tool's help command to provide the user with usage information.
453
   */
454
  @Override
455
  public void printHelp(NlsBundle bundle) {
456

457
    super.printHelp(bundle);
3✔
458
    String toolHelpArgs = getToolHelpArguments();
3✔
459
    if (toolHelpArgs != null && getInstalledVersion() != null) {
5!
460
      ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.LOG_WARNING)
6✔
461
          .executable(Path.of(getBinaryName())).addArgs(toolHelpArgs);
13✔
462
      pc.run(ProcessMode.DEFAULT);
4✔
463
    }
464
  }
1✔
465

466
  /**
467
   * @return the tool's specific help command. Usually help, --help or -h. Return null if not applicable.
468
   */
469
  public String getToolHelpArguments() {
470

471
    return null;
×
472
  }
473

474
  /**
475
   * Creates a start script for the tool using the tool name.
476
   *
477
   * @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
478
   *     instead.
479
   * @param binary name of the binary to execute from the start script.
480
   */
481
  protected void createStartScript(Path targetDir, String binary) {
482

483
    createStartScript(targetDir, binary, false);
×
484
  }
×
485

486
  /**
487
   * Creates a start script for the tool using the tool name.
488
   *
489
   * @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
490
   *     instead.
491
   * @param binary name of the binary to execute from the start script.
492
   * @param background {@code true} to run the {@code binary} in background, {@code false} otherwise (foreground).
493
   */
494
  protected void createStartScript(Path targetDir, String binary, boolean background) {
495

496
    Path binFolder = targetDir.resolve("bin");
×
497
    if (!Files.exists(binFolder)) {
×
498
      if (this.context.getSystemInfo().isMac()) {
×
499
        MacOsHelper macOsHelper = getMacOsHelper();
×
500
        Path appDir = macOsHelper.findAppDir(targetDir);
×
501
        binFolder = macOsHelper.findLinkDir(appDir, binary);
×
502
      } else {
×
503
        binFolder = targetDir;
×
504
      }
505
      assert (Files.exists(binFolder));
×
506
    }
507
    Path bashFile = binFolder.resolve(getName());
×
508
    String bashFileContentStart = "#!/usr/bin/env bash\n\"$(dirname \"$0\")/";
×
509
    String bashFileContentEnd = "\" $@";
×
510
    if (background) {
×
511
      bashFileContentEnd += " &";
×
512
    }
513
    try {
514
      Files.writeString(bashFile, bashFileContentStart + binary + bashFileContentEnd);
×
515
    } catch (IOException e) {
×
516
      throw new RuntimeException(e);
×
517
    }
×
518
    assert (Files.exists(bashFile));
×
519
    context.getFileAccess().makeExecutable(bashFile);
×
520
  }
×
521

522
  @Override
523
  public void reset() {
524
    super.reset();
2✔
525
    this.executionDirectory = null;
3✔
526
  }
1✔
527
}
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