From 06832b9bb5201690f85d3aefb65d93ee129a9f28 Mon Sep 17 00:00:00 2001 From: Tarik Abu Mukh Date: Tue, 24 Mar 2026 14:36:24 +0100 Subject: [PATCH 01/27] Add Rust URL updater using rustup script --- .../rust/RustGithubUrlTagUpdaterTest.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 url-updater/src/test/java/com/devonfw/tools/ide/url/tool/rust/RustGithubUrlTagUpdaterTest.java diff --git a/url-updater/src/test/java/com/devonfw/tools/ide/url/tool/rust/RustGithubUrlTagUpdaterTest.java b/url-updater/src/test/java/com/devonfw/tools/ide/url/tool/rust/RustGithubUrlTagUpdaterTest.java new file mode 100644 index 0000000000..4f21d71125 --- /dev/null +++ b/url-updater/src/test/java/com/devonfw/tools/ide/url/tool/rust/RustGithubUrlTagUpdaterTest.java @@ -0,0 +1,52 @@ +package com.devonfw.tools.ide.url.tool.rust; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.any; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; + +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import com.devonfw.tools.ide.url.model.folder.UrlRepository; +import com.devonfw.tools.ide.url.updater.AbstractUrlUpdaterTest; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; + +/** + * Test of {@link RustUrlUpdater}. + */ +@WireMockTest +class RustGithubUrlTagUpdaterTest extends AbstractUrlUpdaterTest { + + @Test + void testRustGithubUrlUpdater(@TempDir Path tempDir, WireMockRuntimeInfo wmRuntimeInfo) { + + // arrange + stubFor(get(urlMatching("/repos/rust-lang/rustup/git/refs/tags")).willReturn(aResponse().withStatus(200) + .withBody(readAndResolve(PATH_INTEGRATION_TEST.resolve("RustUrlUpdater").resolve("github-tags.json"), wmRuntimeInfo)))); + stubFor(any(urlMatching("/rustup\\.sh")).willReturn(aResponse().withStatus(200).withHeader("content-type", "text/plain").withBody(DOWNLOAD_CONTENT))); + + UrlRepository urlRepository = UrlRepository.load(tempDir); + RustUrlUpdaterMock updater = new RustUrlUpdaterMock(wmRuntimeInfo); + + // act + updater.update(urlRepository); + + // assert + Path rustEditionDir = tempDir.resolve("rust").resolve("rust"); + assertUrlVersionAgnostic(rustEditionDir.resolve("1.79.0")); + assertUrlVersionAgnostic(rustEditionDir.resolve("1.80.1")); + assertThat(rustEditionDir.resolve("release-0.7")).doesNotExist(); + } +} + + + + + + + From 844d8dd5c7ae5b30c19ee2ca586d11427826fc8d Mon Sep 17 00:00:00 2001 From: Tarik Abu Mukh Date: Tue, 24 Mar 2026 14:38:06 +0100 Subject: [PATCH 02/27] Add Rust commandlet installer with Windows MSVC setup --- .../ide/commandlet/CommandletManagerImpl.java | 2 + .../com/devonfw/tools/ide/tool/rust/Rust.java | 134 ++++++++++++++++++ .../devonfw/tools/ide/tool/rust/RustTest.java | 46 ++++++ .../rust/_ide/urls/rust/rust/1.80.1/urls | 2 + .../rust/project/settings/ide.properties | 0 .../rust/project/workspaces/main/.gitkeep | 0 .../repository/rust/rust/default/content.sh | 17 +++ 7 files changed, 201 insertions(+) create mode 100644 cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java create mode 100644 cli/src/test/java/com/devonfw/tools/ide/tool/rust/RustTest.java create mode 100644 cli/src/test/resources/ide-projects/rust/_ide/urls/rust/rust/1.80.1/urls create mode 100644 cli/src/test/resources/ide-projects/rust/project/settings/ide.properties create mode 100644 cli/src/test/resources/ide-projects/rust/project/workspaces/main/.gitkeep create mode 100644 cli/src/test/resources/ide-projects/rust/repository/rust/rust/default/content.sh diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java index 537196461e..bc5868c99d 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java @@ -56,6 +56,7 @@ import com.devonfw.tools.ide.tool.pycharm.Pycharm; import com.devonfw.tools.ide.tool.python.Python; import com.devonfw.tools.ide.tool.quarkus.Quarkus; +import com.devonfw.tools.ide.tool.rust.Rust; import com.devonfw.tools.ide.tool.sonar.Sonar; import com.devonfw.tools.ide.tool.spring.Spring; import com.devonfw.tools.ide.tool.squirrelsql.SquirrelSql; @@ -132,6 +133,7 @@ public CommandletManagerImpl(IdeContext context) { add(new Terraform(context)); add(new Oc(context)); add(new Quarkus(context)); + add(new Rust(context)); add(new Kotlinc(context)); add(new KotlincNative(context)); add(new KubeCtl(context)); diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java b/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java new file mode 100644 index 0000000000..84c8cfd3ef --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java @@ -0,0 +1,134 @@ +package com.devonfw.tools.ide.tool.rust; + +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.util.List; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.devonfw.tools.ide.common.Tag; +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.io.FileAccess; +import com.devonfw.tools.ide.os.WindowsPathSyntax; +import com.devonfw.tools.ide.process.ProcessContext; +import com.devonfw.tools.ide.process.ProcessErrorHandling; +import com.devonfw.tools.ide.tool.LocalToolCommandlet; +import com.devonfw.tools.ide.tool.ToolInstallRequest; +import com.devonfw.tools.ide.version.VersionIdentifier; + +/** + * {@link LocalToolCommandlet} for Rust. + */ +public class Rust extends LocalToolCommandlet { + + private static final Logger LOG = LoggerFactory.getLogger(Rust.class); + + private static final String MSVC_SETUP_URL = "https://aka.ms/vs/17/release/vs_BuildTools.exe"; + + /** + * The constructor. + * + * @param context the {@link IdeContext}. + */ + public Rust(IdeContext context) { + + super(context, "rust", Set.of(Tag.RUST)); + } + + @Override + public String getBinaryName() { + + return "rustc"; + } + + @Override + public String getToolHelpArguments() { + + return "--help"; + } + + @Override + protected boolean isExtract() { + + // The rustup installer script is an executable script and must not be extracted. + return false; + } + + @Override + protected void installDependencies() { + + if (this.context.getSystemInfo().isWindows()) { + installWindowsMsvcBuildTools(); + } + } + + protected String getMsvcSetupUrl() { + + return MSVC_SETUP_URL; + } + + protected List getMsvcInstallerArgs() { + + return List.of("--quiet", "--wait", "--norestart", "--nocache", "--add", "Microsoft.VisualStudio.Workload.VCTools"); + } + + private void installWindowsMsvcBuildTools() { + + FileAccess fileAccess = this.context.getFileAccess(); + Path tempDir = fileAccess.createTempDir("msvc-setup"); + Path installer = tempDir.resolve("vs_BuildTools.exe"); + fileAccess.download(getMsvcSetupUrl(), installer); + + ProcessContext process = this.context.newProcess().errorHandling(ProcessErrorHandling.THROW_CLI).executable(installer) + .withExitCodeAcceptor(code -> (code.intValue() == 0) || (code.intValue() == 3010)).addArgs(getMsvcInstallerArgs()); + process.run(); + } + + @Override + protected void performToolInstallation(ToolInstallRequest request, Path installationPath) { + + VersionIdentifier resolvedVersion = request.getRequested().getResolvedVersion(); + FileAccess fileAccess = this.context.getFileAccess(); + + if (Files.isDirectory(installationPath)) { + fileAccess.backup(installationPath); + } + fileAccess.mkdirs(installationPath); + + Path cargoHome = installationPath.resolve(".cargo"); + Path rustupHome = installationPath.resolve(".rustup"); + fileAccess.mkdirs(cargoHome); + fileAccess.mkdirs(rustupHome); + + Path installerScript = downloadTool(request.getRequested().getEdition().edition(), resolvedVersion); + if (Files.isDirectory(installerScript)) { + // ToolRepositoryMock may provide an unpacked folder instead of a single download file. + installerScript = installerScript.resolve("content.sh"); + } + String installerScriptArg = installerScript.toAbsolutePath().toString(); + if (this.context.getSystemInfo().isWindows()) { + installerScriptArg = WindowsPathSyntax.normalize(installerScriptArg, true); + } + + ProcessContext process = request.getProcessContext().createChild().errorHandling(ProcessErrorHandling.THROW_CLI).directory(installationPath) + .withEnvVar("CARGO_HOME", cargoHome.toString()).withEnvVar("RUSTUP_HOME", rustupHome.toString()) + .executable(this.context.findBashRequired()).addArgs(installerScriptArg, "-y", "--no-modify-path", "--profile", "default", + "--default-toolchain", resolvedVersion.toString()); + process.run(); + + Path cargoBin = cargoHome.resolve("bin"); + Path toolBin = installationPath.resolve("bin"); + if (Files.exists(toolBin, LinkOption.NOFOLLOW_LINKS)) { + fileAccess.delete(toolBin); + } + if (Files.isDirectory(cargoBin)) { + fileAccess.symlink(cargoBin, toolBin); + } + + this.context.writeVersionFile(resolvedVersion, installationPath); + LOG.debug("Installed {} in version {} at {}", this.tool, resolvedVersion, installationPath); + } +} diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/rust/RustTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/rust/RustTest.java new file mode 100644 index 0000000000..341be6ab19 --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/rust/RustTest.java @@ -0,0 +1,46 @@ +package com.devonfw.tools.ide.tool.rust; + +import org.junit.jupiter.api.Test; + +import com.devonfw.tools.ide.context.AbstractIdeContextTest; +import com.devonfw.tools.ide.context.IdeTestContext; + +/** + * Test of {@link Rust}. + */ +class RustTest extends AbstractIdeContextTest { + + private static final String PROJECT_RUST = "rust"; + + private static final String RUST_VERSION = "1.80.1"; + + @Test + void testRustInstallViaRustupScript() { + + // arrange + IdeTestContext context = newContext(PROJECT_RUST); + Rust rust = new RustForTest(context); + + // act + rust.install(); + + // assert + assertThat(context.getSoftwarePath().resolve("rust/.ide.software.version")).exists().hasContent(RUST_VERSION); + assertThat(context).logAtSuccess().hasMessageContaining("Successfully installed rust in version " + RUST_VERSION); + } + + private static class RustForTest extends Rust { + + RustForTest(IdeTestContext context) { + + super(context); + } + + @Override + protected void installDependencies() { + + // Skip heavyweight MSVC installer execution in tests. + } + } +} + diff --git a/cli/src/test/resources/ide-projects/rust/_ide/urls/rust/rust/1.80.1/urls b/cli/src/test/resources/ide-projects/rust/_ide/urls/rust/rust/1.80.1/urls new file mode 100644 index 0000000000..d763e4b8fe --- /dev/null +++ b/cli/src/test/resources/ide-projects/rust/_ide/urls/rust/rust/1.80.1/urls @@ -0,0 +1,2 @@ +https://sh.rustup.rs + diff --git a/cli/src/test/resources/ide-projects/rust/project/settings/ide.properties b/cli/src/test/resources/ide-projects/rust/project/settings/ide.properties new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cli/src/test/resources/ide-projects/rust/project/workspaces/main/.gitkeep b/cli/src/test/resources/ide-projects/rust/project/workspaces/main/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cli/src/test/resources/ide-projects/rust/repository/rust/rust/default/content.sh b/cli/src/test/resources/ide-projects/rust/repository/rust/rust/default/content.sh new file mode 100644 index 0000000000..c1664d9c76 --- /dev/null +++ b/cli/src/test/resources/ide-projects/rust/repository/rust/rust/default/content.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -eu + +mkdir -p "${CARGO_HOME}/bin" +mkdir -p "${RUSTUP_HOME}" + +cat > "${CARGO_HOME}/bin/rustc" <<'EOF' +#!/usr/bin/env bash +echo rustc "$@" +EOF +chmod +x "${CARGO_HOME}/bin/rustc" + +cat > "${CARGO_HOME}/bin/rustc.cmd" <<'EOF' +@echo off +echo rustc %* +EOF + From fca354a60c6c62fe3d086a49d340e3f4f10ff91a Mon Sep 17 00:00:00 2001 From: Tarik Abu Mukh Date: Tue, 7 Apr 2026 08:07:34 +0200 Subject: [PATCH 03/27] #1719: Change Rust MSVC Name --- .../com/devonfw/tools/ide/tool/rust/Rust.java | 74 ++++++++++++++++--- 1 file changed, 65 insertions(+), 9 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java b/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java index 84c8cfd3ef..44aacbd5aa 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java @@ -12,7 +12,7 @@ import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.io.FileAccess; -import com.devonfw.tools.ide.os.WindowsPathSyntax; +import com.devonfw.tools.ide.io.FileCopyMode; import com.devonfw.tools.ide.process.ProcessContext; import com.devonfw.tools.ide.process.ProcessErrorHandling; import com.devonfw.tools.ide.tool.LocalToolCommandlet; @@ -28,6 +28,8 @@ public class Rust extends LocalToolCommandlet { private static final String MSVC_SETUP_URL = "https://aka.ms/vs/17/release/vs_BuildTools.exe"; + private static final String WINDOWS_RUSTUP_INIT_EXE = "rustup-init.exe"; + /** * The constructor. * @@ -75,6 +77,11 @@ protected List getMsvcInstallerArgs() { return List.of("--quiet", "--wait", "--norestart", "--nocache", "--add", "Microsoft.VisualStudio.Workload.VCTools"); } + protected List getRustupInstallerArgs(VersionIdentifier version) { + + return List.of("-y", "--no-modify-path", "--profile", "default", "--default-toolchain", version.toString()); + } + private void installWindowsMsvcBuildTools() { FileAccess fileAccess = this.context.getFileAccess(); @@ -83,7 +90,7 @@ private void installWindowsMsvcBuildTools() { fileAccess.download(getMsvcSetupUrl(), installer); ProcessContext process = this.context.newProcess().errorHandling(ProcessErrorHandling.THROW_CLI).executable(installer) - .withExitCodeAcceptor(code -> (code.intValue() == 0) || (code.intValue() == 3010)).addArgs(getMsvcInstallerArgs()); + .withExitCodeAcceptor(code -> (code == 0) || (code == 3010)).addArgs(getMsvcInstallerArgs()); process.run(); } @@ -108,15 +115,55 @@ protected void performToolInstallation(ToolInstallRequest request, Path installa // ToolRepositoryMock may provide an unpacked folder instead of a single download file. installerScript = installerScript.resolve("content.sh"); } - String installerScriptArg = installerScript.toAbsolutePath().toString(); - if (this.context.getSystemInfo().isWindows()) { - installerScriptArg = WindowsPathSyntax.normalize(installerScriptArg, true); - } + List installerArgs = getRustupInstallerArgs(resolvedVersion); ProcessContext process = request.getProcessContext().createChild().errorHandling(ProcessErrorHandling.THROW_CLI).directory(installationPath) - .withEnvVar("CARGO_HOME", cargoHome.toString()).withEnvVar("RUSTUP_HOME", rustupHome.toString()) - .executable(this.context.findBashRequired()).addArgs(installerScriptArg, "-y", "--no-modify-path", "--profile", "default", - "--default-toolchain", resolvedVersion.toString()); + .withEnvVar("CARGO_HOME", cargoHome.toString()).withEnvVar("RUSTUP_HOME", rustupHome.toString()); + if (isWindowsExeInstaller(installerScript)) { + Path installerExecutable = installerScript; + String fileName = installerScript.getFileName().toString(); + if (!WINDOWS_RUSTUP_INIT_EXE.equalsIgnoreCase(fileName)) { + Path canonicalInstaller = installerScript.resolveSibling(WINDOWS_RUSTUP_INIT_EXE); + + // Handle corrupted installations where rustup-init.exe might exist as a file or directory + if (Files.exists(canonicalInstaller, LinkOption.NOFOLLOW_LINKS)) { + LOG.info("Found existing installer at {}, checking type", canonicalInstaller); + boolean isDirectory = Files.isDirectory(canonicalInstaller, LinkOption.NOFOLLOW_LINKS); + LOG.info("Existing installer is {} (directory: {}), deleting it", canonicalInstaller, isDirectory); + + try { + // First attempt: delete using fileAccess which handles files, directories, and symlinks + fileAccess.delete(canonicalInstaller); + LOG.debug("Successfully deleted existing installer at {}", canonicalInstaller); + } catch (IllegalStateException e) { + LOG.warn("First deletion attempt failed for {}, retrying: {}", canonicalInstaller, e.getMessage()); + // Retry in case of transient lock issues + try { + fileAccess.delete(canonicalInstaller); + LOG.debug("Successfully deleted existing installer on retry at {}", canonicalInstaller); + } catch (IllegalStateException e2) { + LOG.error("Failed to delete {} after retry: {}", canonicalInstaller, e2.getMessage(), e2); + throw new IllegalStateException("Failed to clean up corrupted installer at " + canonicalInstaller + + " (may be locked by another process or permission denied)", e2); + } + } + + // Verify deletion was successful + if (Files.exists(canonicalInstaller, LinkOption.NOFOLLOW_LINKS)) { + boolean stillIsDirectory = Files.isDirectory(canonicalInstaller, LinkOption.NOFOLLOW_LINKS); + throw new IllegalStateException("Failed to delete corrupted installer at " + canonicalInstaller + + " (is directory: " + stillIsDirectory + ", may be locked by another process)"); + } + } + + fileAccess.copy(installerScript, canonicalInstaller, FileCopyMode.COPY_FILE_TO_TARGET_OVERRIDE); + installerExecutable = canonicalInstaller; + } + process.executable(installerExecutable).addArgs(installerArgs); + } else { + String installerScriptArg = installerScript.toAbsolutePath().toString(); + process.executable(this.context.findBashRequired()).addArgs(installerScriptArg).addArgs(installerArgs); + } process.run(); Path cargoBin = cargoHome.resolve("bin"); @@ -131,4 +178,13 @@ protected void performToolInstallation(ToolInstallRequest request, Path installa this.context.writeVersionFile(resolvedVersion, installationPath); LOG.debug("Installed {} in version {} at {}", this.tool, resolvedVersion, installationPath); } + + private boolean isWindowsExeInstaller(Path installerPath) { + + if (!this.context.getSystemInfo().isWindows()) { + return false; + } + Path fileName = installerPath.getFileName(); + return (fileName != null) && fileName.toString().toLowerCase().endsWith(".exe"); + } } From 58f45de0717742acce2e802393bdc80664ec49d6 Mon Sep 17 00:00:00 2001 From: Tarik Abu Mukh Date: Thu, 16 Apr 2026 11:42:52 +0200 Subject: [PATCH 04/27] Added Rust commandlet properties --- cli/src/main/resources/nls/Help.properties | 2 ++ cli/src/main/resources/nls/Help_de.properties | 2 ++ 2 files changed, 4 insertions(+) diff --git a/cli/src/main/resources/nls/Help.properties b/cli/src/main/resources/nls/Help.properties index 191e8a9d5c..7ccda7abf4 100644 --- a/cli/src/main/resources/nls/Help.properties +++ b/cli/src/main/resources/nls/Help.properties @@ -112,6 +112,8 @@ cmd.quarkus.detail=Quarkus is a Kubernetes-native Java framework for building cl cmd.repository=Set up pre-configured git repositories using 'ide repository setup ' cmd.repository.detail=Without further arguments this will set up all pre-configured git repositories.\nAlso, you can provide an explicit git repo as `` argument and IDEasy will automatically clone, build and set up your project based on the existing property file.\nRepositories are configured in 'settings/repository/.properties' and can therefore be shared with your project team for automatic or optional setup. cmd.repository.val.repository=The name of the properties file of the pre-configured git repository to set up, omit to set up all active repositories. +cmd.rust=Tool commandlet for Rust (programming language). +cmd.rust.detail=Rust ist eine Systemprogrammiersprache mit Fokus auf Sicherheit und Performance. Detaillierte Dokumentation unter https://www.rust-lang.org/learn``` cmd.set-edition=Set the edition of the selected tool. cmd.set-edition.detail=This will set the according tool edition variable in your configuration file. If you want to roll out such change and share it with your team, you can commit and push your settings git repository.\nBy default these changes are saved in the project specific settings. Use --conf --home or --workspace to specify otherwise. cmd.set-edition.opt.--cfg=Selection of the configuration file (settings | home | conf | workspace). diff --git a/cli/src/main/resources/nls/Help_de.properties b/cli/src/main/resources/nls/Help_de.properties index 951d0204e4..a38f18f3cf 100644 --- a/cli/src/main/resources/nls/Help_de.properties +++ b/cli/src/main/resources/nls/Help_de.properties @@ -112,6 +112,8 @@ cmd.quarkus.detail=Quarkus ist ein Kubernetes-native Java-Framework zur Entwickl cmd.repository=Richtet das vorkonfigurierte Git Repository ein mittels 'ide repository setup '. cmd.repository.detail=Dies wird alle vorkonfigurierten Repositories einrichten. Rufen Sie einfach 'ide repository setup ' auf, ersetzen Sie durch den Namen Ihrer Projektkonfigurationsdatei, die sich in 'settings/repository/your_project_name' befindet und IDEasy wird Ihr Projekt basierend auf der vorhandenen Eigenschaftsdatei automatisch klonen, bauen und einrichten.\nWenn Sie den Projektnamen weglassen, werden alle im Repository-Verzeichnis gefundenen Projekte vorkonfiguriert. cmd.repository.val.repository=Der Name der Properties-Datei des vorkonfigurierten Git Repositories zum Einrichten. Falls nicht angegeben, werden alle aktiven Projekte eingerichtet. +cmd.rust=Werkzeug Kommando für Rust (Programmiersprache). +cmd.rust.detail=Rust is a systems programming language focused on safety and performance. Detailed documentation can be found at https://www.rust-lang.org/learndiff --git a/cli/src/main/resources/nls/Help_de.properties b/cli/src/main/resources/nls/Help_de.properties@@+cmd.rust=Tool-Commandlet für Rust. cmd.set-edition=Setzt die Edition des selektierten Werkzeugs. cmd.set-edition.detail=Dies setzt die entsprechende Werkzeug-Edition Variable in der Konfigurationsdatei. Um solche Anpassungen auszurollen und mit dem Team zu teilen, kann diese im Settings git repository committed und gepushed werden.\nDiese Änderungen werden in der projektspezifischen Konfiguration gespeichert, es sei denn es wird mit --conf --home oder --workspace einen anderer Benutzer- oder Workspace-spezifischer Speicherort definiert. cmd.set-edition.opt.--cfg=Auswahl der Konfigurationsdatei (settings | home | conf | workspace). From e9a55a7bd4c5c341b97b6e00c3cf721ca287f0a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Hohwiller?= Date: Fri, 24 Apr 2026 18:40:39 +0200 Subject: [PATCH 05/27] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jörg Hohwiller --- cli/src/main/resources/nls/Help.properties | 2 +- cli/src/main/resources/nls/Help_de.properties | 2 +- .../ide/url/tool/rust/RustGithubUrlTagUpdaterTest.java | 7 ------- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/cli/src/main/resources/nls/Help.properties b/cli/src/main/resources/nls/Help.properties index 7ccda7abf4..e975fbf07c 100644 --- a/cli/src/main/resources/nls/Help.properties +++ b/cli/src/main/resources/nls/Help.properties @@ -113,7 +113,7 @@ cmd.repository=Set up pre-configured git repositories using 'ide repository setu cmd.repository.detail=Without further arguments this will set up all pre-configured git repositories.\nAlso, you can provide an explicit git repo as `` argument and IDEasy will automatically clone, build and set up your project based on the existing property file.\nRepositories are configured in 'settings/repository/.properties' and can therefore be shared with your project team for automatic or optional setup. cmd.repository.val.repository=The name of the properties file of the pre-configured git repository to set up, omit to set up all active repositories. cmd.rust=Tool commandlet for Rust (programming language). -cmd.rust.detail=Rust ist eine Systemprogrammiersprache mit Fokus auf Sicherheit und Performance. Detaillierte Dokumentation unter https://www.rust-lang.org/learn``` +cmd.rust.detail=Rust is a programming-language focused on safety and performance. Detailed documentation can be found at https://www.rust-lang.org/learn cmd.set-edition=Set the edition of the selected tool. cmd.set-edition.detail=This will set the according tool edition variable in your configuration file. If you want to roll out such change and share it with your team, you can commit and push your settings git repository.\nBy default these changes are saved in the project specific settings. Use --conf --home or --workspace to specify otherwise. cmd.set-edition.opt.--cfg=Selection of the configuration file (settings | home | conf | workspace). diff --git a/cli/src/main/resources/nls/Help_de.properties b/cli/src/main/resources/nls/Help_de.properties index a38f18f3cf..f424d72d6b 100644 --- a/cli/src/main/resources/nls/Help_de.properties +++ b/cli/src/main/resources/nls/Help_de.properties @@ -113,7 +113,7 @@ cmd.repository=Richtet das vorkonfigurierte Git Repository ein mittels 'ide repo cmd.repository.detail=Dies wird alle vorkonfigurierten Repositories einrichten. Rufen Sie einfach 'ide repository setup ' auf, ersetzen Sie durch den Namen Ihrer Projektkonfigurationsdatei, die sich in 'settings/repository/your_project_name' befindet und IDEasy wird Ihr Projekt basierend auf der vorhandenen Eigenschaftsdatei automatisch klonen, bauen und einrichten.\nWenn Sie den Projektnamen weglassen, werden alle im Repository-Verzeichnis gefundenen Projekte vorkonfiguriert. cmd.repository.val.repository=Der Name der Properties-Datei des vorkonfigurierten Git Repositories zum Einrichten. Falls nicht angegeben, werden alle aktiven Projekte eingerichtet. cmd.rust=Werkzeug Kommando für Rust (Programmiersprache). -cmd.rust.detail=Rust is a systems programming language focused on safety and performance. Detailed documentation can be found at https://www.rust-lang.org/learndiff --git a/cli/src/main/resources/nls/Help_de.properties b/cli/src/main/resources/nls/Help_de.properties@@+cmd.rust=Tool-Commandlet für Rust. +cmd.rust.detail=Rust ist eine Programmiersprache mit Fokus auf Sicherheit und Performance. Detaillierte Dokumentation findet sich unter https://www.rust-lang.org/learn cmd.set-edition=Setzt die Edition des selektierten Werkzeugs. cmd.set-edition.detail=Dies setzt die entsprechende Werkzeug-Edition Variable in der Konfigurationsdatei. Um solche Anpassungen auszurollen und mit dem Team zu teilen, kann diese im Settings git repository committed und gepushed werden.\nDiese Änderungen werden in der projektspezifischen Konfiguration gespeichert, es sei denn es wird mit --conf --home oder --workspace einen anderer Benutzer- oder Workspace-spezifischer Speicherort definiert. cmd.set-edition.opt.--cfg=Auswahl der Konfigurationsdatei (settings | home | conf | workspace). diff --git a/url-updater/src/test/java/com/devonfw/tools/ide/url/tool/rust/RustGithubUrlTagUpdaterTest.java b/url-updater/src/test/java/com/devonfw/tools/ide/url/tool/rust/RustGithubUrlTagUpdaterTest.java index 4f21d71125..8d2dd6aaec 100644 --- a/url-updater/src/test/java/com/devonfw/tools/ide/url/tool/rust/RustGithubUrlTagUpdaterTest.java +++ b/url-updater/src/test/java/com/devonfw/tools/ide/url/tool/rust/RustGithubUrlTagUpdaterTest.java @@ -43,10 +43,3 @@ void testRustGithubUrlUpdater(@TempDir Path tempDir, WireMockRuntimeInfo wmRunti assertThat(rustEditionDir.resolve("release-0.7")).doesNotExist(); } } - - - - - - - From 2342b92f88fec26c02072311681ebb35c9c621ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Hohwiller?= Date: Fri, 24 Apr 2026 20:10:23 +0200 Subject: [PATCH 06/27] fix installDependencies --- cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java b/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java index 44aacbd5aa..a5bb9bbfa4 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java @@ -59,8 +59,7 @@ protected boolean isExtract() { return false; } - @Override - protected void installDependencies() { + private void installDependencies() { if (this.context.getSystemInfo().isWindows()) { installWindowsMsvcBuildTools(); @@ -97,6 +96,7 @@ private void installWindowsMsvcBuildTools() { @Override protected void performToolInstallation(ToolInstallRequest request, Path installationPath) { + installDependencies(); VersionIdentifier resolvedVersion = request.getRequested().getResolvedVersion(); FileAccess fileAccess = this.context.getFileAccess(); From c2e1c7d9ff7a8d09ea17c7695bf5fc48230348fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Hohwiller?= Date: Fri, 24 Apr 2026 20:20:59 +0200 Subject: [PATCH 07/27] do not mock commandlet --- .../devonfw/tools/ide/tool/rust/RustTest.java | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/rust/RustTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/rust/RustTest.java index 341be6ab19..b78526c97c 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/tool/rust/RustTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/rust/RustTest.java @@ -19,7 +19,7 @@ void testRustInstallViaRustupScript() { // arrange IdeTestContext context = newContext(PROJECT_RUST); - Rust rust = new RustForTest(context); + Rust rust = context.getCommandletManager().getCommandlet(Rust.class); // act rust.install(); @@ -28,19 +28,5 @@ void testRustInstallViaRustupScript() { assertThat(context.getSoftwarePath().resolve("rust/.ide.software.version")).exists().hasContent(RUST_VERSION); assertThat(context).logAtSuccess().hasMessageContaining("Successfully installed rust in version " + RUST_VERSION); } - - private static class RustForTest extends Rust { - - RustForTest(IdeTestContext context) { - - super(context); - } - - @Override - protected void installDependencies() { - - // Skip heavyweight MSVC installer execution in tests. - } - } } From 65fe33e883951be9587ea27d3d7f4e6fba41827a Mon Sep 17 00:00:00 2001 From: Laert Llaveshi Date: Tue, 19 May 2026 11:45:47 +0200 Subject: [PATCH 08/27] #1719: Refactor LocalToolCommandlet and Rust.java --- .../tools/ide/tool/LocalToolCommandlet.java | 46 ++++++++++++++----- .../com/devonfw/tools/ide/tool/rust/Rust.java | 12 ++--- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java index 0a5d043bda..4cccc96996 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java @@ -223,25 +223,18 @@ public ToolInstallation installTool(ToolInstallRequest request) { } /** - * Performs the actual installation of the {@link #getName() tool} by downloading its binary, optionally extracting it, backing up any existing installation, - * and writing the version file. - *

- * This method assumes that the version has already been resolved and dependencies installed. It handles the final steps of placing the tool into the - * appropriate installation directory. + * Performs the actual installation of the {@link #getName() tool}. * * @param request the {@link ToolInstallRequest}. * @param installationPath the target {@link Path} where the {@link #getName() tool} should be installed. + * @see #doInstall(ToolInstallRequest, Path) */ protected void performToolInstallation(ToolInstallRequest request, Path installationPath) { + installDependencies(request); FileAccess fileAccess = this.context.getFileAccess(); ToolEditionAndVersion requested = request.getRequested(); VersionIdentifier resolvedVersion = requested.getResolvedVersion(); - Path downloadedToolFile = downloadTool(requested.getEdition().edition(), resolvedVersion); - boolean extract = isExtract(); - if (!extract) { - LOG.trace("Extraction is disabled for '{}' hence just moving the downloaded file {}.", this.tool, downloadedToolFile); - } if (Files.isDirectory(installationPath)) { if (this.tool.equals(IdeasyCommandlet.TOOL_NAME)) { LOG.warn("Your IDEasy installation is missing the version file."); @@ -250,13 +243,44 @@ protected void performToolInstallation(ToolInstallRequest request, Path installa } } fileAccess.mkdirs(installationPath.getParent()); - fileAccess.extract(downloadedToolFile, installationPath, this::postExtract, extract); + + doInstall(request, installationPath); + this.context.writeVersionFile(resolvedVersion, installationPath); // fix macOS Gatekeeper blocking - must run after version file is written but before any executables are launched getMacOsHelper().removeQuarantineAttribute(installationPath); LOG.debug("Installed {} in version {} at {}", this.tool, resolvedVersion, installationPath); } + /** + * Hook to install dependencies of this tool. + * + * @param request the {@link ToolInstallRequest}. + */ + protected void installDependencies(ToolInstallRequest request) { + + // nothing to do by default... + } + + /** + * Hook for the actual installation of the {@link #getName() tool}. The default implementation performs a + * {@link #downloadTool(String, VersionIdentifier) download} and {@link FileAccess#extract(Path, Path, java.util.function.Consumer, boolean) extraction}. + * + * @param request the {@link ToolInstallRequest}. + * @param installationPath the target {@link Path} where the {@link #getName() tool} should be installed. + */ + protected void doInstall(ToolInstallRequest request, Path installationPath) { + + ToolEditionAndVersion requested = request.getRequested(); + VersionIdentifier resolvedVersion = requested.getResolvedVersion(); + Path downloadedToolFile = downloadTool(requested.getEdition().edition(), resolvedVersion); + boolean extract = isExtract(); + if (!extract) { + LOG.trace("Extraction is disabled for '{}' hence just moving the downloaded file {}.", this.tool, downloadedToolFile); + } + this.context.getFileAccess().extract(downloadedToolFile, installationPath, this::postExtract, extract); + } + /** * @param edition the {@link #getConfiguredEdition() tool edition} to download. * @param resolvedVersion the resolved {@link VersionIdentifier version} to download. diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java b/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java index a5bb9bbfa4..9c978632c2 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java @@ -59,7 +59,8 @@ protected boolean isExtract() { return false; } - private void installDependencies() { + @Override + protected void installDependencies(ToolInstallRequest request) { if (this.context.getSystemInfo().isWindows()) { installWindowsMsvcBuildTools(); @@ -94,15 +95,11 @@ private void installWindowsMsvcBuildTools() { } @Override - protected void performToolInstallation(ToolInstallRequest request, Path installationPath) { + protected void doInstall(ToolInstallRequest request, Path installationPath) { - installDependencies(); VersionIdentifier resolvedVersion = request.getRequested().getResolvedVersion(); FileAccess fileAccess = this.context.getFileAccess(); - if (Files.isDirectory(installationPath)) { - fileAccess.backup(installationPath); - } fileAccess.mkdirs(installationPath); Path cargoHome = installationPath.resolve(".cargo"); @@ -174,9 +171,6 @@ protected void performToolInstallation(ToolInstallRequest request, Path installa if (Files.isDirectory(cargoBin)) { fileAccess.symlink(cargoBin, toolBin); } - - this.context.writeVersionFile(resolvedVersion, installationPath); - LOG.debug("Installed {} in version {} at {}", this.tool, resolvedVersion, installationPath); } private boolean isWindowsExeInstaller(Path installerPath) { From 99e7bd43555393e4ea07e1d2a3cad0cc0c785f41 Mon Sep 17 00:00:00 2001 From: Laert Llaveshi Date: Tue, 19 May 2026 13:35:24 +0200 Subject: [PATCH 09/27] #1719: Add getDownloadedToolFile method --- .../tools/ide/tool/LocalToolCommandlet.java | 27 +++++++++++++------ .../com/devonfw/tools/ide/tool/rust/Rust.java | 3 +-- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java index 4cccc96996..84bc4135a4 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java @@ -227,7 +227,7 @@ public ToolInstallation installTool(ToolInstallRequest request) { * * @param request the {@link ToolInstallRequest}. * @param installationPath the target {@link Path} where the {@link #getName() tool} should be installed. - * @see #doInstall(ToolInstallRequest, Path) + * @see #doInstall(ToolInstallRequest, Path, Path) */ protected void performToolInstallation(ToolInstallRequest request, Path installationPath) { @@ -235,6 +235,8 @@ protected void performToolInstallation(ToolInstallRequest request, Path installa FileAccess fileAccess = this.context.getFileAccess(); ToolEditionAndVersion requested = request.getRequested(); VersionIdentifier resolvedVersion = requested.getResolvedVersion(); + Path downloadedToolFile = getDownloadedToolFile(request); + if (Files.isDirectory(installationPath)) { if (this.tool.equals(IdeasyCommandlet.TOOL_NAME)) { LOG.warn("Your IDEasy installation is missing the version file."); @@ -244,7 +246,7 @@ protected void performToolInstallation(ToolInstallRequest request, Path installa } fileAccess.mkdirs(installationPath.getParent()); - doInstall(request, installationPath); + doInstall(request, installationPath, downloadedToolFile); this.context.writeVersionFile(resolvedVersion, installationPath); // fix macOS Gatekeeper blocking - must run after version file is written but before any executables are launched @@ -252,6 +254,17 @@ protected void performToolInstallation(ToolInstallRequest request, Path installa LOG.debug("Installed {} in version {} at {}", this.tool, resolvedVersion, installationPath); } + /** + * @param request the {@link ToolInstallRequest}. + * @return the {@link Path} to the downloaded tool file. + */ + protected Path getDownloadedToolFile(ToolInstallRequest request) { + + ToolEditionAndVersion requested = request.getRequested(); + VersionIdentifier resolvedVersion = requested.getResolvedVersion(); + return downloadTool(requested.getEdition().edition(), resolvedVersion); + } + /** * Hook to install dependencies of this tool. * @@ -263,17 +276,15 @@ protected void installDependencies(ToolInstallRequest request) { } /** - * Hook for the actual installation of the {@link #getName() tool}. The default implementation performs a - * {@link #downloadTool(String, VersionIdentifier) download} and {@link FileAccess#extract(Path, Path, java.util.function.Consumer, boolean) extraction}. + * Hook for the actual installation of the {@link #getName() tool}. The default implementation performs an + * {@link FileAccess#extract(Path, Path, java.util.function.Consumer, boolean) extraction}. * * @param request the {@link ToolInstallRequest}. * @param installationPath the target {@link Path} where the {@link #getName() tool} should be installed. + * @param downloadedToolFile the {@link Path} to the downloaded tool file. */ - protected void doInstall(ToolInstallRequest request, Path installationPath) { + protected void doInstall(ToolInstallRequest request, Path installationPath, Path downloadedToolFile) { - ToolEditionAndVersion requested = request.getRequested(); - VersionIdentifier resolvedVersion = requested.getResolvedVersion(); - Path downloadedToolFile = downloadTool(requested.getEdition().edition(), resolvedVersion); boolean extract = isExtract(); if (!extract) { LOG.trace("Extraction is disabled for '{}' hence just moving the downloaded file {}.", this.tool, downloadedToolFile); diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java b/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java index 9c978632c2..9d09d113f6 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java @@ -95,7 +95,7 @@ private void installWindowsMsvcBuildTools() { } @Override - protected void doInstall(ToolInstallRequest request, Path installationPath) { + protected void doInstall(ToolInstallRequest request, Path installationPath, Path installerScript) { VersionIdentifier resolvedVersion = request.getRequested().getResolvedVersion(); FileAccess fileAccess = this.context.getFileAccess(); @@ -107,7 +107,6 @@ protected void doInstall(ToolInstallRequest request, Path installationPath) { fileAccess.mkdirs(cargoHome); fileAccess.mkdirs(rustupHome); - Path installerScript = downloadTool(request.getRequested().getEdition().edition(), resolvedVersion); if (Files.isDirectory(installerScript)) { // ToolRepositoryMock may provide an unpacked folder instead of a single download file. installerScript = installerScript.resolve("content.sh"); From 4facfe8c5682dac8d304ef01dd6bc20a9237ac05 Mon Sep 17 00:00:00 2001 From: Laert Llaveshi Date: Fri, 22 May 2026 09:40:49 +0200 Subject: [PATCH 10/27] #1719: More refactoring --- .../tools/ide/tool/LocalToolCommandlet.java | 36 +++-- .../com/devonfw/tools/ide/tool/rust/Rust.java | 126 ++++++++---------- 2 files changed, 71 insertions(+), 91 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java index 84bc4135a4..233fa5910d 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java @@ -227,7 +227,6 @@ public ToolInstallation installTool(ToolInstallRequest request) { * * @param request the {@link ToolInstallRequest}. * @param installationPath the target {@link Path} where the {@link #getName() tool} should be installed. - * @see #doInstall(ToolInstallRequest, Path, Path) */ protected void performToolInstallation(ToolInstallRequest request, Path installationPath) { @@ -246,7 +245,7 @@ protected void performToolInstallation(ToolInstallRequest request, Path installa } fileAccess.mkdirs(installationPath.getParent()); - doInstall(request, installationPath, downloadedToolFile); + onInstall(request, installationPath, downloadedToolFile); this.context.writeVersionFile(resolvedVersion, installationPath); // fix macOS Gatekeeper blocking - must run after version file is written but before any executables are launched @@ -254,6 +253,22 @@ protected void performToolInstallation(ToolInstallRequest request, Path installa LOG.debug("Installed {} in version {} at {}", this.tool, resolvedVersion, installationPath); } + /** + * Performs the actual installation of the tool bits. + * + * @param request the {@link ToolInstallRequest}. + * @param installationPath the target {@link Path} where the tool should be installed. + * @param downloadedToolFile the {@link Path} to the downloaded tool file. + */ + protected void onInstall(ToolInstallRequest request, Path installationPath, Path downloadedToolFile) { + + boolean extract = isExtract(); + if (!extract) { + LOG.trace("Extraction is disabled for '{}' hence just moving the downloaded file {}.", this.tool, downloadedToolFile); + } + this.context.getFileAccess().extract(downloadedToolFile, installationPath, this::postExtract, extract); + } + /** * @param request the {@link ToolInstallRequest}. * @return the {@link Path} to the downloaded tool file. @@ -275,23 +290,6 @@ protected void installDependencies(ToolInstallRequest request) { // nothing to do by default... } - /** - * Hook for the actual installation of the {@link #getName() tool}. The default implementation performs an - * {@link FileAccess#extract(Path, Path, java.util.function.Consumer, boolean) extraction}. - * - * @param request the {@link ToolInstallRequest}. - * @param installationPath the target {@link Path} where the {@link #getName() tool} should be installed. - * @param downloadedToolFile the {@link Path} to the downloaded tool file. - */ - protected void doInstall(ToolInstallRequest request, Path installationPath, Path downloadedToolFile) { - - boolean extract = isExtract(); - if (!extract) { - LOG.trace("Extraction is disabled for '{}' hence just moving the downloaded file {}.", this.tool, downloadedToolFile); - } - this.context.getFileAccess().extract(downloadedToolFile, installationPath, this::postExtract, extract); - } - /** * @param edition the {@link #getConfiguredEdition() tool edition} to download. * @param resolvedVersion the resolved {@link VersionIdentifier version} to download. diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java b/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java index 9d09d113f6..69b7b9f5b7 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java @@ -13,10 +13,12 @@ import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.io.FileAccess; import com.devonfw.tools.ide.io.FileCopyMode; +import com.devonfw.tools.ide.process.EnvironmentContext; import com.devonfw.tools.ide.process.ProcessContext; import com.devonfw.tools.ide.process.ProcessErrorHandling; import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolInstallRequest; +import com.devonfw.tools.ide.tool.ToolInstallation; import com.devonfw.tools.ide.version.VersionIdentifier; /** @@ -67,41 +69,19 @@ protected void installDependencies(ToolInstallRequest request) { } } - protected String getMsvcSetupUrl() { - - return MSVC_SETUP_URL; - } - - protected List getMsvcInstallerArgs() { - - return List.of("--quiet", "--wait", "--norestart", "--nocache", "--add", "Microsoft.VisualStudio.Workload.VCTools"); - } - - protected List getRustupInstallerArgs(VersionIdentifier version) { - - return List.of("-y", "--no-modify-path", "--profile", "default", "--default-toolchain", version.toString()); - } - - private void installWindowsMsvcBuildTools() { - - FileAccess fileAccess = this.context.getFileAccess(); - Path tempDir = fileAccess.createTempDir("msvc-setup"); - Path installer = tempDir.resolve("vs_BuildTools.exe"); - fileAccess.download(getMsvcSetupUrl(), installer); - - ProcessContext process = this.context.newProcess().errorHandling(ProcessErrorHandling.THROW_CLI).executable(installer) - .withExitCodeAcceptor(code -> (code == 0) || (code == 3010)).addArgs(getMsvcInstallerArgs()); - process.run(); - } - + /** + * Performs the actual installation of the tool bits. + * + * @param request the {@link ToolInstallRequest}. + * @param installationPath the target {@link Path} where the tool should be installed. + * @param installerScript the {@link Path} to the downloaded tool file. + */ @Override - protected void doInstall(ToolInstallRequest request, Path installationPath, Path installerScript) { + protected void onInstall(ToolInstallRequest request, Path installationPath, Path installerScript) { VersionIdentifier resolvedVersion = request.getRequested().getResolvedVersion(); FileAccess fileAccess = this.context.getFileAccess(); - fileAccess.mkdirs(installationPath); - Path cargoHome = installationPath.resolve(".cargo"); Path rustupHome = installationPath.resolve(".rustup"); fileAccess.mkdirs(cargoHome); @@ -112,53 +92,16 @@ protected void doInstall(ToolInstallRequest request, Path installationPath, Path installerScript = installerScript.resolve("content.sh"); } - List installerArgs = getRustupInstallerArgs(resolvedVersion); ProcessContext process = request.getProcessContext().createChild().errorHandling(ProcessErrorHandling.THROW_CLI).directory(installationPath) .withEnvVar("CARGO_HOME", cargoHome.toString()).withEnvVar("RUSTUP_HOME", rustupHome.toString()); + + List installerArgs = List.of("-y", "--no-modify-path", "--profile", "default", "--default-toolchain", resolvedVersion.toString()); + if (isWindowsExeInstaller(installerScript)) { - Path installerExecutable = installerScript; - String fileName = installerScript.getFileName().toString(); - if (!WINDOWS_RUSTUP_INIT_EXE.equalsIgnoreCase(fileName)) { - Path canonicalInstaller = installerScript.resolveSibling(WINDOWS_RUSTUP_INIT_EXE); - - // Handle corrupted installations where rustup-init.exe might exist as a file or directory - if (Files.exists(canonicalInstaller, LinkOption.NOFOLLOW_LINKS)) { - LOG.info("Found existing installer at {}, checking type", canonicalInstaller); - boolean isDirectory = Files.isDirectory(canonicalInstaller, LinkOption.NOFOLLOW_LINKS); - LOG.info("Existing installer is {} (directory: {}), deleting it", canonicalInstaller, isDirectory); - - try { - // First attempt: delete using fileAccess which handles files, directories, and symlinks - fileAccess.delete(canonicalInstaller); - LOG.debug("Successfully deleted existing installer at {}", canonicalInstaller); - } catch (IllegalStateException e) { - LOG.warn("First deletion attempt failed for {}, retrying: {}", canonicalInstaller, e.getMessage()); - // Retry in case of transient lock issues - try { - fileAccess.delete(canonicalInstaller); - LOG.debug("Successfully deleted existing installer on retry at {}", canonicalInstaller); - } catch (IllegalStateException e2) { - LOG.error("Failed to delete {} after retry: {}", canonicalInstaller, e2.getMessage(), e2); - throw new IllegalStateException("Failed to clean up corrupted installer at " + canonicalInstaller - + " (may be locked by another process or permission denied)", e2); - } - } - - // Verify deletion was successful - if (Files.exists(canonicalInstaller, LinkOption.NOFOLLOW_LINKS)) { - boolean stillIsDirectory = Files.isDirectory(canonicalInstaller, LinkOption.NOFOLLOW_LINKS); - throw new IllegalStateException("Failed to delete corrupted installer at " + canonicalInstaller - + " (is directory: " + stillIsDirectory + ", may be locked by another process)"); - } - } - - fileAccess.copy(installerScript, canonicalInstaller, FileCopyMode.COPY_FILE_TO_TARGET_OVERRIDE); - installerExecutable = canonicalInstaller; - } + Path installerExecutable = prepareWindowsInstaller(fileAccess, installerScript); process.executable(installerExecutable).addArgs(installerArgs); } else { - String installerScriptArg = installerScript.toAbsolutePath().toString(); - process.executable(this.context.findBashRequired()).addArgs(installerScriptArg).addArgs(installerArgs); + process.executable(this.context.findBashRequired()).addArgs(installerScript.toAbsolutePath().toString()).addArgs(installerArgs); } process.run(); @@ -172,6 +115,45 @@ protected void doInstall(ToolInstallRequest request, Path installationPath, Path } } + @Override + public void setEnvironment(EnvironmentContext environmentContext, ToolInstallation toolInstallation, boolean additionalInstallation) { + + super.setEnvironment(environmentContext, toolInstallation, additionalInstallation); + Path rootDir = toolInstallation.rootDir(); + environmentContext.withEnvVar("CARGO_HOME", rootDir.resolve(".cargo").toString()); + environmentContext.withEnvVar("RUSTUP_HOME", rootDir.resolve(".rustup").toString()); + } + + private void installWindowsMsvcBuildTools() { + + FileAccess fileAccess = this.context.getFileAccess(); + Path tempDir = fileAccess.createTempDir("msvc-setup"); + Path installer = tempDir.resolve("vs_BuildTools.exe"); + fileAccess.download(MSVC_SETUP_URL, installer); + + this.context.newProcess().errorHandling(ProcessErrorHandling.THROW_CLI).executable(installer) + .withExitCodeAcceptor(code -> (code == 0) || (code == 3010)) + .addArgs("--quiet", "--wait", "--norestart", "--nocache", "--add", "Microsoft.VisualStudio.Workload.VCTools") + .run(); + } + + private Path prepareWindowsInstaller(FileAccess fileAccess, Path installerScript) { + + String fileName = installerScript.getFileName().toString(); + if (WINDOWS_RUSTUP_INIT_EXE.equalsIgnoreCase(fileName)) { + return installerScript; + } + Path canonicalInstaller = installerScript.resolveSibling(WINDOWS_RUSTUP_INIT_EXE); + if (Files.exists(canonicalInstaller, LinkOption.NOFOLLOW_LINKS)) { + LOG.info("Found existing installer at {}, checking type", canonicalInstaller); + boolean isDirectory = Files.isDirectory(canonicalInstaller, LinkOption.NOFOLLOW_LINKS); + LOG.info("Existing installer is {} (directory: {}), deleting it", canonicalInstaller, isDirectory); + fileAccess.delete(canonicalInstaller); + } + fileAccess.copy(installerScript, canonicalInstaller, FileCopyMode.COPY_FILE_TO_TARGET_OVERRIDE); + return canonicalInstaller; + } + private boolean isWindowsExeInstaller(Path installerPath) { if (!this.context.getSystemInfo().isWindows()) { From 325c3f3de6204fa6b8a34a232739c0fb78125fb9 Mon Sep 17 00:00:00 2001 From: Laert Llaveshi Date: Fri, 22 May 2026 09:52:03 +0200 Subject: [PATCH 11/27] #1719: Revert unnecessary docstring change --- .../com/devonfw/tools/ide/tool/LocalToolCommandlet.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java index 233fa5910d..7f6e464e55 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java @@ -223,7 +223,12 @@ public ToolInstallation installTool(ToolInstallRequest request) { } /** - * Performs the actual installation of the {@link #getName() tool}. + * Performs the actual installation of the {@link #getName() tool} by orchestrating the installation steps: + * dependency installation, tool-specific placement, versioning, and cleanup. + *

+ * This method assumes that the version has already been resolved. It handles the lifecycle of + * placing the tool into the appropriate installation directory by delegating the actual bit + * placement to {@link #onInstall(ToolInstallRequest, Path, Path)}. * * @param request the {@link ToolInstallRequest}. * @param installationPath the target {@link Path} where the {@link #getName() tool} should be installed. From 0094c8aa68f6ae9dafbee42b9079f4f6e04618c4 Mon Sep 17 00:00:00 2001 From: Laert Llaveshi Date: Fri, 22 May 2026 10:03:35 +0200 Subject: [PATCH 12/27] #1719: Cleanup --- .../devonfw/tools/ide/tool/LocalToolCommandlet.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java index 7f6e464e55..07701b182a 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java @@ -223,12 +223,11 @@ public ToolInstallation installTool(ToolInstallRequest request) { } /** - * Performs the actual installation of the {@link #getName() tool} by orchestrating the installation steps: - * dependency installation, tool-specific placement, versioning, and cleanup. + * Performs the installation of the {@link #getName() tool} by using {@link #onInstall(ToolInstallRequest, Path, Path)} │ │ + * for tool-specific logic, backing up any existing installation, and writing the version file. *

- * This method assumes that the version has already been resolved. It handles the lifecycle of - * placing the tool into the appropriate installation directory by delegating the actual bit - * placement to {@link #onInstall(ToolInstallRequest, Path, Path)}. + * This method assumes that the version has already been resolved and dependencies installed. It handles the final steps of placing the tool into the + * appropriate installation directory. * * @param request the {@link ToolInstallRequest}. * @param installationPath the target {@link Path} where the {@link #getName() tool} should be installed. @@ -292,7 +291,7 @@ protected Path getDownloadedToolFile(ToolInstallRequest request) { */ protected void installDependencies(ToolInstallRequest request) { - // nothing to do by default... + // nothing to do by default } /** From d9c6f7dda2f1d4939b93318668e129242812a53e Mon Sep 17 00:00:00 2001 From: Laert Llaveshi Date: Fri, 22 May 2026 10:04:58 +0200 Subject: [PATCH 13/27] #1719: Cleanup --- .../java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java index 07701b182a..b569fbb3f4 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java @@ -223,8 +223,8 @@ public ToolInstallation installTool(ToolInstallRequest request) { } /** - * Performs the installation of the {@link #getName() tool} by using {@link #onInstall(ToolInstallRequest, Path, Path)} │ │ - * for tool-specific logic, backing up any existing installation, and writing the version file. + * Performs the installation of the {@link #getName() tool} by using {@link #onInstall(ToolInstallRequest, Path, Path)} + * for tool-specific logic, backing up any existing installation, and writing the version file. *

* This method assumes that the version has already been resolved and dependencies installed. It handles the final steps of placing the tool into the * appropriate installation directory. From 7f446353221651b56a6fef164178844342334dd4 Mon Sep 17 00:00:00 2001 From: Laert Llaveshi Date: Fri, 22 May 2026 10:19:40 +0200 Subject: [PATCH 14/27] #1719: Rename of RustGithubUrlTagUpdaterTest --- .../tools/ide/url/tool/rust/RustGithubUrlTagUpdaterTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/url-updater/src/test/java/com/devonfw/tools/ide/url/tool/rust/RustGithubUrlTagUpdaterTest.java b/url-updater/src/test/java/com/devonfw/tools/ide/url/tool/rust/RustGithubUrlTagUpdaterTest.java index 8d2dd6aaec..42b10cef73 100644 --- a/url-updater/src/test/java/com/devonfw/tools/ide/url/tool/rust/RustGithubUrlTagUpdaterTest.java +++ b/url-updater/src/test/java/com/devonfw/tools/ide/url/tool/rust/RustGithubUrlTagUpdaterTest.java @@ -20,7 +20,7 @@ * Test of {@link RustUrlUpdater}. */ @WireMockTest -class RustGithubUrlTagUpdaterTest extends AbstractUrlUpdaterTest { +class RustUrlUpdaterTest extends AbstractUrlUpdaterTest { @Test void testRustGithubUrlUpdater(@TempDir Path tempDir, WireMockRuntimeInfo wmRuntimeInfo) { From 4ab5e21f420f7cf27919a78a97bb873402637684 Mon Sep 17 00:00:00 2001 From: Laert Llaveshi Date: Fri, 22 May 2026 10:38:33 +0200 Subject: [PATCH 15/27] #1719: Revert rename of RustGithubUrlTagUpdaterTest --- .../tools/ide/url/tool/rust/RustGithubUrlTagUpdaterTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/url-updater/src/test/java/com/devonfw/tools/ide/url/tool/rust/RustGithubUrlTagUpdaterTest.java b/url-updater/src/test/java/com/devonfw/tools/ide/url/tool/rust/RustGithubUrlTagUpdaterTest.java index 42b10cef73..8d2dd6aaec 100644 --- a/url-updater/src/test/java/com/devonfw/tools/ide/url/tool/rust/RustGithubUrlTagUpdaterTest.java +++ b/url-updater/src/test/java/com/devonfw/tools/ide/url/tool/rust/RustGithubUrlTagUpdaterTest.java @@ -20,7 +20,7 @@ * Test of {@link RustUrlUpdater}. */ @WireMockTest -class RustUrlUpdaterTest extends AbstractUrlUpdaterTest { +class RustGithubUrlTagUpdaterTest extends AbstractUrlUpdaterTest { @Test void testRustGithubUrlUpdater(@TempDir Path tempDir, WireMockRuntimeInfo wmRuntimeInfo) { From 6ba349bf68491ba1cb83ffac93b01bc0f4bac6f4 Mon Sep 17 00:00:00 2001 From: Laert Llaveshi Date: Fri, 22 May 2026 13:08:36 +0200 Subject: [PATCH 16/27] #1719: Removed installDependencies method --- .../devonfw/tools/ide/tool/LocalToolCommandlet.java | 11 ----------- .../java/com/devonfw/tools/ide/tool/rust/Rust.java | 12 ++++-------- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java index b569fbb3f4..1b286ac4ea 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java @@ -234,7 +234,6 @@ public ToolInstallation installTool(ToolInstallRequest request) { */ protected void performToolInstallation(ToolInstallRequest request, Path installationPath) { - installDependencies(request); FileAccess fileAccess = this.context.getFileAccess(); ToolEditionAndVersion requested = request.getRequested(); VersionIdentifier resolvedVersion = requested.getResolvedVersion(); @@ -284,16 +283,6 @@ protected Path getDownloadedToolFile(ToolInstallRequest request) { return downloadTool(requested.getEdition().edition(), resolvedVersion); } - /** - * Hook to install dependencies of this tool. - * - * @param request the {@link ToolInstallRequest}. - */ - protected void installDependencies(ToolInstallRequest request) { - - // nothing to do by default - } - /** * @param edition the {@link #getConfiguredEdition() tool edition} to download. * @param resolvedVersion the resolved {@link VersionIdentifier version} to download. diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java b/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java index 69b7b9f5b7..a9dca7aa8c 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java @@ -61,14 +61,6 @@ protected boolean isExtract() { return false; } - @Override - protected void installDependencies(ToolInstallRequest request) { - - if (this.context.getSystemInfo().isWindows()) { - installWindowsMsvcBuildTools(); - } - } - /** * Performs the actual installation of the tool bits. * @@ -79,6 +71,10 @@ protected void installDependencies(ToolInstallRequest request) { @Override protected void onInstall(ToolInstallRequest request, Path installationPath, Path installerScript) { + if (this.context.getSystemInfo().isWindows()) { + installWindowsMsvcBuildTools(); + } + VersionIdentifier resolvedVersion = request.getRequested().getResolvedVersion(); FileAccess fileAccess = this.context.getFileAccess(); From eeede769dbe13beb20f7469d8b608470d22283b8 Mon Sep 17 00:00:00 2001 From: Laert Llaveshi Date: Tue, 26 May 2026 08:31:38 +0200 Subject: [PATCH 17/27] #1719: MSVC url updater and installation with dependencies.json --- .../com/devonfw/tools/ide/tool/msvc/Msvc.java | 30 ++++++++++ .../com/devonfw/tools/ide/tool/rust/Rust.java | 59 +------------------ .../ide/url/tool/msvc/MsvcUrlUpdater.java | 35 +++++++++++ .../tools/ide/url/updater/UpdateManager.java | 3 +- 4 files changed, 68 insertions(+), 59 deletions(-) create mode 100644 cli/src/main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java create mode 100644 url-updater/src/main/java/com/devonfw/tools/ide/url/tool/msvc/MsvcUrlUpdater.java diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java b/cli/src/main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java new file mode 100644 index 0000000000..88b87d318f --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java @@ -0,0 +1,30 @@ +package com.devonfw.tools.ide.tool.msvc; + +import java.nio.file.Path; +import java.util.Set; + +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.process.ProcessErrorHandling; +import com.devonfw.tools.ide.tool.LocalToolCommandlet; +import com.devonfw.tools.ide.tool.ToolInstallRequest; + +public class Msvc extends LocalToolCommandlet { + + public Msvc(IdeContext context) { + super(context, "msvc", Set.of()); + } + + @Override + protected boolean isExtract() { + return false; + } + + @Override + protected void onInstall(ToolInstallRequest request, Path installationPath, Path installerScript) { + this.context.newProcess().errorHandling(ProcessErrorHandling.THROW_CLI).executable(installerScript) + .withExitCodeAcceptor(code -> (code == 0) || (code == 3010)) + .addArgs("--quiet", "--wait", "--norestart", "--nocache", "--add", "Microsoft.VisualStudio.Workload.VCTools") + .run(); + } + +} diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java b/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java index a9dca7aa8c..79af7cafbb 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java @@ -6,13 +6,9 @@ import java.util.List; import java.util.Set; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.io.FileAccess; -import com.devonfw.tools.ide.io.FileCopyMode; import com.devonfw.tools.ide.process.EnvironmentContext; import com.devonfw.tools.ide.process.ProcessContext; import com.devonfw.tools.ide.process.ProcessErrorHandling; @@ -26,12 +22,6 @@ */ public class Rust extends LocalToolCommandlet { - private static final Logger LOG = LoggerFactory.getLogger(Rust.class); - - private static final String MSVC_SETUP_URL = "https://aka.ms/vs/17/release/vs_BuildTools.exe"; - - private static final String WINDOWS_RUSTUP_INIT_EXE = "rustup-init.exe"; - /** * The constructor. * @@ -71,10 +61,6 @@ protected boolean isExtract() { @Override protected void onInstall(ToolInstallRequest request, Path installationPath, Path installerScript) { - if (this.context.getSystemInfo().isWindows()) { - installWindowsMsvcBuildTools(); - } - VersionIdentifier resolvedVersion = request.getRequested().getResolvedVersion(); FileAccess fileAccess = this.context.getFileAccess(); @@ -93,12 +79,7 @@ protected void onInstall(ToolInstallRequest request, Path installationPath, Path List installerArgs = List.of("-y", "--no-modify-path", "--profile", "default", "--default-toolchain", resolvedVersion.toString()); - if (isWindowsExeInstaller(installerScript)) { - Path installerExecutable = prepareWindowsInstaller(fileAccess, installerScript); - process.executable(installerExecutable).addArgs(installerArgs); - } else { - process.executable(this.context.findBashRequired()).addArgs(installerScript.toAbsolutePath().toString()).addArgs(installerArgs); - } + process.executable(this.context.findBashRequired()).addArgs(installerScript.toAbsolutePath().toString()).addArgs(installerArgs); process.run(); Path cargoBin = cargoHome.resolve("bin"); @@ -120,42 +101,4 @@ public void setEnvironment(EnvironmentContext environmentContext, ToolInstallati environmentContext.withEnvVar("RUSTUP_HOME", rootDir.resolve(".rustup").toString()); } - private void installWindowsMsvcBuildTools() { - - FileAccess fileAccess = this.context.getFileAccess(); - Path tempDir = fileAccess.createTempDir("msvc-setup"); - Path installer = tempDir.resolve("vs_BuildTools.exe"); - fileAccess.download(MSVC_SETUP_URL, installer); - - this.context.newProcess().errorHandling(ProcessErrorHandling.THROW_CLI).executable(installer) - .withExitCodeAcceptor(code -> (code == 0) || (code == 3010)) - .addArgs("--quiet", "--wait", "--norestart", "--nocache", "--add", "Microsoft.VisualStudio.Workload.VCTools") - .run(); - } - - private Path prepareWindowsInstaller(FileAccess fileAccess, Path installerScript) { - - String fileName = installerScript.getFileName().toString(); - if (WINDOWS_RUSTUP_INIT_EXE.equalsIgnoreCase(fileName)) { - return installerScript; - } - Path canonicalInstaller = installerScript.resolveSibling(WINDOWS_RUSTUP_INIT_EXE); - if (Files.exists(canonicalInstaller, LinkOption.NOFOLLOW_LINKS)) { - LOG.info("Found existing installer at {}, checking type", canonicalInstaller); - boolean isDirectory = Files.isDirectory(canonicalInstaller, LinkOption.NOFOLLOW_LINKS); - LOG.info("Existing installer is {} (directory: {}), deleting it", canonicalInstaller, isDirectory); - fileAccess.delete(canonicalInstaller); - } - fileAccess.copy(installerScript, canonicalInstaller, FileCopyMode.COPY_FILE_TO_TARGET_OVERRIDE); - return canonicalInstaller; - } - - private boolean isWindowsExeInstaller(Path installerPath) { - - if (!this.context.getSystemInfo().isWindows()) { - return false; - } - Path fileName = installerPath.getFileName(); - return (fileName != null) && fileName.toString().toLowerCase().endsWith(".exe"); - } } diff --git a/url-updater/src/main/java/com/devonfw/tools/ide/url/tool/msvc/MsvcUrlUpdater.java b/url-updater/src/main/java/com/devonfw/tools/ide/url/tool/msvc/MsvcUrlUpdater.java new file mode 100644 index 0000000000..37e3dd44c3 --- /dev/null +++ b/url-updater/src/main/java/com/devonfw/tools/ide/url/tool/msvc/MsvcUrlUpdater.java @@ -0,0 +1,35 @@ +package com.devonfw.tools.ide.url.tool.msvc; + +import java.util.Set; + +import com.devonfw.tools.ide.os.OperatingSystem; +import com.devonfw.tools.ide.url.model.folder.UrlVersion; +import com.devonfw.tools.ide.url.updater.AbstractUrlUpdater; + +public class MsvcUrlUpdater extends AbstractUrlUpdater { + + @Override + public String getTool() { + return "msvc"; + } + + @Override + protected Set getVersions() { + return Set.of("17.0"); + } + + @Override + protected String getVersionBaseUrl() { + return "https://aka.ms/vs"; + } + + @Override + protected String getDownloadBaseUrl() { + return "https://aka.ms/vs"; + } + + @Override + protected void addVersion(UrlVersion urlVersion) { + doAddVersion(urlVersion, "https://aka.ms/vs/17/release/vs_BuildTools.exe", OperatingSystem.WINDOWS); + } +} diff --git a/url-updater/src/main/java/com/devonfw/tools/ide/url/updater/UpdateManager.java b/url-updater/src/main/java/com/devonfw/tools/ide/url/updater/UpdateManager.java index 5f51a4e10d..b469a515c8 100644 --- a/url-updater/src/main/java/com/devonfw/tools/ide/url/updater/UpdateManager.java +++ b/url-updater/src/main/java/com/devonfw/tools/ide/url/updater/UpdateManager.java @@ -40,6 +40,7 @@ import com.devonfw.tools.ide.url.tool.kotlinc.KotlincNativeUrlUpdater; import com.devonfw.tools.ide.url.tool.kotlinc.KotlincUrlUpdater; import com.devonfw.tools.ide.url.tool.lazydocker.LazyDockerUrlUpdater; +import com.devonfw.tools.ide.url.tool.msvc.MsvcUrlUpdater; import com.devonfw.tools.ide.url.tool.mvn.MvnUrlUpdater; import com.devonfw.tools.ide.url.tool.mvnd.MvndUrlUpdater; import com.devonfw.tools.ide.url.tool.ng.NgUrlUpdater; @@ -79,7 +80,7 @@ public class UpdateManager extends AbstractProcessorWithTimeout { new GcViewerUrlUpdater(), new GhUrlUpdater(), new GoUrlUpdater(), new GraalVmCommunityUpdater(), new GraalVmOracleUrlUpdater(), new GradleUrlUpdater(), new HelmUrlUpdater(), new InsoUrlUpdater(), new IntellijUrlUpdater(), new JasyptUrlUpdater(), new JavaAzulUrlUpdater(), new JavaUrlUpdater(), new JenkinsUrlUpdater(), new JmcUrlUpdater(), new KotlincUrlUpdater(), - new KotlincNativeUrlUpdater(), new LazyDockerUrlUpdater(), new MvnUrlUpdater(), new MvndUrlUpdater(), + new KotlincNativeUrlUpdater(), new LazyDockerUrlUpdater(), new MsvcUrlUpdater(), new MvnUrlUpdater(), new MvndUrlUpdater(), new NgUrlUpdater(), new NodeUrlUpdater(), new NpmUrlUpdater(), new OcUrlUpdater(), new PgAdminUrlUpdater(), new PipUrlUpdater(), new PycharmUrlUpdater(), new PythonUrlUpdater(), new QuarkusUrlUpdater(), new RustUrlUpdater(), new DockerRancherDesktopUrlUpdater(), new SonarUrlUpdater(), new SquirrelSqlUrlUpdater(), From 91a346a4b0a9908d28eb21c783d239f59024d083 Mon Sep 17 00:00:00 2001 From: Laert Llaveshi Date: Tue, 26 May 2026 11:02:01 +0200 Subject: [PATCH 18/27] #1719: MSVC download link --- .../ide/commandlet/CommandletManagerImpl.java | 2 + .../com/devonfw/tools/ide/tool/msvc/Msvc.java | 41 +++++++++++-------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java index bc5868c99d..1a18e534ae 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java @@ -46,6 +46,7 @@ import com.devonfw.tools.ide.tool.kubectl.KubeCtl; import com.devonfw.tools.ide.tool.lazydocker.LazyDocker; import com.devonfw.tools.ide.tool.mvn.Mvn; +import com.devonfw.tools.ide.tool.msvc.Msvc; import com.devonfw.tools.ide.tool.nest.Nest; import com.devonfw.tools.ide.tool.ng.Ng; import com.devonfw.tools.ide.tool.node.Node; @@ -127,6 +128,7 @@ public CommandletManagerImpl(IdeContext context) { add(new Node(context)); add(new Npm(context)); add(new Mvn(context)); + add(new Msvc(context)); add(new GcViewer(context)); add(new Gradle(context)); add(new Eclipse(context)); diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java b/cli/src/main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java index 88b87d318f..6439541f3e 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java @@ -2,29 +2,36 @@ import java.nio.file.Path; import java.util.Set; - import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.process.ProcessErrorHandling; import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolInstallRequest; +import com.devonfw.tools.ide.version.VersionIdentifier; public class Msvc extends LocalToolCommandlet { - - public Msvc(IdeContext context) { - super(context, "msvc", Set.of()); - } + private static final String MSVC_INSTALLER_URL = "https://aka.ms/vs/17/release/vs_BuildTools.exe"; + + public Msvc(IdeContext context) { + super(context, "msvc", Set.of()); + } + + @Override + protected boolean isExtract() { + return false; // Native .exe installer + } - @Override - protected boolean isExtract() { - return false; - } + @Override + protected Path downloadTool(String edition, VersionIdentifier resolvedVersion) { + Path tempFile = this.context.getFileAccess().createTempFile("msvc", ".exe"); + this.context.getFileAccess().download(MSVC_INSTALLER_URL, tempFile); + return tempFile; + } - @Override - protected void onInstall(ToolInstallRequest request, Path installationPath, Path installerScript) { - this.context.newProcess().errorHandling(ProcessErrorHandling.THROW_CLI).executable(installerScript) - .withExitCodeAcceptor(code -> (code == 0) || (code == 3010)) - .addArgs("--quiet", "--wait", "--norestart", "--nocache", "--add", "Microsoft.VisualStudio.Workload.VCTools") - .run(); - } - + @Override + protected void onInstall(ToolInstallRequest request, Path installationPath, Path installerScript) { + this.context.newProcess().errorHandling(ProcessErrorHandling.THROW_CLI).executable(installerScript) + .withExitCodeAcceptor(code -> (code == 0) || (code == 3010)) + .addArgs("--quiet", "--wait", "--norestart", "--nocache", "--add", "Microsoft.VisualStudio.Workload.VCTools") + .run(); + } } From 4d34c41e368bf4678f449dd3af6874fd1abd8852 Mon Sep 17 00:00:00 2001 From: Laert Llaveshi Date: Tue, 26 May 2026 15:48:32 +0200 Subject: [PATCH 19/27] #1719: Fixing MSVC commandlet --- .../com/devonfw/tools/ide/tool/msvc/Msvc.java | 9 ----- .../ide/url/tool/msvc/MsvcUrlUpdater.java | 35 ------------------- .../tools/ide/url/updater/UpdateManager.java | 3 +- 3 files changed, 1 insertion(+), 46 deletions(-) delete mode 100644 url-updater/src/main/java/com/devonfw/tools/ide/url/tool/msvc/MsvcUrlUpdater.java diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java b/cli/src/main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java index 6439541f3e..f0ae678df2 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java @@ -6,10 +6,8 @@ import com.devonfw.tools.ide.process.ProcessErrorHandling; import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolInstallRequest; -import com.devonfw.tools.ide.version.VersionIdentifier; public class Msvc extends LocalToolCommandlet { - private static final String MSVC_INSTALLER_URL = "https://aka.ms/vs/17/release/vs_BuildTools.exe"; public Msvc(IdeContext context) { super(context, "msvc", Set.of()); @@ -20,13 +18,6 @@ protected boolean isExtract() { return false; // Native .exe installer } - @Override - protected Path downloadTool(String edition, VersionIdentifier resolvedVersion) { - Path tempFile = this.context.getFileAccess().createTempFile("msvc", ".exe"); - this.context.getFileAccess().download(MSVC_INSTALLER_URL, tempFile); - return tempFile; - } - @Override protected void onInstall(ToolInstallRequest request, Path installationPath, Path installerScript) { this.context.newProcess().errorHandling(ProcessErrorHandling.THROW_CLI).executable(installerScript) diff --git a/url-updater/src/main/java/com/devonfw/tools/ide/url/tool/msvc/MsvcUrlUpdater.java b/url-updater/src/main/java/com/devonfw/tools/ide/url/tool/msvc/MsvcUrlUpdater.java deleted file mode 100644 index 37e3dd44c3..0000000000 --- a/url-updater/src/main/java/com/devonfw/tools/ide/url/tool/msvc/MsvcUrlUpdater.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.devonfw.tools.ide.url.tool.msvc; - -import java.util.Set; - -import com.devonfw.tools.ide.os.OperatingSystem; -import com.devonfw.tools.ide.url.model.folder.UrlVersion; -import com.devonfw.tools.ide.url.updater.AbstractUrlUpdater; - -public class MsvcUrlUpdater extends AbstractUrlUpdater { - - @Override - public String getTool() { - return "msvc"; - } - - @Override - protected Set getVersions() { - return Set.of("17.0"); - } - - @Override - protected String getVersionBaseUrl() { - return "https://aka.ms/vs"; - } - - @Override - protected String getDownloadBaseUrl() { - return "https://aka.ms/vs"; - } - - @Override - protected void addVersion(UrlVersion urlVersion) { - doAddVersion(urlVersion, "https://aka.ms/vs/17/release/vs_BuildTools.exe", OperatingSystem.WINDOWS); - } -} diff --git a/url-updater/src/main/java/com/devonfw/tools/ide/url/updater/UpdateManager.java b/url-updater/src/main/java/com/devonfw/tools/ide/url/updater/UpdateManager.java index b469a515c8..5f51a4e10d 100644 --- a/url-updater/src/main/java/com/devonfw/tools/ide/url/updater/UpdateManager.java +++ b/url-updater/src/main/java/com/devonfw/tools/ide/url/updater/UpdateManager.java @@ -40,7 +40,6 @@ import com.devonfw.tools.ide.url.tool.kotlinc.KotlincNativeUrlUpdater; import com.devonfw.tools.ide.url.tool.kotlinc.KotlincUrlUpdater; import com.devonfw.tools.ide.url.tool.lazydocker.LazyDockerUrlUpdater; -import com.devonfw.tools.ide.url.tool.msvc.MsvcUrlUpdater; import com.devonfw.tools.ide.url.tool.mvn.MvnUrlUpdater; import com.devonfw.tools.ide.url.tool.mvnd.MvndUrlUpdater; import com.devonfw.tools.ide.url.tool.ng.NgUrlUpdater; @@ -80,7 +79,7 @@ public class UpdateManager extends AbstractProcessorWithTimeout { new GcViewerUrlUpdater(), new GhUrlUpdater(), new GoUrlUpdater(), new GraalVmCommunityUpdater(), new GraalVmOracleUrlUpdater(), new GradleUrlUpdater(), new HelmUrlUpdater(), new InsoUrlUpdater(), new IntellijUrlUpdater(), new JasyptUrlUpdater(), new JavaAzulUrlUpdater(), new JavaUrlUpdater(), new JenkinsUrlUpdater(), new JmcUrlUpdater(), new KotlincUrlUpdater(), - new KotlincNativeUrlUpdater(), new LazyDockerUrlUpdater(), new MsvcUrlUpdater(), new MvnUrlUpdater(), new MvndUrlUpdater(), + new KotlincNativeUrlUpdater(), new LazyDockerUrlUpdater(), new MvnUrlUpdater(), new MvndUrlUpdater(), new NgUrlUpdater(), new NodeUrlUpdater(), new NpmUrlUpdater(), new OcUrlUpdater(), new PgAdminUrlUpdater(), new PipUrlUpdater(), new PycharmUrlUpdater(), new PythonUrlUpdater(), new QuarkusUrlUpdater(), new RustUrlUpdater(), new DockerRancherDesktopUrlUpdater(), new SonarUrlUpdater(), new SquirrelSqlUrlUpdater(), From ba3671af4ab69cbc4b9f321d560352d5a9041eb2 Mon Sep 17 00:00:00 2001 From: Laert Llaveshi Date: Tue, 26 May 2026 19:57:11 +0200 Subject: [PATCH 20/27] =?UTF-8?q?=C2=96=C2=96#1719:=20Fix=20MSVC=20command?= =?UTF-8?q?let?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/devonfw/tools/ide/tool/msvc/Msvc.java | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java b/cli/src/main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java index f0ae678df2..47c2dbe94f 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java @@ -2,6 +2,7 @@ import java.nio.file.Path; import java.util.Set; + import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.process.ProcessErrorHandling; import com.devonfw.tools.ide.tool.LocalToolCommandlet; @@ -9,20 +10,36 @@ public class Msvc extends LocalToolCommandlet { + private static final String BUILDTOOLS_URL = "https://aka.ms/vs/17/release/vs_BuildTools.exe"; + public Msvc(IdeContext context) { super(context, "msvc", Set.of()); } @Override - protected boolean isExtract() { - return false; // Native .exe installer + protected boolean isIgnoreMissingSoftwareVersionFile() { + return true; } @Override - protected void onInstall(ToolInstallRequest request, Path installationPath, Path installerScript) { - this.context.newProcess().errorHandling(ProcessErrorHandling.THROW_CLI).executable(installerScript) + protected void performToolInstallation(ToolInstallRequest request, Path installationPath) { + + this.context.getFileAccess().mkdirs(installationPath); + + Path installer = this.context.getDownloadPath().resolve("vs_BuildTools.exe"); + this.context.newProcess().errorHandling(ProcessErrorHandling.THROW_CLI) + .executable("curl.exe") + .addArgs("-fSL", "-o", installer.toString(), BUILDTOOLS_URL) + .run(); + + this.context.writeVersionFile(request.getRequested().getResolvedVersion(), installationPath); + + this.context.newProcess().errorHandling(ProcessErrorHandling.THROW_CLI) + .executable(installer) .withExitCodeAcceptor(code -> (code == 0) || (code == 3010)) - .addArgs("--quiet", "--wait", "--norestart", "--nocache", "--add", "Microsoft.VisualStudio.Workload.VCTools") + .addArgs("--installPath", installationPath.toString(), + "--add", "Microsoft.VisualStudio.Workload.VCTools", + "--quiet", "--wait", "--norestart", "--nocache") .run(); } } From ba5d98fb3ed0f5ecec50f5661f7521c746353a16 Mon Sep 17 00:00:00 2001 From: Laert Llaveshi Date: Wed, 27 May 2026 10:13:49 +0200 Subject: [PATCH 21/27] #1719: Refactor MSVC commandlet --- .../main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java b/cli/src/main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java index 47c2dbe94f..1cd1f65c69 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java @@ -10,17 +10,12 @@ public class Msvc extends LocalToolCommandlet { - private static final String BUILDTOOLS_URL = "https://aka.ms/vs/17/release/vs_BuildTools.exe"; + private static final String MSVC_SETUP_URL = "https://aka.ms/vs/17/release/vs_BuildTools.exe"; public Msvc(IdeContext context) { super(context, "msvc", Set.of()); } - @Override - protected boolean isIgnoreMissingSoftwareVersionFile() { - return true; - } - @Override protected void performToolInstallation(ToolInstallRequest request, Path installationPath) { @@ -29,7 +24,7 @@ protected void performToolInstallation(ToolInstallRequest request, Path installa Path installer = this.context.getDownloadPath().resolve("vs_BuildTools.exe"); this.context.newProcess().errorHandling(ProcessErrorHandling.THROW_CLI) .executable("curl.exe") - .addArgs("-fSL", "-o", installer.toString(), BUILDTOOLS_URL) + .addArgs("-fSL", "-o", installer.toString(), MSVC_SETUP_URL) .run(); this.context.writeVersionFile(request.getRequested().getResolvedVersion(), installationPath); From 302a4cccf001fbeef80247a8555ebdaba7974351 Mon Sep 17 00:00:00 2001 From: Laert Llaveshi Date: Wed, 27 May 2026 11:26:15 +0200 Subject: [PATCH 22/27] #1719: Add MSVC help message --- cli/src/main/resources/nls/Help.properties | 2 ++ cli/src/main/resources/nls/Help_de.properties | 2 ++ 2 files changed, 4 insertions(+) diff --git a/cli/src/main/resources/nls/Help.properties b/cli/src/main/resources/nls/Help.properties index e975fbf07c..e8a6f1a8ac 100644 --- a/cli/src/main/resources/nls/Help.properties +++ b/cli/src/main/resources/nls/Help.properties @@ -87,6 +87,8 @@ cmd.list-versions=List the available versions of the selected tool. cmd.list-versions.detail=To list all available versions of e.g. 'intellij' simply type: 'ide list-versions intellij'. cmd.ln=Create a symbolic or hard link. cmd.ln.detail=Creates a link similar to the command "ln" but working cross-platform (unlike in git-bash on Windows that typically only creates a copy). +cmd.msvc=Tool commandlet for MSVC (Microsoft Visual C++ Build Tools). +cmd.msvc.detail=MSVC provides the C++ compiler and build tools required by Rust on Windows. Detailed documentation can be found at https://visualstudio.microsoft.com/visual-cpp-build-tools/ cmd.mvn=Tool commandlet for Maven (Build-Tool). cmd.mvn.detail=Apache Maven is a build automation and dependency management tool for Java projects. Detailed documentation can be found at https://maven.apache.org/guides/index.html cmd.nest=Tool commandlet for Nest CLI. diff --git a/cli/src/main/resources/nls/Help_de.properties b/cli/src/main/resources/nls/Help_de.properties index f424d72d6b..861ea6a082 100644 --- a/cli/src/main/resources/nls/Help_de.properties +++ b/cli/src/main/resources/nls/Help_de.properties @@ -87,6 +87,8 @@ cmd.list-versions=Listet die verfügbaren Versionen des selektierten Werkzeugs a cmd.list-versions.detail=Um alle verfügbaren Versionen von z.B. 'intellij' aufzulisten, geben Sie einfach 'ide list-versions intellij' in die Konsole ein. cmd.ln=Erstellt einen symbolischen oder harten Link cmd.ln.detail=Erstellt einen Link wie das Kommando "ln" jedoch plattform-übergreifend (anders als in Git-Bash unter Windows wo typischerweise nur eine Kopie erstellt wird). +cmd.msvc=Werkzeug Kommando für MSVC (Microsoft Visual C++ Build-Werkzeuge). +cmd.msvc.detail=MSVC stellt den C++ Compiler und Build-Werkzeuge bereit, die von Rust unter Windows benötigt werden. Detaillierte Dokumentation ist zu finden unter https://visualstudio.microsoft.com/visual-cpp-build-tools/ cmd.mvn=Werkzeug Kommando für Maven (Build-Werkzeug). cmd.mvn.detail=Apache Maven ist ein Build-Automatisierungs- und Abhängigkeitsverwaltungstool für Java-Projekte. Detaillierte Dokumentation ist zu finden unter https://maven.apache.org/guides/index.html cmd.nest=Werkzeug Kommando für Nest CLI. From 48e915c23fcc3461a1a278a5e19c01b2d6503921 Mon Sep 17 00:00:00 2001 From: Laert Llaveshi Date: Thu, 28 May 2026 11:15:53 +0200 Subject: [PATCH 23/27] #1719: Temporary tests fix --- .../main/java/com/devonfw/tools/ide/io/FileAccessImpl.java | 2 +- .../com/devonfw/tools/ide/completion/IdeCompleterTest.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java b/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java index cfd17d2520..c2c2cf5d9b 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java @@ -1125,7 +1125,7 @@ private void compressRecursive(Path path, ArchiveOutput Iterator iterator = childStream.iterator(); while (iterator.hasNext()) { Path child = iterator.next(); - String relativeChildPath = relativePath + "/" + child.getFileName().toString(); + String relativeChildPath = relativePath.isEmpty() ? child.getFileName().toString() : relativePath + "/" + child.getFileName().toString(); boolean isDirectory = Files.isDirectory(child); E archiveEntry = out.createArchiveEntry(child, relativeChildPath); FileTime none = FileTime.fromMillis(0); diff --git a/cli/src/test/java/com/devonfw/tools/ide/completion/IdeCompleterTest.java b/cli/src/test/java/com/devonfw/tools/ide/completion/IdeCompleterTest.java index 00e125ba64..efe209d0aa 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/completion/IdeCompleterTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/completion/IdeCompleterTest.java @@ -49,7 +49,7 @@ void testIdeCompleterBatch() { void testIdeCompleterInstall() { this.reader.setCompleter(newCompleter()); - assertBuffer("install mvn ", new TestBuffer("install m").tab()); + assertBuffer("install mvn ", new TestBuffer("install mv").tab()); } /** @@ -59,7 +59,7 @@ void testIdeCompleterInstall() { void testIdeCompleterHelpWithToolCompletion() { this.reader.setCompleter(newCompleter()); - assertBuffer("help mvn ", new TestBuffer("help m").tab().tab()); + assertBuffer("help mvn ", new TestBuffer("help mv").tab().tab()); } /** From bd951d4d3ed6bc922632ef6d9c486ea1eba7a117 Mon Sep 17 00:00:00 2001 From: Laert Llaveshi Date: Thu, 28 May 2026 14:51:39 +0200 Subject: [PATCH 24/27] #1719: Extend RustTest.java --- .../devonfw/tools/ide/tool/rust/RustTest.java | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/rust/RustTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/rust/RustTest.java index b78526c97c..e77b243608 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/tool/rust/RustTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/rust/RustTest.java @@ -1,5 +1,7 @@ package com.devonfw.tools.ide.tool.rust; +import java.nio.file.Path; + import org.junit.jupiter.api.Test; import com.devonfw.tools.ide.context.AbstractIdeContextTest; @@ -19,7 +21,7 @@ void testRustInstallViaRustupScript() { // arrange IdeTestContext context = newContext(PROJECT_RUST); - Rust rust = context.getCommandletManager().getCommandlet(Rust.class); + Rust rust = new Rust(context); // act rust.install(); @@ -28,5 +30,23 @@ void testRustInstallViaRustupScript() { assertThat(context.getSoftwarePath().resolve("rust/.ide.software.version")).exists().hasContent(RUST_VERSION); assertThat(context).logAtSuccess().hasMessageContaining("Successfully installed rust in version " + RUST_VERSION); } -} + @Test + void testRustInstallProducesCargoLayoutAndBinLink() { + + // arrange + IdeTestContext context = newContext(PROJECT_RUST); + Rust rust = new Rust(context); + String rustcName = context.getSystemInfo().isWindows() ? "rustc.cmd" : "rustc"; + + // act + rust.install(); + + // assert + Path softwareRust = context.getSoftwarePath().resolve("rust"); + assertThat(softwareRust.resolve(".cargo")).exists(); + assertThat(softwareRust.resolve(".rustup")).exists(); + assertThat(softwareRust.resolve(".cargo/bin").resolve(rustcName)).exists(); + assertThat(softwareRust.resolve("bin").resolve(rustcName)).exists(); + } +} From 7fb9752e1791de6d732c3ad07899165f653c310e Mon Sep 17 00:00:00 2001 From: Laert Llaveshi Date: Thu, 28 May 2026 15:46:51 +0200 Subject: [PATCH 25/27] #1719: Remove unnecessary RustGithubUrlTagUpdaterTest.java --- .../rust/RustGithubUrlTagUpdaterTest.java | 45 ------------------- 1 file changed, 45 deletions(-) delete mode 100644 url-updater/src/test/java/com/devonfw/tools/ide/url/tool/rust/RustGithubUrlTagUpdaterTest.java diff --git a/url-updater/src/test/java/com/devonfw/tools/ide/url/tool/rust/RustGithubUrlTagUpdaterTest.java b/url-updater/src/test/java/com/devonfw/tools/ide/url/tool/rust/RustGithubUrlTagUpdaterTest.java deleted file mode 100644 index 8d2dd6aaec..0000000000 --- a/url-updater/src/test/java/com/devonfw/tools/ide/url/tool/rust/RustGithubUrlTagUpdaterTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.devonfw.tools.ide.url.tool.rust; - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.any; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; -import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; - -import java.nio.file.Path; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import com.devonfw.tools.ide.url.model.folder.UrlRepository; -import com.devonfw.tools.ide.url.updater.AbstractUrlUpdaterTest; -import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; -import com.github.tomakehurst.wiremock.junit5.WireMockTest; - -/** - * Test of {@link RustUrlUpdater}. - */ -@WireMockTest -class RustGithubUrlTagUpdaterTest extends AbstractUrlUpdaterTest { - - @Test - void testRustGithubUrlUpdater(@TempDir Path tempDir, WireMockRuntimeInfo wmRuntimeInfo) { - - // arrange - stubFor(get(urlMatching("/repos/rust-lang/rustup/git/refs/tags")).willReturn(aResponse().withStatus(200) - .withBody(readAndResolve(PATH_INTEGRATION_TEST.resolve("RustUrlUpdater").resolve("github-tags.json"), wmRuntimeInfo)))); - stubFor(any(urlMatching("/rustup\\.sh")).willReturn(aResponse().withStatus(200).withHeader("content-type", "text/plain").withBody(DOWNLOAD_CONTENT))); - - UrlRepository urlRepository = UrlRepository.load(tempDir); - RustUrlUpdaterMock updater = new RustUrlUpdaterMock(wmRuntimeInfo); - - // act - updater.update(urlRepository); - - // assert - Path rustEditionDir = tempDir.resolve("rust").resolve("rust"); - assertUrlVersionAgnostic(rustEditionDir.resolve("1.79.0")); - assertUrlVersionAgnostic(rustEditionDir.resolve("1.80.1")); - assertThat(rustEditionDir.resolve("release-0.7")).doesNotExist(); - } -} From 342d5d1e6452e7b7b5f6cdaac588607e462725a3 Mon Sep 17 00:00:00 2001 From: Laert Llaveshi Date: Thu, 28 May 2026 16:22:28 +0200 Subject: [PATCH 26/27] #1719: Add License, tiny Msvc.java tweaks --- .../main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java | 8 +++++++- documentation/LICENSE.adoc | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java b/cli/src/main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java index 1cd1f65c69..b0d2e09250 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java @@ -3,6 +3,8 @@ import java.nio.file.Path; import java.util.Set; +import com.devonfw.tools.ide.cli.CliException; +import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.process.ProcessErrorHandling; import com.devonfw.tools.ide.tool.LocalToolCommandlet; @@ -13,12 +15,16 @@ public class Msvc extends LocalToolCommandlet { private static final String MSVC_SETUP_URL = "https://aka.ms/vs/17/release/vs_BuildTools.exe"; public Msvc(IdeContext context) { - super(context, "msvc", Set.of()); + super(context, "msvc", Set.of(Tag.BUILD, Tag.CPP)); } @Override protected void performToolInstallation(ToolInstallRequest request, Path installationPath) { + if (!this.context.getSystemInfo().isWindows()) { + throw new CliException("The tool 'msvc' is only available on Windows."); + } + this.context.getFileAccess().mkdirs(installationPath); Path installer = this.context.getDownloadPath().resolve("vs_BuildTools.exe"); diff --git a/documentation/LICENSE.adoc b/documentation/LICENSE.adoc index a2c60286a9..d1bb3e3230 100644 --- a/documentation/LICENSE.adoc +++ b/documentation/LICENSE.adoc @@ -107,6 +107,8 @@ The column `inclusion` indicates the way the component is included: |https://docs.nestjs.com/cli/overview[NestJS CLI] |Optional|https://github.com/nestjs/nest-cli/blob/master/LICENSE[MIT License] |https://docs.aws.amazon.com/cdk/v2/guide/home.html[AWS CDK CLI] |Optional|https://github.com/aws/aws-cdk-cli/blob/main/LICENSE[Apache 2.0] |https://developer.konghq.com/inso-cli |Optional|https://github.com/Kong/insomnia/blob/develop/LICENSE[Apache 2.0] +|https://www.rust-lang.org/[Rust] |Optional|https://www.rust-lang.org/policies/licenses[MIT and Apache 2.0] +|https://visualstudio.microsoft.com/visual-cpp-build-tools/[MSVC Build Tools] |Optional|https://visualstudio.microsoft.com/license-terms/vs2022-ga-community/[Microsoft Software License Terms] |=== == Apache Software License - Version 2.0 From decc25fd01361a4d04a26dc8a2105fae8e6f59e6 Mon Sep 17 00:00:00 2001 From: Laert Llaveshi Date: Thu, 28 May 2026 16:43:11 +0200 Subject: [PATCH 27/27] #1719: Cleanup --- .../com/devonfw/tools/ide/tool/LocalToolCommandlet.java | 6 +++--- .../main/java/com/devonfw/tools/ide/tool/rust/Rust.java | 9 +-------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java index 1b286ac4ea..4e696ac6cf 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java @@ -223,7 +223,7 @@ public ToolInstallation installTool(ToolInstallRequest request) { } /** - * Performs the installation of the {@link #getName() tool} by using {@link #onInstall(ToolInstallRequest, Path, Path)} + * Performs the installation of the {@link #getName() tool} by using {@link #installDownloadedToolPayload(ToolInstallRequest, Path, Path)} * for tool-specific logic, backing up any existing installation, and writing the version file. *

* This method assumes that the version has already been resolved and dependencies installed. It handles the final steps of placing the tool into the @@ -248,7 +248,7 @@ protected void performToolInstallation(ToolInstallRequest request, Path installa } fileAccess.mkdirs(installationPath.getParent()); - onInstall(request, installationPath, downloadedToolFile); + installDownloadedToolPayload(request, installationPath, downloadedToolFile); this.context.writeVersionFile(resolvedVersion, installationPath); // fix macOS Gatekeeper blocking - must run after version file is written but before any executables are launched @@ -263,7 +263,7 @@ protected void performToolInstallation(ToolInstallRequest request, Path installa * @param installationPath the target {@link Path} where the tool should be installed. * @param downloadedToolFile the {@link Path} to the downloaded tool file. */ - protected void onInstall(ToolInstallRequest request, Path installationPath, Path downloadedToolFile) { + protected void installDownloadedToolPayload(ToolInstallRequest request, Path installationPath, Path downloadedToolFile) { boolean extract = isExtract(); if (!extract) { diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java b/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java index 79af7cafbb..e82274eb68 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java @@ -51,15 +51,8 @@ protected boolean isExtract() { return false; } - /** - * Performs the actual installation of the tool bits. - * - * @param request the {@link ToolInstallRequest}. - * @param installationPath the target {@link Path} where the tool should be installed. - * @param installerScript the {@link Path} to the downloaded tool file. - */ @Override - protected void onInstall(ToolInstallRequest request, Path installationPath, Path installerScript) { + protected void installDownloadedToolPayload(ToolInstallRequest request, Path installationPath, Path installerScript) { VersionIdentifier resolvedVersion = request.getRequested().getResolvedVersion(); FileAccess fileAccess = this.context.getFileAccess();