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

devonfw / IDEasy / 10161831933

30 Jul 2024 11:54AM UTC coverage: 61.217% (+0.02%) from 61.2%
10161831933

push

github

web-flow
#352: implemented new design for tools like python and node (#495)

2009 of 3611 branches covered (55.64%)

Branch coverage included in aggregate %.

5315 of 8353 relevant lines covered (63.63%)

2.8 hits per line

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

83.14
cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.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.nio.file.StandardOpenOption;
7
import java.util.HashMap;
8
import java.util.List;
9
import java.util.Set;
10

11
import com.devonfw.tools.ide.common.Tag;
12
import com.devonfw.tools.ide.context.IdeContext;
13
import com.devonfw.tools.ide.io.FileAccess;
14
import com.devonfw.tools.ide.io.FileCopyMode;
15
import com.devonfw.tools.ide.log.IdeLogLevel;
16
import com.devonfw.tools.ide.process.ProcessContext;
17
import com.devonfw.tools.ide.process.ProcessErrorHandling;
18
import com.devonfw.tools.ide.process.ProcessMode;
19
import com.devonfw.tools.ide.repo.ToolRepository;
20
import com.devonfw.tools.ide.step.Step;
21
import com.devonfw.tools.ide.url.model.file.dependencyJson.DependencyInfo;
22
import com.devonfw.tools.ide.version.VersionIdentifier;
23

24
/**
25
 * {@link ToolCommandlet} that is installed locally into the IDE.
26
 */
