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.



Source link