diff --git a/misc/tools.func b/misc/tools.func index 82f87ffea..95cda0141 100644 --- a/misc/tools.func +++ b/misc/tools.func @@ -8677,6 +8677,7 @@ setup_postgresql_db() { export PG_DB_USER export PG_DB_PASS } + # ------------------------------------------------------------------------------ # Installs rbenv and ruby-build, installs Ruby and optionally Rails. # @@ -8696,8 +8697,26 @@ setup_ruby() { local RBENV_DIR="$HOME/.rbenv" local RBENV_BIN="$RBENV_DIR/bin/rbenv" local PROFILE_FILE="$HOME/.profile" + local BASH_PROFILE_FILE="$HOME/.bash_profile" + local BASHRC_FILE="$HOME/.bashrc" local TMP_DIR=$(mktemp -d) + if ! grep -q 'rbenv init' "$PROFILE_FILE" 2>/dev/null; then + cat <<'EOF' >>"$PROFILE_FILE" +export PATH="$HOME/.rbenv/bin:$PATH" +eval "$(rbenv init -)" +EOF + fi + if ! grep -q '.rbenv/shims' "$PROFILE_FILE" 2>/dev/null; then + echo 'export PATH="$HOME/.rbenv/shims:$HOME/.rbenv/bin:$PATH"' >>"$PROFILE_FILE" + fi + if [[ -f "$BASH_PROFILE_FILE" ]] && ! grep -q '.rbenv/shims' "$BASH_PROFILE_FILE"; then + echo 'export PATH="$HOME/.rbenv/shims:$HOME/.rbenv/bin:$PATH"' >>"$BASH_PROFILE_FILE" + fi + if [[ -f "$BASHRC_FILE" ]] && ! grep -q '.rbenv/shims' "$BASHRC_FILE"; then + echo 'export PATH="$HOME/.rbenv/shims:$HOME/.rbenv/bin:$PATH"' >>"$BASHRC_FILE" + fi + # Get currently installed Ruby version local CURRENT_RUBY_VERSION="" if [[ -x "$RBENV_BIN" ]]; then @@ -8795,42 +8814,32 @@ setup_ruby() { return 150 } - # Setup profile - if ! grep -q 'rbenv init' "$PROFILE_FILE" 2>/dev/null; then - echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >>"$PROFILE_FILE" - echo 'eval "$(rbenv init -)"' >>"$PROFILE_FILE" - fi fi # Install ruby-build plugin - _install_ruby_build_plugin() { + if [[ ! -d "$RBENV_DIR/plugins/ruby-build" ]]; then local RUBY_BUILD_RELEASE RUBY_BUILD_RELEASE=$(get_latest_github_release "rbenv/ruby-build") || { msg_error "Failed to fetch latest ruby-build version from GitHub" + rm -rf "$TMP_DIR" return 7 } if ! curl_with_retry "https://github.com/rbenv/ruby-build/archive/refs/tags/v${RUBY_BUILD_RELEASE}.tar.gz" "$TMP_DIR/ruby-build.tar.gz"; then msg_error "Failed to download ruby-build" msg_error "Hint: Check connectivity to github.com/rbenv/ruby-build" + rm -rf "$TMP_DIR" return 7 fi tar -xzf "$TMP_DIR/ruby-build.tar.gz" -C "$TMP_DIR" || { msg_error "Failed to extract ruby-build" + rm -rf "$TMP_DIR" return 251 } mkdir -p "$RBENV_DIR/plugins/ruby-build" cp -r "$TMP_DIR/ruby-build-${RUBY_BUILD_RELEASE}/." "$RBENV_DIR/plugins/ruby-build/" - return 0 - } - - if [[ ! -d "$RBENV_DIR/plugins/ruby-build" ]]; then - _install_ruby_build_plugin || { - rm -rf "$TMP_DIR" - return 7 - } fi # Setup PATH and install Ruby version @@ -8838,14 +8847,6 @@ setup_ruby() { eval "$("$RBENV_BIN" init - bash)" 2>/dev/null || true if ! "$RBENV_BIN" versions --bare 2>/dev/null | grep -qx "$RUBY_VERSION"; then - if [[ ! -f "$RBENV_DIR/plugins/ruby-build/share/ruby-build/$RUBY_VERSION" ]]; then - msg_info "Updating ruby-build definitions" - _install_ruby_build_plugin || { - rm -rf "$TMP_DIR" - return 7 - } - msg_ok "Updated ruby-build definitions" - fi $STD "$RBENV_BIN" install "$RUBY_VERSION" || { msg_error "Failed to install Ruby $RUBY_VERSION" rm -rf "$TMP_DIR" @@ -9029,6 +9030,162 @@ setup_rust() { fi } +# ------------------------------------------------------------------------------ +# Installs Rust toolchain and optional global crates via cargo. +# +# Description: +# - Installs rustup (if missing) +# - Installs or updates desired Rust toolchain (stable, nightly, or versioned) +# - Installs or updates specified global crates using `cargo install` +# +# Notes: +# - Skips crate install if exact version is already present +# - Updates crate if newer version or different version is requested +# +# Variables: +# RUST_TOOLCHAIN - Rust toolchain to install (default: stable) +# RUST_PROFILE - Rust installation profile (default: default, e.g. minimal) +# RUST_CRATES - Comma-separated list of crates (e.g. "cargo-edit,wasm-pack@0.12.1") +# ------------------------------------------------------------------------------ + +setup_rust() { + local RUST_TOOLCHAIN="${RUST_TOOLCHAIN:-stable}" + local RUST_PROFILE="${RUST_PROFILE:-default}" + local RUST_CRATES="${RUST_CRATES:-}" + local CARGO_BIN="${HOME}/.cargo/bin" + + # Get currently installed version + local CURRENT_VERSION="" + if command -v rustc &>/dev/null; then + CURRENT_VERSION=$(rustc --version 2>/dev/null | awk '{print $2}' 2>/dev/null) || true + fi + + # Scenario 1: Rustup not installed - fresh install + if ! command -v rustup &>/dev/null; then + msg_info "Setup Rust ($RUST_TOOLCHAIN, profile: $RUST_PROFILE)" + curl -fsSL https://sh.rustup.rs | $STD sh -s -- -y --profile "$RUST_PROFILE" --default-toolchain "$RUST_TOOLCHAIN" || { + msg_error "Failed to install Rust" + msg_error "Hint: Check connectivity to sh.rustup.rs and static.rust-lang.org" + return 7 + } + export PATH="$CARGO_BIN:$PATH" + echo 'export PATH="$HOME/.cargo/bin:$PATH"' >>"$HOME/.profile" + + # Verify installation + if ! command -v rustc >/dev/null 2>&1; then + msg_error "Rust binary not found after installation" + return 127 + fi + + local RUST_VERSION + RUST_VERSION=$(rustc --version 2>/dev/null | awk '{print $2}' 2>/dev/null) || true + if [[ -z "$RUST_VERSION" ]]; then + msg_error "Failed to determine Rust version" + return 250 + fi + + cache_installed_version "rust" "$RUST_VERSION" + msg_ok "Setup Rust $RUST_VERSION" + else + # Scenario 2: Rustup already installed - update/maintain + msg_info "Update Rust ($RUST_TOOLCHAIN)" + + # Ensure default toolchain is set + $STD rustup default "$RUST_TOOLCHAIN" 2>/dev/null || { + # If default fails, install the toolchain first + $STD rustup install "$RUST_TOOLCHAIN" || { + msg_error "Failed to install Rust toolchain $RUST_TOOLCHAIN" + return 150 + } + $STD rustup default "$RUST_TOOLCHAIN" || { + msg_error "Failed to set default Rust toolchain" + return 150 + } + } + + # Update to latest patch version + $STD rustup update "$RUST_TOOLCHAIN" /dev/null | awk '{print $2}' 2>/dev/null) || true + if [[ -z "$RUST_VERSION" ]]; then + msg_error "Failed to determine Rust version after update" + return 250 + fi + + cache_installed_version "rust" "$RUST_VERSION" + msg_ok "Update Rust $RUST_VERSION" + fi + + # Install global crates + if [[ -n "$RUST_CRATES" ]]; then + msg_info "Processing Rust crates: $RUST_CRATES" + IFS=',' read -ra CRATES <<<"$RUST_CRATES" + for crate in "${CRATES[@]}"; do + crate=$(echo "$crate" | xargs) # trim whitespace + [[ -z "$crate" ]] && continue # skip empty entries + + local NAME VER INSTALLED_VER CRATE_LIST + if [[ "$crate" == *"@"* ]]; then + NAME="${crate%@*}" + VER="${crate##*@}" + else + NAME="$crate" + VER="" + fi + + # Get list of installed crates once + CRATE_LIST=$(cargo install --list 2>/dev/null || echo "") + + # Check if already installed + if echo "$CRATE_LIST" | grep -q "^${NAME} "; then + INSTALLED_VER=$(echo "$CRATE_LIST" | grep "^${NAME} " | head -1 | awk '{print $2}' 2>/dev/null | tr -d 'v:' || echo '') + + if [[ -n "$VER" && "$VER" != "$INSTALLED_VER" ]]; then + msg_info "Upgrading $NAME from v$INSTALLED_VER to v$VER" + $STD cargo install "$NAME" --version "$VER" --force || { + msg_error "Failed to install $NAME@$VER" + return 150 + } + msg_ok "Upgraded $NAME to v$VER" + elif [[ -z "$VER" ]]; then + msg_info "Upgrading $NAME to latest" + $STD cargo install "$NAME" --force || { + msg_error "Failed to upgrade $NAME" + return 150 + } + local NEW_VER=$(cargo install --list 2>/dev/null | grep "^${NAME} " | head -1 | awk '{print $2}' 2>/dev/null | tr -d 'v:' || echo 'unknown') + msg_ok "Upgraded $NAME to v$NEW_VER" + else + msg_ok "$NAME v$INSTALLED_VER already installed" + fi + else + msg_info "Installing $NAME${VER:+@$VER}" + if [[ -n "$VER" ]]; then + $STD cargo install "$NAME" --version "$VER" || { + msg_error "Failed to install $NAME@$VER" + return 150 + } + msg_ok "Installed $NAME v$VER" + else + $STD cargo install "$NAME" || { + msg_error "Failed to install $NAME" + return 150 + } + local NEW_VER=$(cargo install --list 2>/dev/null | grep "^${NAME} " | head -1 | awk '{print $2}' 2>/dev/null | tr -d 'v:' || echo 'unknown') + msg_ok "Installed $NAME v$NEW_VER" + fi + fi + done + msg_ok "Processed Rust crates" + fi +} + # ------------------------------------------------------------------------------ # Installs or upgrades uv (Python package manager) from GitHub releases. # - Downloads platform-specific tarball (no install.sh!)