diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc
index 53a168492d..ec33428874 100644
--- a/CHANGELOG.adoc
+++ b/CHANGELOG.adoc
@@ -15,6 +15,7 @@ Release with new features and bugfixes:
* https://github.com/devonfw/IDEasy/issues/1849[#1849]: Add VSCodium support
* https://github.com/devonfw/IDEasy/issues/1391[#1391]: Fix bashrc messed with terraform completions
* https://github.com/devonfw/IDEasy/issues/1922[#1922]: Add Task CLI to IDEasy commandlets
+* https://github.com/devonfw/IDEasy/issues/1719[#1719]: Add Rust and MSVC support
The full list of changes for this release can be found in https://github.com/devonfw/IDEasy/milestone/45?closed=1[milestone 2026.06.001].
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 fcb36fb559..87c34f7c20 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;
@@ -56,6 +57,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;
@@ -127,12 +129,14 @@ 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));
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/LocalToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java
index 0d655aa8cb..def9af76db 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 actual installation of the {@link #getName() tool} by downloading its binary, optionally extracting it, backing up any existing installation,
- * and writing the version file.
+ * 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
* appropriate installation directory.
@@ -238,10 +238,7 @@ protected void performToolInstallation(ToolInstallRequest request, Path installa
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 +247,31 @@ protected void performToolInstallation(ToolInstallRequest request, Path installa
}
}
fileAccess.mkdirs(installationPath.getParent());
- fileAccess.extract(downloadedToolFile, installationPath, this::postExtract, extract);
+
+ 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
getMacOsHelper().removeQuarantineAttribute(installationPath);
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 installDownloadedToolPayload(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/msvc/Msvc.java b/cli/src/main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java
new file mode 100644
index 0000000000..8cecb9c0ad
--- /dev/null
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/msvc/Msvc.java
@@ -0,0 +1,52 @@
+package com.devonfw.tools.ide.tool.msvc;
+
+import java.nio.file.Path;
+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.process.ProcessErrorHandling;
+import com.devonfw.tools.ide.tool.LocalToolCommandlet;
+import com.devonfw.tools.ide.tool.ToolInstallRequest;
+import com.devonfw.tools.ide.tool.ToolInstallation;
+
+public class Msvc extends LocalToolCommandlet {
+
+ private static final Logger LOG = LoggerFactory.getLogger(Msvc.class);
+
+ public Msvc(IdeContext context) {
+ super(context, "msvc", Set.of(Tag.BUILD, Tag.CPP));
+ }
+
+ @Override
+ protected boolean isExtract() {
+ return false;
+ }
+
+ @Override
+ public ToolInstallation installTool(ToolInstallRequest request) {
+
+ if (this.context.getSystemInfo().isWindows()) {
+ return super.installTool(request);
+ }
+ LOG.trace("Skipping msvc installation that is only available on Windows.");
+ return null;
+ }
+
+ @Override
+ protected void installDownloadedToolPayload(ToolInstallRequest request, Path installationPath, Path installer) {
+
+ this.context.getFileAccess().mkdirs(installationPath);
+
+ this.context.newProcess().errorHandling(ProcessErrorHandling.THROW_CLI)
+ .executable(installer)
+ .withExitCodeAcceptor(code -> (code == 0) || (code == 3010))
+ .addArgs("--installPath", installationPath.toString(),
+ "--add", "Microsoft.VisualStudio.Workload.VCTools",
+ "--quiet", "--wait", "--norestart", "--nocache")
+ .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
new file mode 100644
index 0000000000..e82274eb68
--- /dev/null
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/rust/Rust.java
@@ -0,0 +1,97 @@
+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 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.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;
+
+/**
+ * {@link LocalToolCommandlet} for Rust.
+ */
+public class Rust extends LocalToolCommandlet {
+
+ /**
+ * 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 installDownloadedToolPayload(ToolInstallRequest request, Path installationPath, Path installerScript) {
+
+ VersionIdentifier resolvedVersion = request.getRequested().getResolvedVersion();
+ FileAccess fileAccess = this.context.getFileAccess();
+
+ Path cargoHome = installationPath.resolve(".cargo");
+ Path rustupHome = installationPath.resolve(".rustup");
+ fileAccess.mkdirs(cargoHome);
+ fileAccess.mkdirs(rustupHome);
+
+ if (Files.isDirectory(installerScript)) {
+ // ToolRepositoryMock may provide an unpacked folder instead of a single download file.
+ installerScript = installerScript.resolve("content.sh");
+ }
+
+ 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());
+
+ process.executable(this.context.findBashRequired()).addArgs(installerScript.toAbsolutePath().toString()).addArgs(installerArgs);
+ 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);
+ }
+ }
+
+ @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());
+ }
+
+}
diff --git a/cli/src/main/resources/nls/Help.properties b/cli/src/main/resources/nls/Help.properties
index f08a8d5b13..67776f8bf5 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.
@@ -112,6 +114,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 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 3617a1d5b1..eb467713f3 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.
@@ -112,6 +114,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 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/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());
}
/**
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..e77b243608
--- /dev/null
+++ b/cli/src/test/java/com/devonfw/tools/ide/tool/rust/RustTest.java
@@ -0,0 +1,52 @@
+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;
+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 Rust(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);
+ }
+
+ @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();
+ }
+}
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
+
diff --git a/documentation/LICENSE.adoc b/documentation/LICENSE.adoc
index 44d7f4caa4..148f7d2fa0 100644
--- a/documentation/LICENSE.adoc
+++ b/documentation/LICENSE.adoc
@@ -109,6 +109,8 @@ The column `inclusion` indicates the way the component is included:
|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://taskfile.dev/[Task CLI] |Optional|https://github.com/go-task/task/blob/main/LICENSE[MIT License]
|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