27
public abstract class LocalToolCommandlet extends ToolCommandlet {
1✔
28

29
  protected HashMap<String, String> dependenciesEnvVariableNames = null;
3✔
30

31
  protected HashMap<String, String> dependenciesEnvVariablePaths = new HashMap<>();
5✔
32

33
  private final Dependency dependency = new Dependency(this.context, this.tool);
9✔
34

35
  /**
36
   * The constructor.
37
   *
38
   * @param context the {@link IdeContext}.
39
   * @param tool the {@link #getName() tool name}.
40
   * @param tags the {@link #getTags() tags} classifying the tool. Should be created via {@link Set#of(Object) Set.of} method.
41
   */
42
  public LocalToolCommandlet(IdeContext context, String tool, Set<Tag> tags) {
43

44
    super(context, tool, tags);
5✔
45
  }
1✔
46

47
  /**
48
   * @return the {@link Path} where the tool is located (installed).
49
   */
50
  public Path getToolPath() {
51

52
    return this.context.getSoftwarePath().resolve(getName());
7✔
53
  }
54

55
  /**
56
   * @return the {@link Path} where the executables of the tool can be found. Typically a "bin" folder inside {@link #getToolPath() tool path}.
57
   */
58
  public Path getToolBinPath() {
59

60
    Path toolPath = getToolPath();
3✔
61
    Path binPath = this.context.getFileAccess().findFirst(toolPath, path -> path.getFileName().toString().equals("bin"), false);
14✔
62
    if ((binPath != null) && Files.isDirectory(binPath)) {
7!
63
      return binPath;
2✔
64
    }
65
    return toolPath;
2✔
66
  }
67

68
  @Override
69
  protected boolean doInstall(boolean silent) {
70

71
    VersionIdentifier configuredVersion = getConfiguredVersion();
3✔
72
    // get installed version before installInRepo actually may install the software
73
    VersionIdentifier installedVersion = getInstalledVersion();
3✔
74
    Step step = this.context.newStep(silent, "Install " + this.tool, configuredVersion);
14✔
75
    try {
76
      // install configured version of our tool in the software repository if not already installed
77
      ToolInstallation installation = installTool(configuredVersion);
4✔
78
      // check if we already have this version installed (linked) locally in IDE_HOME/software
79
      VersionIdentifier resolvedVersion = installation.resolvedVersion();
3✔
80
      if (resolvedVersion.equals(installedVersion) && !installation.newInstallation()) {
7!
81
        IdeLogLevel level = silent ? IdeLogLevel.DEBUG : IdeLogLevel.INFO;
6✔
82
        this.context.level(level).log("Version {} of tool {} is already installed", installedVersion, getToolWithEdition());
18✔
83
        step.success();
2✔
84
        return false;
4✔
85
      }
86
      if (!isIgnoreSoftwareRepo()) {
3✔
87
        // we need to link the version or update the link.
88
        Path toolPath = getToolPath();
3✔
89
        FileAccess fileAccess = this.context.getFileAccess();
4✔
90
        if (Files.exists(toolPath)) {
5✔
91
          fileAccess.backup(toolPath);
3✔
92
        }
93
        fileAccess.mkdirs(toolPath.getParent());
4✔
94
        fileAccess.symlink(installation.linkDir(), toolPath);
5✔
95
      }
96
      this.context.getPath().setPath(this.tool, installation.binDir());
8✔
97
      postInstall();
2✔
98
      if (installedVersion == null) {
2!
99
        step.success("Successfully installed {} in version {}", this.tool, resolvedVersion);
15✔
100
      } else {
101
        step.success("Successfully installed {} in version {} replacing previous version {}", this.tool, resolvedVersion, installedVersion);
×
102
      }
103
      return true;
4✔
104
    } catch (RuntimeException e) {
×
105
      step.error(e, true);
×
106
      throw e;
×
107
    } finally {
108
      step.close();
2✔
109
    }
110

111
  }
112

113
  /**
114
   * Determines whether this tool should be installed directly in the software folder or in the software repository.
115
   *
116
   * @return {@code true} if the tool should be installed directly in the software folder, ignoring the central software repository; {@code false} if
117
   * the tool should be installed in the central software repository (default behavior).
118
   */
119
  protected boolean isIgnoreSoftwareRepo() {
120

121
    return false;
2✔
122
  }
123

124
  /**
125
   * Performs the installation of the {@link #getName() tool} managed by this {@link com.devonfw.tools.ide.commandlet.Commandlet}.
126
   *
127
   * @param version the {@link VersionIdentifier} requested to be installed. May also be a {@link VersionIdentifier#isPattern() version pattern}.
128
   * @return the {@link ToolInstallation} matching the given {@code version}.
129
   */
130
  public ToolInstallation installTool(VersionIdentifier version) {
131

132
    return installTool(version, getConfiguredEdition());
6✔
133
  }
134

135
  /**
136
   * Performs the installation of the {@link #getName() tool} managed by this {@link com.devonfw.tools.ide.commandlet.Commandlet}.
137
   *
138
   * @param version the {@link VersionIdentifier} requested to be installed. May also be a {@link VersionIdentifier#isPattern() version pattern}.
139
   * @param edition the specific edition to install.
140
   * @return the {@link ToolInstallation} matching the given {@code version}.
141
   */
142
  public ToolInstallation installTool(VersionIdentifier version, String edition) {
143

144
    return installTool(version, edition, this.context.getDefaultToolRepository());
8✔
145
  }
146

147
  /**
148
   * Performs the installation of the {@link #getName() tool} managed by this {@link com.devonfw.tools.ide.commandlet.Commandlet}.
149
   *
150
   * @param version the {@link VersionIdentifier} requested to be installed. May also be a {@link VersionIdentifier#isPattern() version pattern}.
151
   * @param edition the specific edition to install.
152
   * @param toolRepository the {@link ToolRepository} to use.
153
   * @return the {@link ToolInstallation} matching the given {@code version}.
154
   */
155
  public ToolInstallation installTool(VersionIdentifier version, String edition, ToolRepository toolRepository) {
156

157
    VersionIdentifier resolvedVersion = toolRepository.resolveVersion(this.tool, edition, version);
7✔
158

159
    if (Files.exists(this.dependency.getDependencyJsonPath(getConfiguredEdition()))) {
9✔
160
      installDependencies(resolvedVersion);
4✔
161
    } else {
162
      this.context.trace("No Dependencies file found");
4✔
163
    }
164

165
    Path toolPath;
166
    if (isIgnoreSoftwareRepo()) {
3✔
167
      toolPath = getToolPath();
4✔
168
    } else {
169
      toolPath = this.context.getSoftwareRepositoryPath().resolve(toolRepository.getId()).resolve(this.tool).resolve(edition)
12✔
170
          .resolve(resolvedVersion.toString());
3✔
171
    }
172
    Path toolVersionFile = toolPath.resolve(IdeContext.FILE_SOFTWARE_VERSION);
4✔
173
    FileAccess fileAccess = this.context.getFileAccess();
4✔
174
    if (Files.isDirectory(toolPath)) {
5✔
175
      if (Files.exists(toolVersionFile)) {
5!
176
        if (this.context.isForceMode()) {
4!
177
          fileAccess.delete(toolPath);
×
178
        } else {
179
          if (!isIgnoreSoftwareRepo() || resolvedVersion.equals(getInstalledVersion())) {
3!
180
            this.context.debug("Version {} of tool {} is already installed at {}", resolvedVersion, getToolWithEdition(this.tool, edition), toolPath);
21✔
181
            return createToolInstallation(toolPath, resolvedVersion, toolVersionFile);
6✔
182
          }
183
        }
184
      } else {
185
        this.context.warning("Deleting corrupted installation at {}", toolPath);
×
186
        fileAccess.delete(toolPath);
×
187
      }
188
    }
189
    Path target = toolRepository.download(this.tool, edition, resolvedVersion);
7✔
190
    boolean extract = isExtract();
3✔
191
    if (!extract) {
2✔
192
      this.context.trace("Extraction is disabled for '{}' hence just moving the downloaded file {}.", this.tool, target);
15✔
193
    }
194
    if (Files.exists(toolPath)) {
5!
195
      fileAccess.backup(toolPath);
×
196
    }
197
    fileAccess.mkdirs(toolPath.getParent());
4✔
198
    fileAccess.extract(target, toolPath, this::postExtract, extract);
7✔
199
    try {
200
      Files.writeString(toolVersionFile, resolvedVersion.toString(), StandardOpenOption.CREATE_NEW);
11✔
201
    } catch (IOException e) {
×
202
      throw new IllegalStateException("Failed to write version file " + toolVersionFile, e);
×
203
    }
1✔
204
    // newInstallation results in above conditions to be true if isForceMode is true or if the tool version file was
205
    // missing
206
    return createToolInstallation(toolPath, resolvedVersion, toolVersionFile, true);
7✔
207
  }
208

209
  /**
210
   * Post-extraction hook that can be overridden to add custom processing after unpacking and before moving to the final destination folder.
211
   *
212
   * @param extractedDir the {@link Path} to the folder with the unpacked tool.
213
   */
214
  protected void postExtract(Path extractedDir) {
215

216
  }
1✔
217

218
  @Override
219
  public VersionIdentifier getInstalledVersion() {
220

221
    return getInstalledVersion(this.context.getSoftwarePath().resolve(getName()));
9✔
222
  }
223

224
  /**
225
   * @param toolPath the installation {@link Path} where to find the version file.
226
   * @return the currently installed {@link VersionIdentifier version} of this tool or {@code null} if not installed.
227
   */
228
  protected VersionIdentifier getInstalledVersion(Path toolPath) {
229

230
    if (!Files.isDirectory(toolPath)) {
5✔
231
      this.context.debug("Tool {} not installed in {}", getName(), toolPath);
15✔
232
      return null;
2✔
233
    }
234
    Path toolVersionFile = toolPath.resolve(IdeContext.FILE_SOFTWARE_VERSION);
4✔
235
    if (!Files.exists(toolVersionFile)) {
5✔
236
      Path legacyToolVersionFile = toolPath.resolve(IdeContext.FILE_LEGACY_SOFTWARE_VERSION);
4✔
237
      if (Files.exists(legacyToolVersionFile)) {
5✔
238
        toolVersionFile = legacyToolVersionFile;
3✔
239
      } else {
240
        this.context.warning("Tool {} is missing version file in {}", getName(), toolVersionFile);
15✔
241
        return null;
2✔
242
      }
243
    }
244
    try {
245
      String version = Files.readString(toolVersionFile).trim();
4✔
246
      return VersionIdentifier.of(version);
3✔
247
    } catch (IOException e) {
×
248
      throw new IllegalStateException("Failed to read file " + toolVersionFile, e);
×
249
    }
250
  }
251

252
  @Override
253
  public String getInstalledEdition() {
254

255
    return getInstalledEdition(this.context.getSoftwarePath().resolve(getName()));
9✔
256
  }
257

258
  /**
259
   * @param toolPath the installation {@link Path} where to find currently installed tool. The name of the parent directory of the real path corresponding to
260
   * the passed {@link Path path} must be the name of the edition.
261
   * @return the installed edition of this tool or {@code null} if not installed.
262
   */
263
  public String getInstalledEdition(Path toolPath) {
264

265
    if (!Files.isDirectory(toolPath)) {
5!
266
      this.context.debug("Tool {} not installed in {}", getName(), toolPath);
×
267
      return null;
×
268
    }
269
    try {
270
      String edition = toolPath.toRealPath().getParent().getFileName().toString();
8✔
271
      if (!this.context.getUrls().getSortedEditions(getName()).contains(edition)) {
9!
272
        edition = getConfiguredEdition();
3✔
273
      }
274
      return edition;
2✔
275
    } catch (IOException e) {
×
276
      throw new IllegalStateException(
×
277
          "Couldn't determine the edition of " + getName() + " from the directory structure of its software path "
×
278
              + toolPath
279
              + ", assuming the name of the parent directory of the real path of the software path to be the edition "
280
              + "of the tool.", e);
281
    }
282

283
  }
284

285
  @Override
286
  public void uninstall() {
287

288
    try {
289
      Path softwarePath = getToolPath();
3✔
290
      if (Files.exists(softwarePath)) {
5✔
291
        try {
292
          this.context.getFileAccess().delete(softwarePath);
5✔
293
          this.context.success("Successfully uninstalled " + this.tool);
6✔
294
        } catch (Exception e) {
1✔
295
          this.context.error("Couldn't uninstall " + this.tool);
6✔
296
        }
2✔
297
      } else {
298
        this.context.warning("An installed version of " + this.tool + " does not exist");
6✔
299
      }
300
    } catch (Exception e) {
×
301
      this.context.error(e.getMessage());
×
302
    }
1✔
303
  }
1✔
304

305
  private ToolInstallation createToolInstallation(Path rootDir, VersionIdentifier resolvedVersion, Path toolVersionFile,
306
      boolean newInstallation) {
307

308
    Path linkDir = getMacOsHelper().findLinkDir(rootDir, getBinaryName());
7✔
309
    Path binDir = linkDir;
2✔
310
    Path binFolder = binDir.resolve(IdeContext.FOLDER_BIN);
4✔
311
    if (Files.isDirectory(binFolder)) {
5✔
312
      binDir = binFolder;
2✔
313
    }
314
    if (linkDir != rootDir) {
3✔
315
      assert (!linkDir.equals(rootDir));
5!
316
      this.context.getFileAccess().copy(toolVersionFile, linkDir, FileCopyMode.COPY_FILE_OVERRIDE);
7✔
317
    }
318
    return new ToolInstallation(rootDir, linkDir, binDir, resolvedVersion, newInstallation);
9✔
319
  }
320

321
  private ToolInstallation createToolInstallation(Path rootDir, VersionIdentifier resolvedVersion, Path toolVersionFile) {
322

323
    return createToolInstallation(rootDir, resolvedVersion, toolVersionFile, false);
7✔
324
  }
325

326
  @Override
327
  public void runTool(ProcessMode processMode, VersionIdentifier toolVersion, String... args) {
328

329
    Path binaryPath;
330
    Path toolPath = Path.of(getBinaryName());
6✔
331
    if (toolVersion == null) {
2!
332
      install(true);
4✔
333
      binaryPath = toolPath;
3✔
334
    } else {
335
      throw new UnsupportedOperationException("Not yet implemented!");
×
336
    }
337

338
    if (Files.exists(this.dependency.getDependencyJsonPath(getConfiguredEdition()))) {
9✔
339
      setDependencyRepository(getInstalledVersion());
5✔
340
    } else {
341
      this.context.trace("No Dependencies file found");
4✔
342
    }
343

344
    ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.WARNING).executable(binaryPath).addArgs(args);
10✔
345

346
    for (String key : this.dependenciesEnvVariablePaths.keySet()) {
12✔
347

348
      String dependencyPath = this.dependenciesEnvVariablePaths.get(key);
6✔
349
      pc = pc.withEnvVar(key, dependencyPath);
5✔
350
    }
1✔
351

352
    pc.run(processMode);
4✔
353
  }
1✔
354

355
  private void installDependencies(VersionIdentifier version) {
356

357
    List<DependencyInfo> dependencies = this.dependency.readJson(version, getConfiguredEdition());
7✔
358

359
    for (DependencyInfo dependencyInfo : dependencies) {
10✔
360

361
      String dependencyName = dependencyInfo.getTool();
3✔
362
      VersionIdentifier dependencyVersionToInstall = this.dependency.findDependencyVersionToInstall(dependencyInfo);
5✔
363
      if (dependencyVersionToInstall == null) {
2!
364
        continue;
×
365
      }
366

367
      ToolCommandlet dependencyTool = this.context.getCommandletManager().getToolCommandlet(dependencyName);
6✔
368
      Path dependencyRepository = getDependencySoftwareRepository(dependencyName,
5✔
369
          dependencyTool.getConfiguredEdition());
1✔
370

371
      if (!Files.exists(dependencyRepository)) {
5!
372
        installDependencyInRepo(dependencyName, dependencyTool, dependencyVersionToInstall);
×
373
      } else {
374
        Path versionExistingInRepository = this.dependency.versionExistsInRepository(dependencyRepository, dependencyInfo.getVersionRange());
7✔
375
        if (versionExistingInRepository.equals(Path.of(""))) {
7✔
376
          installDependencyInRepo(dependencyName, dependencyTool, dependencyVersionToInstall);
6✔
377
        } else {
378
          this.context.info("Necessary version of the dependency {} is already installed in repository", dependencyName);
10✔
379
        }
380
      }
381
    }
1✔
382
  }
