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

devonfw / IDEasy / 13063267543

30 Jan 2025 11:34PM UTC coverage: 68.379% (-0.2%) from 68.557%
13063267543

push

github

web-flow
#954: improve repository support (#990)

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

2857 of 4597 branches covered (62.15%)

Branch coverage included in aggregate %.

7391 of 10390 relevant lines covered (71.14%)

3.1 hits per line

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

63.68
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.Set;
8

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

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

31
  /** @see #getName() */
32
  protected final String tool;
33

34
  private final Set<Tag> tags;
35

36
  /** The commandline arguments to pass to the tool. */
37
  public final StringProperty arguments;
38

39
  private Path executionDirectory;
40

41
  private MacOsHelper macOsHelper;
42

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

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

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

65
    add(this.arguments);
5✔
66
  }
1✔
67

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

74
    return this.tool;
3✔
75
  }
76

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

82
    return this.tool;
3✔
83
  }
84

85
  @Override
86
  public final Set<Tag> getTags() {
87

88
    return this.tags;
×
89
  }
90

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

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

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

112
    return this.context.getVariables().getToolVersion(getName());
7✔
113
  }
114

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

120
    return this.context.getVariables().getToolEdition(getName());
7✔
121
  }
122

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

129
    return getToolWithEdition(getName(), getConfiguredEdition());
6✔
130
  }
131

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

139
    if (tool.equals(edition)) {
4!
140
      return tool;
2✔
141
    }
142
    return tool + "/" + edition;
×
143
  }
144

145
  @Override
146
  public void run() {
147

148
    runTool(this.arguments.asArray());
5✔
149
  }
1✔
150

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

157
    runTool(ProcessMode.DEFAULT, null, args);
5✔
158
  }
1✔
159

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

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

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

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

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

200
    pc.executable(Path.of(getBinaryName()));
8✔
201
  }
1✔
202

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

211
    pc.addArgs(args);
4✔
212
  }
1✔
213

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

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

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

233
    return install(true);
4✔
234
  }
235

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

244
    return install(silent, EnvironmentContext.getEmpty());
5✔
245
  }
246

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

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

262
    return true;
2✔
263
  }
264

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

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

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

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

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

291
  /**
292
   * List the available editions of this tool.
293
   */
294
  public void listEditions() {
295

296
    List<String> editions = this.context.getUrls().getSortedEditions(getName());
7✔
297
    for (String edition : editions) {
10✔
298
      this.context.info(edition);
4✔
299
    }
1✔
300
  }
1✔
301

302
  /**
303
   * List the available versions of this tool.
304
   */
305
  public void listVersions() {
306

307
    List<VersionIdentifier> versions = this.context.getUrls().getSortedVersions(getName(), getConfiguredEdition());
9✔
308
    for (VersionIdentifier vi : versions) {
10✔
309
      this.context.info(vi.toString());
5✔
310
    }
1✔
311
  }
1✔
312

313
  /**
314
   * Sets the tool version in the environment variable configuration file.
315
   *
316
   * @param version the version (pattern) to set.
317
   */
318
  public void setVersion(String version) {
319

320
    if ((version == null) || version.isBlank()) {
×
321
      throw new IllegalStateException("Version has to be specified!");
×
322
    }
323
    VersionIdentifier configuredVersion = VersionIdentifier.of(version);
×
324
    if (!configuredVersion.isPattern() && !configuredVersion.isValid()) {
×
325
      this.context.warning("Version {} seems to be invalid", version);
×
326
    }
327
    setVersion(configuredVersion, true);
×
328
  }
×
329

330
  /**
331
   * Sets the tool version in the environment variable configuration file.
332
   *
333
   * @param version the version to set. May also be a {@link VersionIdentifier#isPattern() version pattern}.
334
   * @param hint - {@code true} to print the installation hint, {@code false} otherwise.
335
   */
336
  public void setVersion(VersionIdentifier version, boolean hint) {
337

338
    setVersion(version, hint, null);
5✔
339
  }
1✔
340

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

350
    String edition = getConfiguredEdition();
3✔
351
    this.context.getUrls()
7✔
352
        .getVersionFolder(this.tool, edition, version); // CliException is thrown if the version is not existing
2✔
353

354
    EnvironmentVariables variables = this.context.getVariables();
4✔
355
    if (destination == null) {
2✔
356
      //use default location
357
      destination = EnvironmentVariablesFiles.SETTINGS;
2✔
358
    }
359
    EnvironmentVariables settingsVariables = variables.getByType(destination.toType());
5✔
360
    String name = EnvironmentVariables.getToolVersionVariable(this.tool);
4✔
361
    VersionIdentifier resolvedVersion = this.context.getUrls().getVersion(this.tool, edition, version);
9✔
362
    if (version.isPattern()) {
3!
363
      this.context.debug("Resolved version {} to {} for tool {}/{}", version, resolvedVersion, this.tool, edition);
×
364
    }
365
    settingsVariables.set(name, resolvedVersion.toString(), false);
7✔
366
    settingsVariables.save();
2✔
367
    this.context.info("{}={} has been set in {}", name, version, settingsVariables.getSource());
19✔
368
    EnvironmentVariables declaringVariables = variables.findVariable(name);
