Search code examples
xcodeshjava-homesdkman

How to make /usr/libexec/java_home find JDK installed using SDKMAN on MacOS?


I am making iOS apps in Kotlin, which relies on having a Script Build Phase calling Gradle from XCode. With a JDK installed using SDKMAN, it does not work and produces this error:

The operation couldn’t be completed. Unable to locate a Java Runtime.
Please visit http://www.java.com for information on installing Java.

The JDK installed with SDKMAN is working correctly on my system, SDKMAN sets JAVA_HOME to /Users/{user}/.sdkman/candidates/java/current by sourcing its sdkman-init.sh script from .bash_profile or .zshrc. But XCode Script Phase uses /bin/sh, and it does not seem to read these kinds of files.

I tried exporting JAVA_HOME in files like ~/.profile, /etc/profile, etc. with no result.

After some research, I found that this output is from a call to /usr/libexec/java_home producing no result. I also found that manually calling /usr/libexec/java_home inside a Terminal where JAVA_HOME is set correctly does not pick it up, so toying with JAVA_HOME is useless in this case: it just doesn't read it.

So, how do you make the /usr/libexec/java_home command find JDK installed using SDKMAN on MacOS?


Solution

  • The /usr/libexec/java_home seems to be mostly undocumented, it's very hard to find information about how it works. Combining the little information about it found online and some trial and error, I managed to trick /usr/libexec/java_home into returning SDKMAN's current JDK.

    There are 2 issues that we need to work around:

    • SDKMAN-installed JDKs don't look like manually installed JDKs
    • SDKMAN-installed JDKs are not in a location /usr/libexec/java_home knows

    On MacOS, manually installed JDKs are installed in /Library/Java/JavaVirtualMachines and look like this (non-exhaustive):

    jdk-root-folder/
      Contents/
        Info.plist
        Home/
          <actual JDK files here>
    

    But SDKMAN only installs the actual JDK files. The solution is to fake everything SDKMAN does not install:

    • Create a folder in /Library/Java/JavaVirtualMachines for your JDK, I'll call mine sdkman-current but the name does not matter.
    • Create a Contents folder inside sdkman-current
    • Create a symlink named Home inside the Contents folder, linking to your actual JDK:
      sudo ln -s /Users/{REPLACE_ME}/.sdkman/candidates/java/current /Library/Java/JavaVirtualMachines/sdkman-current/Contents/Home
      
    • Create a Info.plist file in the Contents folder with the following content:
      <?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>JavaVM</key>
          <dict>
              <key>JVMPlatformVersion</key>
              <string>9999</string>
              <key>JVMVendor</key>
              <string>Homebrew</string>
              <key>JVMVersion</key>
              <string>9999</string>
          </dict>
      </dict>
      </plist>
      
      You can mostly use whatever <string> values you want in there, but note that removing any of the entries will prevent /usr/libexec/java_home from finding your JDK. I'm setting its version to 9999 so that its always the one returned by /usr/libexec/java_home.
    • Check that everything works:
      ➜  /usr/libexec/java_home
      /Library/Java/JavaVirtualMachines/sdkman-current/Contents/Home
      ➜  /usr/libexec/java_home -V
      Matching Java Virtual Machines (1):
          9999 (arm64) "Homebrew" - "SDKMAN Current JDK" /Library/Java/JavaVirtualMachines/sdkman-current/Contents/Home
      /Library/Java/JavaVirtualMachines/sdkman-current/Contents/Home
      

    Now, stuff relying on /usr/libexec/java_home to find a JDK should work, including XCode Script Build Phase using /bin/sh.