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

devonfw / IDEasy / 11910441375

19 Nov 2024 09:50AM UTC coverage: 67.287% (+0.005%) from 67.282%
11910441375

push

github

web-flow
#751: Moved postInstall() and changed tool installation message (#763)

2461 of 3999 branches covered (61.54%)

Branch coverage included in aggregate %.

6400 of 9170 relevant lines covered (69.79%)

3.08 hits per line

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

77.49
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 MacOsHelper macOsHelper;
40

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

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

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

63
    add(this.arguments);
5✔
64
  }
1✔
65

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

72
    return this.tool;
3✔
73
  }
74

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

80
    return this.tool;
3✔
81
  }
82

83
  @Override
84
  public final Set<Tag> getTags() {
85

86
    return this.tags;
×
87
  }
88

89
  /**
90
   * @return the {@link EnvironmentVariables#getToolVersion(String) tool version}.
91
   */
92
  public VersionIdentifier getConfiguredVersion() {
93

94
    return this.context.getVariables().getToolVersion(getName());
7✔
95
  }
96

97
  /**
98
   * @return the {@link EnvironmentVariables#getToolEdition(String) tool edition}.
99
   */
100
  public String getConfiguredEdition() {
101

102
    return this.context.getVariables().getToolEdition(getName());
7✔
103
  }
104

105
  /**
106
   * @return the {@link #getName() tool} with its {@link #getConfiguredEdition() edition}. The edition will be omitted if same as tool.
107
   * @see #getToolWithEdition(String, String)
108
   */
109
  protected final String getToolWithEdition() {
110

111
    return getToolWithEdition(getName(), getConfiguredEdition());
6✔
112
  }
113

114
  /**
115
   * @param tool the tool name.
116
   * @param edition the edition.
117
   * @return the {@link #getName() tool} with its {@link #getConfiguredEdition() edition}. The edition will be omitted if same as tool.
118
   */
119
  protected final static String getToolWithEdition(String tool, String edition) {
120

121
    if (tool.equals(edition)) {
4!
122
      return tool;
2✔
123
    }
124
    return tool + "/" + edition;
×
125
  }
126

127
  @Override
128
  public void run() {
129

130
    runTool(this.arguments.asArray());
5✔
131
  }
1✔
132

133
  /**
134
   * @param args the command-line arguments to run the tool.
135
   * @see ToolCommandlet#runTool(ProcessMode, GenericVersionRange, String...)
136
   */
137
  public void runTool(String... args) {
138

139
    runTool(ProcessMode.DEFAULT, null, args);
5✔
140
  }
1✔
141

142
  /**
143
   * Ensures the tool is installed and then runs this tool with the given arguments.
144
   *
145
   * @param processMode the {@link ProcessMode}. Should typically be {@link ProcessMode#DEFAULT} or {@link ProcessMode#BACKGROUND}.
146
   * @param toolVersion the explicit {@link GenericVersionRange version} to run. Typically {@code null} to run the
147
   *     {@link #getConfiguredVersion() configured version}. Otherwise, the specified version will be used (from the software repository, if not compatible).
148
   * @param args the command-line arguments to run the tool.
149
   */
150
  public final void runTool(ProcessMode processMode, GenericVersionRange toolVersion, String... args) {
151

152
    runTool(processMode, toolVersion, ProcessErrorHandling.THROW_CLI, args);
7✔
153
  }
1✔
154

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

165
    ProcessContext pc = this.context.newProcess().errorHandling(errorHandling);
6✔
166
    install(true, pc);
5✔
167
    configureToolBinary(pc, processMode, errorHandling);
5✔
168
    configureToolArgs(pc, processMode, errorHandling, args);
6✔
169
    return pc.run(processMode);
4✔
170
  }
171

172
  /**
173
   * @param pc the {@link ProcessContext}.
174
   * @param processMode the {@link ProcessMode}.
175
   * @param errorHandling the {@link ProcessErrorHandling}.
176
   */
177
  protected void configureToolBinary(ProcessContext pc, ProcessMode processMode, ProcessErrorHandling errorHandling) {
178

179
    pc.executable(Path.of(getBinaryName()));
8✔
180
  }
1✔
181

