كيف تجعل macOS ترى Java الخاصة بـ SDKMAN: إصلاح بسطر واحد
كيف تدلّ /usr/libexec/java_home على نسخة JDK التي ثبّتها عبر SDKMAN على macOS، وتُصلح فشل بناء Xcode وتكامل بقية الأدوات بأمرٍ واحد.
إن حاولت يومًا بناء مشروع Kotlin Multiplatform في Xcode مع JDK يثبّته SDKMAN، فلا بدّ أنك اصطدمت بهذا الخطأ المحبِط:
The operation couldn't be completed. Unable to locate a Java Runtime.
Please visit http://www.java.com for information on installing Java.
Java عندك تعمل في الطرفية أحسن ما تكون، وjava -version يطبع ما تتوقعه بالضبط. أما Xcode؟ أعمى عن تثبيتك بالكامل.
المشكلة
واجهتني هذه المشكلة وأنا أطوّر تطبيقات iOS بـ Kotlin. في هذه المشاريع يستدعي Xcode أداة Gradle ضمن مراحل البناء النصية (Script Build Phases)، لكن جذر المشكلة أبعد من Xcode نفسه: SDKMAN وmacOS ينظر كلٌّ منهما إلى «تثبيت Java» نظرةً مختلفة تمامًا.
يثبّت SDKMAN نسخ JDK هنا:
~/.sdkman/candidates/java/
أما macOS فيبحث عنها هنا:
/Library/Java/JavaVirtualMachines/
وصاحب الدور الرئيسي في الحكاية هو /usr/libexec/java_home: أداة macOS التي لا تكاد تجد لها توثيقًا، يستعين بها Xcode وأمثاله للعثور على تثبيتات Java. هذه الأداة لا تعبأ بمتغيّرات البيئة مثل JAVA_HOME في شيء؛ لا تبحث عن JDK إلا في الموقع القياسي على macOS، وببنية مجلدات بعينها.
الفضل في اكتشاف هذا الحل وتوثيقه يعود إلى Ribesg على StackOverflow، وقد أراحت إجابته آلاف المطوّرين ممن اصطدموا بالمشكلة نفسها. أما أنا فأتمتُّ خطواته اليدوية في سكربت تثبيت بسطر واحد.
الحل اليدوي (شكرًا Ribesg!)
توصّل Ribesg إلى أن /usr/libexec/java_home لا يرضى إلا بهذا الهيكل تحديدًا:
jdk-root-folder/
Contents/
Info.plist
Home/
<actual JDK files>
والحل هو بناء هيكل JDK «مزيّف» يشير عبر رابطٍ رمزي إلى تثبيت SDKMAN عندك:
- أنشئ المجلد
/Library/Java/JavaVirtualMachines/sdkman-current/Contents/ - اجعل
Contents/Homeرابطًا رمزيًا يشير إلى~/.sdkman/candidates/java/current - أضف ملف
Info.plistبالبيانات الوصفية الصحيحة
وأذكى ما في الأمر: حين تضبط رقم الإصدار على 9999 في الـ plist، يصبح هذا الـ JDK هو الافتراضي دائمًا متى تعدّدت الإصدارات.
الإصلاح بسطر واحد
الإعداد اليدوي كثير الخطأ ومُملّ، فكتبتُ سكربتًا يقوم به عنك:
curl -fsSL https://gist.githubusercontent.com/abd3lraouf/1db9bf863144802733bfd29bb5dada87/raw/install.sh | bash -s installهذا ما يفعله السكربت:
- ينشئ هيكل المجلدات الصحيح في
/Library/Java/JavaVirtualMachines/ - ينشئ رابطًا رمزيًا إلى النسخة الحالية من JDK لدى SDKMAN
- يولّد ملف
Info.plistالمطلوب ببياناته الوصفية الصحيحة - يتأكد أن كل شيء يعمل كما ينبغي
والأجمل؟ بدّل إصدار Java بـ sdk use java 17 فيتتبّع التكامل التبديل تلقائيًا، لأنه يشير إلى الرابط الرمزي current لدى SDKMAN لا إلى إصدارٍ بعينه.
التحقق
بعد تشغيل السكربت، تأكّد أن كل شيء على ما يرام:
/usr/libexec/java_home
# Output: /Library/Java/JavaVirtualMachines/sdkman-current/Contents/Home
/usr/libexec/java_home -V
# Shows your SDKMAN JDK in the listوبهذا يغدو تثبيت SDKMAN في متناول مراحل بناء Xcode، وإضافات Java في VSCode، وأي أداة تعتمد على /usr/libexec/java_home.
خلف الكواليس
هذا ما ينشئه السكربت:
/Library/Java/JavaVirtualMachines/sdkman-current/
├── Contents/
│ ├── Home → ~/.sdkman/candidates/java/current
│ └── Info.plist
يحتوي ملف Info.plist على:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "...">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>sdkman.current</string>
<key>CFBundleName</key>
<string>SDKMAN Current JDK</string>
<key>JavaVM</key>
<dict>
<key>JVMVersion</key>
<string>9999</string>
</dict>
</dict>
</plist>أوامر أخرى
التثبيت ليس كل ما في جعبة السكربت:
# Verify the setup is working
./install.sh verify
# Clean removal
./install.sh uninstall
# Show help
./install.sh helpلماذا هذا مهم
المسألة أكبر من Xcode. أدوات كثيرة تعتمد على /usr/libexec/java_home:
- إضافات Java في VSCode/Cursor
- IntelliJ IDEA (أحيانًا)
- Gradle حين تشغّله من طرفيةٍ ليس فيها
JAVA_HOME - Android Studio في بعض الإعدادات
- أي تطبيق بواجهة رسومية يستخدم
JavaLauncher.app
إن كنت تستخدم SDKMAN — وأنصحك به، فهو أفضل وسيلة لإدارة إصدارات Java — فإن هذا الإصلاح بسطر واحد يسدّ الفجوة بين حرية SDKMAN في التنقّل بين الإصدارات وجمود macOS في توقّعاته.
المصادر
دع عنك مصارعة تثبيتات Java. نفّذ السطر، وعُد إلى ما كنت تبنيه.