Skip to content

Build Spring Native Maven #6

Build Spring Native Maven

Build Spring Native Maven #6

# Build specialized Spring Boot nmvn native images (nmvn-spring-<bootVersion>) per platform.
#
# Same commands as local. Scripts write under build/ by default:
# build/catalogs/ — catalogs
# build/work/ — scratch
# build/nmvn-spring-* — images
# Maven dist stays in apache-maven/target/.
#
# Toolchain: download GraalVM CE 25.2.4, set JAVA_HOME (+ bin on PATH). Finding native-image
# (including Windows native-image.cmd) and Crema checks live in build-nmvn-prebuilt.sh.
#
# Per platform, for Boot 4.1.0 and 4.0.7:
# resolve_boot_catalog.py → build-nmvn-catalog.sh → zip to dist/
#
name: Build Spring Native Maven
on:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions: {}
env:
# https://github.com/graalvm/graalvm-ce-builds/releases/tag/graal-25.2.4
GRAAL_TAG: graal-25.2.4
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest-32-cores
platform: linux-x86_64
graal_file: graalvm-community-jdk-25i2-25.0.4_linux-x64_bin.tar.gz
- os: ubuntu-latest-32-cores-arm64
platform: linux-aarch64
graal_file: graalvm-community-jdk-25i2-25.0.4_linux-aarch64_bin.tar.gz
- os: macos-arm64-cipool
platform: macos-aarch64
graal_file: graalvm-community-jdk-25i2-25.0.4_macos-aarch64_bin.tar.gz
- os: windows-latest-16-cores
platform: windows-x86_64
graal_file: graalvm-community-jdk-25i2-25.0.4_windows-x64_bin.zip
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash
permissions:
contents: read
env:
BOOT_VERSIONS: "4.1.0 4.0.7"
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download and install GraalVM 25.2
run: |
set -euo pipefail
GRAAL_URL="https://github.com/graalvm/graalvm-ce-builds/releases/download/${GRAAL_TAG}/${{ matrix.graal_file }}"
INSTALL_ROOT="${RUNNER_TEMP:-/tmp}/graalvm-25.2"
ARCHIVE="${RUNNER_TEMP:-/tmp}/${{ matrix.graal_file }}"
echo ">>> Downloading ${GRAAL_URL}"
rm -rf "${INSTALL_ROOT}"
mkdir -p "${INSTALL_ROOT}"
curl -fsSL "${GRAAL_URL}" -o "${ARCHIVE}"
case "${{ matrix.graal_file }}" in
*.zip)
if command -v unzip >/dev/null 2>&1; then
unzip -q "${ARCHIVE}" -d "${INSTALL_ROOT}"
else
pwsh -NoProfile -Command "Expand-Archive -Path '${ARCHIVE}' -DestinationPath '${INSTALL_ROOT}' -Force"
fi
;;
*)
tar -xzf "${ARCHIVE}" -C "${INSTALL_ROOT}"
;;
esac
# Locate JAVA_HOME (macOS: Contents/Home; Linux/Windows: unpacked root).
JAVA_HOME_CANDIDATE=""
for d in "${INSTALL_ROOT}"/*/Contents/Home "${INSTALL_ROOT}"/*/*/Contents/Home; do
if [ -f "${d}/bin/native-image" ] || [ -f "${d}/bin/native-image.cmd" ] || [ -f "${d}/bin/native-image.exe" ]; then
JAVA_HOME_CANDIDATE="$d"
break
fi
done
if [ -z "${JAVA_HOME_CANDIDATE}" ]; then
for d in "${INSTALL_ROOT}"/*; do
if [ -d "$d" ] && { [ -f "${d}/bin/native-image" ] || [ -f "${d}/bin/native-image.cmd" ] || [ -f "${d}/bin/native-image.exe" ]; }; then
JAVA_HOME_CANDIDATE="$d"
break
fi
done
fi
if [ -z "${JAVA_HOME_CANDIDATE}" ]; then
echo "::error::Could not find native-image under ${INSTALL_ROOT}"
find "${INSTALL_ROOT}" -maxdepth 5 -type f \( -name 'native-image' -o -name 'native-image.cmd' -o -name 'native-image.exe' \) 2>/dev/null | head -20 || true
exit 1
fi
# Git Bash on Windows: store Unix-style JAVA_HOME for later bash steps.
if command -v cygpath >/dev/null 2>&1; then
JAVA_HOME_CANDIDATE="$(cygpath -u "${JAVA_HOME_CANDIDATE}")"
fi
# Subsequent steps inherit these (GITHUB_ENV / GITHUB_PATH).
echo "JAVA_HOME=${JAVA_HOME_CANDIDATE}" >> "$GITHUB_ENV"
echo "${JAVA_HOME_CANDIDATE}/bin" >> "$GITHUB_PATH"
echo ">>> JAVA_HOME=${JAVA_HOME_CANDIDATE}"
- name: Strip SNAPSHOT from versions
run: LC_ALL=C find . -name 'pom.xml' -exec sed -i'' -e 's/-SNAPSHOT//g' {} +
- name: Build Maven distribution
run: |
chmod +x ./mvnw
./mvnw clean package -B -DskipTests -Drat.skip=true -Dcheckstyle.skip=true
- name: Resolve catalogs and build Spring specialized images
run: |
set -euo pipefail
chmod +x catalog/resolve_boot_catalog.py build-nmvn-catalog.sh build-nmvn-prebuilt.sh
# Fresh build tree (same layout as local defaults).
rm -rf build dist
mkdir -p dist
package_zip() {
local zip_name="$1"
shift
if command -v zip >/dev/null 2>&1; then
zip -j "${zip_name}" "$@"
else
pwsh -NoProfile -Command \
"Compress-Archive -Path @($(printf "'%s'," "$@" | sed 's/,$//')) -DestinationPath '${zip_name}' -Force"
fi
}
for BOOT in $BOOT_VERSIONS; do
CATALOG="build/catalogs/nmvn-spring-${BOOT}.json"
echo "=========================================="
echo ">>> Boot ${BOOT}"
echo "=========================================="
./catalog/resolve_boot_catalog.py \
--boot-version "${BOOT}" \
--language java
./build-nmvn-catalog.sh "${CATALOG}"
BIN="build/nmvn-spring-${BOOT}"
if [ -f "${BIN}.exe" ]; then
BIN="${BIN}.exe"
fi
if [ ! -f "${BIN}" ]; then
echo "::error::Expected binary ${BIN} after build for Boot ${BOOT}"
ls -la build/ || true
exit 1
fi
PLUGINS="build/nmvn-spring-${BOOT}.plugins"
ZIP="nmvn-spring-${BOOT}-${{ matrix.platform }}-${{ github.sha }}.zip"
package_zip "dist/${ZIP}" "${BIN}" "${PLUGINS}"
echo ">>> Packaged dist/${ZIP}"
done
ls -la dist/
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: nmvn-spring-${{ matrix.platform }}
path: dist/*.zip
if-no-files-found: error
- name: Clean workspace outputs
if: always()
run: rm -rf build dist
publish:
needs: build
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' }}
permissions:
contents: read
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
merge-multiple: true
path: artifacts
- name: List artifacts
run: find artifacts -type f | sort
- name: Publish to R2
uses: elide-tools/r2-upload-action@main
with:
path: artifacts
target: native-maven
bucket: elide-userdata-public
account-id: ${{ secrets.R2_ACCOUNT_ID }}
access-key-id: ${{ secrets.R2_ACCESS_KEY_ID }}
secret-access-key: ${{ secrets.R2_SECRET_ACCESS_KEY }}
- name: Clean downloaded artifacts
if: always()
run: rm -rf artifacts