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

devonfw / IDEasy / 9818906813

06 Jul 2024 11:28AM UTC coverage: 61.598% (-0.2%) from 61.755%
9818906813

Pull #453

github

web-flow
Merge b8ff8f16e into 09abbeb67
Pull Request #453: #451: mac gatekeeper

1990 of 3551 branches covered (56.04%)

Branch coverage included in aggregate %.

5278 of 8248 relevant lines covered (63.99%)

2.8 hits per line

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

75.28
cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java
1
package com.devonfw.tools.ide.tool;
2

3
import com.devonfw.tools.ide.common.Tag;
4
import com.devonfw.tools.ide.context.AbstractIdeContext;
5
import com.devonfw.tools.ide.context.IdeContext;
6
import com.devonfw.tools.ide.io.FileAccess;
7
import com.devonfw.tools.ide.io.FileCopyMode;
8
import com.devonfw.tools.ide.log.IdeLogLevel;
9
import com.devonfw.tools.ide.process.ProcessContext;
10
import com.devonfw.tools.ide.process.ProcessErrorHandling;
11
import com.devonfw.tools.ide.process.ProcessMode;
12
import com.devonfw.tools.ide.repo.ToolRepository;
13
import com.devonfw.tools.ide.step.Step;
14
import com.devonfw.tools.ide.url.model.file.dependencyJson.DependencyInfo;
15
import com.devonfw.tools.ide.version.VersionIdentifier;
16

17
import java.io.IOException;
18
import java.nio.file.Files;
19
import java.nio.file.Path;
20
import java.nio.file.StandardOpenOption;
21
import java.util.HashMap;
22
import java.util.List;
23
import java.util.Set;
24

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

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

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

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

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

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

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

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

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

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

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

72
    VersionIdentifier configuredVersion = getConfiguredVersion();
3✔
73
    // get installed version before installInRepo actually may install the software
74
    VersionIdentifier installedVersion = getInstalledVersion();
3✔
75
    Step step = this.context.newStep(silent, "Install " + this.tool, configuredVersion);
