Skip to content

#1676 import extra sdks automatically into ide #2073

Open
vivu001 wants to merge 22 commits into
devonfw:mainfrom
vivu001:feature/1676-Import-extra-SDKs-automatically-into-IDE-
Open

#1676 import extra sdks automatically into ide #2073
vivu001 wants to merge 22 commits into
devonfw:mainfrom
vivu001:feature/1676-Import-extra-SDKs-automatically-into-IDE-

Conversation

@vivu001

@vivu001 vivu001 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

This PR fixes #1676

Implemented changes:

  • Read extra tool installations from $IDE_HOME/settings/ide-extra-tools.json.
  • Add jdk.table.xml to $IDE_HOME/settings/intellij/workspace/repository/.intellij/config/options as the template for importing extra Java SDKs into IntelliJ. (Related PR #1676: import extra sdks automatically into ide (new) ide-settings#91).
  • Verify that configured extra installations exist before importing them into IntelliJ.
  • Use the configured extra installation name as the SDK name.
  • Synchronize IntelliJ extra SDKs during ide update so one update is enough.

Testing instructions

Preconditions

Make sure the following files are available for testing:

  • $IDE_HOME/settings/ide-extra-tools.json
{
  "java": {
    "client": {
      "version": "11.0.31_11",
	   "edition": "azul"
    },
    "process-engine": {
      "version": "21.*"
    }
  },
  "mvn": {
    "m4": {
      "version": "4.0.0-rc-5"
    }
  }
}
  • $IDE_HOME/settings/intellij/workspace/repository/.intellij/config/options/jdk.table.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<application xmlns:merge="https://github.com/devonfw/IDEasy/merge" merge:strategy="combine">
  <component name="ProjectJdkTable">
    <jdk version="2" merge:id="./jdk/name[@value='$[EXTRA_JAVA_NAME]']/.." merge:strategy="override">
      <name value="$[EXTRA_JAVA_NAME]"/>
      <type value="JavaSDK"/>
      <version value="$$[EXTRA_JAVA_VERSION]"/>
      <homePath value="$[EXTRA_JAVA_HOME]"/>
      <roots>
        <annotationsPath>
          <root type="composite">
            <root type="simple" url="jar://$APPLICATION_HOME_DIR$/plugins/java/lib/resources/jdkAnnotations.jar!/"/>
          </root>
        </annotationsPath>
        <classPath>
          <root type="composite">
            <root type="simple" url="jrt://$[EXTRA_JAVA_HOME]!/java.base"/>
          </root>
        </classPath>
        <javadocPath>
          <root type="composite"/>
        </javadocPath>
        <sourcePath>
          <root type="composite">
            <root type="simple" url="jar://$[EXTRA_JAVA_HOME]/lib/src.zip!/java.se"/>
          </root>
        </sourcePath>
      </roots>
      <additional/>
    </jdk>
  </component>
</application>

Manual test

  1. Run the update command: ide update.
  2. Verify that:
  • the extra sdks are installed successfully at
/
├──/.../software/extra/java/
│  ├──/ client/
│  ├──/ process-engine/
└──/ .../
  • IntelliJ workspace configuration is updated with the new SDKs in $IDE_HOME/workspaces/main/.intellij/config/options/jdk.table.xml
  1. Restart IntelliJ and confirm that the configured SDK names appear as expected.
image

Automated test

Run the IntelliJ-related tests, especially the update flow test coverage:

mvn -pl cli clean test  

Checklist for this PR

Make sure everything is checked before merging this PR. For further info please also see
our DoD.

  • When running mvn clean test locally all tests pass and build is successful
  • PR title is of the form #«issue-id»: «brief summary» (e.g. #921: fixed setup.bat). If no issue ID exists, title only.
  • PR top-level comment summarizes what has been done and contains link to addressed issue(s)
  • PR and issue(s) have suitable labels
  • Issue is set to In Progress and assigned to you or there is no issue (might happen for very small PRs)
  • You followed all coding conventions
  • You have added the issue implemented by your PR in CHANGELOG.adoc unless issue is labeled
    with internal
  • You have formulated clear instructions on how to test your contribution under "Testing instructions"

vivu001 and others added 7 commits June 23, 2026 15:12
- Load extra SDKs from ide-extra-tools.json and register supported ones in IntelliJ.
- The IntelliJ merge template is expected from the settings repository.
…utomatically-into-IDE-' into feature/1676-Import-extra-SDKs-automatically-into-IDE-
@github-project-automation github-project-automation Bot moved this to 🆕 New in IDEasy board Jun 25, 2026
@vivu001 vivu001 added enhancement New feature or request settings ide-settings repo and replated processes and features ready-to-implement labels Jun 25, 2026
@tineff96 tineff96 moved this from 🆕 New to Team Review in IDEasy board Jun 25, 2026
@coveralls

coveralls commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Coverage Report for CI Build 29021898039

Coverage increased (+0.09%) to 72.311%

Details

  • Coverage increased (+0.09%) from the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • 14 coverage regressions across 3 files.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

14 previously-covered lines in 3 files lost coverage.

File Lines Losing Coverage Coverage
com/devonfw/tools/ide/tool/ide/IdeToolCommandlet.java 9 86.72%
com/devonfw/tools/ide/tool/intellij/Intellij.java 4 89.41%
com/devonfw/tools/ide/tool/extra/ExtraTools.java 1 85.0%

Coverage Stats

Coverage Status
Relevant Lines: 16762
Covered Lines: 12628
Line Coverage: 75.34%
Relevant Branches: 7482
Covered Branches: 4903
Branch Coverage: 65.53%
Branches in Coverage %: Yes
Coverage Strength: 3.19 hits per line

💛 - Coveralls

@JoelAdbu JoelAdbu requested review from JoelAdbu and removed request for JoelAdbu July 3, 2026 12:35
@JoelAdbu JoelAdbu self-assigned this Jul 3, 2026

@hohwille hohwille left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vivu001 thanks for your PR and sorry to interrupt before team review.
I had a look into your diff and found some misunderstandings and before moving any further, I wanted to guide the way to the proper solution.
You did a lot perfectly correct, but there are some misunderstandings between #1166 and #1676 that seemed not clear.
Also preparing this implementation for VSCode, Eclipse and other potential IDEs or even future SDKs I suggest some more generic approach and according refactoring.
Please have a look and see if you can follow my idea and suggestions.
If there are any questions do not hesitate to reach out.

Comment on lines +353 to +400
@@ -373,6 +376,28 @@ private void installExtraToolInstallations(String tool, List<ExtraToolInstallati
}
}

