How to Compile Java into Native Binaries with Mill and Graal :: The Mill Build Tool
Although Graal doesn’t let you cross-build from a single platform, you can still easily
publish artifacts for all supported versions by taking advantage of CI systems like
Github Actions that provide worker machines on different platforms.
For Mill, which is distributed as native binaries, we maintain a
matrix of Github actions jobs
running on Mac, Windows, and Linux to create these binaries and upload them to Maven Central
for users.
on:
push:
tags:
- '**'
workflow_dispatch:
jobs:
publish-sonatype:
# when in master repo, publish all tags and manual runs on main
if: github.repository == 'com-lihaoyi/mill'
runs-on: ${{ matrix.os }}
# only run one publish job for the same sha at the same time
# e.g. when a main-branch push is also tagged
concurrency: publish-sonatype-${{ matrix.os }}-${{ github.sha }}
strategy:
matrix:
include:
- os: ubuntu-latest
coursierarchive: ""
publishartifacts: __.publishArtifacts
- os: ubuntu-24.04-arm
coursierarchive: ""
publishartifacts: dist.native.publishArtifacts
- os: macos-13
coursierarchive: ""
publishartifacts: dist.native.publishArtifacts
- os: macos-latest
coursierarchive: ""
publishartifacts: dist.native.publishArtifacts
- os: windows-latest
coursierarchive: C:/coursier-arc
publishartifacts: dist.native.publishArtifacts
# No windows-arm support becaues Graal native image doesn't support it
# https://github.com/oracle/graal/issues/9215
env:
MILL_STABLE_VERSION: 1
MILL_SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
MILL_SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
MILL_PGP_SECRET_BASE64: ${{ secrets.SONATYPE_PGP_PRIVATE_KEY }}
MILL_PGP_PASSPHRASE: ${{ secrets.SONATYPE_PGP_PRIVATE_KEY_PASSWORD }}
LANG: "en_US.UTF-8"
LC_MESSAGES: "en_US.UTF-8"
LC_ALL: "en_US.UTF-8"
COURSIER_ARCHIVE_CACHE: ${{ matrix.coursierarchive }}
REPO_ACCESS_TOKEN: ${{ secrets.REPO_ACCESS_TOKEN }}
steps:
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '11'
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- run: ./mill -i mill.scalalib.PublishModule/ --publishArtifacts ${{ matrix.publishartifacts }}
Note that the default ubuntu-latest
job publishes __.publishArtifacts
(all artifacts),
while the other platform-specific jobs publish only dist.native.publishArtifacts
(the native
artifacts in the dist.native
folder). This ensures that the non-native jars which are
portable get published only once across all platforms, while the native CPU-specific binary
gets published once per platform
Each job overrides artifactName
based on os.name
and os.arch
such that it publishes to a
different artifact on Maven Central, and we override def jar
to replace
the default .jar
artifact with our native image:
def artifactOsSuffix = Task {
val osName = System.getProperty("os.name").toLowerCase
if (osName.contains("mac")) "mac"
else if (osName.contains("windows")) "windows"
else "linux"
}
def artifactCpuSuffix = Task {
System.getProperty("os.arch") match {
case "x86_64" => "amd64"
case s => s
}
}
override def artifactName = s"${super.artifactName()}-${artifactOsSuffix()}-${artifactCpuSuffix()}"
override def jar = nativeImage()
This results in the following artifacts being published:
# JVM platform-agnostic artifact
com.lihaoyi:mill-dist:0.12.6
# native platform-specific artifacts
com.lihaoyi:mill-dist-native-mac-amd64:0.12.6
com.lihaoyi:mill-dist-native-mac-aarch64:0.12.6
com.lihaoyi:mill-dist-native-linux-amd64:0.12.6
com.lihaoyi:mill-dist-native-linux-aarch64:0.12.6
com.lihaoyi:mill-dist-native-windows-amd64:0.12.6
These artifacts can be seen online:
curl https://repo1.maven.org/maven2/com/lihaoyi/mill-dist-native-mac-aarch64/0.12.6/mill-dist-native-mac-aarch64-0.12.6.jar -o mill-dist-native
chmod +x mill-dist-native
./mill-dist-native version
0.12.6
Any application using these binaries can similarly look at the OS/CPU they are running
on and resolve the appropriate executable for them to use.