Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Release with new features and bugfixes:
* https://github.com/devonfw/IDEasy/issues/1808[#1808]: Clarify the selected workspace in `ide` and `icd` messages
* https://github.com/devonfw/IDEasy/issues/2252[#2252]: Add uninstall support for globally installed tools on macOS
* https://github.com/devonfw/IDEasy/issues/1628[#1628]: Support for OS-specific CVEs
* https://github.com/devonfw/IDEasy/issues/2271[#2271]: Fix `ide install pgadmin` on Linux
* https://github.com/devonfw/IDEasy/issues/2431[#2431]: Improve uv support to make it usable for IDE_ROOT on non-default drive

The full list of changes for this release can be found in https://github.com/devonfw/IDEasy/milestone/50?closed=1[milestone 2026.09.002].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.devonfw.tools.ide.process.ProcessResult;
import com.devonfw.tools.ide.step.Step;
import com.devonfw.tools.ide.tool.repository.ToolRepository;
import com.devonfw.tools.ide.version.GenericVersionRange;
import com.devonfw.tools.ide.version.VersionIdentifier;

/**
Expand Down Expand Up @@ -140,12 +141,16 @@ protected ToolInstallation doInstall(ToolInstallRequest request) {
VersionIdentifier resolvedVersion = request.getRequested().getResolvedVersion();
if (this.context.getSystemInfo().isLinux()) {
// on Linux global tools are typically installed via the package manager of the OS
// if a global tool implements getNativePackages() to returns at least one NativePackage, then we will install this way.
// if a global tool implements getNativePackages() to return at least one NativePackage, then we will install this way.
List<PackageManagerCommand> commands = getInstallPackageManagerCommands(resolvedVersion);
if (!commands.isEmpty()) {
boolean newInstallation = runWithPackageManager(request.isSilent(), commands, NativePackageAction.INSTALL);
Path rootDir = getInstallationPath(getConfiguredEdition(), resolvedVersion);
return createToolInstallation(rootDir, resolvedVersion, newInstallation, request.getProcessContext(), request.isAdditionalInstallation());

VersionIdentifier installedVersion = getInstalledVersion();
Path rootDir = getInstallationPath(getConfiguredEdition(), installedVersion);

return createToolInstallation(rootDir, installedVersion, newInstallation, request.getProcessContext(),
request.isAdditionalInstallation());
}
}

Expand Down Expand Up @@ -191,6 +196,20 @@ protected ToolInstallation doInstall(ToolInstallRequest request) {
return createToolInstallation(installationPath, resolvedVersion, true, pc, false);
}

@Override
protected VersionIdentifier resolveVersionForInstall(String edition, GenericVersionRange version) {

if (this.context.getSystemInfo().isLinux() && !getNativePackages().isEmpty()) {
if (version instanceof VersionIdentifier versionIdentifier && !versionIdentifier.isPattern()) {
return versionIdentifier;
}

return null;
}

return super.resolveVersionForInstall(edition, version);
}

/**
* @return the {@link List} of {@link NativePackage}s this tool consists of on Linux - one per supported {@link NativePackageManager}. Override this method
* instead of {@link #getInstallPackageManagerCommands} so that the commands for installation and uninstallation can both be derived from this
Expand Down Expand Up @@ -314,7 +333,7 @@ protected Path getInstallationPath(String edition, VersionIdentifier resolvedVer
public void uninstall() {
if (this.context.getSystemInfo().isWindows()) {
WindowsHelper.get(this.context).uninstallApplication(getWindowsRegistryAppName());
} else if (this.context.getSystemInfo().isLinux()) {
} else if (this.context.getSystemInfo().isLinux() && !getNativePackages().isEmpty()) {
runWithPackageManager(false, getUninstallPackageManagerCommands(), NativePackageAction.UNINSTALL);
} else if (this.context.getSystemInfo().isMac()) {
uninstallMac();
Expand Down
22 changes: 19 additions & 3 deletions cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ private void completeRequestRequested(ToolInstallRequest request) {
version = request.isIgnoreProject() ? VersionIdentifier.LATEST : getConfiguredVersion();
requested.setVersion(version);
}

VersionIdentifier resolvedVersion = requested.getResolvedVersion();
if (resolvedVersion == null) {
if (this.context.isSkipUpdatesMode()) {
Expand All @@ -474,17 +475,32 @@ private void completeRequestRequested(ToolInstallRequest request) {
}
}
if (resolvedVersion == null) {
resolvedVersion = getToolRepository().resolveVersion(this.tool, edition.edition(), version, this);
resolvedVersion = resolveVersionForInstall(edition.edition(), version);
}

if (resolvedVersion != null) {
requested.setResolvedVersion(resolvedVersion);
}
requested.setResolvedVersion(resolvedVersion);
}
}

/**
* Resolves the requested version for installation.
*
* @param edition the requested tool edition.
* @param version the requested version or version range.
* @return the resolved {@link VersionIdentifier} or {@code null} if version resolution is delegated to another installation mechanism.
*/
protected VersionIdentifier resolveVersionForInstall(String edition, GenericVersionRange version) {

return getToolRepository().resolveVersion(this.tool, edition, version, this);
}

/**
* Hook for subclasses to adjust the requested tool edition before the version is finalized.
*
* @param requested the requested {@link ToolEditionAndVersion}
* @return the given or trgansformed {@link ToolEditionAndVersion}
* @return the given or transformed {@link ToolEditionAndVersion}
*/
protected ToolEditionAndVersion adjustRequestedEdition(ToolEditionAndVersion requested) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,45 @@ void testGetUninstallPackageManagerCommandsDerivesFromNativePackages() {
"sudo rm -f /etc/apt/sources.list.d/mytool.list");
}

/**
* Verifies that an explicitly requested exact version is preserved for a Linux tool installed via a native package manager.
*/
@Test
void testResolveVersionForInstallKeepsExactVersionForLinuxNativePackage() {

// arrange
IdeTestContext context = newContext(PROJECT_BASIC);
context.setSystemInfo(SystemInfoMock.LINUX_X64);
PackageManagedToolCommandlet commandlet = new PackageManagedToolCommandlet(context);
VersionIdentifier version = VersionIdentifier.of("1.2.3");

// act
VersionIdentifier resolvedVersion = commandlet.resolveVersionForInstall("default", version);

// assert
assertThat(resolvedVersion).isEqualTo(version);
}

/**
* Verifies that a version pattern is not resolved by IDEasy for a Linux tool installed via a native package manager, so the package manager can determine the
* concrete version.
*/
@Test
void testResolveVersionForInstallReturnsNullForPatternOnLinuxNativePackage() {

// arrange
IdeTestContext context = newContext(PROJECT_BASIC);
context.setSystemInfo(SystemInfoMock.LINUX_X64);
PackageManagedToolCommandlet commandlet = new PackageManagedToolCommandlet(context);
VersionIdentifier version = VersionIdentifier.of("1.2.*");

// act
VersionIdentifier resolvedVersion = commandlet.resolveVersionForInstall("default", version);

// assert
assertThat(resolvedVersion).isNull();
}

/**
* Dummy {@link GlobalToolCommandlet} that declares a Homebrew cask for testing macOS uninstall via {@link NativePackageManager#BREW_CASK}.
*/
Expand Down
Loading