1

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?

Ribesg
  • 358
  • 4
  • 15

1 Answers1

1

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.

Ribesg
  • 358
  • 4
  • 15