79808402

Date: 2025-11-03 23:46:28
Score: 0.5
Natty:
Report link

A One liner to create a symlink from macOS's expected Java location to SDKMAN's installation:

curl -fsSL https://gist.githubusercontent.com/abd3lraouf/1db9bf863144802733bfd29bb5dada87/raw/install.sh | bash -s install

This creates /Library/Java/JavaVirtualMachines/sdkman-current/ pointing to ~/.sdkman/candidates/java/current, allowing /usr/libexec/java_home and Xcode to find your SDKMAN Java installation.

When you switch Java versions with sdk use java [version], the symlink automatically points to the new version. Full script and manual setup instructions.Retry

Gist: https://gist.github.com/abd3lraouf/1db9bf863144802733bfd29bb5dada87

Full script here:

#!/usr/bin/env bash

################################################################################
# Setup SDKMAN JDK Integration with /usr/libexec/java_home
################################################################################
# This script configures macOS to recognize SDKMAN-installed JDKs through
# /usr/libexec/java_home by creating a proper directory structure and symlink
# in /Library/Java/JavaVirtualMachines.
#
# Usage:
#   ./setup_sdkman_java_home.sh [install|uninstall|verify|help]
#
# Options:
#   install   - Set up the SDKMAN JDK integration (default)
#   uninstall - Remove the SDKMAN JDK integration
#   verify    - Check if the setup is working correctly
#   help      - Display this help message
#
# Requirements:
#   - macOS
#   - SDKMAN installed with at least one JDK
#   - sudo privileges
################################################################################

set -eo pipefail

# Constants (temporarily disable -u for BASH_SOURCE check)
readonly SCRIPT_NAME="$(basename "${BASH_SOURCE[0]:-install.sh}")"
readonly SCRIPT_VERSION="1.0.0"

# Re-enable unbound variable checking
set -u
readonly TARGET_DIR="/Library/Java/JavaVirtualMachines/sdkman-current"
readonly CONTENTS_DIR="${TARGET_DIR}/Contents"
readonly HOME_LINK="${CONTENTS_DIR}/Home"
readonly PLIST_FILE="${CONTENTS_DIR}/Info.plist"

# Colors for output
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly BOLD='\033[1m'
readonly NC='\033[0m' # No Color

# Cleanup on error - silently exit (specific errors are already shown)
cleanup() {
    local exit_code=$?
    # Exit silently - specific error messages are already displayed
    exit $exit_code
}

trap cleanup EXIT

################################################################################
# Utility Functions
################################################################################

# Print colored messages
print_header() {
    echo -e "\n${BOLD}${BLUE}===${NC} ${BOLD}$1${NC} ${BOLD}${BLUE}===${NC}\n"
}

success() {
    echo -e "${GREEN}✓${NC} $1"
}

info() {
    echo -e "${BLUE}ℹ${NC} $1"
}

warn() {
    echo -e "${YELLOW}⚠${NC} $1"
}

error() {
    echo -e "${RED}✗${NC} $1" >&2
}

# Check if running on macOS
check_macos() {
    if [[ "$(uname -s)" != "Darwin" ]]; then
        error "This script only works on macOS"
        exit 1
    fi
}

# Check if SDKMAN is installed
check_sdkman() {
    local sdkman_dir="${SDKMAN_DIR:-$HOME/.sdkman}"

    if [[ ! -d "$sdkman_dir" ]]; then
        error "SDKMAN not found at $sdkman_dir"
        info "Install SDKMAN from: https://sdkman.io/install"
        exit 1
    fi

    success "SDKMAN found at $sdkman_dir"
}

# Get SDKMAN current JDK path
get_sdkman_current_jdk() {
    local sdkman_dir="${SDKMAN_DIR:-$HOME/.sdkman}"
    local current_jdk="${sdkman_dir}/candidates/java/current"

    if [[ ! -e "$current_jdk" ]]; then
        error "No current JDK set in SDKMAN"
        info "Install a JDK with: sdk install java"
        info "Or set current with: sdk use java <version>"
        exit 1
    fi

    # Resolve symlink to get actual JDK path
    local actual_jdk
    actual_jdk="$(readlink "$current_jdk" 2>/dev/null || echo "$current_jdk")"

    echo "$current_jdk"
}

# Get JDK version from SDKMAN
get_jdk_version() {
    local jdk_path="$1"

    # Source SDKMAN and get current version
    local sdkman_init="${SDKMAN_DIR:-$HOME/.sdkman}/bin/sdkman-init.sh"
    if [[ -f "$sdkman_init" ]]; then
        # Temporarily disable -u for sourcing SDKMAN and calling sdk (they have undefined variables)
        set +u
        # shellcheck source=/dev/null
        source "$sdkman_init" 2>/dev/null || true

        local version
        version="$(sdk current java 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+[^[:space:]]*' || echo "unknown")"
        set -u
        echo "$version"
    else
        echo "unknown"
    fi
}