14✔
76
    try {
77
      ToolRepository repository = this.context.getDefaultToolRepository();
4✔
78
      String edition = getEdition();
3✔
79
      VersionIdentifier resolvedVersion = repository.resolveVersion(this.tool, edition , configuredVersion);
7✔
80
      if (resolvedVersion.equals(installedVersion)) {
4✔
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
      // install configured version of our tool in the software repository if not already installed
87
      ToolInstallation installation = installInRepo(resolvedVersion, edition, repository);
6✔
88
      // we need to link the version or update the link.
89
      Path toolPath = getToolPath();
3✔
90
      FileAccess fileAccess = this.context.getFileAccess();
4✔
91
      if (Files.exists(toolPath)) {
5✔
92
        fileAccess.backup(toolPath);
3✔
93
      }
94
      fileAccess.mkdirs(toolPath.getParent());
4✔
95
      fileAccess.symlink(installation.linkDir(), toolPath);
5✔
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
   * Performs the installation of the {@link #getName() tool} managed by this {@link com.devonfw.tools.ide.commandlet.Commandlet} only in the central software
115
   * repository without touching the IDE installation.
116
   *
117
   * @param version the {@link VersionIdentifier} requested to be installed. May also be a {@link VersionIdentifier#isPattern() version pattern}.
118
   * @return the {@link ToolInstallation} in the central software repository matching the given {@code version}.
119
   */
120
  public ToolInstallation installInRepo(VersionIdentifier version) {
121

122
    return installInRepo(version, getEdition());
6✔
123
  }
124

125
  /**
126
   * Performs the installation of the {@link #getName() tool} managed by this {@link com.devonfw.tools.ide.commandlet.Commandlet} only in the central software
127
   * repository without touching the IDE installation.
128
   *
129
   * @param version the {@link VersionIdentifier} requested to be installed. May also be a {@link VersionIdentifier#isPattern() version pattern}.
130
   * @param edition the specific edition to install.
131
   * @return the {@link ToolInstallation} in the central software repository matching the given {@code version}.
132
   */
133
  public ToolInstallation installInRepo(VersionIdentifier version, String edition) {
134

135
    return installInRepo(version, edition, this.context.getDefaultToolRepository());
8✔
136
  }
137

138
  /**
139
   * Performs the installation of the {@link #getName() tool} managed by this {@link com.devonfw.tools.ide.commandlet.Commandlet} only in the central software
140
   * repository without touching the IDE installation.
141
   *
142
   * @param version        the {@link VersionIdentifier} requested to be installed. May also be a {@link VersionIdentifier#isPattern() version pattern}.
143
   * @param edition the specific edition to install.
144
   * @param toolRepository the {@link ToolRepository} to use.
145
   * @return the {@link ToolInstallation} in the central software repository matching the given {@code version}.
146
   */
147
  public ToolInstallation installInRepo(VersionIdentifier version, String edition, ToolRepository toolRepository) {
148

149
    VersionIdentifier resolvedVersion = toolRepository.resolveVersion(this.tool, edition, version);
7✔
150
    Path toolPath = this.context.getSoftwareRepositoryPath().resolve(toolRepository.getId()).resolve(this.tool).resolve(edition)
12✔
151
        .resolve(resolvedVersion.toString());
3✔
152
    Path toolVersionFile = toolPath.resolve(IdeContext.FILE_SOFTWARE_VERSION);
4✔
153
    FileAccess fileAccess = this.context.getFileAccess();
4✔
154
    if (Files.isDirectory(toolPath)) {
5✔
155
      if (Files.exists(toolVersionFile)) {
5!
156
        this.context.debug("Version {} of tool {} is already installed at {}", resolvedVersion, getToolWithEdition(this.tool, edition), toolPath);
21✔
157
        return createToolInstallation(toolPath, resolvedVersion, toolVersionFile);
6✔
158
      } else {
159
        this.context.warning("Archiving corrupted installation at {}", toolPath);
×
160
        fileAccess.backup(toolPath);
×
161
      }
162
    }
163

164
    if (Files.exists(this.dependency.getDependencyJsonPath(getEdition()))) {
9✔
165
      installDependencies(resolvedVersion);
4✔
166
    } else {
167
      this.context.trace("No Dependencies file found");
4✔
168
    }
169

170
    Path target = toolRepository.download(this.tool, edition, resolvedVersion);
7✔
171
    fileAccess.mkdirs(toolPath.getParent());
4✔
172
    boolean extract = isExtract();
3✔
173
    if (!extract) {
2✔
174
      this.context.trace("Extraction is disabled for '{}' hence just moving the downloaded file {}.", this.tool, target);
15✔
175
    }
176
    fileAccess.extract(target, toolPath, this::postExtract, extract);
7✔
177
    try {
178
      Files.writeString(toolVersionFile, resolvedVersion.toString(), StandardOpenOption.CREATE_NEW);
11✔
179
    } catch (IOException e) {
×
180
      throw new IllegalStateException("Failed to write version file " + toolVersionFile, e);
×
181
    }
1✔
182
    // newInstallation results in above conditions to be true if isForceMode is true or if the tool version file was
183
    // missing
184
    return createToolInstallation(toolPath, resolvedVersion, toolVersionFile, true);
7✔
185
  }
186

187
  /**
188
   * Post-extraction hook that can be overridden to add custom processing after unpacking and before moving to the final destination folder.
189
   *
190
   * @param extractedDir the {@link Path} to the folder with the unpacked tool.
191
   */
192
  protected void postExtract(Path extractedDir) {
193

194
  }
1✔
195

196
  @Override
197
  public VersionIdentifier getInstalledVersion() {
198

199
    return getInstalledVersion(this.context.getSoftwarePath().resolve(getName()));
9✔
200
  }
201

202
  /**
203
   * @param toolPath the installation {@link Path} where to find the version file.
204
   * @return the currently installed {@link VersionIdentifier version} of this tool or {@code null} if not installed.
205
   */
206
  protected VersionIdentifier getInstalledVersion(Path toolPath) {
207

208
    if (!Files.isDirectory(toolPath)) {
5✔
209
      this.context.debug("Tool {} not installed in {}", getName(), toolPath);
15✔
210
      return null;
2✔
211
    }
212
    Path toolVersionFile = toolPath.resolve(IdeContext.FILE_SOFTWARE_VERSION);
4✔
213
    if (!Files.exists(toolVersionFile)) {
5✔
214
      Path legacyToolVersionFile = toolPath.resolve(IdeContext.FILE_LEGACY_SOFTWARE_VERSION);
4✔
215
      if (Files.exists(legacyToolVersionFile)) {
5✔
216
        toolVersionFile = legacyToolVersionFile;
3✔
217
      } else {
218
        this.context.warning("Tool {} is missing version file in {}", getName(), toolVersionFile);
15✔
219
        return null;
2✔
220
      }
221
    }
222
    try {
223
      String version = Files.readString(toolVersionFile).trim();
4✔
224
      return VersionIdentifier.of(version);
3✔
225
    } catch (IOException e) {
×
226
      throw new IllegalStateException("Failed to read file " + toolVersionFile, e);
×
227
    }
228
  }
229

230
  @Override
231
  public String getInstalledEdition() {
232

233
    return getInstalledEdition(this.context.getSoftwarePath().resolve(getName()));
9✔
234
  }
235

236
  /**
237
   * @param toolPath the installation {@link Path} where to find currently installed tool. The name of the parent
238
   *     directory of the real path corresponding to the passed {@link Path path} must be the name of the edition.
239
   * @return the installed edition of this tool or {@code null} if not installed.
240
   */
241
  public String getInstalledEdition(Path toolPath) {
242

243
    if (!Files.isDirectory(toolPath)) {
5!
244
      this.context.debug("Tool {} not installed in {}", getName(), toolPath);
×
245
      return null;
×
246
    }
247
    try {
248
      String edition = toolPath.toRealPath().getParent().getFileName().toString();
8✔
249
      if (!this.context.getUrls().getSortedEditions(getName()).contains(edition)) {
9!
250
        edition = getEdition();
3✔
251
      }
252
      return edition;
2✔
253
    } catch (IOException e) {
×
254
      throw new IllegalStateException(
×
255
          "Couldn't determine the edition of " + getName() + " from the directory structure of its software path "
×
256
              + toolPath
257
              + ", assuming the name of the parent directory of the real path of the software path to be the edition "
258
              + "of the tool.", e);
259
    }
260

261
  }
262

263
  @Override
264
  public void uninstall() {
265

266
    try {
267
      Path softwarePath = getToolPath();
3✔
268
      if (Files.exists(softwarePath)) {
5✔
269
        try {
270
          this.context.getFileAccess().delete(softwarePath);
5✔
271
          this.context.success("Successfully uninstalled " + this.tool);
6✔
272
        } catch (Exception e) {
1✔
273
          this.context.error("Couldn't uninstall " + this.tool);
6✔
274
        }
2✔
275
      } else {
276
        this.context.warning("An installed version of " + this.tool + " does not exist");
6✔
277
      }
278
    } catch (Exception e) {
×
279
      this.context.error(e.getMessage());
×
280
    }
1✔
281
  }
1✔
282

283
  private ToolInstallation createToolInstallation(Path rootDir, VersionIdentifier resolvedVersion, Path toolVersionFile,
284
                                                  boolean newInstallation) {
285

286
    Path linkDir = getMacOsHelper().findLinkDir(rootDir, getBinaryName());
7✔
287
    Path binDir = linkDir;
2✔
288
    Path binFolder = binDir.resolve(IdeContext.FOLDER_BIN);
4✔
289
    if (Files.isDirectory(binFolder)) {
5✔
290
      binDir = binFolder;
2✔
291
    }
292
    if (newInstallation && (linkDir != rootDir)) {
5✔
293
      assert (!linkDir.equals(rootDir));
5!
294
      this.context.getFileAccess().copy(toolVersionFile, linkDir, FileCopyMode.COPY_FILE_OVERRIDE);
7✔
295
      if (this.context.getSystemInfo().isMac() && !((AbstractIdeContext) this.context).isTest()) {
10!
296
        Path macApp = findMacApp(linkDir);
×
297
        if (macApp != null) {
×
298
          ProcessContext pc = this.context.newProcess();
×
299
          pc.executable("sudo").addArgs("xattr", "-d", "com.apple.quarantine", macApp);
×
300
          pc.run();
×
301
        }
302
      }
303
    }
304
    return new ToolInstallation(rootDir, linkDir, binDir, resolvedVersion, newInstallation);
9✔
305
  }
306

307
  private Path findMacApp(Path path) {
308

309
    while (path != null) {
×
310
      Path fileName = path.getFileName();
×
311
      if (fileName == null) {
×
312
        return null;
×
313
      }
314
      String filename = fileName.toString();
×
315
      if (filename.endsWith(".app")) {
×
316
        return path;
×
317
      } else if (filename.equals(IdeContext.FOLDER_CONTENTS)) {
×
318
        return path.getParent();
×
319
      }
320
      path = path.getParent();
×
321
    }
×
322
    return null;
×
323
  }
324

325
  private ToolInstallation createToolInstallation(Path rootDir, VersionIdentifier resolvedVersion, Path toolVersionFile) {
326

327
    return createToolInstallation(rootDir, resolvedVersion, toolVersionFile, false);
7✔
328
  }
329

330
  @Override
331
  public void runTool(ProcessMode processMode, VersionIdentifier toolVersion, String... args) {
332

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

342
    if (Files.exists(this.dependency.getDependencyJsonPath(getEdition()))) {
9✔
343
      setDependencyRepository(getInstalledVersion());
5✔
344
    } else {
345
      this.context.trace("No Dependencies file found");
4✔
346
    }
347

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

350
    for (String key : this.dependenciesEnvVariablePaths.keySet()) {
12✔
351

352
      String dependencyPath = this.dependenciesEnvVariablePaths.get(key);
6✔
353
      pc = pc.withEnvVar(key, dependencyPath);
5✔
354
    }
1✔
355

356
    pc.run(processMode);
4✔
357
  }
1✔
358

359
  private void installDependencies(VersionIdentifier version) {
360

361
    List<DependencyInfo> dependencies = this.dependency.readJson(version, getEdition());
7✔
362

363
    for (DependencyInfo dependencyInfo : dependencies) {
10✔
364

365
      String dependencyName = dependencyInfo.getTool();
3✔
366
      VersionIdentifier dependencyVersionToInstall = this.dependency.findDependencyVersionToInstall(dependencyInfo);
5✔
367
      if (dependencyVersionToInstall == null) {
2!
368
        continue;
×
369
      }
370

371
      ToolCommandlet dependencyTool = this.context.getCommandletManager().getToolCommandlet(dependencyName);
6✔
372
      Path dependencyRepository = getDependencySoftwareRepository(dependencyName, dependencyTool.getEdition());
6✔
373

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

387
  private void installDependencyInRepo(String dependencyName, ToolCommandlet dependencyTool, VersionIdentifier dependencyVersionToInstall) {
388

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

395
  protected void setDependencyRepository(VersionIdentifier version) {
396

397
    List<DependencyInfo> dependencies = this.dependency.readJson(version, getEdition());
7✔
398

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

406
      ToolCommandlet dependencyTool = this.context.getCommandletManager().getToolCommandlet(dependencyName);
6✔
407
      Path dependencyRepository = getDependencySoftwareRepository(dependencyName, dependencyTool.getEdition());
6✔
408
      Path versionExistingInRepository = this.dependency.versionExistsInRepository(dependencyRepository, dependencyInfo.getVersionRange());
7✔
409
      Path dependencyPath;
410

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

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

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

424
  }
1✔
425

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

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

434
    return this.dependenciesEnvVariableNames;
×
435
  }
436

437
  private String getDependencyEnvironmentName(String dependencyName) {
438

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

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

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

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

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

453
    return dependencyRepository;
2✔
454
  }
455

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