1✔
383

384
  private void installDependencyInRepo(String dependencyName, ToolCommandlet dependencyTool, VersionIdentifier dependencyVersionToInstall) {
385

386
    this.context.info("The version {} of the dependency {} is being installed", dependencyVersionToInstall, dependencyName);
14✔
387
    LocalToolCommandlet dependencyLocal = (LocalToolCommandlet) dependencyTool;
3✔
388
    dependencyLocal.installTool(dependencyVersionToInstall);
4✔
389
    this.context.info("The version {} of the dependency {} was successfully installed", dependencyVersionToInstall, dependencyName);
14✔
390
  }
1✔
391

392
  protected void setDependencyRepository(VersionIdentifier version) {
393

394
    List<DependencyInfo> dependencies = this.dependency.readJson(version, getConfiguredEdition());
7✔
395

396
    for (DependencyInfo dependencyInfo : dependencies) {
10✔
397
      String dependencyName = dependencyInfo.getTool();
3✔
398
      VersionIdentifier dependencyVersionToInstall = this.dependency.findDependencyVersionToInstall(dependencyInfo);
5✔
399
      if (dependencyVersionToInstall == null) {
2!
400
        continue;
×
401
      }
402

403
      ToolCommandlet dependencyTool = this.context.getCommandletManager().getToolCommandlet(dependencyName);
6✔
404
      Path dependencyRepository = getDependencySoftwareRepository(dependencyName,
5✔
405
          dependencyTool.getConfiguredEdition());
1✔
406
      Path versionExistingInRepository = this.dependency.versionExistsInRepository(dependencyRepository,
6✔
407
          dependencyInfo.getVersionRange());
1✔
408
      Path dependencyPath;
409

410
      if (versionExistingInRepository.equals(Path.of(""))) {
7!
411
        dependencyPath = dependencyRepository.resolve(dependencyVersionToInstall.toString());
×
412
      } else {
413
        dependencyPath = dependencyRepository.resolve(versionExistingInRepository);
4✔
414
      }
415
      setDependencyEnvironmentPath(getDependencyEnvironmentName(dependencyName), dependencyPath);
6✔
416
    }
1✔
417
  }