# Get JDK vendor from path
get_jdk_vendor() {
    local jdk_path="$1"
    local vendor="SDKMAN"

    # Extract vendor from path if possible (e.g., 21.0.9-amzn -> Amazon)
    if [[ "$jdk_path" =~ -([a-z]+)$ ]]; then
        local vendor_code="${BASH_REMATCH[1]}"
        case "$vendor_code" in
            amzn) vendor="Amazon Corretto" ;;
            tem) vendor="Eclipse Temurin" ;;
            zulu) vendor="Azul Zulu" ;;
            graal*) vendor="GraalVM" ;;
            liberica) vendor="BellSoft Liberica" ;;
            sapmchn) vendor="SAP Machine" ;;
            *) vendor="SDKMAN ($vendor_code)" ;;
        esac
    fi

    echo "$vendor"
}

# Check if setup already exists
check_existing_setup() {
    [[ -d "$TARGET_DIR" ]]
}

# Verify sudo access
verify_sudo() {
    # Try non-interactive sudo first
    if sudo -n true 2>/dev/null; then
        # Already authenticated
        # Keep sudo alive
        while true; do sudo -n true; sleep 60; kill -0 "$$" || exit; done 2>/dev/null &
        return 0
    fi

    # Check if running non-interactively
    if ! [ -t 0 ]; then
        error "This script requires sudo access"
        echo ""
        echo "When running via pipe, sudo cannot prompt for password."
        echo "Please authenticate sudo first, then re-run:"
        echo ""
        echo "  sudo -v"
        echo "  curl -fsSL https://gist.githubusercontent.com/abd3lraouf/1db9bf863144802733bfd29bb5dada87/raw/install.sh | bash -s install"
        echo ""
        echo "Or download and run directly:"
        echo ""
        echo "  curl -O https://gist.githubusercontent.com/abd3lraouf/1db9bf863144802733bfd29bb5dada87/raw/install.sh"
        echo "  chmod +x install.sh"
        echo "  ./install.sh install"
        exit 1
    fi

    # Interactive mode - can prompt
    if ! sudo -v; then
        error "Sudo privileges required"
        exit 1
    fi

    # Keep sudo alive
    while true; do sudo -n true; sleep 60; kill -0 "$$" || exit; done 2>/dev/null &
}

################################################################################
# Installation Functions
################################################################################

install_setup() {
    print_header "Installing SDKMAN JDK Integration"

    # Get SDKMAN JDK path
    local sdkman_jdk
    sdkman_jdk="$(get_sdkman_current_jdk)"
    local jdk_version
    jdk_version="$(get_jdk_version "$sdkman_jdk")"
    local jdk_vendor
    jdk_vendor="$(get_jdk_vendor "$sdkman_jdk")"

    info "SDKMAN Current JDK: $sdkman_jdk"
    info "Version: $jdk_version"
    info "Vendor: $jdk_vendor"

    # Check for existing setup
    if check_existing_setup; then
        warn "Existing setup found at $TARGET_DIR"

        # Check if running interactively (can prompt)
        if [ -t 0 ]; then
            read -rp "Remove and reinstall? (y/N): " response
            if [[ "$response" =~ ^[Yy]$ ]]; then
                uninstall_setup
            else
                info "Installation cancelled"
                exit 0
            fi
        else
            # Non-interactive: auto-reinstall
            info "Non-interactive mode: auto-reinstalling..."
            uninstall_setup
        fi
    fi

    # Request sudo access
    verify_sudo

    # Create directory structure
    info "Creating directory structure..."
    sudo mkdir -p "$CONTENTS_DIR"
    success "Created $CONTENTS_DIR"

    # Create symlink
    info "Creating symlink to SDKMAN JDK..."
    sudo ln -sf "$sdkman_jdk" "$HOME_LINK"
    success "Created symlink: $HOME_LINK -> $sdkman_jdk"

    # Create Info.plist
    info "Creating Info.plist..."
    sudo tee "$PLIST_FILE" > /dev/null << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleIdentifier</key>
    <string>sdkman.current</string>
    <key>CFBundleName</key>
    <string>SDKMAN Current JDK</string>
    <key>CFBundleVersion</key>
    <string>${jdk_version}</string>
    <key>JavaVM</key>
    <dict>
        <key>JVMPlatformVersion</key>
        <string>9999</string>
        <key>JVMVendor</key>
        <string>${jdk_vendor}</string>
        <key>JVMVersion</key>
        <string>9999</string>
    </dict>
</dict>
</plist>
EOF
    success "Created Info.plist"

    # Set proper permissions
    info "Setting permissions..."
    sudo chmod -R 755 "$TARGET_DIR"
    success "Permissions set"

    print_header "Installation Complete"
    success "SDKMAN JDK integration installed successfully"

    # Verify installation
    verify_setup
}

################################################################################
# Uninstallation Functions
################################################################################