182
  /**
183
   * @param pc the {@link ProcessContext}.
184
   * @param processMode the {@link ProcessMode}.
185
   * @param errorHandling the {@link ProcessErrorHandling}.
186
   * @param args the command-line arguments to {@link ProcessContext#addArgs(Object...) add}.
187
   */
188
  protected void configureToolArgs(ProcessContext pc, ProcessMode processMode, ProcessErrorHandling errorHandling, String... args) {
189

190
    pc.addArgs(args);
4✔
191
  }
1✔
192

193
  /**
194
   * Creates a new {@link ProcessContext} from the given executable with the provided arguments attached.
195
   *
196
   * @param binaryPath path to the binary executable for this process
197
   * @param args the command-line arguments for this process
198
   * @return {@link ProcessContext}
199
   */
200
  protected ProcessContext createProcessContext(Path binaryPath, String... args) {
201

202
    return this.context.newProcess().errorHandling(ProcessErrorHandling.THROW_ERR).executable(binaryPath).addArgs(args);
×
203
  }
204

205
  /**
206
   * Installs or updates the managed {@link #getName() tool}.
207
   *
208
   * @return {@code true} if the tool was newly installed, {@code false} if the tool was already installed before and nothing has changed.
209
   */
210
  public boolean install() {
211

212
    return install(true);
4✔
213
  }
214

215
  /**
216
   * Performs the installation of the {@link #getName() tool} managed by this {@link com.devonfw.tools.ide.commandlet.Commandlet}.
217
   *
218
   * @param silent - {@code true} if called recursively to suppress verbose logging, {@code false} otherwise.
219
   * @return {@code true} if the tool was newly installed, {@code false} if the tool was already installed before and nothing has changed.
220
   */
221
  public boolean install(boolean silent) {
222

223
    return install(silent, EnvironmentContext.getEmpty());
5✔
224
  }
225

226
  /**
227
   * Installs or updates the managed {@link #getName() tool}.
228
   *
229
   * @param silent - {@code true} if called recursively to suppress verbose logging, {@code false} otherwise.
230
   * @param environmentContext the {@link EnvironmentContext} used to
231
   *     {@link LocalToolCommandlet#setEnvironment(EnvironmentContext, ToolInstallation, boolean) configure environment variables}.
232
   * @return {@code true} if the tool was newly installed, {@code false} if the tool was already installed before and nothing has changed.
233
   */
234
  public abstract boolean install(boolean silent, EnvironmentContext environmentContext);
235

236
  /**
237
   * @return {@code true} to extract (unpack) the downloaded binary file, {@code false} otherwise.
238
   */
239
  protected boolean isExtract() {
240

241
    return true;
2✔
242
  }
243

244
  /**
245
   * @return the {@link MacOsHelper} instance.
246
   */
247
  protected MacOsHelper getMacOsHelper() {
248

249
    if (this.macOsHelper == null) {
3✔
250
      this.macOsHelper = new MacOsHelper(this.context);
7✔
251
    }
252
    return this.macOsHelper;
3✔
253
  }
254

255
  /**
256
   * @return the currently installed {@link VersionIdentifier version} of this tool or {@code null} if not installed.
257
   */
258
  public abstract VersionIdentifier getInstalledVersion();
259

260
  /**
261
   * @return the installed edition of this tool or {@code null} if not installed.
262
   */
263
  public abstract String getInstalledEdition();
264

265
  /**
266
   * Uninstalls the {@link #getName() tool}.
267
   */
268
  public abstract void uninstall();
269

270
  /**
271
   * List the available editions of this tool.
272
   */
273
  public void listEditions() {
274

275
    List<String> editions = this.context.getUrls().getSortedEditions(getName());
7✔
276
    for (String edition : editions) {
10✔
277
      this.context.info(edition);
4✔
278
    }
1✔
279
  }
1✔
280

281
  /**
282
   * List the available versions of this tool.
283
   */
284
  public void listVersions() {
285

286
    List<VersionIdentifier> versions = this.context.getUrls().getSortedVersions(getName(), getConfiguredEdition());
9✔
287
    for (VersionIdentifier vi : versions) {
10✔
288
      this.context.info(vi.toString());
5✔
289
    }
1✔
290
  }
1✔
291

292
  /**
293
   * Sets the tool version in the environment variable configuration file.
294
   *
295
   * @param version the version (pattern) to set.
296
   */
