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..163dfc5c0 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,38 @@ if [ "${UPDATE_RUST}" = "true" ]; then echo "Updating Rust..." rustup update 2>&1 fi -# Install Rust components -echo "Installing Rust components..." +# Install Rust components (skip entirely when explicitly set to "none") +# Trim each entry, drop empties, and track whether the "none" sentinel was requested. +resolved_components=() +components_none="false" for component in "${components[@]}"; do - # Trim leading and trailing whitespace component="${component#"${component%%[![:space:]]*}"}" && component="${component%"${component##*[![:space:]]}"}" - if [ -n "${component}" ]; then + 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 "${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 - 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..8566bb548 --- /dev/null +++ b/test/rust/rust_with_none_components.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# 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",