uninstall_setup() {
    print_header "Uninstalling SDKMAN JDK Integration"

    if ! check_existing_setup; then
        warn "No existing setup found at $TARGET_DIR"
        info "Nothing to uninstall"
        return 0
    fi

    # Request sudo access if not already verified
    verify_sudo

    info "Removing $TARGET_DIR..."
    sudo rm -rf "$TARGET_DIR"
    success "Removed SDKMAN JDK integration"

    print_header "Uninstallation Complete"
}

################################################################################
# Verification Functions
################################################################################

verify_setup() {
    print_header "Verifying Setup"

    # Check if setup exists
    if ! check_existing_setup; then
        error "Setup not found at $TARGET_DIR"
        info "Run with 'install' to set up the integration"
        exit 1
    fi

    # Check symlink
    if [[ ! -L "$HOME_LINK" ]]; then
        error "Symlink not found at $HOME_LINK"
        exit 1
    fi
    success "Symlink exists: $HOME_LINK"

    # Check if symlink target exists
    if [[ ! -e "$HOME_LINK" ]]; then
        error "Symlink target does not exist"
        local target
        target="$(readlink "$HOME_LINK")"
        error "Broken symlink: $HOME_LINK -> $target"
        exit 1
    fi
    local symlink_target
    symlink_target="$(readlink "$HOME_LINK")"
    success "Symlink target valid: $symlink_target"

    # Check Info.plist
    if [[ ! -f "$PLIST_FILE" ]]; then
        error "Info.plist not found at $PLIST_FILE"
        exit 1
    fi
    success "Info.plist exists"

    # Test /usr/libexec/java_home
    info "Testing /usr/libexec/java_home..."
    local java_home_output
    if java_home_output=$(/usr/libexec/java_home 2>&1); then
        success "/usr/libexec/java_home works"
        echo -e "\n${BOLD}Output:${NC}"
        echo "$java_home_output"
    else
        error "/usr/libexec/java_home failed"
        echo "$java_home_output"
        exit 1
    fi

    # Show all available JVMs
    echo -e "\n${BOLD}Available Java Virtual Machines:${NC}"
    /usr/libexec/java_home -V 2>&1 || true

    print_header "Verification Complete"
    success "Setup is working correctly"

    echo
    info "Notes:"
    echo "  • When you change JDK with 'sdk use java <version>', the symlink"
    echo "    will automatically point to the new current version"
    echo "  • Xcode and other tools can now find Java using /usr/libexec/java_home"
    echo "  • The version is set to 9999 to ensure this JDK is always selected"
}

################################################################################
# Help Function
################################################################################

show_help() {
    cat << 'EOF'
install.sh - Version 1.0.0

DESCRIPTION
    Configure macOS to recognize SDKMAN-installed JDKs through /usr/libexec/java_home
    by creating a proper directory structure in /Library/Java/JavaVirtualMachines.

USAGE
    install.sh [COMMAND]

COMMANDS
    install     Set up the SDKMAN JDK integration (default)
    uninstall   Remove the SDKMAN JDK integration
    verify      Verify the setup is working correctly
    help        Display this help message

EXAMPLES
    # Install the integration
    install.sh install

    # Verify it's working
    install.sh verify

    # Remove the integration
    install.sh uninstall

HOW IT WORKS
    1. Creates /Library/Java/JavaVirtualMachines/sdkman-current/Contents/
    2. Symlinks Contents/Home to SDKMAN's current JDK
    3. Creates Info.plist with proper metadata
    4. Sets version to 9999 to ensure it's always selected

REQUIREMENTS
    - macOS
    - SDKMAN installed with at least one JDK
    - sudo privileges

FILES CREATED
    /Library/Java/JavaVirtualMachines/sdkman-current/
    /Library/Java/JavaVirtualMachines/sdkman-current/Contents/
    /Library/Java/JavaVirtualMachines/sdkman-current/Contents/Home (symlink)
    /Library/Java/JavaVirtualMachines/sdkman-current/Contents/Info.plist

MORE INFO
    - SDKMAN: https://sdkman.io/
    - Gist: https://gist.github.com/abd3lraouf/1db9bf863144802733bfd29bb5dada87

EOF
}

################################################################################
# Main Function
################################################################################

main() {
    # Check prerequisites
    check_macos

    # Parse command
    local command="${1:-install}"

    case "$command" in
        install)
            check_sdkman
            install_setup
            ;;
        uninstall)
            uninstall_setup
            ;;
        verify)
            check_sdkman
            verify_setup
            ;;
        help|--help|-h)
            show_help
            ;;
        *)
            error "Unknown command: $command"
            echo
            show_help
            exit 1
            ;;
    esac
}

# Run main function
main "$@"
Reasons:
  • Blacklisted phrase (1): help me
  • Contains signature (1):
  • Long answer (-1):
  • Has code block (-0.5):
Posted by: abd3lraouf