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

devonfw / IDEasy / 22300285724

23 Feb 2026 09:32AM UTC coverage: 70.754% (+0.3%) from 70.474%
22300285724

Pull #1714

github

web-flow
Merge caa980897 into 379acdc9d
Pull Request #1714: #404: #1713: advanced logging

4064 of 6348 branches covered (64.02%)

Branch coverage included in aggregate %.

10640 of 14434 relevant lines covered (73.71%)

3.1 hits per line

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

2.31
cli/src/main/java/com/devonfw/tools/ide/tool/GlobalToolCommandlet.java
1
package com.devonfw.tools.ide.tool;
2

3
import java.nio.file.Files;
4
import java.nio.file.Path;
5
import java.util.Arrays;
6
import java.util.List;
7
import java.util.Set;
8

9
import org.slf4j.Logger;
10
import org.slf4j.LoggerFactory;
11
import org.slf4j.Marker;
12

13
import com.devonfw.tools.ide.cli.CliException;
14
import com.devonfw.tools.ide.common.Tag;
15
import com.devonfw.tools.ide.context.IdeContext;
16
import com.devonfw.tools.ide.io.FileAccess;
17
import com.devonfw.tools.ide.log.IdeLogLevel;
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.step.Step;
22
import com.devonfw.tools.ide.tool.repository.ToolRepository;
23
import com.devonfw.tools.ide.version.VersionIdentifier;
24

25
/**
26
 * {@link ToolCommandlet} that is installed globally.
27
 */
28
public abstract class GlobalToolCommandlet extends ToolCommandlet {
29

30
  private static final Logger LOG = LoggerFactory.getLogger(GlobalToolCommandlet.class);
4✔
31

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

41
    super(context, tool, tags);
5✔
42
  }
1✔
43

44
  /**
45
   * Performs the installation or uninstallation of the {@link #getName() tool} via a package manager.
46
   *
47
   * @param silent {@code true} if called recursively to suppress verbose logging, {@code false} otherwise.
48
   * @param commandStrings commandStrings The package manager command strings to execute.
49
   * @return {@code true} if installation or uninstallation succeeds with any of the package manager commands, {@code false} otherwise.
50
   */
51
  protected boolean runWithPackageManager(boolean silent, String... commandStrings) {
52

53
    List<PackageManagerCommand> pmCommands = Arrays.stream(commandStrings).map(PackageManagerCommand::of).toList();
×
54
    return runWithPackageManager(silent, pmCommands);
×
55
  }
56

57
  /**
58
   * Performs the installation or uninstallation of the {@link #getName() tool} via a package manager.
59
   *
60
   * @param silent {@code true} if called recursively to suppress verbose logging, {@code false} otherwise.
61
   * @param pmCommands A list of {@link PackageManagerCommand} to be used for installation or uninstallation.
62
   * @return {@code true} if installation or uninstallation succeeds with any of the package manager commands, {@code false} otherwise.
63
   */
64
  protected boolean runWithPackageManager(boolean silent, List<PackageManagerCommand> pmCommands) {
65

66
    for (PackageManagerCommand pmCommand : pmCommands) {
×
67
      NativePackageManager packageManager = pmCommand.packageManager();
×
68
      Path packageManagerPath = this.context.getPath().findBinary(Path.of(packageManager.getBinaryName()));
×
69
      if (packageManagerPath == null || !Files.exists(packageManagerPath)) {
×
70
        LOG.debug("{} is not installed", packageManager.toString());
×
71
        continue; // Skip to the next package manager command
×
72
      }
73

74
      if (executePackageManagerCommand(pmCommand, silent)) {
×
75
        return true; // Success
×
76
      }
77
    }
×
78
    return false; // None of the package manager commands were successful
×
79
  }
80

81
  private void logPackageManagerCommands(PackageManagerCommand pmCommand) {
82

83
    Marker marker = IdeLogLevel.INTERACTION.getSlf4jMarker();
×
84
    LOG.info(marker, "We need to run the following privileged command(s):");
×
85
    for (String command : pmCommand.commands()) {
×
86
      LOG.info(marker, command);
×
87
    }
×
88
    LOG.info(marker, "This will require root permissions!");
×
89
  }
×
90

91
  /**
92
   * Executes the provided package manager command.
93
   *
94
   * @param pmCommand The {@link PackageManagerCommand} containing the commands to execute.
95
   * @param silent {@code true} if called recursively to suppress verbose logging, {@code false} otherwise.
96
   * @return {@code true} if the package manager commands execute successfully, {@code false} otherwise.
97
   */
98
  private boolean executePackageManagerCommand(PackageManagerCommand pmCommand, boolean silent) {
99

100
    String bashPath = this.context.findBashRequired().toString();
×
101
    logPackageManagerCommands(pmCommand);
×
102
    for (String command : pmCommand.commands()) {
×
103
      ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.LOG_WARNING).executable(bashPath)
×
104
          .addArgs("-c", command);
×
105
      int exitCode = pc.run();
×
106
      if (exitCode != 0) {
×
107
        LOG.warn("{} command did not execute successfully", command);
×
108
        return false;
×
109
      }
110
    }
×
111

112
    if (!silent) {
×
113
      LOG.info(IdeLogLevel.SUCCESS.getSlf4jMarker(), "Successfully installed {}", this.tool);
×
114
    }