1✔
418

419
  private void setDependencyEnvironmentPath(String dependencyEnvironmentName, Path dependencyPath) {
420

421
    this.dependenciesEnvVariablePaths.put(dependencyEnvironmentName, dependencyPath.toString());
7✔
422

423
  }
1✔
424

425
  /**
426
   * Method to return the list of the environment variable name for the dependencies. If necessary, it should be overridden in the specific tool
427
   *
428
   * @return the {@link HashMap} with the dependency name mapped to the env variable, for example ( java: JAVA_HOME )
429
   */
430

431
  protected HashMap<String, String> listOfDependencyEnvVariableNames() {
432

433
    return this.dependenciesEnvVariableNames;
×
434
  }
435

436
  private String getDependencyEnvironmentName(String dependencyName) {
437

438
    HashMap<String, String> envVariableName = listOfDependencyEnvVariableNames();
3✔
439

440
    if (envVariableName != null) {
2!
441
      return envVariableName.get(dependencyName);
5✔
442
    }
443

444
    return dependencyName.toUpperCase() + "_HOME";
×
445
  }
446

447
  private Path getDependencySoftwareRepository(String dependencyName, String dependencyEdition) {
448

449
    String defaultToolRepositoryId = this.context.getDefaultToolRepository().getId();
5✔
450
    Path dependencyRepository = this.context.getSoftwareRepositoryPath().resolve(defaultToolRepositoryId).resolve(dependencyName).resolve(dependencyEdition);
10✔
451

452
    return dependencyRepository;
2✔
453
  }
454

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