CI: build the Intel Mac installer on Apple Silicon via Rosetta #3
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Desktop installers | |
| # Builds native desktop installers (each with a bundled Java runtime) via jpackage, then | |
| # publishes them to the OPF artifact server and attaches them to the GitHub Release. | |
| # | |
| # jpackage cannot cross-compile, and JavaFX ships arch-specific native libraries, so every | |
| # target is built on a runner whose JDK matches that target's architecture: | |
| # Linux x64 (ubuntu-latest) -> .deb | |
| # macOS x64 (macos-latest + Rosetta 2) -> .dmg | |
| # macOS arm64 (macos-latest, Apple) -> .dmg | |
| # Windows x64 (windows-latest) -> .msi | |
| # | |
| # The Intel .dmg is built on an Apple Silicon runner with an x64 JDK running under Rosetta 2, | |
| # rather than on the scarce macos-13 (Intel) pool that frequently never allocates a runner. | |
| # Under Rosetta the JVM reports os.arch=x86_64, so Maven activates the javafx-mac-x64 profile | |
| # and jpackage bundles an x64 runtime: the output is a genuine x64 build, not a mislabel. | |
| # | |
| # There is no Linux arm64 target: JavaFX publishes no linux-aarch64 build, so the native | |
| # window cannot be packaged there. ARM Linux users run the server or Docker instead. | |
| # | |
| # Trigger: | |
| # - a v* tag -> build, upload to the artifact server, attach to the Release | |
| # - manual dispatch -> build and upload the installers as workflow artifacts only | |
| on: | |
| push: | |
| tags: ['v*'] | |
| workflow_dispatch: | |
| permissions: | |
| contents: write # needed to create/attach to the GitHub Release | |
| jobs: | |
| package: | |
| name: ${{ matrix.artifact }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| artifact: linux-x64 | |
| - os: macos-latest # Apple Silicon, x64 JDK under Rosetta 2 -> Intel .dmg | |
| artifact: mac-x64 | |
| jdk_arch: x64 | |
| target_arch: x64 | |
| rosetta: true | |
| - os: macos-latest # Apple Silicon | |
| artifact: mac-arm64 | |
| - os: windows-latest | |
| artifact: windows-x64 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install Rosetta 2 (x64 build on Apple Silicon) | |
| # Rosetta must be present before any x64 java runs, including setup-java's version check. | |
| # Idempotent: exits 0 when Rosetta is already installed. | |
| if: matrix.rosetta | |
| run: softwareupdate --install-rosetta --agree-to-license | |
| - name: Set up JDK 17 (Temurin, includes jpackage) | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: '17' | |
| # Empty for native runners (setup-java defaults to the host arch); 'x64' forces the | |
| # Intel JDK on the Apple Silicon runner so jpackage emits an x64 bundle under Rosetta. | |
| architecture: ${{ matrix.jdk_arch || '' }} | |
| cache: maven | |
| - name: Set up Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| - name: Ensure WiX Toolset 3.x (Windows MSI) | |
| # jpackage on JDK 17 uses WiX 3 (candle.exe / light.exe). windows-latest already ships | |
| # WiX 3.14; --force makes a re-install idempotent instead of exiting 1. | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: choco install wixtoolset -y --no-progress --force | |
| - name: Build the installer | |
| shell: bash | |
| env: | |
| # Labels the output arch explicitly for the Rosetta cross-build, where uname would | |
| # report the host's arm64. Empty on native runners, which fall back to uname. | |
| TARGET_ARCH: ${{ matrix.target_arch || '' }} | |
| run: ./build-desktop.sh | |
| - name: Upload to the OPF artifact server | |
| # Only on a tag, and only when the token is configured. A missing token skips cleanly | |
| # rather than failing the build, so forks and untokened runs still produce artifacts. | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| shell: bash | |
| env: | |
| TOKEN: ${{ secrets.ARTIFACT_UPLOAD_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${TOKEN}" ]; then | |
| echo "ARTIFACT_UPLOAD_TOKEN not set; skipping artifact-server upload." | |
| exit 0 | |
| fi | |
| REPO_SAFE="$(echo '${{ github.repository }}' | tr '/' '-')" | |
| VERSION_SAFE="$(echo '${{ github.ref_name }}' | tr '/:' '--')" | |
| BASE="https://artifacts.opf-labs.org/upload/${REPO_SAFE}/${VERSION_SAFE}" | |
| # Portable hashing: macOS has md5/shasum, Linux and git-bash have md5sum/sha256sum. | |
| md5_of() { if command -v md5sum >/dev/null; then md5sum "$1" | awk '{print $1}'; else md5 -q "$1"; fi; } | |
| sha256_of() { if command -v sha256sum >/dev/null; then sha256sum "$1" | awk '{print $1}'; else shasum -a 256 "$1" | awk '{print $1}'; fi; } | |
| shopt -s nullglob | |
| for f in target/desktop/*.deb target/desktop/*.rpm target/desktop/*.dmg target/desktop/*.msi target/desktop/*.exe; do | |
| case "$f" in | |
| *.deb) TYPE="Debian Package" ;; | |
| *.rpm) TYPE="RPM Package" ;; | |
| *.dmg) TYPE="macOS Disk Image" ;; | |
| *.msi) TYPE="Windows Installer" ;; | |
| *.exe) TYPE="Windows Installer" ;; | |
| *) TYPE="Desktop Installer" ;; | |
| esac | |
| echo "Uploading $(basename "$f") (${TYPE})…" | |
| curl -X POST "${BASE}" \ | |
| -H "Authorization: Bearer ${TOKEN}" \ | |
| -F "file=@${f}" \ | |
| -F "md5=$(md5_of "$f")" \ | |
| -F "sha256=$(sha256_of "$f")" \ | |
| -F "file_type=${TYPE}" \ | |
| --max-time 1800 \ | |
| --fail-with-body | |
| done | |
| echo "Browse: https://artifacts.opf-labs.org/browse/${REPO_SAFE}/${VERSION_SAFE}" | |
| - name: Upload installers as workflow artifacts | |
| # Always, so manual (dispatch) runs and untokened tag runs still yield downloads. | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: openfixity-${{ matrix.artifact }} | |
| path: | | |
| target/desktop/*.deb | |
| target/desktop/*.rpm | |
| target/desktop/*.dmg | |
| target/desktop/*.msi | |
| target/desktop/*.exe | |
| if-no-files-found: warn | |
| - name: Attach to the GitHub Release | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| # Pre-release for -ALPHA / -BETA / -RC tags, so it does not show as "Latest". | |
| prerelease: ${{ contains(github.ref_name, '-') }} | |
| files: | | |
| target/desktop/*.deb | |
| target/desktop/*.rpm | |
| target/desktop/*.dmg | |
| target/desktop/*.msi | |
| target/desktop/*.exe | |
| fail_on_unmatched_files: false | |
| body: | | |
| OpenFixity desktop installers. | |
| These installers are **not code-signed**. On first launch: | |
| - **macOS**: right-click the app and choose *Open*, then confirm, to get past Gatekeeper. | |
| - **Windows**: on the SmartScreen prompt, choose *More info* then *Run anyway*. | |
| See INSTALL.md for details. |