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

devonfw / IDEasy / 30931531291

04 Aug 2026 04:56PM UTC coverage: 72.693% (+0.1%) from 72.587%
30931531291

Pull #2073

github

web-flow
Merge 3b254b8be into cd9770b9c
Pull Request #2073: #1676 import extra sdks automatically into ide

5038 of 7681 branches covered (65.59%)

Branch coverage included in aggregate %.

13133 of 17316 relevant lines covered (75.84%)

3.22 hits per line

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

89.39
cli/src/main/java/com/devonfw/tools/ide/tool/ide/IdeToolCommandlet.java
1
package com.devonfw.tools.ide.tool.ide;
2

3
import java.nio.file.Files;
4
import java.nio.file.Path;
5
import java.util.HashMap;
6
import java.util.HashSet;
7
import java.util.List;
8
import java.util.Locale;
9
import java.util.Map;
10
import java.util.Set;
11

12
import org.slf4j.Logger;
13
import org.slf4j.LoggerFactory;
14
import org.w3c.dom.Document;
15

16
import com.devonfw.tools.ide.common.Tag;
17
import com.devonfw.tools.ide.context.IdeContext;
18
import com.devonfw.tools.ide.environment.AbstractEnvironmentVariables;
19
import com.devonfw.tools.ide.environment.ExtensibleEnvironmentVariables;
20
import com.devonfw.tools.ide.io.FileAccess;
21
import com.devonfw.tools.ide.log.IdeLogLevel;
22
import com.devonfw.tools.ide.merge.xml.XmlMergeDocument;
23
import com.devonfw.tools.ide.merge.xml.XmlMerger;
24
import com.devonfw.tools.ide.process.ProcessContext;
25
import com.devonfw.tools.ide.process.ProcessMode;
26
import com.devonfw.tools.ide.process.ProcessResult;
27
import com.devonfw.tools.ide.step.Step;
28
import com.devonfw.tools.ide.tool.ToolCommandlet;
29
import com.devonfw.tools.ide.tool.eclipse.Eclipse;
30
import com.devonfw.tools.ide.tool.extra.ExtraToolInstallation;
31
import com.devonfw.tools.ide.tool.extra.ExtraTools;
32
import com.devonfw.tools.ide.tool.extra.ExtraToolsMapper;
33
import com.devonfw.tools.ide.tool.intellij.Intellij;
34
import com.devonfw.tools.ide.tool.plugin.PluginBasedCommandlet;
35
import com.devonfw.tools.ide.tool.vscode.Vscode;
36

37
/**
38
 * {@link ToolCommandlet} for an IDE (integrated development environment) such as {@link Eclipse}, {@link Vscode}, or {@link Intellij}.
39
 */
40
public abstract class IdeToolCommandlet extends PluginBasedCommandlet {
41

42
  private static final Logger LOG = LoggerFactory.getLogger(IdeToolCommandlet.class);
4✔
43

44
  private final Map<String, Set<Path>> extraSdkMap;
45

46
  /**
47
   * The constructor.
48
   *
49
   * @param context the {@link IdeContext}.
50
   * @param tool the {@link #getName() tool name}.
51
   * @param tags the {@link #getTags() tags} classifying the tool. Should be created via {@link Set#of(Object) Set.of} method.
52
   */
53
  public IdeToolCommandlet(IdeContext context, String tool, Set<Tag> tags) {
54

55
    super(context, tool, tags);
5✔
56
    assert (hasIde(tags));
5!
57
    this.extraSdkMap = new HashMap<>();
5✔
58
  }
1✔
59

60
  private boolean hasIde(Set<Tag> tags) {
61

62
    for (Tag tag : tags) {
10!
63
      if (tag.isAncestorOf(Tag.IDE) || (tag == Tag.IDE)) {
7!
64
        return true;
2✔
65
      }
66
    }
×
67
    throw new IllegalStateException("Tags of IdeTool has to be connected with tag IDE: " + tags);
×
68
  }
69

70
  @Override
71
  protected final void doRun() {
72
    super.doRun();
2✔
73
  }
1✔
74

75
  @Override
76
  public ProcessResult runTool(List<String> args) {
77

78
    return runTool(ProcessMode.BACKGROUND, null, args);
6✔
79
  }
80

81
  @Override
82
  public ProcessResult runTool(ProcessContext pc, ProcessMode processMode, List<String> args) {
83

84
    if (processMode == ProcessMode.BACKGROUND) {
3✔
85
      configureWorkspace();
2✔
86
    }
87
    return super.runTool(pc, processMode, args);
6✔
88
  }
89

90
  /**
91
   * @return the {@link Path} to the IDE-specific metadata folder for the {@link IdeContext#getWorkspaceName() current workspace}, located at
92
   *     {@code $IDE_HOME/.ide/«ide»/«workspace»}. Unlike {@link IdeContext#getWorkspacePath() the workspace path} (which holds the projects to open), this
93
   *     folder keeps IDE-specific metadata (e.g. {@code .vmoptions} or {@code *.properties} files) out of the workspace so it stays clean and independent of
94
   *     the IDE being used.
95
   */
96
  protected Path getIdeMetadataPath() {
97

98
    return this.context.getIdeHome().resolve(IdeContext.FOLDER_DOT_IDE).resolve(getName()).resolve(this.context.getWorkspaceName());
13✔
99
  }
100

101
  /**
102
   * Configure (initialize or update) the workspace for this IDE using the templates from the settings.
103
   */
104
  public void configureWorkspace() {
105

106
    FileAccess fileAccess = this.context.getFileAccess();
4✔
107
    Path workspaceFolder = this.context.getWorkspacePath();
4✔
108
    if (!fileAccess.isExpectedFolder(workspaceFolder)) {
4!
109
      LOG.warn("Current workspace does not exist: {}", workspaceFolder);
×
110
      return; // should actually never happen...
×
111
    }
112
    Step step = this.context.newStep("Configuring workspace " + workspaceFolder.getFileName() + " for IDE " + this.tool);
10✔
113
    step.run(() -> doMergeWorkspaceStep(step, workspaceFolder));
12✔
114
  }
1✔
115

116
  private void doMergeWorkspaceStep(Step step, Path workspaceFolder) {
117

118
    int errors = 0;
2✔
119
    errors = mergeWorkspace(this.context.getUserHomeIde(), workspaceFolder, errors);
8✔
120
    errors = mergeWorkspace(this.context.getSettingsPath(), workspaceFolder, errors);
8✔
121
    errors = mergeWorkspace(this.context.getConfPath(), workspaceFolder, errors);
8✔
122

123
    synchronizeExtraToolInstallations();
2✔
124

125
    if (errors == 0) {
2!
126
      step.success();
3✔
127
    } else {
128
      step.error("Your workspace configuration failed with {} error(s) - see log above.\n"
×
129
          + "This is either a configuration error in your settings git repository or a bug in IDEasy.\n"
130
          + "Please analyze the above errors with your team or IDE-admin and try to fix the problem.", errors);
×
131
      this.context.askToContinue(
×
132
          "In order to prevent you from being blocked, you can start your IDE anyhow but some configuration may not be in sync.");
133
    }
134
  }
1✔
135

136
  private int mergeWorkspace(Path configFolder, Path workspaceFolder, int errors) {
137

138
    int result = errors;
2✔
139
    result = mergeWorkspaceSingle(configFolder.resolve(IdeContext.FOLDER_WORKSPACE), workspaceFolder, result);
8✔
140
    result = mergeWorkspaceSingle(configFolder.resolve(this.tool).resolve(IdeContext.FOLDER_WORKSPACE), workspaceFolder, result);
11✔
141
    return result;
2✔
142
  }
143

144
  private int mergeWorkspaceSingle(Path templatesFolder, Path workspaceFolder, int errors) {
145

146
    Path setupFolder = templatesFolder.resolve(IdeContext.FOLDER_SETUP);
4✔
147
    Path updateFolder = templatesFolder.resolve(IdeContext.FOLDER_UPDATE);
4✔
148
    if (!Files.isDirectory(setupFolder) && !Files.isDirectory(updateFolder)) {
10✔
149
      LOG.trace("Skipping empty or non-existing workspace template folder {}.", templatesFolder);
4✔
150
      return errors;
2✔
151
    }
152
    LOG.debug("Merging workspace templates from {}...", templatesFolder);
4✔
153
    return errors + this.context.getWorkspaceMerger().merge(setupFolder, updateFolder, this.context.getVariables(), workspaceFolder);
13✔
154
  }
155

156
  /**
157
   * Imports the repository specified by the given {@link Path} into the IDE managed by this {@link IdeToolCommandlet}.
158
   *
159
   * @param repositoryPath the {@link Path} to the repository directory to import.
160
   */
161
  public void importRepository(Path repositoryPath) {
162

163
    throw new UnsupportedOperationException("Repository import is not yet implemented for IDE " + this.tool);
×
164
  }
165

166
  /**
167
   * Registers support for synchronizing an extra SDK/template for this IDE.
168
   *
169
   * <p>
170
   * The registered template path must be relative to the IDE workspace root. During workspace synchronization, the generic extra-SDK handling in
171
   * {@link #synchronizeExtraToolInstallations()} uses this mapping to locate the corresponding template file in the settings repository and merge it into the
172
   * current workspace.
173
   * </p>
174
   *
175
   * @param sdk the name of the extra SDK/tool as configured in {@code ide-extra-tools.json}.
176
   * @param relativeTemplatePath the workspace-relative path of the IDE-specific template file to merge.
177
   */
178
  protected void registerExtraSdkTemplate(String sdk, Path relativeTemplatePath) {
179

180
    Set<Path> templatePaths = this.extraSdkMap.computeIfAbsent(sdk, _ -> new HashSet<>());
11✔
181
    templatePaths.add(relativeTemplatePath);
4✔
182
  }
1✔
183

184
  /**
185
   * Synchronizes extra IDEasy tool installations into the current IDE workspace configuration if supported.
186
   *
187
   * <p>
188
   * By default, nothing will happen. Your IDE commandlet has to register one or more according templates in its constructor.
189
   * </p>
190
   */
191
  protected void synchronizeExtraToolInstallations() {
192

193
    ExtraTools extraTools = ExtraToolsMapper.get().loadJsonFromFolder(this.context.getSettingsPath());
7✔
194
    if (extraTools == null) {
2✔
195
      return;
1✔
196
    }
197
    for (String sdk : extraTools.getSortedToolNames()) {
11✔
198
      Set<Path> templatePaths = this.extraSdkMap.get(sdk);
6✔
199
      if ((templatePaths == null) || templatePaths.isEmpty()) {
5!
200
        LOG.debug("Skipping import of extra tool {} into {} because not configured or supported.", sdk, this.tool);
6✔
201
        continue;
1✔
202
      }
203
      List<ExtraToolInstallation> extraInstallations = extraTools.getExtraInstallations(sdk);
4✔
204
      synchronizeExtraToolInstallation(sdk, templatePaths, extraInstallations);
5✔
205
    }
1✔
206
  }
1✔
207

208
  private void synchronizeExtraToolInstallation(String sdk, Set<Path> templatePaths, List<ExtraToolInstallation> extraInstallations) {
209

210
    for (Path templatePath : templatePaths) {
10✔
211
      Path workspaceFile = this.context.getWorkspacePath().resolve(templatePath);
6✔
212
      Path templateFile = this.context.getSettingsPath().resolve(this.tool).resolve(IdeContext.FOLDER_WORKSPACE)
9✔
213
          .resolve(IdeContext.FOLDER_REPOSITORY)
2✔
214
          .resolve(templatePath);
2✔
215
      if (Files.exists(templateFile)) {
5✔
216
        for (ExtraToolInstallation extraInstallation : extraInstallations) {
10✔
217
          synchronizeExtraToolInstallation(sdk, templateFile, workspaceFile, extraInstallation);
6✔
218
        }
2✔
219
      } else {
220
        LOG.warn("You are missing a template file at {}.", templatePath);
4✔
221
        IdeLogLevel.INTERACTION.log(LOG, "Please ask the IDEasy admin in your project to merge your settings with upstream.");
4✔
222
      }
223
    }
1✔
224
  }
1✔
225

226
  private void synchronizeExtraToolInstallation(String sdk, Path templateFile, Path workspaceFile, ExtraToolInstallation installation) {
227

228
    String name = installation.name();
3✔
229
    Path extraToolHome = this.context.getSoftwareExtraPath().resolve(sdk).resolve(name);
8✔
230
    if (!Files.isDirectory(extraToolHome)) {
5✔
231
      LOG.warn("Skipping extra tool installation import to {} because it is missing at {}", this.tool, extraToolHome);
6✔
232
      IdeLogLevel.INTERACTION.log(LOG, "Please run the following command to fix:\nide update");
4✔
233
      return;
1✔
234
    }
235
    ExtensibleEnvironmentVariables environmentVariables = new ExtensibleEnvironmentVariables(
4✔
236
        (AbstractEnvironmentVariables) this.context.getVariables().getParent(), this.context);
7✔
237
    String variablePrefix = "EXTRA_" + sdk.toUpperCase(Locale.ROOT);
5✔
238
    environmentVariables.setValue(variablePrefix + "_NAME", name);
5✔
239
    environmentVariables.setValue(variablePrefix + "_HOME", extraToolHome.toString().replace('\\', '/'));
9✔
240
    environmentVariables.setValue(variablePrefix + "_VERSION", installation.version().toString());
7✔
241
    if (installation.edition() != null) {
3✔
242
      environmentVariables.setValue(variablePrefix + "_EDITION", installation.edition());
6✔
243
    }
244

245
    XmlMerger xmlMerger = new XmlMerger(this.context);
6✔
246
    XmlMergeDocument workspaceDocument = xmlMerger.load(workspaceFile);
4✔
247
    XmlMergeDocument templateDocument = xmlMerger.loadAndResolve(templateFile, environmentVariables);
5✔
248
    Document mergedDocument = xmlMerger.merge(templateDocument, workspaceDocument, false);
6✔
249
    xmlMerger.save(mergedDocument, workspaceFile);
4✔
250
  }
1✔
251
}
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