297
  public void setVersion(String version) {
298

299
    if ((version == null) || version.isBlank()) {
×
300
      throw new IllegalStateException("Version has to be specified!");
×
301
    }
302
    VersionIdentifier configuredVersion = VersionIdentifier.of(version);
×
303
    if (!configuredVersion.isPattern() && !configuredVersion.isValid()) {
×
304
      this.context.warning("Version {} seems to be invalid", version);
×
305
    }
306
    setVersion(configuredVersion, true);
×
307
  }
×
308

309
  /**
310
   * Sets the tool version in the environment variable configuration file.
311
   *
312
   * @param version the version to set. May also be a {@link VersionIdentifier#isPattern() version pattern}.
313
   * @param hint - {@code true} to print the installation hint, {@code false} otherwise.
314
   */
315
  public void setVersion(VersionIdentifier version, boolean hint) {
316

317
    setVersion(version, hint, null);
5✔
318
  }
1✔
319

320
  /**
321
   * Sets the tool version in the environment variable configuration file.
322
   *
323
   * @param version the version to set. May also be a {@link VersionIdentifier#isPattern() version pattern}.
324
   * @param hint - {@code true} to print the installation hint, {@code false} otherwise.
325
   * @param destination - the destination for the property to be set
326
   */
327
  public void setVersion(VersionIdentifier version, boolean hint, EnvironmentVariablesFiles destination) {
328

329
    String edition = getConfiguredEdition();
3✔
330
    this.context.getUrls()
7✔
331
        .getVersionFolder(this.tool, edition, version); // CliException is thrown if the version is not existing
2✔
332

333
    EnvironmentVariables variables = this.context.getVariables();
4✔
334
    if (destination == null) {
2✔
335
      //use default location
336
      destination = EnvironmentVariablesFiles.SETTINGS;
2✔
337
    }
338
    EnvironmentVariables settingsVariables = variables.getByType(destination.toType());
5✔
339
    String name = EnvironmentVariables.getToolVersionVariable(this.tool);
4✔
340
    VersionIdentifier resolvedVersion = this.context.getUrls().getVersion(this.tool, edition, version);
9✔
341
    if (version.isPattern()) {
3!
342
      this.context.debug("Resolved version {} to {} for tool {}/{}", version, resolvedVersion, this.tool, edition);
×
343
    }
344
    settingsVariables.set(name, resolvedVersion.toString(), false);
7✔
345
    settingsVariables.save();
2✔
346
    this.context.info("{}={} has been set in {}", name, version, settingsVariables.getSource());
19✔
347
    EnvironmentVariables declaringVariables = variables.findVariable(name);
4✔
348
    if ((declaringVariables != null) && (declaringVariables != settingsVariables)) {
5!
349
      this.context.warning("The variable {} is overridden in {}. Please remove the overridden declaration in order to make the change affect.", name,
13✔
350
          declaringVariables.getSource());
2✔
351
    }
352
    if (hint) {
2✔
353
      this.context.info("To install that version call the following command:");
4✔
354
      this.context.info("ide install {}", this.tool);
11✔
355
    }
356
  }
1✔
357

358
  /**
359
   * Sets the tool edition in the environment variable configuration file.
360
   *
361
   * @param edition the edition to set.
362
   */
363
  public void setEdition(String edition) {
364

365
    setEdition(edition, true);
×
366
  }
×
367

368
  /**
369
   * Sets the tool edition in the environment variable configuration file.
370
   *
371
   * @param edition the edition to set
372
   * @param hint - {@code true} to print the installation hint, {@code false} otherwise.
373
   */
374
  public void setEdition(String edition, boolean hint) {
375

376
    setEdition(edition, hint, null);
×
377
  }
×
378

379
  /**
380
   * Sets the tool edition in the environment variable configuration file.
381
   *
382
   * @param edition the edition to set
383
   * @param hint - {@code true} to print the installation hint, {@code false} otherwise.
384
   * @param destination - the destination for the property to be set
385
   */
