Skip to content

Commit 2caed36

Browse files
fvarruiclaude
andcommitted
Add macOS smoke tests in GitHub Actions
Package a sample app (samples/hello-world) on Apple Silicon macOS runners for each macStartup mode and check the bundle, native arm64 launch, DMG and PKG. An extra job with administratorRequired=true reproduces #473/#398. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent c7530cf commit 2caed36

5 files changed

Lines changed: 229 additions & 0 deletions

File tree

‎.github/scripts/check-macos-app.sh‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#!/usr/bin/env bash
2+
# Checks the macOS app generated from samples/hello-world.
3+
# Usage: check-macos-app.sh <target dir> <macStartup> <administratorRequired>
4+
set -u
5+
6+
TARGET="$1"
7+
MAC_STARTUP="$2"
8+
ADMIN="$3"
9+
10+
NAME="HelloWorld"
11+
VERSION="1.0.0"
12+
APP="$TARGET/$NAME/$NAME.app"
13+
MACOS="$APP/Contents/MacOS"
14+
STUB="$MACOS/universalJavaApplicationStub"
15+
16+
failures=0
17+
pass() { echo "PASS: $*"; }
18+
fail() { echo "FAIL: $*"; failures=$((failures + 1)); }
19+
20+
# app bundle
21+
if [ -d "$APP" ]; then pass "app bundle exists: $APP"; else fail "app bundle missing: $APP"; exit 1; fi
22+
23+
# executable declared in Info.plist
24+
EXE_NAME=$(/usr/libexec/PlistBuddy -c "Print :CFBundleExecutable" "$APP/Contents/Info.plist")
25+
EXE="$MACOS/$EXE_NAME"
26+
echo "CFBundleExecutable: $EXE_NAME"
27+
if [ -x "$EXE" ]; then pass "executable present: $EXE"; else fail "executable missing or not executable: $EXE"; fi
28+
29+
# launcher stub (also called by the 'startup' script when administratorRequired=true, issues #473/#398)
30+
if [ -x "$STUB" ]; then pass "launcher present: $STUB"; else fail "launcher missing: $STUB"; fi
31+
32+
# compiled launchers must contain native arm64 code (issues #448/#449)
33+
if [ "$MAC_STARTUP" != "SCRIPT" ] && [ -f "$STUB" ]; then
34+
file "$STUB"
35+
ARCHS=$(lipo -archs "$STUB" 2>/dev/null || true)
36+
echo "launcher archs: $ARCHS"
37+
case "$ARCHS" in
38+
*arm64*) pass "launcher has arm64 code" ;;
39+
*) fail "launcher has no arm64 code" ;;
40+
esac
41+
fi
42+
43+
# run the app (not possible when administratorRequired=true: it asks for a password)
44+
if [ "$ADMIN" != "true" ] && [ -x "$EXE" ]; then
45+
OUTPUT=$("$EXE" 2>&1)
46+
echo "--- app output ---"
47+
echo "$OUTPUT"
48+
echo "------------------"
49+
if echo "$OUTPUT" | grep -q "JavaPackager smoke test OK"; then pass "app runs"; else fail "app did not print the expected line"; fi
50+
if echo "$OUTPUT" | grep -q "os.arch=aarch64"; then pass "JVM runs natively on arm64"; else fail "JVM is not running as arm64 (Rosetta?)"; fi
51+
fi
52+
53+
# installers (DMG customization runs osascript, issues #474/#377)
54+
DMG="$TARGET/${NAME}_${VERSION}.dmg"
55+
PKG="$TARGET/${NAME}_${VERSION}.pkg"
56+
if [ -f "$DMG" ]; then pass "DMG generated: $DMG"; else fail "DMG not generated (see 'DMG image generation failed' in the build log)"; fi
57+
if [ -f "$PKG" ]; then pass "PKG generated: $PKG"; else fail "PKG not generated"; fi
58+
59+
echo "$failures check(s) failed"
60+
[ "$failures" -eq 0 ]

