#!/usr/bin/env bash set -Eeuo pipefail SOURCE_DIR=${1:?usage: ensure-rust-toolchain.sh SOURCE_DIR} [[ -r "$SOURCE_DIR/Cargo.toml" ]] || { echo "missing Cargo.toml: $SOURCE_DIR" >&2; exit 1; } metadata=$(python3.11 - "$SOURCE_DIR" <<'PY' import pathlib, re, sys, tomllib root=pathlib.Path(sys.argv[1]) manifest=tomllib.loads((root/'Cargo.toml').read_text()) minimum=manifest.get('package',{}).get('rust-version','') pinned='' p=root/'rust-toolchain.toml' if p.exists(): pinned=tomllib.loads(p.read_text()).get('toolchain',{}).get('channel','') if minimum and not re.fullmatch(r'[0-9]+\.[0-9]+(?:\.[0-9]+)?', minimum): raise SystemExit('unsupported rust-version') if pinned and not (pinned=='stable' or re.fullmatch(r'[0-9]+\.[0-9]+(?:\.[0-9]+)?', pinned)): raise SystemExit('unsupported rust-toolchain channel') print(minimum) print(pinned) PY ) mapfile -t requested <<< "$metadata" minimum=${requested[0]:-} channel=${requested[1]:-stable} [[ -n "$channel" ]] || channel=stable if ! rustup run "$channel" rustc --version >/dev/null 2>&1; then echo "installing Rust toolchain $channel" >&2 rustup toolchain install "$channel" --profile minimal >&2 fi installed=$(rustup run "$channel" rustc --version | awk '{print $2}') if [[ -n "$minimum" ]]; then lowest=$(printf '%s\n%s\n' "$installed" "$minimum" | sort -V | head -n1) if [[ "$lowest" != "$minimum" ]]; then echo "Rust $installed does not satisfy project MSRV $minimum" >&2 exit 1 fi fi printf '%s\n' "$channel"