Skip to content

#2296: Refactor ToolCommandlet to determine InstalledEdition and Version in a Single Cached Lookup - #2306

Open
krystynaShatkovska wants to merge 3 commits into
devonfw:mainfrom
krystynaShatkovska:feature/issue-2296
Open

#2296: Refactor ToolCommandlet to determine InstalledEdition and Version in a Single Cached Lookup#2306
krystynaShatkovska wants to merge 3 commits into
devonfw:mainfrom
krystynaShatkovska:feature/issue-2296

Conversation

@krystynaShatkovska

@krystynaShatkovska krystynaShatkovska commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

This PR fixes #2296

Implemented changes:

This PR refactors the way IDEasy determines the installed edition and version of tools. Previously, getInstalledEdition() and getInstalledVersion() were separate methods
that often performed redundant expensive lookups (registry queries, process execution, etc.). The new combined approach computes both values in a single call.

  • Introduced the record EditionAndVersion(String edition, VersionIdentifier version) to hold both values together
  • Added getInstalledEditionAndVersion() with CachedValue to ToolCommandlet — delegates to computeInstalledEditionAndVersion()
  • Made getInstalledVersion() and getInstalledEdition() final — they now delegate to the combined method
  • Implemented computeInstalledEditionAndVersion() in LocalToolCommandlet (resolves edition + version via software link target)
  • GlobalToolCommandlet: getWindowsRegistryAppNames() returns Map<String, String> mapping edition → registry app name
  • Docker: returns Map.of(\"docker\", \"Docker Desktop\", \"rancher\", \"Rancher Desktop\") — enabling correct detection of Rancher Desktop edition
  • Implemented computeInstalledEditionAndVersion() in LocalToolCommandlet (resolves edition + version via software link target)
  • GlobalToolCommandlet: getWindowsRegistryAppNames() returns Map<String, String> mapping edition → registry app name
  • Docker: returns Map.of(\"docker\", \"Docker Desktop\", \"rancher\", \"Rancher Desktop\") — enabling correct detection of Rancher Desktop edition
  • Updated all relevant subclasses (IdeasyCommandlet, KubeCtl, DelegatingToolCommandlet, PackageManagerBasedLocalToolCommandlet, NodeBasedCommandlet)
  • Added cache invalidation after installation via invalidateInstalledEditionAndVersion()
  • Fixed pre-existing GUI compilation issue (IdeContext.findProjects was package-private instead of public)

Backward compatibility is maintained via deprecated hooks (computeInstalledEdition(), computeInstalledVersion(), getInstalledVersionDeprecated(),
getInstalledEditionDeprecated()).


Testing instructions

  1. Run `mvn clean test` — all 901 tests (CLI: 845, GUI: 56) pass with BUILD SUCCESS
  2. Test Docker edition detection: install Rancher Desktop and run `ideasy docker info` — should correctly report edition as "rancher" instead of "docker"
  3. Test global tool detection on Windows: run `ideasy docker install` and verify the registry lookup correctly identifies the installed edition

Checklist for this PR

  • When running `mvn clean test` locally all tests pass and build is successful
  • PR title is of the form `#«issue-id»: «brief summary»`
  • 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
  • You followed all coding conventions
  • You have added the issue implemented by your PR in CHANGELOG.adoc
  • You have not changed any dependency in `pom.xml` files
  • You have formulated clear instructions on how to test your contribution under "Testing instructions"" 2>&1

@github-project-automation github-project-automation Bot moved this to 🆕 New in IDEasy board Aug 11, 2026
@krystynaShatkovska krystynaShatkovska self-assigned this Aug 11, 2026
@krystynaShatkovska krystynaShatkovska added enhancement New feature or request core FileAccess, ProcessUtil, IdeContext, etc. ready-to-implement labels Aug 11, 2026
@krystynaShatkovska krystynaShatkovska moved this from 🆕 New to Team Review in IDEasy board Aug 11, 2026
@krystynaShatkovska krystynaShatkovska changed the title Feature/issue 2296 #2296: Refactor ToolCommandlet to determine InstalledEdition and Version in a Single Cached Lookup Aug 11, 2026
@laert-ll laert-ll self-assigned this Aug 11, 2026
@coveralls

coveralls commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Coverage Report for CI Build 32465591394

Coverage increased (+0.1%) to 73.077%

Details

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

Uncovered Changes

No uncovered changes found.

Coverage Regressions

224 previously-covered lines in 11 files lost coverage.

Top 10 Files by Coverage Loss Lines Losing Coverage Coverage
com/devonfw/tools/ide/tool/IdeasyCommandlet.java 71 75.04%
com/devonfw/tools/ide/tool/ToolCommandlet.java 66 74.18%
com/devonfw/tools/ide/tool/GlobalToolCommandlet.java 31 11.97%
com/devonfw/tools/ide/tool/docker/Docker.java 23 47.06%
com/devonfw/tools/ide/tool/LocalToolCommandlet.java 23 79.36%
com/devonfw/tools/ide/tool/PackageManagerBasedLocalToolCommandlet.java 3 90.7%
com/devonfw/tools/ide/tool/DelegatingToolCommandlet.java 2 16.67%
com/devonfw/tools/ide/tool/node/NodeBasedCommandlet.java 2 83.33%
com/devonfw/tools/ide/tool/ide/IdeToolCommandlet.java 1 78.69%
com/devonfw/tools/ide/tool/kubectl/KubeCtl.java 1 81.82%

Coverage Stats

Coverage Status
Relevant Lines: 17597
Covered Lines: 13412
Line Coverage: 76.22%
Relevant Branches: 7790
Covered Branches: 5140
Branch Coverage: 65.98%
Branches in Coverage %: Yes
Coverage Strength: 3.24 hits per line

💛 - Coveralls

@laert-ll laert-ll left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for your work! The overall direction is nice, and the EditionAndVersion with a single cached lookup is exactly what the issue was after. I've left a few inline comments on some details that need some improvement.

The main thing I noticed is that the branch seems to accidentally include some unrelated work (the GUI changes from #2278 and some changelog changes as well). Make sure to rebase to main to clean this up and fix the merge conflicts. Apart from that I would also suggest adding some tests for this.

Other than that, thanks for your contribution again!

Comment thread CHANGELOG.adoc
Comment thread cli/src/main/java/com/devonfw/tools/ide/tool/kubectl/KubeCtl.java Outdated
Comment thread cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java
Comment thread cli/src/main/java/com/devonfw/tools/ide/tool/docker/Docker.java Outdated
Comment thread cli/src/main/java/com/devonfw/tools/ide/tool/kubectl/KubeCtl.java Outdated
@krystynaShatkovska krystynaShatkovska moved this from 👀 In review to Team Review in IDEasy board Aug 17, 2026
)

Introduce EditionAndVersion to resolve a tool's installed edition and
version in a single cached lookup, with a protected
computeInstalledEditionAndVersion() hook.

- KubeCtl: read version from 'kubectl version --client' and route through
  the public getInstalledEditionAndVersion() override so it is actually
  used; add KubeCtlTest.
- LocalToolCommandlet: resolve edition and version in one tool-path lookup.
- Docker: keep the resolved edition consistent ('docker'/'rancher') across
  all OSes instead of the bogus 'desktop'; detect the Docker Desktop
  version on macOS from the Docker.app bundle; add DockerTest.
- CHANGELOG: add devonfw#2296 line under 2026.08.002.

Rebased onto upstream/main (drops unrelated devonfw#2278 work).
@krystynaShatkovska krystynaShatkovska moved this from Team Review to 👀 In review in IDEasy board Aug 21, 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.

@krystynaShatkovska thanks for your PR. You got the story right and made your way perfectly through this. Great that you added missing tests for docker and kubectl. Maybe your mockito approach is worth sharing and presenting to the team. Great job 👍

I left some comments for rework.

Follow up (not part of this PR):
Once comments are resolved and this is merged, we should have the foundation to rework our .ide.software.version issues. For software installed via repository we can then resolve the version together with the edition from the symlink. A fallback to .ide.software.version could only happen for devonfw-ide legacy compatibility if no (sym)link was found.
For other tools like Python we could simply add some code that determines the version from cli (e.g. python --version) and auto-recovers a lost .ide.software.version` file.

Comment on lines +875 to +913
/**
* @deprecated Override {@link #computeInstalledEditionAndVersion()} instead.
* @return the currently installed version.
*/
@Deprecated
protected VersionIdentifier computeInstalledVersion() {

return getInstalledVersionDeprecated();
}

/**
* @deprecated Override {@link #computeInstalledEditionAndVersion()} instead.
* @return the installed edition.
*/
@Deprecated
protected String computeInstalledEdition() {

return getInstalledEditionDeprecated();
}

/**
* @deprecated Override {@link #computeInstalledEditionAndVersion()} instead.
* @return the installed version.
*/
@Deprecated
protected VersionIdentifier getInstalledVersionDeprecated() {

return null;
}

/**
* @deprecated Override {@link #computeInstalledEditionAndVersion()} instead.
* @return the installed edition.
*/
@Deprecated
protected String getInstalledEditionDeprecated() {

return null;
}

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.

do we still need this or was that only and intermediate state during refactoring?

/** Invalidates the cached installed edition and version so the next call to {@link #getInstalledEditionAndVersion()} recomputes the result. */
protected void invalidateInstalledEditionAndVersion() {

this.installedEditionAndVersion = null;

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
this.installedEditionAndVersion = null;
if (this.installedEditionAndVersion != null) {
this.installedEditionAndVersion.invalidate();
}

BTW: I would even suggest to make installedEditionAndVersion final and initialize it in the constructor since the creation is cheap and does not compute the value. Then you can skip such if statements and also the lazy init in getInstalledEditionAndVersion() is not needed any more. BTW: In the GUI we will have more concurrency and initialization in constructor is always thread-safe while lazy init in getter could cause race condition.

// Resolve edition and version from a single tool-path lookup (one pass) instead of two separate lookups.
String edition = getInstalledEdition(toolPath);
VersionIdentifier version = getInstalledVersion(toolPath);
if ((edition == null) && (version == null)) {

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.

Just for the record: having an edition here but no version seems like an inconsistent state to me.
Hopefully never happens but wouldn't this be more consistent?

Suggested change
if ((edition == null) && (version == null)) {
if (version == null) {

// Log a warning and return null (instead of throwing) when the command produces no usable output, e.g. when
// Docker Desktop is not installed via apt.
String output = this.context.newProcess().runAndGetSingleOutput(IdeLogLevel.WARNING, "bash", "-lc", dockerDesktopVersionLinuxCommand);
return (output != null) ? super.resolveVersionWithPattern(output, DOCKER_DESKTOP_VERSION_PATTERN) : null;

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.

Not your fault - was already like this before but prefixing method calls with super should only be used when overridden as otherwise this is only causing confusion:

Suggested change
return (output != null) ? super.resolveVersionWithPattern(output, DOCKER_DESKTOP_VERSION_PATTERN) : null;
return (output != null) ? resolveVersionWithPattern(output, DOCKER_DESKTOP_VERSION_PATTERN) : null;

// Log a warning and return null (instead of throwing) when the command produces no usable output, e.g. when
// Docker Desktop is not installed at /Applications/Docker.app.
String output = this.context.newProcess().runAndGetSingleOutput(IdeLogLevel.WARNING, "bash", "-lc", dockerDesktopVersionMacCommand);
return (output != null) ? super.resolveVersionWithPattern(output, DOCKER_DESKTOP_VERSION_PATTERN) : null;

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
return (output != null) ? super.resolveVersionWithPattern(output, DOCKER_DESKTOP_VERSION_PATTERN) : null;
return (output != null) ? resolveVersionWithPattern(output, DOCKER_DESKTOP_VERSION_PATTERN) : null;


@Override
public VersionIdentifier getInstalledVersion() {
public EditionAndVersion getInstalledEditionAndVersion() {

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.

Shouldn't this happen in computeInstalledEditionAndVersion()?
If you make getInstalledEditionAndVersion() final the compiler will help you to find such things.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core FileAccess, ProcessUtil, IdeContext, etc. enhancement New feature or request ready-to-implement

Projects

Status: 👀 In review

Development

Successfully merging this pull request may close these issues.

Improve support to get installed version and edition

4 participants