386
  public void setEdition(String edition, boolean hint, EnvironmentVariablesFiles destination) {
387

388
    if ((edition == null) || edition.isBlank()) {
5!
389
      throw new IllegalStateException("Edition has to be specified!");
×
390
    }
391

392
    if (destination == null) {
2✔
393
      //use default location
394
      destination = EnvironmentVariablesFiles.SETTINGS;
2✔
395
    }
396
    if (!Files.exists(this.context.getUrls().getEdition(getName(), edition).getPath())) {
12!
397
      this.context.warning("Edition {} seems to be invalid", edition);
10✔
398

399
    }
400
    EnvironmentVariables variables = this.context.getVariables();
4✔
401
    EnvironmentVariables settingsVariables = variables.getByType(destination.toType());
5✔
402
    String name = EnvironmentVariables.getToolEditionVariable(this.tool);
4✔
403
    settingsVariables.set(name, edition, false);
6✔
404
    settingsVariables.save();
2✔
405

406
    this.context.info("{}={} has been set in {}", name, edition, settingsVariables.getSource());
19✔
407
    EnvironmentVariables declaringVariables = variables.findVariable(name);
4✔
408
    if ((declaringVariables != null) && (declaringVariables != settingsVariables)) {
5!
409
      this.context.warning("The variable {} is overridden in {}. Please remove the overridden declaration in order to make the change affect.", name,
13✔
410
          declaringVariables.getSource());
2✔
411
    }
412
    if (hint) {
2!
413
      this.context.info("To install that edition call the following command:");
4✔
414
      this.context.info("ide install {}", this.tool);
11✔
415
    }
416
  }
1✔
417

418
  /**
419
   * Runs the tool's help command to provide the user with usage information.
420
   */
421
  @Override
422
  public void printHelp(NlsBundle bundle) {
423

424
    super.printHelp(bundle);
3✔
425
    String toolHelpArgs = getToolHelpArguments();
3✔
426
    if (toolHelpArgs != null && getInstalledVersion() != null) {
5!
427
      ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.LOG_WARNING)
6✔
428
          .executable(Path.of(getBinaryName())).addArgs(toolHelpArgs);
13✔
429
      pc.run(ProcessMode.DEFAULT);
4✔
430
    }
431
  }
1✔
432

433
  /**
434
   * @return the tool's specific help command. Usually help, --help or -h. Return null if not applicable.
435
   */
436
  public String getToolHelpArguments() {
437

438
    return null;
×
439
  }
440

441
  /**
442
   * Creates a start script for the tool using the tool name.
443
   *
444
   * @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
445
   *     instead.
446
   * @param binary name of the binary to execute from the start script.
447
   */
448
  protected void createStartScript(Path targetDir, String binary) {
449

450
    createStartScript(targetDir, binary, false);
×
451
  }
×
452

453
  /**
454
   * Creates a start script for the tool using the tool name.
455
   *
456
   * @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
457
   *     instead.
458
   * @param binary name of the binary to execute from the start script.
459
   * @param background {@code true} to run the {@code binary} in background, {@code false} otherwise (foreground).
460
   */
461
  protected void createStartScript(Path targetDir, String binary, boolean background) {
462

463
    Path binFolder = targetDir.resolve("bin");
4✔
464
    if (!Files.exists(binFolder)) {
5✔
465
      if (this.context.getSystemInfo().isMac()) {
5!
466
        MacOsHelper macOsHelper = getMacOsHelper();
3✔
467
        Path appDir = macOsHelper.findAppDir(targetDir);
4✔
468
        binFolder = macOsHelper.findLinkDir(appDir, binary);
5✔
469
      } else {
1✔
470
        binFolder = targetDir;
×
471
      }
472
      assert (Files.exists(binFolder));
6!
473
    }
474
    Path bashFile = binFolder.resolve(getName());
5✔
475
    String bashFileContentStart = "#!/usr/bin/env bash\n\"$(dirname \"$0\")/";
2✔
476
    String bashFileContentEnd = "\" $@";
2✔
477
    if (background) {
2✔
478
      bashFileContentEnd += " &";
3✔
479
    }
480
    try {
481
      Files.writeString(bashFile, bashFileContentStart + binary + bashFileContentEnd);
9✔
482
    } catch (IOException e) {
×
483
      throw new RuntimeException(e);
×
484
    }
1✔
485
    assert (Files.exists(bashFile));
6!
486
    context.getFileAccess().makeExecutable(bashFile);
5✔
487
  }
1✔
488

489
}
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