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

devonfw / IDEasy / 31083928084

06 Aug 2026 08:12AM UTC coverage: 72.616% (+0.006%) from 72.61%
31083928084

Pull #2258

github

web-flow
Merge 276049d85 into cd9770b9c
Pull Request #2258: #2250 uninstall commands from package manager

5038 of 7697 branches covered (65.45%)

Branch coverage included in aggregate %.

13137 of 17332 relevant lines covered (75.8%)

3.22 hits per line

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

1.76
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

12
import com.devonfw.tools.ide.cli.CliException;
13
import com.devonfw.tools.ide.common.Tag;
14
import com.devonfw.tools.ide.context.IdeContext;
15
import com.devonfw.tools.ide.io.FileAccess;
16
import com.devonfw.tools.ide.log.IdeLogLevel;
17
import com.devonfw.tools.ide.process.ProcessContext;
18
import com.devonfw.tools.ide.process.ProcessErrorHandling;
19
import com.devonfw.tools.ide.process.ProcessMode;
20
import com.devonfw.tools.ide.process.ProcessResult;
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
   * @param action the {@link NativePackageAction} tht is performed - only used for logging.
50
   * @return {@code true} if installation or uninstallation succeeds with any of the package manager commands, {@code false} otherwise.
51
   */
52
  protected boolean runWithPackageManager(boolean silent, NativePackageAction action, String... commandStrings) {
53

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

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

68
    for (PackageManagerCommand pmCommand : pmCommands) {
×
69
      NativePackageManager packageManager = pmCommand.packageManager();
×
70
      if (!isPackageManagerAvailable(packageManager)) {
×
71
        LOG.debug("{} is not installed", packageManager);
×
72
        continue; // Skip to the next package manager command
×
73
      }
74

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

82
  private void logPackageManagerCommands(PackageManagerCommand pmCommand) {
83

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

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

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

114
    if (!silent) {
×
115
      IdeLogLevel.SUCCESS.log(LOG, "Successfully {} {}", action.getAction(), this.tool);
×
116
    }
117
    return true;
×
118
  }
119

120
  @Override
121
  protected boolean isExtract() {
122

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

128
  @Override
129
  protected ToolInstallation doInstall(ToolInstallRequest request) {
130

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

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

185
  /**
186
   * @return the {@link List} of {@link NativePackage}s this tool consists of on Linux - one per supported {@link NativePackageManager}. Override this method
187
   *     instead of {@link #getInstallPackageManagerCommands} so that the commands for installation and uninstallation can both be derived from this
188
   *     declaration. If empty, no package manager installation will be triggered on Linux.
189
   */
190
  protected List<NativePackage> getNativePackages() {
191
    return List.of();
×
192
  }
193

194
  /**
195
   * @return the {@link List} of {@link PackageManagerCommand}s to use on Linux to install this tool. If empty, no package manager installation will be
196
   *     triggered on Linux.
197
   */
198
  protected List<PackageManagerCommand> getInstallPackageManagerCommands(VersionIdentifier resolvedVersion) {
199
    String version = (resolvedVersion == null) ? null : resolvedVersion.toString();
×
200
    return getNativePackages().stream().map(nativePackage -> nativePackage.install(version)).toList();
×
201
  }
202

203
  /**
204
   * @return the {@link List} of {@link PackageManagerCommand}s to use on Linux to uninstall this tool. If empty, no package manager uninstallation will be
205
   *     triggered on Linux.
206
   */
207
  protected List<PackageManagerCommand> getUninstallPackageManagerCommands() {
208
    return getNativePackages().stream().map(NativePackage::uninstall).toList();
×
209
  }
210

211
  /**
212
   * @param packageManager the {@link NativePackageManager} to check.
213
   * @return {@code true} if the given {@link NativePackageManager} is available on the current system, {@code false} otherwise.
214
   */
215
  protected boolean isPackageManagerAvailable(NativePackageManager packageManager) {
216
    Path binary = Path.of(packageManager.getBinaryName());
×
217
    Path binaryPath = this.context.getPath().findBinary(binary);
×
218
    return (binaryPath != binary) && Files.exists(binaryPath);
×
219
  }
220

221
  /**
222
   * @param nativePackage the {@link NativePackage} to query.
223
   * @return the raw version reported by the {@link NativePackageManager} or {@code null} if the package is not installed.
224
   */
225
  protected String queryNativePackageVersion(NativePackage nativePackage) {
226
    List<String> command = nativePackage.getVersionQueryCommand();
×
227
    String[] args = command.subList(1, command.size()).toArray(String[]::new);
×
228
    ProcessResult result = this.context.newProcess().errorHandling(ProcessErrorHandling.NONE).executable(command.getFirst()).addArgs(args)
×
229
        .run(ProcessMode.DEFAULT_CAPTURE);
×
230
    if (!result.isSuccessful()) {
×
231
      return null;
×
232
    }
233
    return nativePackage.getPackageManager().parseVersionQueryOutput(result.getSingleOutput(IdeLogLevel.DEBUG));
×
234
  }
235

236
  /**
237
   * @return the {@link VersionIdentifier} of this tool as reported by the OS native package manager it was installed with or {@code null} if this tool is not
238
   *     installed via any of its {@link #getNativePackages() native packages}.
239
   */
240
  protected VersionIdentifier getNativePackageVersion() {
241
    for (NativePackage nativePackage : getNativePackages()) {
×
242
      if (!isPackageManagerAvailable(nativePackage.getPackageManager())) {
×
243
        continue;
×
244
      }
245
      String version = queryNativePackageVersion(nativePackage);
×
246
      if ((version != null) && !version.isBlank()) {
×
247
        return VersionIdentifier.of(version.trim());
×
248
      }
249
    }
×
250
    return null;
×
251
  }
252

253
  @Override
254
  public VersionIdentifier getInstalledVersion() {
255
    if (this.context.getSystemInfo().isLinux()) {
×
256
      return getNativePackageVersion();
×
257
    }
258
    //TODO: handle "get-version <globaltool>"
259
    return null;
×
260
  }
261

262
  @Override
263
  public String getInstalledEdition() {
264
    //TODO: handle "get-edition <globaltool>"
265
    return null;
×
266
  }
267

268
  @Override
269
  protected Path getInstallationPath(String edition, VersionIdentifier resolvedVersion) {
270

271
    Path toolBinary = Path.of(getBinaryName());
×
272
    Path binaryPath = this.context.getPath().findBinary(toolBinary);
×
273
    if ((binaryPath == toolBinary) || !Files.exists(binaryPath)) {
×
274
      return null;
×
275
    }
276
    Path binPath = binaryPath.getParent();
×
277
    if (binPath == null) {
×
278
      return null;
×
279
    }
280
    return this.context.getFileAccess().getBinParentPath(binPath);
×
281
  }
282

283
  @Override
284
  public void uninstall() {
285
    if (this.context.getSystemInfo().isLinux()) {
×
286
      runWithPackageManager(false, getUninstallPackageManagerCommands(), NativePackageAction.UNINSTALL);
×
287
    } else {
288
      LOG.error("Couldn't uninstall {} on this OS. Please uninstall manually.", this.getName());
×
289
    }
290
  }
×
291
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc