|
#!/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 "$@" |