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

devonfw / IDEasy / 30882542201

04 Aug 2026 06:01AM UTC coverage: 72.616% (+0.03%) from 72.587%
30882542201

Pull #2246

github

web-flow
Merge 33bd8f4a3 into cd9770b9c
Pull Request #2246: #1965: improve dependent installation

5017 of 7657 branches covered (65.52%)

Branch coverage included in aggregate %.

13087 of 17274 relevant lines covered (75.76%)

3.21 hits per line

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

90.59
cli/src/main/java/com/devonfw/tools/ide/tool/PackageManagerBasedLocalToolCommandlet.java
1
package com.devonfw.tools.ide.tool;
2

3
import java.nio.file.Path;
4
import java.util.List;
5
import java.util.Set;
6

7
import org.slf4j.Logger;
8
import org.slf4j.LoggerFactory;
9

10
import com.devonfw.tools.ide.cache.CachedValue;
11
import com.devonfw.tools.ide.common.Tag;
12
import com.devonfw.tools.ide.context.IdeContext;
13
import com.devonfw.tools.ide.process.ProcessContext;
14
import com.devonfw.tools.ide.process.ProcessErrorHandling;
15
import com.devonfw.tools.ide.process.ProcessMode;
16
import com.devonfw.tools.ide.process.ProcessResult;
17
import com.devonfw.tools.ide.version.VersionIdentifier;
18

19
/**
20
 * {@link LocalToolCommandlet} for tools that have their own {@link #getToolRepository() repository} and do not follow standard installation mechanism.
21
 *
22
 * @param <P> type of the {@link ToolCommandlet} acting as {@link #getPackageManagerClass() package manager}.
23
 */
24
public abstract class PackageManagerBasedLocalToolCommandlet<P extends ToolCommandlet> extends LocalToolCommandlet {
25

26
  private static final Logger LOG = LoggerFactory.getLogger(PackageManagerBasedLocalToolCommandlet.class);
4✔
27

28
  private final CachedValue<VersionIdentifier> installedVersion;
29

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

39
    super(context, tool, tags);
5✔
40
    this.installedVersion = new CachedValue<>(this::determineInstalledVersion);
7✔
41
  }
1✔
42

43
  @Override
44
  protected boolean isIgnoreSoftwareRepo() {
45

46
    // python/pip and node/npm/yarn are messy - see https://github.com/devonfw/IDEasy/issues/352
47
    return true;
2✔
48
  }
49

50
  @Override
51
  public boolean isInstalled() {
52

53
    // Check if parent tool is installed first - if not, this tool cannot be installed
54
    LocalToolCommandlet parentTool = getParentTool();
3✔
55
    if (!parentTool.isInstalled()) {
3!
56
      return false;
2✔
57
    }
58

59
    // Check if the tool binary is found
60
    return getBinaryExecutable() != null;
×
61
  }
62

63
  protected abstract Class<P> getPackageManagerClass();
64

65
  /**
66
   * @return the package name of this tool in the underlying repository (e.g. MVN repo, NPM registry, PyPI). Typically, this is the same as the
67
   *     {@link #getName() tool name} but may be overridden in some special cases.
68
   */
69
  public String getPackageName() {
70

71
    return this.tool;
3✔
72
  }
73

74
  /**
75
   * @param request the {@link PackageManagerRequest}.
76
   * @return the {@link ProcessResult}.
77
   */
78
  public ProcessResult runPackageManager(PackageManagerRequest request) {
79
    return runPackageManager(request, false);
5✔
80
  }
81

82
  /**
83
   * @param request the {@link PackageManagerRequest}.
84
   * @param skipInstallation {@code true} if the caller can guarantee that this package manager tool is already installed, {@code false} otherwise (run
85
   *     install method again to ensure the tool is installed).
86
   * @return the {@link ProcessResult}.
87
   */
88
  public ProcessResult runPackageManager(PackageManagerRequest request, boolean skipInstallation) {
89

90
    completeRequest(request);
3✔
91
    ProcessContext pc = request.getProcessContext();
3✔
92
    ToolCommandlet pm = request.getPackageManager();
3✔
93
    if (!skipInstallation) {
2✔
94
      ToolInstallRequest installRequest = new ToolInstallRequest(request);
5✔
95
      pm.install(installRequest);
4✔
96
    }
97
    return pm.runTool(pc, request.getProcessMode(), request.getArgs());
8✔
98
  }
99

100
  protected void completeRequest(PackageManagerRequest request) {
101

102
    if (request.getProcessContext() == null) {
3✔
103
      request.setProcessContext(this.context.newProcess().errorHandling(ProcessErrorHandling.THROW_CLI));
8✔
104
    }
105
    if (request.getPackageManager() == null) {
3✔
106
      request.setPackageManager(this.context.getCommandletManager().getCommandlet(getPackageManagerClass()));
10✔
107
    }
108
    if (request.getProcessMode() == null) {
3✔
109
      request.setProcessMode(ProcessMode.DEFAULT);
4✔
110
    }
111
    if (request.getArgs().isEmpty()) {
4✔
112
      completeRequestArgs(request);
3✔
113
    }
114
  }
1✔
115