private void synchronizeIdeExtraToolInstallations(ExtraTools extraTools) {

if (extraTools == null) {
return;
}

Path workspacePath = this.context.getWorkspacePath();
if (!Files.isDirectory(workspacePath)) {
LOG.debug("Skipping IDE extra SDK synchronization because workspace path does not exist: {}", workspacePath);
return;
}

Intellij intellij = this.context.getCommandletManager().getCommandlet(Intellij.class);
if (intellij != null) {
this.context.newStep("Synchronize IntelliJ extra SDKs").run(() -> intellij.synchronizeExtraToolInstallations(workspacePath));
}

// later:
// Eclipse eclipse = ...
// VsCode vscode = ...
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO this is the wrong place to trigger this:
The workspace will just be the current workspace what is typically main for ide update and will always be for ide create.
However, when I start ide intellij the workspace configuration is updated and IntelliJ is started in the currnet workspace that may differ from the one used by ide update.
Therefore you need to apply the changes when running the IDE.
This also gives us better SoC (separation of concerns):
With the current approach, the AbstractUpdateCommandlet needs to know about all IDE commandlets that can handle extra tools. Instead we should only trigger this inside the according IDE commandlet like Intellij, when the workspace is synchronized. So simply extend this method:

I will add the idea as extra review comment to IdeToolCommandlet.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've fixed it in the latest commits.

Comment on lines +291 to +294
if ((config.reservedName() != null) && config.reservedName().equalsIgnoreCase(name)) {
LOG.warn("Skipping IntelliJ import for extra {} installation '{}': name conflicts with main {} installation.", tool, name, tool);
return;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid point: You want to prevent that an extra installation of <tool> (e.g. java) also has the name <tool>:

{
  "java": {
    "java": { "version":"11.0.27_6", "edition":"azul" }
  }
}

I fully agree but IMHO we should add this as a validation to ExtraTools (or ExtraToolsMapper) so it already fails fast.

Comment on lines +305 to +309
if (!Files.exists(templateFile)) {
throw new CliException(
"Cannot import extra " + tool + " installation into IntelliJ: template file not found at " + templateFile + "\n"
+ "Please do an upstream merge of your settings git repository.");
}

@hohwille hohwille Jul 3, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dont do this:
This is likely to happen for existing projects that have already forked their own ide-settings.
Since you do not run this code inside an extra step, any exception, will terminate the entire process.
That means users will be blocked and cannot start IntelliJ any more with ide intellij.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been removed in the latest commits.

errors = mergeWorkspace(this.context.getUserHomeIde(), workspaceFolder, errors);
errors = mergeWorkspace(this.context.getSettingsPath(), workspaceFolder, errors);
errors = mergeWorkspace(this.context.getConfPath(), workspaceFolder, errors);
if (errors == 0) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here add the following call above:

Suggested change
synchronizeExtraToolInstallations();
if (errors == 0) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also thought this method is called at the correct place. But actually, IntelliJ’s workspace sync runs before the software/extra/java/... folders exist, so it cannot import the new sdks.

 "java": {
    "client": {
      "version": "11.0.31_11",
      "edition": "azul"
    },
    "process-engine": {
      "version": "21.*"
    }
  }
  ...

The warning is therefore shown some thing like that:

Skipping extra tool installation import to intellij because it is missing at [$IDE_HOME]\software\extra\java\client.
Please run the following command to fix: 
ide update

Skipping extra tool installation import to intellij because it is missing at [$IDE_HOME]\software\extra\java\process-engine.
Please run the following command to fix: 
ide update

But I’m struggling to find out how to fix it. 😃

public abstract class IdeToolCommandlet extends PluginBasedCommandlet {

private static final Logger LOG = LoggerFactory.getLogger(IdeToolCommandlet.class);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private final Map<String, Set<Path>> extraSdkMap;

Comment on lines +135 to +147
/**
* Synchronizes extra IDEasy tool installations into the IDE workspace configuration if supported.
*
* <p>
* The default implementation does nothing. IDEs such as IntelliJ may override this hook to import extra SDK/tool installations configured via
* {@code settings/ide-extra-tools.json}.
* </p>
*
* @param workspacePath the workspace root whose IDE configuration should be synchronized.
*/
public void synchronizeExtraToolInstallations(Path workspacePath) {
// default no-op
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/**
* Synchronizes extra IDEasy tool installations into the IDE workspace configuration if supported.
*
* <p>
* The default implementation does nothing. IDEs such as IntelliJ may override this hook to import extra SDK/tool installations configured via
* {@code settings/ide-extra-tools.json}.
* </p>
*
* @param workspacePath the workspace root whose IDE configuration should be synchronized.
*/
public void synchronizeExtraToolInstallations(Path workspacePath) {
// default no-op
}
/**
* TODO: add JavaDoc please
*/
protected void registerExtraSdkTemplate(String sdk, Path relativeTemplatePath) {
Set<Path> templatePaths = this.extraSdkMap.computeIfAbsent(sdk, HashSet::new);
templatePaths.add(relativeTemplatePath);
}
/**
* Synchronizes extra IDEasy tool installations into the current IDE workspace configuration if supported.
*
* <p>
* By default nothing will happen. Your IDE commandlet has to register one or more according templates in its constructor.
* </p>
*/
protected void synchronizeExtraToolInstallations() {
ExtraTools extraTools = ExtraToolsMapper.get().loadJsonFromFolder(this.context.getSettingsPath());
if (extraTools == null) {
return;
}
for (String sdk : extraTools.getSortedToolNames()) {
Set<Path> templatePaths = this.extraSdkMap.get(sdk);
if ((templatePaths == null) || templatePaths.isEmpty()) {
LOG.debug("Skipping import of extra tool {} into {} because not configured or supported.", sdk, this.tool);
continue;
}
List<ExtraToolInstallation> extraInstallations = extraTools.getExtraInstallations(sdk);
synchronizeExtraToolInstallation(sdk, templatePaths, extraInstallations);
}
}
private void synchronizeExtraToolInstallation(String sdk, Set<Path> templatePaths, List<ExtraToolInstallation> extraInstallations) {
for (Path templatePath : templatePaths) {
Path workspaceFile = this.context.getWorkspacePath().resolve(templatePath);
Path templateFile = this.context.getSettingsPath().resolve(this.tool).resolve(IdeContext.FOLDER_WORKSPACE)
.resolve("repository") // TODO: create and use constant IdeContext.FOLDER_REPOSITORY - this string literal exists at least 16 times in our code base
.resolve(templatePath);
if (Files.exists(templateFile)) {
for (ExtraToolInstallation extraInstallation : extraInstallations) {
synchronizeExtraToolInstallation(sdk, templateFile, workspaceFile, extraInstallation);
}
} else {
LOG.warn("You are missing a template file at {}.");
IdeLogLevel.INTERACTION.log(LOG, "Please ask the IDEasy admin in your project to merge your settings with upstream.");
}
}
}
private void synchronizeExtraToolInstallation(String sdk, Path templateFile, Path workspaceFile, ExtraToolInstallation installation) {
String name = installation.name();
Path extraToolHome = this.context.getSoftwareExtraPath().resolve(tool).resolve(name);
if (!Files.isDirectory(extraToolHome)) {
LOG.warn("Skipping extra tool installation import to {} because it is missing at {}", this.tool, extraToolHome);
IdeLogLevel.INTERACTION.log(LOG, "Please run the following command to fix:\nide update");
return;
}
ExtensibleEnvironmentVariables environmentVariables = new ExtensibleEnvironmentVariables(
(AbstractEnvironmentVariables) this.context.getVariables().getParent(), this.context);
String variablePrefix = "EXTRA_" + tool.toUpperCase(Locale.ROOT);
environmentVariables.setValue(variablePrefix + "_NAME", name);
environmentVariables.setValue(variablePrefix + "_HOME", extraToolHome.toString().replace('\\', '/'));
environmentVariables.setValue(variablePrefix + "_VERSION", installation.version().toString());
if (extraInstallation.edition() != null) {
environmentVariables.setValue(variablePrefix + "_EDITION", installation.edition());
}
XmlMerger xmlMerger = new XmlMerger(this.context);
XmlMergeDocument workspaceDocument = xmlMerger.load(workspaceFile);
XmlMergeDocument templateDocument = xmlMerger.loadAndResolve(templateFile, environmentVariables);
Document mergedDocument = xmlMerger.merge(templateDocument, workspaceDocument, false);
xmlMerger.save(mergedDocument, workspaceFile);
}

Comment on lines +60 to +83
/**
* IntelliJ-specific configuration describing how an extra IDEasy tool installation can be imported as an SDK/tool definition into IntelliJ configuration.
*
* @param templateFile the IntelliJ XML template file used as merge source.
* @param targetFile the IntelliJ configuration file relative to the workspace root that should be updated.
* @param reservedName an optional reserved logical name that must not be reused by an extra installation because it would collide with the main SDK/tool
* definition.
*/
private record IntellijExtraSdkConfig(String templateFile, String targetFile, String reservedName) {

}

/**
* Mapping of IDEasy tool names to IntelliJ-specific SDK import configuration.
*
* <p>
* Only tools contained in this map are imported automatically into IntelliJ. This keeps the import logic generic for IntelliJ while allowing support to be
* added incrementally tool by tool.
* </p>
*/
private static final Map<String, IntellijExtraSdkConfig> EXTRA_TOOL_CONFIGS = Map.of(
"java", new IntellijExtraSdkConfig("jdk-extra-java.xml", ".intellij/config/options/jdk.table.xml", "java")
);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice that you solved this in a generic way.
IMHO this generic pattern is not specific for IntelliJ.
I suggested how to solve this fully generic in IdeToolCommandlet.

Suggested change
/**
* IntelliJ-specific configuration describing how an extra IDEasy tool installation can be imported as an SDK/tool definition into IntelliJ configuration.
*
* @param templateFile the IntelliJ XML template file used as merge source.
* @param targetFile the IntelliJ configuration file relative to the workspace root that should be updated.
* @param reservedName an optional reserved logical name that must not be reused by an extra installation because it would collide with the main SDK/tool
* definition.
*/
private record IntellijExtraSdkConfig(String templateFile, String targetFile, String reservedName) {
}
/**
* Mapping of IDEasy tool names to IntelliJ-specific SDK import configuration.
*
* <p>
* Only tools contained in this map are imported automatically into IntelliJ. This keeps the import logic generic for IntelliJ while allowing support to be
* added incrementally tool by tool.
* </p>
*/
private static final Map<String, IntellijExtraSdkConfig> EXTRA_TOOL_CONFIGS = Map.of(
"java", new IntellijExtraSdkConfig("jdk-extra-java.xml", ".intellij/config/options/jdk.table.xml", "java")
);

*/
public Intellij(IdeContext context) {

super(context, "intellij", Set.of(Tag.INTELLIJ));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now all that we need in IntelliJ is the following:

Suggested change
super(context, "intellij", Set.of(Tag.INTELLIJ));
registerExtraSdkTemplate("java", ".intellij/config/options/jdk.table.xml");

ATTENTION:
But you need to move the template again in ide-settings by another PR:
Instead of intellij/workspace/repository/.idea/jdk-extra-java.xml it should then be intellij/workspace/repository/.intellij/config/options/jdk.table.xml.

IMHO all other changes to IntelliJ below can be reverted.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created a new PR for ide-settings at devonfw/ide-settings#91.

LOG.warn("No supported build descriptor was found for project import in {}", repositoryPath);
}

importExtraToolInstallations(repositoryPath);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a misunderstanding.
In #1166 we want to import some repository into Intellij.
Therefore we need to figure out the workspace of that repository in order to change the workspace where the repository is located (getWorkspacePath(repositoryPath)).

In #1676 we want to ensure that all extra SDKs are configured in the IJ config of the current workspace before we actually launch IntelliJ (this.context.getWorkspacePath()).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've addressed it in the latest commits. Thanks for the suggestion 😊

@vivu001 vivu001 moved this from Team Review to 🏗 In progress in IDEasy board Jul 7, 2026
@vivu001 vivu001 marked this pull request as ready for review July 8, 2026 13:26
@vivu001 vivu001 moved this from 🏗 In progress to Team Review in IDEasy board Jul 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request settings ide-settings repo and replated processes and features

Projects

Status: Team Review

Development

Successfully merging this pull request may close these issues.

Import extra SDKs automatically into IDE

5 participants