4✔
369
    if ((declaringVariables != null) && (declaringVariables != settingsVariables)) {
5!
370
      this.context.warning("The variable {} is overridden in {}. Please remove the overridden declaration in order to make the change affect.", name,
13✔
371
          declaringVariables.getSource());
2✔
372
    }
373
    if (hint) {
2✔
374
      this.context.info("To install that version call the following command:");
4✔
375
      this.context.info("ide install {}", this.tool);
11✔
376
    }
377
  }
1✔
378

379
  /**
380
   * Sets the tool edition in the environment variable configuration file.
381
   *
382
   * @param edition the edition to set.
383
   */
384
  public void setEdition(String edition) {
385

386
    setEdition(edition, true);
×
387
  }
×
388

389
  /**
390
   * Sets the tool edition in the environment variable configuration file.
391
   *
392
   * @param edition the edition to set
393
   * @param hint - {@code true} to print the installation hint, {@code false} otherwise.
394
   */
395
  public void setEdition(String edition, boolean hint) {
396

397
    setEdition(edition, hint, null);
×
398
  }
×
399

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

409
    if ((edition == null) || edition.isBlank()) {
5!
410
      throw new IllegalStateException("Edition has to be specified!");
×
411
    }
412

413
    if (destination == null) {
2✔
414
      //use default location
415
      destination = EnvironmentVariablesFiles.SETTINGS;
2✔
416
    }
417
    if (!Files.exists(this.context.getUrls().getEdition(getName(), edition).getPath())) {
12!
418
      this.context.warning("Edition {} seems to be invalid", edition);
10✔
419

420
    }
421
    EnvironmentVariables variables = this.context.getVariables();
4✔
422
    EnvironmentVariables settingsVariables = variables.getByType(destination.toType());
5✔
423
    String name = EnvironmentVariables.getToolEditionVariable(this.tool);
4✔
424
    settingsVariables.set(name, edition, false);
6✔
425
    settingsVariables.save();
2✔
426

427
    this.context.info("{}={} has been set in {}", name, edition, settingsVariables.getSource());
19✔
428
    EnvironmentVariables declaringVariables = variables.findVariable(name);
4✔
429
    if ((declaringVariables != null) && (declaringVariables != settingsVariables)) {
5!
430
      this.context.warning("The variable {} is overridden in {}. Please remove the overridden declaration in order to make the change affect.", name,
13✔
431
          declaringVariables.getSource());
2✔
432
    }
433
    if (hint) {
2!
434
      this.context.info("To install that edition call the following command:");
4✔
435
      this.context.info("ide install {}", this.tool);
11✔
436
    }
437
  }
1✔
438

439
  /**
440
   * Runs the tool's help command to provide the user with usage information.
441
   */
442
  @Override
443
  public void printHelp(NlsBundle bundle) {
444

445
    super.printHelp(bundle);
3✔
446
    String toolHelpArgs = getToolHelpArguments();
3✔
447
    if (toolHelpArgs != null && getInstalledVersion() != null) {
5!
448
      ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.LOG_WARNING)
6✔
449
          .executable(Path.of(getBinaryName())).addArgs(toolHelpArgs);
13✔
450
      pc.run(ProcessMode.DEFAULT);
4✔
451
    }
452
  }
1✔
453

454
  /**
455
   * @return the tool's specific help command. Usually help, --help or -h. Return null if not applicable.
456
   */
457
  public String getToolHelpArguments() {
458

459
    return null;
×
460
  }
461

462
  /**
463
   * Creates a start script for the tool using the tool name.
464
   *
465
   * @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
466
   *     instead.
467
   * @param binary name of the binary to execute from the start script.
468
   */
469
  protected void createStartScript(Path targetDir, String binary) {
470

471
    createStartScript(targetDir, binary, false);
×
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
   * @param background {@code true} to run the {@code binary} in background, {@code false} otherwise (foreground).
481
   */
482
  protected void createStartScript(Path targetDir, String binary, boolean background) {
483

484
    Path binFolder = targetDir.resolve("bin");
×
485
    if (!Files.exists(binFolder)) {
×
486
      if (this.context.getSystemInfo().isMac()) {
×
487
        MacOsHelper macOsHelper = getMacOsHelper();
×
488
        Path appDir = macOsHelper.findAppDir(targetDir);
×
489
        binFolder = macOsHelper.findLinkDir(appDir, binary);
×
490
      } else {
×
491
        binFolder = targetDir;
×
492
      }
493
      assert (Files.exists(binFolder));
×
494
    }
495
    Path bashFile = binFolder.resolve(getName());
×
496
    String bashFileContentStart = "#!/usr/bin/env bash\n\"$(dirname \"$0\")/";
×
497
    String bashFileContentEnd = "\" $@";
×
498
    if (background) {
×
499
      bashFileContentEnd += " &";
×
500
    }
501
    try {
502
      Files.writeString(bashFile, bashFileContentStart + binary + bashFileContentEnd);
×
503
    } catch (IOException e) {
×
504
      throw new RuntimeException(e);
×
505
    }
×
506
    assert (Files.exists(bashFile));
×
507
    context.getFileAccess().makeExecutable(bashFile);
×
508
  }
×
509

510
  @Override
511
  public void reset() {
512
    super.reset();
2✔
513
    this.executionDirectory = null;
3✔
514
  }
1✔
515
}
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