‎.github/workflows/macos-smoke.yml‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
name: macOS smoke tests
2+
3+
on:
4+
push:
5+
branches: [ devel, master ]
6+
pull_request:
7+
branches: [ devel, master ]
8+
workflow_dispatch:
9+
10+
jobs:
11+
macos:
12+
name: ${{ matrix.os }} / ${{ matrix.macStartup }}${{ matrix.admin && ' / admin' || '' }}
13+
runs-on: ${{ matrix.os }}
14+
timeout-minutes: 30
15+
# administratorRequired is a known failure (issues #473/#398) until fixed
16+
continue-on-error: ${{ matrix.admin }}
17+
strategy:
18+
fail-fast: false
19+
matrix:
20+
os: [ macos-14, macos-15 ]
21+
macStartup: [ UNIVERSAL, ARM64, SCRIPT ]
22+
admin: [ false ]
23+
include:
24+
- os: macos-15
25+
macStartup: SCRIPT
26+
admin: true
27+
28+
steps:
29+
- uses: actions/checkout@v4
30+
31+
- uses: actions/setup-java@v4
32+
with:
33+
distribution: temurin
34+
java-version: 17
35+
cache: gradle
36+
37+
- name: Build and install plugin to Maven local
38+
run: ./gradlew publishToMavenLocal --no-daemon
39+
40+
- name: Get plugin version
41+
id: plugin
42+
run: echo "version=$(./gradlew properties -q --no-daemon | awk '/^version:/ {print $2}')" >> "$GITHUB_OUTPUT"
43+
44+
- name: Package sample app
45+
working-directory: samples/hello-world
46+
run: |
47+
mvn -B package \
48+
-Djavapackager.version=${{ steps.plugin.outputs.version }} \
49+
-DmacStartup=${{ matrix.macStartup }} \
50+
-DadministratorRequired=${{ matrix.admin }} \
51+
2>&1 | tee build.log
52+
exit "${PIPESTATUS[0]}"
53+
54+
- name: Check generated app
55+
run: bash .github/scripts/check-macos-app.sh samples/hello-world/target ${{ matrix.macStartup }} ${{ matrix.admin }}
56+
57+
- name: Upload artifacts
58+
if: always()
59+
uses: actions/upload-artifact@v4
60+
with:
61+
name: hello-world-${{ matrix.os }}-${{ matrix.macStartup }}${{ matrix.admin && '-admin' || '' }}
62+
path: |
63+
samples/hello-world/build.log
64+
samples/hello-world/target/*.dmg
65+
samples/hello-world/target/*.pkg
66+
if-no-files-found: ignore
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# macOS smoke tests in GitHub Actions
2+
3+
## Goal
4+
5+
Package a sample app on GitHub's Apple Silicon macOS runners and check the result, so macOS issues can be verified without owning a Mac.
6+
7+
## Context
8+
9+
- There are no tests and no CI workflows (`.github/` only has issue templates).
10+
- The maintainer has no Mac. Several open issues can only be checked on macOS:
11+
- #448 and #449: launcher fails or runs under Rosetta on ARM / macOS 15. Possibly fixed by the native launcher in devel (f02c0b0), but unconfirmed.
12+
- #473 and #398: with `administratorRequired=true`, `MacPackager.processStartupScript()` never copies the launcher into `Contents/MacOS`.
13+
- #474 and #377: `GenerateDmg` always runs `osascript` to customize the DMG, which fails on headless CI.
14+
- GitHub `macos-14` and `macos-15` runners are arm64 and free for public repos. Docker can't run macOS.
15+
- Output layout: app at `target/<name>/<name>.app`, DMG at `target/<name>_<version>.dmg`, PKG at `target/<name>_<version>.pkg`.
16+
17+
## Changes
18+
19+
- Add a minimal Maven sample app under `samples/hello-world/` that prints a known line and exits. It takes the plugin version as a property, so CI uses the snapshot built from the repo.
20+
- Add the workflow `.github/workflows/macos-smoke.yml`:
21+
- Triggers: push and pull request on `devel` and `master`, plus manual dispatch.
22+
- Builds the plugin with `./gradlew publishToMavenLocal`.
23+
- Matrix over `macos-14` and `macos-15` and `macStartup` (`UNIVERSAL`, `ARM64`, `SCRIPT`), with one extra job using `administratorRequired=true`.
24+
- Packages the sample with `mvn package`, with `bundleJre=true` and installers enabled.
25+
- Checks per job:
26+
- The `.app` exists and `Contents/MacOS/universalJavaApplicationStub` is present and executable.
27+
- For compiled launchers, `file`/`lipo` reports arm64.
28+
- Running the launcher directly prints the sample's known line.
29+
- The DMG and PKG exist. A DMG failure is reported, not hidden.
30+
- Upload `target/*.dmg`, `target/*.pkg` and the build log as artifacts.
31+
- Known failures are allowed: `continue-on-error` on the `administratorRequired` job until #473/#398 is fixed.
32+
33+
## Acceptance
34+
35+
- The workflow runs on `devel` and its results are visible in the Actions tab.
36+
- `UNIVERSAL`, `ARM64` and `SCRIPT` jobs show whether the app launches natively on ARM, which gives an answer for #448/#449.
37+
- The `administratorRequired` job reproduces #473/#398 (missing launcher) before the fix and passes after it.
38+
- The DMG step shows whether `osascript` fails headless on the runner (#474/#377).
39+
- No changes to plugin code in this feature; fixes go in their own bugfix notes.

‎samples/hello-world/pom.xml‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
6+
<modelVersion>4.0.0</modelVersion>
7+
8+
<groupId>io.github.fvarrui.samples</groupId>
9+
<artifactId>hello-world</artifactId>
10+
<version>1.0.0</version>
11+
<name>HelloWorld</name>
12+
<description>Sample app used by JavaPackager smoke tests</description>
13+
14+
<properties>
15+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
16+
<maven.compiler.release>11</maven.compiler.release>
17+
<!-- overridden from CI with the snapshot built from this repo -->
18+
<javapackager.version>1.7.7-SNAPSHOT</javapackager.version>
19+
<macStartup>SCRIPT</macStartup>
20+
<administratorRequired>false</administratorRequired>
21+
</properties>
22+
23+
<build>
24+
<plugins>
25+
<plugin>
26+
<groupId>io.github.fvarrui</groupId>
27+
<artifactId>javapackager</artifactId>
28+
<version>${javapackager.version}</version>
29+
<executions>
30+
<execution>
31+
<phase>package</phase>
32+
<goals>
33+
<goal>package</goal>
34+
</goals>
35+
<configuration>
36+
<mainClass>io.github.fvarrui.samples.HelloWorld</mainClass>
37+
<bundleJre>true</bundleJre>
38+
<generateInstaller>true</generateInstaller>
39+
<administratorRequired>${administratorRequired}</administratorRequired>
40+
<macConfig>
41+
<macStartup>${macStartup}</macStartup>
42+
</macConfig>
43+
</configuration>
44+
</execution>
45+
</executions>
46+
</plugin>
47+
</plugins>
48+
</build>
49+
50+
</project>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package io.github.fvarrui.samples;
2+
3+
/**
4+
* Sample app used by JavaPackager smoke tests: prints a known line and exits
5+
*/
6+
public class HelloWorld {
7+
8+
public static void main(String[] args) {
9+
System.out.println("JavaPackager smoke test OK");
10+
System.out.println("os.arch=" + System.getProperty("os.arch"));
11+
System.out.println("java.version=" + System.getProperty("java.version"));
12+
}
13+
14+
}

0 commit comments

Comments
 (0)