116
  /**
117
   * @param request the {@link PackageManagerRequest} with currently {@link List#isEmpty() empty} {@link PackageManagerRequest#getArgs() args}.
118
   */
119
  protected void completeRequestArgs(PackageManagerRequest request) {
120

121
    String toolWithVersion = request.getTool();
3✔
122
    VersionIdentifier version = request.getVersion();
3✔
123
    if (version != null) {
2✔
124
      toolWithVersion = appendVersion(toolWithVersion, version);
5✔
125
    }
126
    request.addArg(request.getType());
5✔
127
    String option = completeRequestOption(request);
4✔
128
    if (option != null) {
2✔
129
      request.addArg(option);
4✔
130
    }
131
    request.addArg(toolWithVersion);
4✔
132
  }
1✔
133

134
  /**
135
   * @param request the {@link PackageManagerRequest}.
136
   * @return the option to {@link PackageManagerRequest#addArg(String) add as argument} to the package manager sub-command (e.g. "-gf") or {@code null} for no
137
   *     option.
138
   */
139
  protected String completeRequestOption(PackageManagerRequest request) {
140
    return null;
2✔
141
  }
142

143
  /**
144
   * @param tool the {@link PackageManagerRequest#getTool() tool} to manage (e.g. install).
145
   * @param version the {@link PackageManagerRequest#getVersion() version} to append.
146
   * @return the combination of {@code tool} and {@code version} in the syntax of the package manager.
147
   */
148
  protected String appendVersion(String tool, VersionIdentifier version) {
149
    return tool + '@' + version;
5✔
150
  }
151

152
  @Override
153
  protected boolean isIgnoreMissingSoftwareVersionFile() {
154

155
    return true;
×
156
  }
157

158
  private VersionIdentifier determineInstalledVersion() {
159

160
    try {
161
      return computeInstalledVersion();
3✔
162
    } catch (Exception e) {
×
163
      LOG.debug("Failed to compute installed version of {}", this.tool, e);
×
164
      return null;
×
165
    }
166
  }
167

168
  /**
169
   * @return the computed value of the {@link #getInstalledVersion() installed version}.
170
   * @implNote Implementations of this method should NOT trigger any tool installation or download. If you need to call
171
   *     {@link #runPackageManager(PackageManagerRequest)}, make sure to use {@link #runPackageManager(PackageManagerRequest, boolean)} with
172
   *     {@code skipInstallation=true} to avoid inadvertently triggering installations when only checking the version.
173
   */
174
  protected abstract VersionIdentifier computeInstalledVersion();
175

176
  @Override
177
  public VersionIdentifier getInstalledVersion() {
178

179
    return this.installedVersion.get();
5✔
180
  }
181

182
  /**
183
   * Override to ignore the {@code toolPath} parameter and use the package-manager based detection of the actually installed version.
184
   *
185
   * @param toolPath the installation {@link Path} where to find the version file.
186
   * @return the installed version or null if not installed.
187
   */
188
  @Override
189
  protected VersionIdentifier getInstalledVersion(Path toolPath) {
190
    return getInstalledVersion();
3✔
191
  }
192

193
  @Override
194
  protected final void performToolInstallation(ToolInstallRequest request, Path installationPath) {
195

196
    PackageManagerRequest packageManagerRequest =
4✔
197
        new PackageManagerRequest(
198
            PackageManagerRequest.TYPE_INSTALL,
199
            getPackageName())
3✔
200
            .setToolInstallRequest(request)
2✔
201
            .setProcessContext(request.getProcessContext())
3✔
202
            .setVersion(request.getRequested().getResolvedVersion());
4✔
203
    runPackageManager(packageManagerRequest, isSkipInstallation()).failOnError();
6✔
204
    this.installedVersion.invalidate();
3✔
205
  }
1✔
206

207
  /**
208
   * @return {@code false} if the underlying {@link #getPackageManagerClass() package manager} should also be installed, {@code false} to skip that additional
209
   *     installation (e.g. to prevent infinite loop in case of cyclic dependencies between package manager based tools such as node and npm).
210
   */
211
  protected boolean isSkipInstallation() {
212
    return false;
2✔
213
  }
214

215
  /**
216
   * @return {@code true} if the tool can be uninstalled, {@code false} if not.
217
   */
218
  protected boolean canBeUninstalled() {
219
    return true;
2✔
220
  }
221

222
  @Override
223
  protected final void performUninstall(Path toolPath) {
224
    if (canBeUninstalled()) {
3✔
225
      PackageManagerRequest request = new PackageManagerRequest(PackageManagerRequest.TYPE_UNINSTALL, getPackageName());
7✔
226
      runPackageManager(request).failOnError();
4✔
227
      this.installedVersion.invalidate();
3✔
228
    } else {
1✔
229
      LOG.info("IDEasy does not support uninstalling the tool {} since this will break your installation.\n"
6✔
230
          + "If you really want to uninstall it, please uninstall its parent tool via:\n"
231
          + "ide uninstall {}", this.tool, getParentTool().getName());
2✔
232
    }
233
  }
1✔
234

235
  protected abstract LocalToolCommandlet getParentTool();
236

237
  @Override
238
  public Path getToolPath() {
239

240
    return getParentTool().getToolPath();
4✔
241
  }
242

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