From 6cffb5af307e412dddeef21c93122d887889d7e5 Mon Sep 17 00:00:00 2001 From: Venkumahanti Subhankar Date: Wed, 2 Sep 2026 11:30:57 +0000 Subject: [PATCH 1/2] feat(rust) - Add "none" value for Rust feature "components" --- src/rust/README.md | 2 +- src/rust/devcontainer-feature.json | 7 +++-- src/rust/install.sh | 32 ++++++++++++--------- test/rust/rust_with_none_components.sh | 40 ++++++++++++++++++++++++++ test/rust/scenarios.json | 10 +++++++ 5 files changed, 74 insertions(+), 17 deletions(-) create mode 100644 test/rust/rust_with_none_components.sh diff --git a/src/rust/README.md b/src/rust/README.md index eca22932c..bf17736ce 100644 --- a/src/rust/README.md +++ b/src/rust/README.md @@ -18,7 +18,7 @@ Installs Rust, common Rust utilities, and their required dependencies | version | Select or enter a version of Rust to install. | string | latest | | profile | Select a rustup install profile. | string | minimal | | targets | Optional comma separated list of additional Rust targets to install. | string | - | -| components | Optional, comma separated list of Rust components to be installed | string | rust-analyzer,rust-src,rustfmt,clippy | +| components | Optional, comma separated list of Rust components to be installed. Set to 'none' to install no components beyond the selected profile. | string | rust-analyzer,rust-src,rustfmt,clippy | ## Customizations diff --git a/src/rust/devcontainer-feature.json b/src/rust/devcontainer-feature.json index d8d399cde..f94d0dc18 100644 --- a/src/rust/devcontainer-feature.json +++ b/src/rust/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "rust", - "version": "1.5.1", + "version": "1.6.0", "name": "Rust", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/rust", "description": "Installs Rust, common Rust utilities, and their required dependencies", @@ -61,12 +61,13 @@ "components": { "type": "string", "default": "rust-analyzer,rust-src,rustfmt,clippy", - "description": "Optional, comma separated list of Rust components to be installed", + "description": "Optional, comma separated list of Rust components to be installed. Set to 'none' to install no components beyond the selected profile.", "proposals": [ "rust-analyzer,rust-src,rustfmt,clippy", "rust-analyzer,rust-src", "rustfmt,clippy,rust-docs", - "llvm-tools-preview,rust-src,rustfmt" + "llvm-tools-preview,rust-src,rustfmt", + "none" ] } }, diff --git a/src/rust/install.sh b/src/rust/install.sh index 56bb35ea8..171d666c0 100755 --- a/src/rust/install.sh +++ b/src/rust/install.sh @@ -10,7 +10,9 @@ RUST_VERSION="${VERSION:-"latest"}" RUSTUP_PROFILE="${PROFILE:-"minimal"}" RUSTUP_TARGETS="${TARGETS:-""}" -IFS=',' read -ra components <<< "${COMPONENTS:-rust-analyzer,rust-src,rustfmt,clippy}" +# Set to "none" to install no components beyond the selected profile. +RUSTUP_COMPONENTS="${COMPONENTS:-rust-analyzer,rust-src,rustfmt,clippy}" +IFS=',' read -ra components <<< "${RUSTUP_COMPONENTS}" export CARGO_HOME="${CARGO_HOME:-"/usr/local/cargo"}" export RUSTUP_HOME="${RUSTUP_HOME:-"/usr/local/rustup"}" @@ -396,19 +398,23 @@ if [ "${UPDATE_RUST}" = "true" ]; then echo "Updating Rust..." rustup update 2>&1 fi -# Install Rust components -echo "Installing Rust components..." -for component in "${components[@]}"; do - # Trim leading and trailing whitespace - component="${component#"${component%%[![:space:]]*}"}" && component="${component%"${component##*[![:space:]]}"}" - if [ -n "${component}" ]; then - echo "Installing Rust component: ${component}" - if ! rustup component add "${component}" 2>&1; then - echo "Warning: Failed to install component '${component}'. It may not be available for this toolchain." >&2 - exit 1 +# Install Rust components (skip entirely when explicitly set to "none") +if [ "${RUSTUP_COMPONENTS}" = "none" ]; then + echo "Skipping Rust components installation as 'components' is set to 'none'." +else + echo "Installing Rust components..." + for component in "${components[@]}"; do + # Trim leading and trailing whitespace + component="${component#"${component%%[![:space:]]*}"}" && component="${component%"${component##*[![:space:]]}"}" + if [ -n "${component}" ]; then + echo "Installing Rust component: ${component}" + if ! rustup component add "${component}" 2>&1; then + echo "Warning: Failed to install component '${component}'. It may not be available for this toolchain." >&2 + exit 1 + fi fi - fi -done + done +fi if [ -n "${RUSTUP_TARGETS}" ]; then IFS=',' read -ra targets <<< "${RUSTUP_TARGETS}" diff --git a/test/rust/rust_with_none_components.sh b/test/rust/rust_with_none_components.sh new file mode 100644 index 000000000..3cd56442f --- /dev/null +++ b/test/rust/rust_with_none_components.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Helper function to check component is installed +check_component_installed() { + local component=$1 + if rustup component list | grep -q "${component}.*installed"; then + return 0 # Component is installed (success) + else + return 1 # Component is not installed (failure) + fi +} + +# Helper function to check component is NOT installed +check_component_not_installed() { + local component=$1 + if rustup component list | grep -q "${component}.*installed"; then + return 1 # Component is installed (failure) + else + return 0 # Component is not installed (success) + fi +} + +# Definition specific tests +check "cargo version" cargo --version +check "rustc version" rustc --version +check "correct rust version" rustup target list | grep aarch64-unknown-linux-gnu + +# When components is set to "none", none of the default components should be installed +check "rust-analyzer not installed" check_component_not_installed "rust-analyzer" +check "rust-src not installed" check_component_not_installed "rust-src" +check "rustfmt not installed" check_component_not_installed "rustfmt" +check "clippy not installed" check_component_not_installed "clippy" + +# Report result +reportResults diff --git a/test/rust/scenarios.json b/test/rust/scenarios.json index 87c93bfa7..de6c08fb1 100644 --- a/test/rust/scenarios.json +++ b/test/rust/scenarios.json @@ -64,6 +64,16 @@ "components": "" } } + }, + "rust_with_none_components": { + "image": "ubuntu:noble", + "features": { + "rust": { + "version": "latest", + "targets": "aarch64-unknown-linux-gnu", + "components": "none" + } + } }, "rust_with_centos": { "image": "centos:centos7", From 518cf8f223048c8033f5c55f98d53d2be0cbe795 Mon Sep 17 00:00:00 2001 From: Venkumahanti Subhankar Date: Wed, 2 Sep 2026 13:21:20 +0000 Subject: [PATCH 2/2] handle regex edge cases for none --- src/rust/install.sh | 35 ++++++++++++++++++-------- test/rust/rust_with_none_components.sh | 10 -------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/rust/install.sh b/src/rust/install.sh index 171d666c0..163dfc5c0 100755 --- a/src/rust/install.sh +++ b/src/rust/install.sh @@ -399,19 +399,34 @@ if [ "${UPDATE_RUST}" = "true" ]; then rustup update 2>&1 fi # Install Rust components (skip entirely when explicitly set to "none") -if [ "${RUSTUP_COMPONENTS}" = "none" ]; then +# Trim each entry, drop empties, and track whether the "none" sentinel was requested. +resolved_components=() +components_none="false" +for component in "${components[@]}"; do + component="${component#"${component%%[![:space:]]*}"}" && component="${component%"${component##*[![:space:]]}"}" + if [ -z "${component}" ]; then + continue + fi + if [ "${component}" = "none" ]; then + components_none="true" + fi + resolved_components+=("${component}") +done + +if [ "${components_none}" = "true" ] && [ "${#resolved_components[@]}" -gt 1 ]; then + echo "Error: 'none' cannot be combined with other components. Set 'components' to 'none' on its own to skip installing components." >&2 + exit 1 +fi + +if [ "${components_none}" = "true" ]; then echo "Skipping Rust components installation as 'components' is set to 'none'." else echo "Installing Rust components..." - for component in "${components[@]}"; do - # Trim leading and trailing whitespace - component="${component#"${component%%[![:space:]]*}"}" && component="${component%"${component##*[![:space:]]}"}" - if [ -n "${component}" ]; then - echo "Installing Rust component: ${component}" - if ! rustup component add "${component}" 2>&1; then - echo "Warning: Failed to install component '${component}'. It may not be available for this toolchain." >&2 - exit 1 - fi + for component in "${resolved_components[@]}"; do + echo "Installing Rust component: ${component}" + if ! rustup component add "${component}" 2>&1; then + echo "Warning: Failed to install component '${component}'. It may not be available for this toolchain." >&2 + exit 1 fi done fi diff --git a/test/rust/rust_with_none_components.sh b/test/rust/rust_with_none_components.sh index 3cd56442f..8566bb548 100644 --- a/test/rust/rust_with_none_components.sh +++ b/test/rust/rust_with_none_components.sh @@ -5,16 +5,6 @@ set -e # Optional: Import test library source dev-container-features-test-lib -# Helper function to check component is installed -check_component_installed() { - local component=$1 - if rustup component list | grep -q "${component}.*installed"; then - return 0 # Component is installed (success) - else - return 1 # Component is not installed (failure) - fi -} - # Helper function to check component is NOT installed check_component_not_installed() { local component=$1