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

devonfw / IDEasy / 29562456972

17 Jul 2026 07:14AM UTC coverage: 72.486% (-0.02%) from 72.506%
29562456972

Pull #2169

github

web-flow
Merge 9728d0ab4 into de5a980f5
Pull Request #2169: #1965: improve dependent installation (WIP)

4915 of 7500 branches covered (65.53%)

Branch coverage included in aggregate %.

12705 of 16808 relevant lines covered (75.59%)

3.19 hits per line

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

91.21
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) { // See Node.postInstallOnNewInstallation
2✔
94
      ToolInstallRequest parent = request.getInstallRequest();
3✔
95
      ToolInstallRequest installRequest;
96
      if (parent == null) {
2✔
97
        installRequest = new ToolInstallRequest(true);
5✔
98
        installRequest.setProcessContext(pc.createChild());
5✔
99
      } else {
100
        installRequest = new ToolInstallRequest(parent);
5✔
101
      }
102
      pm.install(installRequest);
4✔
103
    }
104
    return pm.runTool(pc, request.getProcessMode(), request.getArgs());
8✔
105
  }
106

107
  protected void completeRequest(PackageManagerRequest request) {
108

109
    if (request.getProcessContext() == null) {
3✔
110
      request.setProcessContext(this.context.newProcess().errorHandling(ProcessErrorHandling.THROW_CLI));
8✔
111
    }
112
    if (request.getPackageManager() == null) {
3✔
113
      request.setPackageManager(this.context.getCommandletManager().getCommandlet(getPackageManagerClass()));
10✔
114
    }
115
    if (request.getProcessMode() == null) {
3✔
116
      request.setProcessMode(ProcessMode.DEFAULT);
4✔
117
    }
118
    if (request.getArgs().isEmpty()) {
4✔
119
      completeRequestArgs(request);
3✔
120
    }
121
  }
1✔
122

123
  /**
124
   * @param request the {@link PackageManagerRequest} with currently {@link List#isEmpty() empty} {@link PackageManagerRequest#getArgs() args}.
125
   */
126
  protected void completeRequestArgs(PackageManagerRequest request) {
127

128
    String toolWithVersion = request.getTool();
3✔
129
    VersionIdentifier version = request.getVersion();
3✔
130
    if (version != null) {
2✔
131
      toolWithVersion = appendVersion(toolWithVersion, version);
5✔
132
    }
133
    request.addArg(request.getType());
5✔
134
    String option = completeRequestOption(request);
4✔
135
    if (option != null) {
2✔
136
      request.addArg(option);
4✔
137
    }
138
    request.addArg(toolWithVersion);
4✔
139
  }
1✔
140

141
  /**
142
   * @param request the {@link PackageManagerRequest}.
143
   * @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
144
   *     option.
145
   */
146
  protected String completeRequestOption(PackageManagerRequest request) {
147
    return null;
2✔
148
  }
149

150
  /**
151
   * @param tool the {@link PackageManagerRequest#getTool() tool} to manage (e.g. install).
152
   * @param version the {@link PackageManagerRequest#getVersion() version} to append.
153
   * @return the combination of {@code tool} and {@code version} in the syntax of the package manager.
154
   */
155
  protected String appendVersion(String tool, VersionIdentifier version) {
156
    return tool + '@' + version;
5✔
157
  }
158

159
  @Override
160
  protected boolean isIgnoreMissingSoftwareVersionFile() {
161

162
    return true;
×
163
  }
164

165
  private VersionIdentifier determineInstalledVersion() {
166

167
    try {
168
      return computeInstalledVersion();
3✔
169
    } catch (Exception e) {
×
170
      LOG.debug("Failed to compute installed version of {}", this.tool, e);
×
171
      return null;
×
172
    }
173
  }
174

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

183
  @Override
184
  public VersionIdentifier getInstalledVersion() {
185

186
    return this.installedVersion.get();
5✔
187
  }
188

189
  /**
190
   * Override to ignore the {@code toolPath} parameter and use the package-manager based detection of the actually installed version.
191
   *
192
   * @param toolPath the installation {@link Path} where to find the version file.
193
   * @return the installed version or null if not installed.
194
   */
195
  @Override
196
  protected VersionIdentifier getInstalledVersion(Path toolPath) {
197
    return getInstalledVersion();
3✔
198
  }
199

200
  @Override
201
  protected final void performToolInstallation(ToolInstallRequest request, Path installationPath) {
202

203
    PackageManagerRequest packageManagerRequest =
4✔
204
        new PackageManagerRequest(
205
            PackageManagerRequest.TYPE_INSTALL,
206
            getPackageName())
3✔
207
            .setProcessContext(request.getProcessContext())
3✔
208
            .setVersion(request.getRequested().getResolvedVersion())
4✔
209
            .setInstallRequest(request);
2✔
210
    runPackageManager(packageManagerRequest, isSkipInstallation()).failOnError();
6✔
211
    this.installedVersion.invalidate();
3✔
212
  }
1✔
213

214
  /**
215
   * @return {@code false} if the underlying {@link #getPackageManagerClass() package manager} should also be installed, {@code false} to skip that additional
216
   *     installation (e.g. to prevent infinite loop in case of cyclic dependencies between package manager based tools such as node and npm).
217
   */
218
  protected boolean isSkipInstallation() {
219
    return false;
2✔
220
  }
221

222
  /**
223
   * @return {@code true} if the tool can be uninstalled, {@code false} if not.
224
   */
225
  protected boolean canBeUninstalled() {
226
    return true;
2✔
227
  }
228

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

242
  protected abstract LocalToolCommandlet getParentTool();
243

244
  @Override
245
  public Path getToolPath() {
246

247
    return getParentTool().getToolPath();
4✔
248
  }
249

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