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

devonfw / IDEasy / 29727615224

20 Jul 2026 08:21AM UTC coverage: 72.474% (+0.04%) from 72.436%
29727615224

Pull #2144

github

web-flow
Merge f5492ab08 into 1389cc21a
Pull Request #2144: #1976: extend tool commandlet installation logic

4937 of 7524 branches covered (65.62%)

Branch coverage included in aggregate %.

12698 of 16809 relevant lines covered (75.54%)

3.2 hits per line

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

71.88
cli/src/main/java/com/devonfw/tools/ide/tool/ToolInstallRequest.java
1
package com.devonfw.tools.ide.tool;
2

3
import java.nio.file.Path;
4

5
import org.slf4j.Logger;
6
import org.slf4j.LoggerFactory;
7

8
import com.devonfw.tools.ide.process.ProcessContext;
9
import com.devonfw.tools.ide.step.Step;
10
import com.devonfw.tools.ide.version.GenericVersionRange;
11
import com.devonfw.tools.ide.version.VersionIdentifier;
12
import com.devonfw.tools.ide.version.VersionRange;
13

14
/**
15
 * Container for data related to a tool installation.
16
 */
17
public final class ToolInstallRequest {
18

19
  private static final Logger LOG = LoggerFactory.getLogger(ToolInstallRequest.class);
4✔
20

21
  private final ToolInstallRequest parent;
22

23
  private final boolean silent;
24

25
  private final boolean direct;
26

27
  private boolean cveCheckDone;
28

29
  private ToolEditionAndVersion requested;
30

31
  private ToolEditionAndVersion installed;
32

33
  private ProcessContext processContext;
34

35
  private Path toolPath;
36

37
  private boolean extraInstallation;
38

39
  private boolean ignoreProject;
40

41
  private Step step;
42

43
  /**
44
   * The constructor.
45
   *
46
   * @param silent the {@link #isSilent() silent} flag.
47
   */
48
  public ToolInstallRequest(boolean silent) {
49
    this(null, silent, false);
5✔
50
  }
1✔
51

52
  /**
53
   * The constructor.
54
   *
55
   * @param parent the parent {@link ToolInstallRequest} (in case of a dependency).
56
   */
57
  public ToolInstallRequest(ToolInstallRequest parent) {
58
    this(parent, parent.silent, false);
6✔
59
  }
1✔
60

61
  /**
62
   * The constructor.
63
   *
64
   * @param silent the {@link #isSilent() silent} flag.
65
   * @param direct the {@link #isDirect() direct} flag.
66
   */
67
  private ToolInstallRequest(ToolInstallRequest parent, boolean silent, boolean direct) {
68
    super();
2✔
69
    this.parent = parent;
3✔
70
    this.silent = silent;
3✔
71
    this.direct = direct;
3✔
72
    if (parent != null) {
2✔
73
      this.processContext = parent.processContext;
4✔
74
      this.ignoreProject = parent.ignoreProject;
4✔
75
    }
76
  }
1✔
77

78
  /**
79
   * @return {@code true} if an installation loop was found and logged, {@code false} otherwise.
80
   */
81
  public boolean isInstallLoop() {
82

83
    if ((this.requested == null) || (this.requested.getEdition() == null)) {
7!
84
      throw new IllegalStateException(); // this method was called too early
×
85
    }
86
    StringBuilder sb = new StringBuilder();
4✔
87
    boolean loopFound = detectInstallLoopRecursively(this.requested, sb);
6✔
88
    if (loopFound) {
2!
89
      LOG.warn("Found installation loop:\n"
×
90
              + "{}\n"
91
              + "This typically indicates an internal bug in IDEasy.\n"
92
              + "Please report this bug, when you see this and include this entire warning message.\n"
93
              + "We are now trying to prevent an infinity loop and abort the recursive installation.",
94
          sb);
95
    }
96
    return loopFound;
2✔
97
  }
98

99
  private boolean detectInstallLoopRecursively(ToolEditionAndVersion toolEditionAndVersion, StringBuilder sb) {
100

101
    if (this.requested != toolEditionAndVersion) {
4✔
102
      if (this.requested.getEdition().equals(toolEditionAndVersion.getEdition())) {
7!
103
        if (this.requested.getResolvedVersion().equals(toolEditionAndVersion.getResolvedVersion())) {
×
104
          sb.append(this.requested);
×
105
          return true;
×
106
        }
107
      }
108
    }
109
    if (this.parent == null) {
3✔
110
      return false;
2✔
111
    }
112
    boolean loopFound = this.parent.detectInstallLoopRecursively(toolEditionAndVersion, sb);
6✔
113
    if (loopFound && (sb != null)) {
2!
114
      sb.append("-->");
×
115
      sb.append(this.requested);
×
116
    }
117
    return loopFound;
2✔
118
  }
119

120
  /**
121
   * @return {@code true} if this installation should be silent and log information like "tool already installed" only on debug level to avoid spam,
122
   *     {@code false} otherwise. A {@link #isDirect() direct} installation should never be silent.
123
   */
124
  public boolean isSilent() {
125

126
    return this.silent;
3✔
127
  }
128

129
  /**
130
   * @return {@code true} if the user directly triggered this tool installation (via "ide install tool"), {@code false} otherwise (indirect installation e.g. as
131
   *     dependency or via "ide create" or "ide update").
132
   */
133
  public boolean isDirect() {
134

135
    return this.direct;
×
136
  }
137

138
  /**
139
   * @return {@code true} if CVEs have already been checked, {@code false} otherwise.
140
   */
141
  public boolean isCveCheckDone() {
142

143
    return this.cveCheckDone;
3✔
144
  }
145

146
  void setCveCheckDone() {
147

148
    assert !this.cveCheckDone;
4!
149
    this.cveCheckDone = true;
3✔
150
  }
1✔
151

152
  /**
153
   * @return the {@link ToolEditionAndVersion} that is requested to be installed.
154
   */
155
  public ToolEditionAndVersion getRequested() {
156

157
    return this.requested;
3✔
158
  }
159

160
  /**
161
   * @param requested new value of {@link #getRequested()}.
162
   */
163
  public void setRequested(ToolEditionAndVersion requested) {
164
    if (this.requested != null) {
3!
165
      throw new IllegalStateException();
×
166
    }
167
    this.requested = requested;
3✔
168
  }
1✔
169

170
  /**
171
   * @return the {@link ToolEditionAndVersion} that is currently installed or {@code null} if the tool is not installed yet.
172
   */
173
  public ToolEditionAndVersion getInstalled() {
174

175
    return this.installed;
3✔
176
  }
177

178
  /**
179
   * @param installed new value of {@link #getInstalled()}.
180
   */
181
  public void setInstalled(ToolEditionAndVersion installed) {
182

183
    if (this.installed != null) {
3!
184
      throw new IllegalStateException();
×
185
    }
186
    this.installed = installed;
3✔
187
  }
1✔
188

189
  /**
190
   * @return the {@link ProcessContext} to use for executing the tool. Will also be configured during the installation (variables set, PATH extended).
191
   */
192
  public ProcessContext getProcessContext() {
193
    return this.processContext;
3✔
194
  }
195

196
  /**
197
   * @param processContext new value of {@link #getProcessContext()}.
198
   */
199
  public void setProcessContext(ProcessContext processContext) {
200

201
    if (this.processContext != null) {
3!
202
      throw new IllegalStateException();
×
203
    }
204
    this.processContext = processContext;
3✔
205
  }
1✔
206

207
  /**
208
   * @return the {@link Path} inside the project where the tool installation should be linked to.
209
   */
210
  public Path getToolPath() {
211

212
    return this.toolPath;
3✔
213
  }
214

215
  /**
216
   * @param toolPath new value of {@link #getToolPath()}.
217
   */
218
  public void setToolPath(Path toolPath) {
219

220
    if (this.toolPath != null) {
3!
221
      throw new IllegalStateException();
×
222
    }
223
    this.toolPath = toolPath;
3✔
224
  }
1✔
225

226
  /**
227
   * Called to trigger an extra installation with a custom tool path.
228
   *
229
   * @param toolPath new value of {@link #getToolPath()}.
230
   */
231
  public void setToolPathForExtraInstallation(Path toolPath) {
232

233
    setToolPath(toolPath);
3✔
234
    this.extraInstallation = true;
3✔
235
  }
1✔
236

237
  /**
238
   * @return {@code true} if this is an extra installation, {@code false} otherwise (standard installation).
239
   */
240
  public boolean isExtraInstallation() {
241

242
    return this.extraInstallation;
3✔
243
  }
244

245
  /**
246
   * @return {@code true} if the current project should be ignored during tool installation, {@code false} otherwise.
247
   */
248
  public boolean isIgnoreProject() {
249

250
    return this.ignoreProject;
3✔
251
  }
252

253
  /**
254
   * @param ignoreProject new value of {@link #isIgnoreProject() ignoreProject}.
255
   */
256
  public void setIgnoreProject(boolean ignoreProject) {
257

258
    this.ignoreProject = ignoreProject;
3✔
259
  }
1✔
260

261
  /**
262
   * @return the {@link Step} for the installation.
263
   */
264
  public Step getStep() {
265

266
    return this.step;
3✔
267
  }
268

269
  /**
270
   * @param step new value of {@link #getStep()}.
271
   */
272
  public void setStep(Step step) {
273

274
    if (this.step != null) {
×
275
      throw new IllegalStateException();
×
276
    }
277
    this.step = step;
×
278
  }
×
279

280
  /**
281
   * @return {@code true} if an additional installation is required not linked to the project's software folder (e.g. because of a transitive installation from
282
   *     a dependency that is incompatible with the project version), {@code false} otherwise.
283
   */
284
  public boolean isAdditionalInstallation() {
285
    if (this.requested != null) {
3!
286
      GenericVersionRange versionToInstall = this.requested.getVersion();
4✔
287
      return (versionToInstall instanceof VersionRange);
3✔
288
    }
289
    return false;
×
290
  }
291

292
  /**
293
   * @return {@code true} if the {@link #getRequested() requested edition and version} matches the {@link #getInstalled() installed edition and version}
294
   */
295
  public boolean isAlreadyInstalled() {
296

297
    if (this.installed == null) {
3✔
298
      return false;
2✔
299
    }
300
    VersionIdentifier installedVersion = this.installed.getResolvedVersion();
4✔
301
    if (installedVersion == null) {
2✔
302
      return false;
2✔
303
    }
304
    ToolEdition installedEdition = this.installed.getEdition();
4✔
305
    if (installedEdition == null) {
2!
306
      return false; // should actually never happen
×
307
    }
308
    if (!this.requested.getEdition().equals(installedEdition)) {
6✔
309
      return false;
2✔
310
    }
311
    return installedVersion.equals(this.requested.getResolvedVersion());
6✔
312
  }
313

314
  /**
315
   * @return a new {@link #isDirect() direct} {@link ToolInstallRequest}.
316
   */
317
  public static ToolInstallRequest ofDirect() {
318

319
    return new ToolInstallRequest(null, false, true);
7✔
320
  }
321
}
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