Skip to content

Instantly share code, notes, and snippets.

@cblichmann
Created February 25, 2021 09:27
Show Gist options
  • Save cblichmann/2c3f002d2be53429dfbef6aef286bca2 to your computer and use it in GitHub Desktop.
Save cblichmann/2c3f002d2be53429dfbef6aef286bca2 to your computer and use it in GitHub Desktop.

How to create Frankenstein's JDK on macOS with Universal 2 Binaries

All of the steps below were tested on an Intel Mac running Big Sur.

Download and extract your zulu JDK and point these variables to it:

export ARM64=${PWD}/zulu17.0.13-ea-jdk17.0.0-ea.2-macosx_aarch64
export AMD64=${PWD}/zulu17.0.13-ea-jdk17.0.0-ea.2-macosx_x64
export BUILD=${PWD}
export UJDK=${PWD}/ujdk

Find native binary files from the x86-64 version and save file names of binaries and non-binaries to temporary text files. The x86-64 version serves as a template.

pushd "${AMD64}"
find zulu-17.jdk/Contents -type f -print0 | \
  sort -z | \
  xargs -0 -n1 -P1 file > "${BUILD}/__files_and_types.txt"
grep -v ': Mach-O.*x86_64$' "${BUILD}/__files_and_types.txt" | \
  cut -d: -f1 > "${BUILD}/__non_binary_files.txt"
grep ': Mach-O.*x86_64$' "${BUILD}/__files_and_types.txt" | \
  cut -d: -f1 > "${BUILD}/__binary_files.txt"
# Copy non-binaries
while read line; do \
  mkdir -p "${UJDK}/$(dirname "${line}")"; \
  cp "${line}" "${UJDK}/$(dirname "${line}")"; \
done < "${BUILD}/__non_binary_files.txt"
popd

For all the regular native binaries, merge them to create Universal 2 Binaries.

while read line; do \
  mkdir -p "${UJDK}/$(dirname "${line}")"; \
  lipo -create \
    -output "${UJDK}/${line}" \
    "${AMD64}/${line}" \
    "${ARM64}/${line}"; \
done < "${BUILD}/__binary_files.txt"

Dealing with Java modules

Java modules also contain binary files embedded in their *.jmod files. For instance, the packaging tool contains a single-arch launcher binary. Hence, we need to extract those, to and re-combine those binaries that occur in both x86-64 and AArch64 architecture builds.

Extract:

pushd "${AMD64}/jmods"
for m in *.jmod; do \
  mkdir -p "${BUILD}/jmod/amd64/${m/.jmod/}"; \
  "${AMD64}/bin/jmod" extract --dir "${BUILD}/jmod/amd64/${m/.jmod/}" "${m}"; \
done
popd
pushd "${ARM64}/jmods"
for m in *.jmod; do \
  mkdir -p "${BUILD}/jmod/arm64/${m/.jmod/}"; \
  "${AMD64}/bin/jmod" extract --dir "${BUILD}/jmod/arm64/${m/.jmod/}" "${m}"; \
done
popd

Again, gather file lists and classify into binaries and non-binaries.

pushd "${BUILD}/jmod/amd64"
find . -type f -print0 | \
  sort -z | \
  xargs -0 -n1 -P1 file > "${BUILD}/jmod/__files_and_types.txt"
grep -v ': Mach-O.*x86_64$' "${BUILD}/jmod/__files_and_types.txt" | \
  cut -d: -f1 > "${BUILD}/jmod/__non_binary_files.txt"
grep ': Mach-O.*x86_64$' "${BUILD}/jmod/__files_and_types.txt" | \
  cut -d: -f1 > "${BUILD}/jmod/__binary_files.txt"
while read line; do \
  mkdir -p "${BUILD}/jmod/ujdk/$(dirname "${line}")"; \
  cp "${line}" "${BUILD}/jmod/ujdk/$(dirname "${line}")"; \
done < "${BUILD}/jmod/__non_binary_files.txt"
popd

Re-combine into Universal 2 BinariesL

while read line; do \
  mkdir -p "${BUILD}/jmod/ujdk/$(dirname "${line}")"; \
  lipo -create \
    -output "${BUILD}/jmod/ujdk/${line}" \
    "${BUILD}/jmod/amd64/${line}" \
    "${BUILD}/jmod/arm64/${line}"; \
done < "${BUILD}/jmod/__binary_files.txt"

Use the jmod tool to package the Java modules again and move the resulting modules into place.

pushd "${BUILD}/jmod/ujdk/"
for m in *; do \
(cd "${m}"; "${AMD64}/bin/jmod" create \
  --class-path classes \
  $([ -d bin     ] && echo --cmds bin) \
  $([ -d conf    ] && echo --config conf) \
  $([ -d include ] && echo --header-files include) \
  $([ -d legal   ] && echo --legal-notices legal) \
  $([ -d lib     ] && echo --libs lib) \
  $([ -d man     ] && echo --man-pages man) \
  "${BUILD}/jmod/${m}.jmod"); \
done
popd
mv "${BUILD}/jmod/"*.jmod "${UJDK}/zulu-17.jdk/Contents/Home/jmods/"

The ${UJDK} directory now contains a "Universal JDK". There are some caveats:

  • I haven't been able to make Java packager work -- the resulting packages won't load on M1 Macs.
  • You may need to an add additional code-signing step before packaging everything up again
  • The resulting JDK will need to be re-notarized as well
% file ${UJDK}/zulu-17.jdk/Contents/Home/bin/java
ujdk/zulu-17.jdk/Contents/Home/bin/java: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64:Mach-O 64-bit executable arm64]
ujdk/zulu-17.jdk/Contents/Home/bin/java (for architecture x86_64):	Mach-O 64-bit executable x86_64
ujdk/zulu-17.jdk/Contents/Home/bin/java (for architecture arm64):	Mach-O 64-bit executable arm64
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment