From 6737a10d43cceb28ffca904778ca294d108e5055 Mon Sep 17 00:00:00 2001 From: mailaenderli Date: Mon, 26 Jan 2026 16:06:23 +0000 Subject: [PATCH 1/2] feat: add rust feature --- .github/workflows/ci.yml | 1 + README.md | 1 + build/build.go | 5 + features/src/rust/NOTES.md | 10 + features/src/rust/README.md | 47 +++++ features/src/rust/devcontainer-feature.json | 66 +++++++ features/src/rust/install.sh | 8 + features/src/rust/installer.go | 183 ++++++++++++++++++ .../test/rust/install-with-windows-target.sh | 9 + .../rust/install-without-windows-target.sh | 9 + features/test/rust/scenarios.json | 36 ++++ features/test/rust/test-images.json | 5 + 12 files changed, 380 insertions(+) create mode 100644 features/src/rust/NOTES.md create mode 100755 features/src/rust/README.md create mode 100644 features/src/rust/devcontainer-feature.json create mode 100755 features/src/rust/install.sh create mode 100644 features/src/rust/installer.go create mode 100755 features/test/rust/install-with-windows-target.sh create mode 100755 features/test/rust/install-without-windows-target.sh create mode 100644 features/test/rust/scenarios.json create mode 100644 features/test/rust/test-images.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d552a4..e3562af 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,7 @@ jobs: "node", "playwright-deps", "python", + "rust", "sonar-scanner-cli", "system-packages", "timezone", diff --git a/README.md b/README.md index 32ce2a0..1d22173 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Below is a list with included features, click on the link for more details. | [node](./features/src/node/README.md) | Installs Node.js. | | [playwright-deps](./features/src/playwright-deps/README.md) | Installs all dependencies required to run Playwright. | | [python](./features/src/python/README.md) | Installs Python. | +| [rust](./features/src/rust/README.md) | A package which installs Rust. | | [sonar-scanner-cli](./features/src/sonar-scanner-cli/README.md) | Installs the SonarScanner CLI. | | [system-packages](./features/src/system-packages/README.md) | Install arbitrary system packages using the system package manager. | | [timezone](./features/src/timezone/README.md) | Allows setting the timezone. | diff --git a/build/build.go b/build/build.go index 4dc885b..c42217b 100644 --- a/build/build.go +++ b/build/build.go @@ -180,6 +180,11 @@ func init() { gotaskr.Task("Feature:python:Test", func() error { return testFeature("python") }) gotaskr.Task("Feature:python:Publish", func() error { return publishFeature("python") }) + ////////// rust + gotaskr.Task("Feature:rust:Package", func() error { return packageFeature("rust") }) + gotaskr.Task("Feature:rust:Test", func() error { return testFeature("rust") }) + gotaskr.Task("Feature:rust:Publish", func() error { return publishFeature("rust") }) + ////////// sonar-scanner-cli gotaskr.Task("Feature:sonar-scanner-cli:Package", func() error { return packageFeature("sonar-scanner-cli") }) gotaskr.Task("Feature:sonar-scanner-cli:Test", func() error { return testFeature("sonar-scanner-cli") }) diff --git a/features/src/rust/NOTES.md b/features/src/rust/NOTES.md new file mode 100644 index 0000000..b0ad17d --- /dev/null +++ b/features/src/rust/NOTES.md @@ -0,0 +1,10 @@ +## Notes + +### System Compatibility + +Debian, Ubuntu + +### Accessed Urls + +Needs access to the following URL for downloading and resolving: +* https://static.rust-lang.org diff --git a/features/src/rust/README.md b/features/src/rust/README.md new file mode 100755 index 0000000..8a15810 --- /dev/null +++ b/features/src/rust/README.md @@ -0,0 +1,47 @@ +# Rust (rust) + +A package which installs Rust. + +## Example Usage + +```json +"features": { + "ghcr.io/postfinance/devcontainer-features/rust:0.1.0": { + "version": "latest", + "rustupVersion": "latest", + "profile": "minimal", + "components": "rustfmt,rust-analyzer,rust-src,clippy", + "enableWindowsTarget": false + } +} +``` + +## Options + +| Option | Description | Type | Default Value | Proposals | +|-----|-----|-----|-----|-----| +| version | The version of Rust to install. | string | latest | latest, 1.76 | +| rustupVersion | The version of rustup to install. | string | latest | latest, 1.27.1 | +| profile | The rustup profile to install. | string | minimal | minimal, default, complete | +| components | A comma separated list with components that should be installed. | string | rustfmt,rust-analyzer,rust-src,clippy | , rustfmt,rust-analyzer, rls,rust-analysis | +| enableWindowsTarget | A flag to indicate if the Windows target (and needed tools) should be installed. | boolean | false | true, false | + +## Customizations + +### VS Code Extensions + +- `vadimcn.vscode-lldb` +- `rust-lang.rust-analyzer` +- `tamasfe.even-better-toml` +- `serayuzgur.crates` + +## Notes + +### System Compatibility + +Debian, Ubuntu + +### Accessed Urls + +Needs access to the following URL for downloading and resolving: +* https://static.rust-lang.org diff --git a/features/src/rust/devcontainer-feature.json b/features/src/rust/devcontainer-feature.json new file mode 100644 index 0000000..52cff44 --- /dev/null +++ b/features/src/rust/devcontainer-feature.json @@ -0,0 +1,66 @@ +{ + "id": "rust", + "version": "0.1.0", + "name": "Rust", + "description": "A package which installs Rust.", + "options": { + "version": { + "type": "string", + "proposals": [ + "latest", + "1.76" + ], + "default": "latest", + "description": "The version of Rust to install." + }, + "rustupVersion": { + "type": "string", + "proposals": [ + "latest", + "1.27.1" + ], + "default": "latest", + "description": "The version of rustup to install." + }, + "profile": { + "type": "string", + "proposals": [ + "minimal", + "default", + "complete" + ], + "default": "minimal", + "description": "The rustup profile to install." + }, + "components": { + "type": "string", + "proposals": [ + "", + "rustfmt,rust-analyzer", + "rls,rust-analysis" + ], + "default": "rustfmt,rust-analyzer,rust-src,clippy", + "description": "A comma separated list with components that should be installed." + }, + "enableWindowsTarget": { + "type": "boolean", + "default": false, + "description": "A flag to indicate if the Windows target (and needed tools) should be installed." + } + }, + "customizations": { + "vscode": { + "extensions": [ + "vadimcn.vscode-lldb", + "rust-lang.rust-analyzer", + "tamasfe.even-better-toml", + "serayuzgur.crates" + ] + } + }, + "containerEnv": { + "CARGO_HOME": "/usr/local/cargo", + "RUSTUP_HOME": "/usr/local/rustup", + "PATH": "/usr/local/cargo/bin:${PATH}" + } +} \ No newline at end of file diff --git a/features/src/rust/install.sh b/features/src/rust/install.sh new file mode 100755 index 0000000..1df7c09 --- /dev/null +++ b/features/src/rust/install.sh @@ -0,0 +1,8 @@ +. ./functions.sh + +"./installer_$(detect_arch)" \ + -version="${VERSION:-"latest"}" \ + -rustupVersion="${RUSTUPVERSION:-"latest"}" \ + -profile="${PROFILE:-"minimal"}" \ + -components="${COMPONENTS:-"rustfmt,rust-analyzer,rust-src,clippy"}" \ + -enableWindowsTarget="${ENABLEWINDOWSTARGET:-"false"}" diff --git a/features/src/rust/installer.go b/features/src/rust/installer.go new file mode 100644 index 0000000..4850037 --- /dev/null +++ b/features/src/rust/installer.go @@ -0,0 +1,183 @@ +package main + +import ( + "builder/installer" + "flag" + "fmt" + "os" + "regexp" + "strings" + + "github.com/roemer/gotaskr/execr" + "github.com/roemer/gover" +) + +////////// +// Configuration +////////// + +// Regex with 2-3 digits like 1.0 or 1.79.0 +var threeDigitRegex *regexp.Regexp = regexp.MustCompile(`^?(\d+)\.(\d+)(?:\.(\d+))?$`) + +// Full Regex versioning, like 1.0.0-alpha.2 +var semVerRegex *regexp.Regexp = regexp.MustCompile(`^?(\d+)\.(\d+)(?:\.(\d+))?(?:-([a-z]+)(?:\.?(\d+))?)?$`) + +////////// +// Main +////////// + +func main() { + if err := runMain(); err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } +} + +func runMain() error { + // Handle the flags + version := flag.String("version", "lts", "") + rustupVersion := flag.String("rustupVersion", "latest", "") + profile := flag.String("profile", "minimal", "") + components := flag.String("components", "rustfmt,rust-analyzer,rust-src,clippy", "") + enableWindowsTarget := flag.Bool("enableWindowsTarget", false, "") + flag.Parse() + + // Create and process the feature + feature := installer.NewFeature("PF Rust", true, + &rustupComponent{ + ComponentBase: installer.NewComponentBase("rustup", *rustupVersion), + profile: *profile, + }, + &rustComponent{ + ComponentBase: installer.NewComponentBase("rust", *version), + components: *components, + profile: *profile, + }, + &buildEssentialComponent{ + ComponentBase: installer.NewComponentBase("build-essential", installer.VERSION_SYSTEM_DEFAULT), + }, + ) + // Optional component + if *enableWindowsTarget { + feature.AddComponents(&windowsTargetComponent{ + ComponentBase: installer.NewComponentBase("windows-target", installer.VERSION_IRRELEVANT), + }) + } + // Last component + feature.AddComponents(&permissionsComponent{ + ComponentBase: installer.NewComponentBase("permissions", installer.VERSION_IRRELEVANT), + }) + return feature.Process() +} + +////////// +// Implementation +////////// + +type rustupComponent struct { + *installer.ComponentBase + profile string +} + +func (c *rustupComponent) GetAllVersions() ([]*gover.Version, error) { + allTags, err := installer.Tools.GitHub.GetTags("rust-lang", "rustup") + if err != nil { + return nil, err + } + return installer.Tools.Versioning.ParseVersionsFromList(allTags, threeDigitRegex, true) +} + +func (c *rustupComponent) InstallVersion(version *gover.Version) error { + // Download the file + archPart, err := installer.Tools.System.MapArchitecture(map[string]string{ + installer.AMD64: "x86_64", + installer.ARM64: "aarch64", + }) + if err != nil { + return err + } + fileName := "rustup-init" + downloadUrl := fmt.Sprintf("https://static.rust-lang.org/rustup/archive/%s/%s-unknown-linux-gnu/rustup-init", version.Raw, archPart) + if err := installer.Tools.Download.ToFile(downloadUrl, fileName, "Rustup-Init"); err != nil { + return err + } + // Install it + if err := os.Chmod(fileName, os.ModePerm); err != nil { + return err + } + if err := execr.Run(true, "./"+fileName, "-y", "--default-toolchain", "none", "--no-modify-path", "--profile", c.profile); err != nil { + return err + } + // Cleanup + if err := os.Remove(fileName); err != nil { + return err + } + return nil +} + +type rustComponent struct { + *installer.ComponentBase + profile string + components string +} + +func (c *rustComponent) GetAllVersions() ([]*gover.Version, error) { + allTags, err := installer.Tools.GitHub.GetTags("rust-lang", "rust") + if err != nil { + return nil, err + } + return installer.Tools.Versioning.ParseVersionsFromList(allTags, semVerRegex, true) +} + +func (c *rustComponent) InstallVersion(version *gover.Version) error { + // Install it + if err := execr.Run(true, "rustup", "toolchain", "install", "--profile", c.profile, "--no-self-update", version.Raw); err != nil { + return err + } + // Installing the components + fmt.Printf("Installing components: %s\n", c.components) + args := []string{ + "component", + "add", + } + for _, component := range strings.Split(c.components, ",") { + trimmed := strings.TrimSpace(component) + if trimmed != "" { + args = append(args, trimmed) + } + } + if err := execr.Run(true, "rustup", args...); err != nil { + return err + } + return nil +} + +type buildEssentialComponent struct { + *installer.ComponentBase +} + +func (c *buildEssentialComponent) InstallVersion(version *gover.Version) error { + return installer.Tools.System.InstallPackages("build-essential") +} + +type windowsTargetComponent struct { + *installer.ComponentBase +} + +func (c *windowsTargetComponent) InstallVersion(version *gover.Version) error { + if err := execr.Run(true, "rustup", "target", "add", "x86_64-pc-windows-gnu"); err != nil { + return err + } + if err := installer.Tools.System.InstallPackages("mingw-w64"); err != nil { + return err + } + return nil +} + +type permissionsComponent struct { + *installer.ComponentBase +} + +func (c *permissionsComponent) InstallVersion(version *gover.Version) error { + return execr.Run(true, "chmod", "-R", "777", os.Getenv("RUSTUP_HOME"), os.Getenv("CARGO_HOME")) +} diff --git a/features/test/rust/install-with-windows-target.sh b/features/test/rust/install-with-windows-target.sh new file mode 100755 index 0000000..7f2bd71 --- /dev/null +++ b/features/test/rust/install-with-windows-target.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e + +[[ -f "$(dirname "$0")/../functions.sh" ]] && source "$(dirname "$0")/../functions.sh" +[[ -f "$(dirname "$0")/functions.sh" ]] && source "$(dirname "$0")/functions.sh" + +check_version "$(rustup --version 2>/dev/null)" "rustup 1.27.1 (54dd3d00f 2024-04-24)" +check_version "$(rustc --version)" "rustc 1.76.0 (07dca489a 2024-02-04)" +check_version "$(rustup target list --installed)" $'x86_64-pc-windows-gnu\nx86_64-unknown-linux-gnu' diff --git a/features/test/rust/install-without-windows-target.sh b/features/test/rust/install-without-windows-target.sh new file mode 100755 index 0000000..199e041 --- /dev/null +++ b/features/test/rust/install-without-windows-target.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e + +[[ -f "$(dirname "$0")/../functions.sh" ]] && source "$(dirname "$0")/../functions.sh" +[[ -f "$(dirname "$0")/functions.sh" ]] && source "$(dirname "$0")/functions.sh" + +check_version "$(rustup --version 2>/dev/null)" "rustup 1.27.1 (54dd3d00f 2024-04-24)" +check_version "$(rustc --version)" "rustc 1.76.0 (07dca489a 2024-02-04)" +check_version "$(rustup target list --installed)" $'x86_64-unknown-linux-gnu' diff --git a/features/test/rust/scenarios.json b/features/test/rust/scenarios.json new file mode 100644 index 0000000..1eadbe4 --- /dev/null +++ b/features/test/rust/scenarios.json @@ -0,0 +1,36 @@ +{ + "install-with-windows-target": { + "build": { + "dockerfile": "Dockerfile", + "options": [ + "--add-host=host.docker.internal:host-gateway" + ] + }, + "features": { + "./rust": { + "version": "1.76", + "rustupVersion": "1.27.1", + "profile": "minimal", + "components": "rustfmt,rust-analyzer,rust-src,clippy", + "enableWindowsTarget": "true" + } + } + }, + "install-without-windows-target": { + "build": { + "dockerfile": "Dockerfile", + "options": [ + "--add-host=host.docker.internal:host-gateway" + ] + }, + "features": { + "./rust": { + "version": "1.76", + "rustupVersion": "1.27.1", + "profile": "minimal", + "components": "rustfmt,rust-analyzer,rust-src,clippy", + "enableWindowsTarget": "false" + } + } + } +} \ No newline at end of file diff --git a/features/test/rust/test-images.json b/features/test/rust/test-images.json new file mode 100644 index 0000000..baaeb21 --- /dev/null +++ b/features/test/rust/test-images.json @@ -0,0 +1,5 @@ +[ + "mcr.microsoft.com/devcontainers/base:debian-11", + "mcr.microsoft.com/devcontainers/base:debian-12", + "mcr.microsoft.com/devcontainers/base:ubuntu-24.04" +] \ No newline at end of file From 28dcb7f7037b6fad9cb398a56086e94f6f5e8f19 Mon Sep 17 00:00:00 2001 From: mailaenderli Date: Wed, 28 Jan 2026 06:37:24 +0000 Subject: [PATCH 2/2] chore: implement mr feedback --- README.md | 2 +- features/src/rust/README.md | 4 ++-- features/src/rust/devcontainer-feature.json | 4 ++-- features/src/rust/installer.go | 2 +- features/test/rust/test-images.json | 1 + 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1d22173..66de47e 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Below is a list with included features, click on the link for more details. | [node](./features/src/node/README.md) | Installs Node.js. | | [playwright-deps](./features/src/playwright-deps/README.md) | Installs all dependencies required to run Playwright. | | [python](./features/src/python/README.md) | Installs Python. | -| [rust](./features/src/rust/README.md) | A package which installs Rust. | +| [rust](./features/src/rust/README.md) | A package which installs Rust, common Rust utilities and their required dependencies. | | [sonar-scanner-cli](./features/src/sonar-scanner-cli/README.md) | Installs the SonarScanner CLI. | | [system-packages](./features/src/system-packages/README.md) | Install arbitrary system packages using the system package manager. | | [timezone](./features/src/timezone/README.md) | Allows setting the timezone. | diff --git a/features/src/rust/README.md b/features/src/rust/README.md index 8a15810..1a52763 100755 --- a/features/src/rust/README.md +++ b/features/src/rust/README.md @@ -1,6 +1,6 @@ # Rust (rust) -A package which installs Rust. +A package which installs Rust, common Rust utilities and their required dependencies. ## Example Usage @@ -20,7 +20,7 @@ A package which installs Rust. | Option | Description | Type | Default Value | Proposals | |-----|-----|-----|-----|-----| -| version | The version of Rust to install. | string | latest | latest, 1.76 | +| version | The version of Rust to install. | string | latest | latest, 1.93.0 | | rustupVersion | The version of rustup to install. | string | latest | latest, 1.27.1 | | profile | The rustup profile to install. | string | minimal | minimal, default, complete | | components | A comma separated list with components that should be installed. | string | rustfmt,rust-analyzer,rust-src,clippy | , rustfmt,rust-analyzer, rls,rust-analysis | diff --git a/features/src/rust/devcontainer-feature.json b/features/src/rust/devcontainer-feature.json index 52cff44..dae2d9a 100644 --- a/features/src/rust/devcontainer-feature.json +++ b/features/src/rust/devcontainer-feature.json @@ -2,13 +2,13 @@ "id": "rust", "version": "0.1.0", "name": "Rust", - "description": "A package which installs Rust.", + "description": "A package which installs Rust, common Rust utilities and their required dependencies.", "options": { "version": { "type": "string", "proposals": [ "latest", - "1.76" + "1.93.0" ], "default": "latest", "description": "The version of Rust to install." diff --git a/features/src/rust/installer.go b/features/src/rust/installer.go index 4850037..109c497 100644 --- a/features/src/rust/installer.go +++ b/features/src/rust/installer.go @@ -43,7 +43,7 @@ func runMain() error { flag.Parse() // Create and process the feature - feature := installer.NewFeature("PF Rust", true, + feature := installer.NewFeature("Rust", true, &rustupComponent{ ComponentBase: installer.NewComponentBase("rustup", *rustupVersion), profile: *profile, diff --git a/features/test/rust/test-images.json b/features/test/rust/test-images.json index baaeb21..e1b870e 100644 --- a/features/test/rust/test-images.json +++ b/features/test/rust/test-images.json @@ -1,5 +1,6 @@ [ "mcr.microsoft.com/devcontainers/base:debian-11", "mcr.microsoft.com/devcontainers/base:debian-12", + "mcr.microsoft.com/devcontainers/base:debian-13", "mcr.microsoft.com/devcontainers/base:ubuntu-24.04" ] \ No newline at end of file