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?
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:
/usr/libexec/java_home
knowsOn 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:
/Library/Java/JavaVirtualMachines
for your JDK, I'll call mine sdkman-current
but the name does not matter.Contents
folder inside sdkman-current
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
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
.➜ /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
.