115
    return true;
×
116
  }
117

118
  @Override
119
  protected boolean isExtract() {
120

121
    // for global tools we usually download installers and do not want to extract them (e.g. installer.msi file shall
122
    // not be extracted)
123
    return false;
×
124
  }
125

126
  @Override
127
  protected ToolInstallation doInstall(ToolInstallRequest request) {
128

129
    VersionIdentifier resolvedVersion = request.getRequested().getResolvedVersion();
×
130
    if (this.context.getSystemInfo().isLinux()) {
×
131
      // on Linux global tools are typically installed via the package manager of the OS
132
      // if a global tool implements this method to return at least one PackageManagerCommand, then we install this way.
133
      List<PackageManagerCommand> commands = getInstallPackageManagerCommands();
×
134
      if (!commands.isEmpty()) {
×
135
        boolean newInstallation = runWithPackageManager(request.isSilent(), commands);
×
136
        Path rootDir = getInstallationPath(getConfiguredEdition(), resolvedVersion);
×
137
        return createToolInstallation(rootDir, resolvedVersion, newInstallation, request.getProcessContext(), request.isAdditionalInstallation());
×
138
      }
139
    }
140

141
    ToolEdition toolEdition = getToolWithConfiguredEdition();
×
142
    Path installationPath = getInstallationPath(toolEdition.edition(), resolvedVersion);
×
143
    // if force mode is enabled, go through with the installation even if the tool is already installed
144
    if ((installationPath != null) && !this.context.isForceMode()) {
×
145
      return toolAlreadyInstalled(request);
×
146
    }
147
    String edition = toolEdition.edition();
×
148
    ToolRepository toolRepository = this.context.getDefaultToolRepository();
×
149
    resolvedVersion = cveCheck(request);
×
150
    // download and install the global tool
151
    FileAccess fileAccess = this.context.getFileAccess();
×
152
    Path target = toolRepository.download(this.tool, edition, resolvedVersion, this);
×
153
    Path executable = target;
×
154
    Path tmpDir = null;
×
155
    boolean extract = isExtract();
×
156
    if (extract) {
×
157
      tmpDir = fileAccess.createTempDir(getName());
×
158
      Path downloadBinaryPath = tmpDir.resolve(target.getFileName());
×
159
      fileAccess.extract(target, downloadBinaryPath);
×
160
      executable = fileAccess.findFirst(downloadBinaryPath, Files::isExecutable, false);
×
161
    }
162
    ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.LOG_WARNING).executable(executable);
×
163
    int exitCode = pc.run(ProcessMode.BACKGROUND).getExitCode();
×
164
    if (tmpDir != null) {
×
165
      fileAccess.delete(tmpDir);
×
166
    }
167
    if (exitCode == 0) {
×
168
      LOG.info(IdeLogLevel.SUCCESS.getSlf4jMarker(), "Installation process for {} in version {} has started", this.tool, resolvedVersion);
×
169
      Step step = request.getStep();
×
170
      if (step != null) {
×
171
        step.success(true);
×
172
      }
173
    } else {
×
174
      throw new CliException("Installation process for " + this.tool + " in version " + resolvedVersion + " failed with exit code " + exitCode + "!");
×
175
    }
176
    installationPath = getInstallationPath(toolEdition.edition(), resolvedVersion);
×
177
    if (installationPath == null) {
×
178
      LOG.warn("Could not find binary {} on PATH after installation.", getBinaryName());
×
179
    }
180
    return createToolInstallation(installationPath, resolvedVersion, true, pc, false);
×
181
  }
182

183
  /**
184
   * @return the {@link List} of {@link PackageManagerCommand}s to use on Linux to install this tool. If empty, no package manager installation will be
185
   *     triggered on Linux.
186
   */
187
  protected List<PackageManagerCommand> getInstallPackageManagerCommands() {
188
    return List.of();
×
189
  }
190

191
  @Override
192
  public VersionIdentifier getInstalledVersion() {
193
    //TODO: handle "get-version <globaltool>"
194
    LOG.error("Couldn't get installed version of " + this.getName());
×
195
    return null;
×
196
  }
197

198
  @Override
199
  public String getInstalledEdition() {
200
    //TODO: handle "get-edition <globaltool>"
201
    LOG.error("Couldn't get installed edition of " + this.getName());
×
202
    return null;
×
203
  }
204

205
  @Override
206
  protected Path getInstallationPath(String edition, VersionIdentifier resolvedVersion) {
207

208
    Path toolBinary = Path.of(getBinaryName());
×
209
    Path binaryPath = this.context.getPath().findBinary(toolBinary);
×
210
    if ((binaryPath == toolBinary) || !Files.exists(binaryPath)) {
×
211
      return null;
×
212
    }
213
    Path binPath = binaryPath.getParent();
×
214
    if (binPath == null) {
×
215
      return null;
×
216
    }
217
    return this.context.getFileAccess().getBinParentPath(binPath);
×
218
  }
219

220
  @Override
221
  public void uninstall() {
222
    //TODO: handle "uninstall <globaltool>"
223
    LOG.error("Couldn't uninstall " + this.getName());
×
224
  }
×
225
}
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