Skip to content

Size CSS border-radius by the CSS box model (#5454) #5758

Size CSS border-radius by the CSS box model (#5454)

Size CSS border-radius by the CSS box model (#5454) #5758

Workflow file for this run

name: Test iOS UI build scripts
on:
# Manual dispatch: this PR's changed-file count exceeds GitHub's paths-filter
# diff limit, so pull_request triggers stopped firing -- dispatch with
# gh workflow run <file> --ref <branch>
workflow_dispatch:
inputs:
watch_only:
description: Run only the watchOS evidence job after the shared port build
required: false
default: false
type: boolean
schedule:
- cron: '15 1 * * *'
pull_request:
paths:
- '.github/workflows/scripts-ios.yml'
- '.github/workflows/_build-ios-port.yml'
- 'scripts/setup-workspace.sh'
- 'scripts/build-ios-port.sh'
- 'scripts/build-ios-app.sh'
- 'scripts/run-ios-ui-tests.sh'
- 'scripts/run-watch-ui-tests.sh'
- 'scripts/run-tv-ui-tests.sh'
- 'scripts/run-ios-native-tests.sh'
- 'scripts/ios/notification-tests/native-tests/**'
- 'scripts/ios/notification-tests/install-native-notification-tests.sh'
- 'scripts/ios/notification-tests/**'
- 'scripts/hellocodenameone/**'
- 'scripts/ios/tests/**'
- 'scripts/ios/screenshots/**'
- 'scripts/ios/screenshots-metal/**'
- 'scripts/ios/screenshots-watch/**'
- 'scripts/ios/screenshots-tv/**'
- 'scripts/templates/**'
- '!scripts/templates/**/*.md'
- 'CodenameOne/src/**'
- '!CodenameOne/src/**/*.md'
- 'Ports/iOSPort/**'
- '!Ports/iOSPort/**/*.md'
- 'native-themes/ios-modern/**'
- '!native-themes/ios-modern/**/*.md'
- 'vm/**'
- '!vm/**/*.md'
- 'tests/**'
- '!tests/**/*.md'
- '!docs/**'
- 'maven/**'
- '!maven/core-unittests/**'
push:
branches: [ master ]
paths:
- '.github/workflows/scripts-ios.yml'
- '.github/workflows/_build-ios-port.yml'
- 'scripts/setup-workspace.sh'
- 'scripts/build-ios-port.sh'
- 'scripts/build-ios-app.sh'
- 'scripts/run-ios-ui-tests.sh'
- 'scripts/run-watch-ui-tests.sh'
- 'scripts/run-tv-ui-tests.sh'
- 'scripts/run-ios-native-tests.sh'
- 'scripts/ios/notification-tests/native-tests/**'
- 'scripts/ios/notification-tests/install-native-notification-tests.sh'
- 'scripts/ios/notification-tests/**'
- 'scripts/hellocodenameone/**'
- 'scripts/ios/tests/**'
- 'scripts/ios/screenshots/**'
- 'scripts/ios/screenshots-metal/**'
- 'scripts/ios/screenshots-watch/**'
- 'scripts/ios/screenshots-tv/**'
- 'scripts/templates/**'
- '!scripts/templates/**/*.md'
- 'CodenameOne/src/**'
- '!CodenameOne/src/**/*.md'
- 'Ports/iOSPort/**'
- '!Ports/iOSPort/**/*.md'
- 'native-themes/ios-modern/**'
- '!native-themes/ios-modern/**/*.md'
- 'vm/**'
- '!vm/**/*.md'
- 'tests/**'
- '!tests/**/*.md'
- '!docs/**'
- 'maven/**'
- '!maven/core-unittests/**'
jobs:
build-port:
uses: ./.github/workflows/_build-ios-port.yml
build-ios:
if: ${{ !inputs.watch_only }}
needs: build-port
permissions:
contents: read
pull-requests: write
issues: write
runs-on: macos-15 # pinning macos-15 avoids surprises during the cutover window
timeout-minutes: 75 # bounded headroom for the 65-minute UI-test step plus setup and artifact upload
concurrency: # ensure only one mac build runs at once
group: mac-ci-${{ github.workflow }}-${{ github.ref_name }}
cancel-in-progress: true
env:
GITHUB_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
GH_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
# Optional: when set, build-ios-app.sh writes it as a bundled resource so
# the GoogleWebMap screenshot test renders a live Google map; absent (e.g.
# fork PRs with no access to the secret) the test skips.
GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }}
steps:
- uses: actions/checkout@v6
- name: Cache CocoaPods and user gems
uses: actions/cache@v5
with:
path: |
~/.gem
~/Library/Caches/CocoaPods
~/.cocoapods/repos
key: ${{ runner.os }}-pods-v1-${{ hashFiles('scripts/setup-workspace.sh') }}
restore-keys: |
${{ runner.os }}-pods-v1-
- name: Ensure CocoaPods tooling
run: |
mkdir -p ~/.codenameone
cp maven/UpdateCodenameOne.jar ~/.codenameone/
set -euo pipefail
if ! command -v ruby >/dev/null; then
echo "ruby not found"; exit 1
fi
GEM_USER_DIR="$(ruby -e 'print Gem.user_dir')"
export PATH="$GEM_USER_DIR/bin:$PATH"
if ! command -v pod >/dev/null 2>&1; then
gem install cocoapods xcodeproj --no-document --user-install
fi
pod --version
- name: Compute setup-workspace hash
id: setup_hash
run: |
set -euo pipefail
echo "hash=$(shasum -a 256 scripts/setup-workspace.sh | awk '{print $1}')" >> "$GITHUB_OUTPUT"
- name: Compute CN1 source hash
id: src_hash
run: |
set -euo pipefail
SRC_HASH=$(find CodenameOne/src Ports/iOSPort vm/JavaAPI vm/ByteCodeTranslator Themes native-themes \
maven/codenameone-maven-plugin/src/main \
-type f \( -name '*.java' -o -name '*.m' -o -name '*.h' -o -name '*.xml' -o -name '*.properties' -o -name '*.css' \) 2>/dev/null \
| sort | xargs shasum -a 256 | shasum -a 256 | awk '{print $1}')
POM_HASH=$(find . -name 'pom.xml' -not -path './scripts/*' 2>/dev/null \
| sort | xargs shasum -a 256 | shasum -a 256 | awk '{print $1}')
SCRIPT_HASH=$(shasum -a 256 \
scripts/setup-workspace.sh \
scripts/build-ios-port.sh \
scripts/build-native-themes.sh \
.github/workflows/_build-ios-port.yml \
| shasum -a 256 | awk '{print $1}')
echo "hash=${SRC_HASH:0:16}-${POM_HASH:0:16}-${SCRIPT_HASH:0:16}" >> "$GITHUB_OUTPUT"
- name: Set TMPDIR
run: echo "TMPDIR=${{ runner.temp }}" >> $GITHUB_ENV
- name: Cache codenameone-tools
uses: actions/cache@v5
with:
path: ${{ runner.temp }}/codenameone-tools
key: ${{ runner.os }}-cn1-tools-${{ steps.setup_hash.outputs.hash }}
restore-keys: |
${{ runner.os }}-cn1-tools-
- name: Cache Maven repository
uses: actions/cache@v5
with:
path: ~/.m2/repository
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-m2-
- name: Restore cn1-binaries cache
uses: actions/cache@v5
with:
path: ../cn1-binaries
key: cn1-binaries-${{ runner.os }}-${{ steps.setup_hash.outputs.hash }}
restore-keys: |
cn1-binaries-${{ runner.os }}-
- name: Download built CN1 + iOS port bundle
uses: actions/download-artifact@v4
with:
name: cn1-built-bundle
path: ${{ runner.temp }}
- name: Restore built CN1 + iOS port tree
run: |
set -euo pipefail
# Replaces the evictable cn1-built cache restore with the artifact
# build-port handed off (not LRU-evicted, survives --failed re-runs).
# The tar was created relative to / so this restores
# ~/.m2/com/codenameone + Themes + Ports/iOSPort/nativeSources to
# their original absolute paths.
tar -C / -xf "$RUNNER_TEMP/cn1-built-bundle.tar"
- name: Force GL backend for hellocodenameone
# IPhoneBuilder defaults ios.metal to true. The GL job exists to
# exercise the legacy OpenGL ES 2 path, so pin the hint to false
# explicitly. Mirror of the Metal-enabling step in build-ios-metal.
run: |
set -euo pipefail
SETTINGS=scripts/hellocodenameone/common/codenameone_settings.properties
if grep -q '^codename1\.arg\.ios\.metal=' "$SETTINGS"; then
sed -i '' 's|^codename1.arg.ios.metal=.*|codename1.arg.ios.metal=false|' "$SETTINGS"
else
awk '
/^codename1\.arg\.ios\.applicationQueriesSchemes=/ {
print
print "codename1.arg.ios.metal=false"
next
}
{ print }
' "$SETTINGS" > "$SETTINGS.tmp" && mv "$SETTINGS.tmp" "$SETTINGS"
fi
echo "--- codenameone_settings.properties (ios.* keys after patch) ---"
grep -n 'codename1\.arg\.ios' "$SETTINGS" || true
- name: Build sample iOS app and compile workspace
id: build-ios-app
run: ./scripts/build-ios-app.sh -q -DskipTests
timeout-minutes: 30
- name: Run iOS UI screenshot tests
env:
ARTIFACTS_DIR: ${{ github.workspace }}/artifacts/ios-ui-tests
# Strict screenshot gating: every expected frame must be captured and
# compared. Enforced centrally in scripts/lib/cn1ss.sh.
CN1SS_FAIL_ON_MISMATCH: '1'
CN1SS_ALLOWED_MISSING: '0'
CN1SS_PORT_ID: ios-gl
run: |
set -euo pipefail
mkdir -p "${ARTIFACTS_DIR}"
echo "workspace='${{ steps.build-ios-app.outputs.workspace }}'"
echo "scheme='${{ steps.build-ios-app.outputs.scheme }}'"
./scripts/run-ios-ui-tests.sh \
"${{ steps.build-ios-app.outputs.workspace }}" \
"" \
"${{ steps.build-ios-app.outputs.scheme }}"
# The script enforces both a 35-minute absolute suite limit and a
# 12-minute no-progress limit. This outer cap also includes its
# native rebuild and cleanup, so it must leave those phases headroom.
timeout-minutes: 65
- name: Upload iOS OpenGL port status
if: always()
uses: actions/upload-artifact@v7
with:
name: port-status-ios-gl
path: artifacts/ios-ui-tests/port-status-ios-gl.json
if-no-files-found: warn
retention-days: 14
- name: Upload iOS artifacts
if: always()
uses: actions/upload-artifact@v7
with:
name: ios-ui-tests
path: |
artifacts/ios-ui-tests
artifacts/*.log
artifacts/*-stats.txt
artifacts/vm_time.txt
artifacts/xcodebuild-list.txt
if-no-files-found: warn
retention-days: 14
build-ios-metal:
if: ${{ !inputs.watch_only }}
# Mirrors build-ios but enables the Metal rendering backend via the
# codename1.arg.ios.metal=true build hint. Keeps the GL path untouched
# so a regression on either path is isolated in its own job.
needs: build-port
permissions:
contents: read
pull-requests: write
issues: write
runs-on: macos-15
timeout-minutes: 75
concurrency:
group: mac-ci-${{ github.workflow }}-metal-${{ github.ref_name }}
cancel-in-progress: true
env:
GITHUB_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
GH_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
# Optional: when set, build-ios-app.sh writes it as a bundled resource so
# the GoogleWebMap screenshot test renders a live Google map; absent (e.g.
# fork PRs with no access to the secret) the test skips.
GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }}
steps:
- uses: actions/checkout@v6
- name: Cache CocoaPods and user gems
uses: actions/cache@v5
with:
path: |
~/.gem
~/Library/Caches/CocoaPods
~/.cocoapods/repos
key: ${{ runner.os }}-pods-v1-${{ hashFiles('scripts/setup-workspace.sh') }}
restore-keys: |
${{ runner.os }}-pods-v1-
- name: Ensure CocoaPods tooling
run: |
mkdir -p ~/.codenameone
cp maven/UpdateCodenameOne.jar ~/.codenameone/
set -euo pipefail
if ! command -v ruby >/dev/null; then
echo "ruby not found"; exit 1
fi
GEM_USER_DIR="$(ruby -e 'print Gem.user_dir')"
export PATH="$GEM_USER_DIR/bin:$PATH"
if ! command -v pod >/dev/null 2>&1; then
gem install cocoapods xcodeproj --no-document --user-install
fi
pod --version
- name: Compute setup-workspace hash
id: setup_hash
run: |
set -euo pipefail
echo "hash=$(shasum -a 256 scripts/setup-workspace.sh | awk '{print $1}')" >> "$GITHUB_OUTPUT"
- name: Compute CN1 source hash
id: src_hash
run: |
set -euo pipefail
SRC_HASH=$(find CodenameOne/src Ports/iOSPort vm/JavaAPI vm/ByteCodeTranslator Themes native-themes \
maven/codenameone-maven-plugin/src/main \
-type f \( -name '*.java' -o -name '*.m' -o -name '*.h' -o -name '*.xml' -o -name '*.properties' -o -name '*.css' \) 2>/dev/null \
| sort | xargs shasum -a 256 | shasum -a 256 | awk '{print $1}')
POM_HASH=$(find . -name 'pom.xml' -not -path './scripts/*' 2>/dev/null \
| sort | xargs shasum -a 256 | shasum -a 256 | awk '{print $1}')
SCRIPT_HASH=$(shasum -a 256 \
scripts/setup-workspace.sh \
scripts/build-ios-port.sh \
scripts/build-native-themes.sh \
.github/workflows/_build-ios-port.yml \
| shasum -a 256 | awk '{print $1}')
echo "hash=${SRC_HASH:0:16}-${POM_HASH:0:16}-${SCRIPT_HASH:0:16}" >> "$GITHUB_OUTPUT"
- name: Set TMPDIR
run: echo "TMPDIR=${{ runner.temp }}" >> $GITHUB_ENV
- name: Cache codenameone-tools
uses: actions/cache@v5
with:
path: ${{ runner.temp }}/codenameone-tools
key: ${{ runner.os }}-cn1-tools-${{ steps.setup_hash.outputs.hash }}
restore-keys: |
${{ runner.os }}-cn1-tools-
- name: Cache Maven repository
uses: actions/cache@v5
with:
path: ~/.m2/repository
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-m2-
- name: Restore cn1-binaries cache
uses: actions/cache@v5
with:
path: ../cn1-binaries
key: cn1-binaries-${{ runner.os }}-${{ steps.setup_hash.outputs.hash }}
restore-keys: |
cn1-binaries-${{ runner.os }}-
- name: Download built CN1 + iOS port bundle
uses: actions/download-artifact@v4
with:
name: cn1-built-bundle
path: ${{ runner.temp }}
- name: Restore built CN1 + iOS port tree
run: |
set -euo pipefail
# Replaces the evictable cn1-built cache restore with the artifact
# build-port handed off (not LRU-evicted, survives --failed re-runs).
# The tar was created relative to / so this restores
# ~/.m2/com/codenameone + Themes + Ports/iOSPort/nativeSources to
# their original absolute paths.
tar -C / -xf "$RUNNER_TEMP/cn1-built-bundle.tar"
- name: Install Metal Toolchain
# Xcode 26+ requires the Metal Toolchain component to be downloaded
# explicitly before .metal files can compile. The runner's default
# xcodebuild is often an older Xcode whose -downloadComponent flag
# doesn't exist, so we point DEVELOPER_DIR at Xcode 26 first (same
# selection logic build-ios-app.sh uses). If the component is
# already cached on the runner image, xcodebuild exits quickly
# without re-downloading.
run: |
set -euo pipefail
XCODE_APP="$(ls -d /Applications/Xcode_26*.app 2>/dev/null | sort -V | tail -n 1 || true)"
if [ ! -x "$XCODE_APP/Contents/Developer/usr/bin/xcodebuild" ]; then
echo "Xcode 26 not found under /Applications. Cannot install Metal Toolchain." >&2
exit 1
fi
echo "Using $XCODE_APP"
export DEVELOPER_DIR="$XCODE_APP/Contents/Developer"
"$DEVELOPER_DIR/usr/bin/xcodebuild" -downloadComponent MetalToolchain
timeout-minutes: 10
- name: Enable Metal backend for hellocodenameone
# -Dcodename1.arg.* on the mvnw CLI doesn't flow into the Codename
# One Maven plugin's BuildRequest -- the plugin reads build args
# from the codenameone_settings.properties file on disk. Inject the
# Metal flag there so IPhoneBuilder's useMetal path activates.
run: |
set -euo pipefail
SETTINGS=scripts/hellocodenameone/common/codenameone_settings.properties
if grep -q '^codename1\.arg\.ios\.metal=' "$SETTINGS"; then
sed -i '' 's|^codename1.arg.ios.metal=.*|codename1.arg.ios.metal=true|' "$SETTINGS"
else
# Insert next to the other ios.* keys (after applicationQueriesSchemes).
awk '
/^codename1\.arg\.ios\.applicationQueriesSchemes=/ {
print
print "codename1.arg.ios.metal=true"
next
}
{ print }
' "$SETTINGS" > "$SETTINGS.tmp" && mv "$SETTINGS.tmp" "$SETTINGS"
fi
echo "--- codenameone_settings.properties (ios.* keys after patch) ---"
grep -n 'codename1\.arg\.ios' "$SETTINGS" || true
- name: Build sample iOS app and compile workspace (Metal)
id: build-ios-app
run: ./scripts/build-ios-app.sh -q -DskipTests
timeout-minutes: 30
- name: Run iOS UI screenshot tests (Metal)
env:
ARTIFACTS_DIR: ${{ github.workspace }}/artifacts/ios-ui-tests-metal
# Compare Metal output against the Metal-specific golden set.
# See scripts/ios/screenshots-metal/README.md for the rationale:
# the Metal backend will not be pixel-identical to the GL
# baselines (especially once CoreText glyph rendering lands).
SCREENSHOT_REF_DIR: ${{ github.workspace }}/scripts/ios/screenshots-metal
# Override the PR comment marker / preview path / title so this
# job posts its own comment instead of overwriting the GL job's
# comment (both jobs ran with the same default marker before, so
# whichever finished last clobbered the other's results).
CN1SS_COMMENT_MARKER: '<!-- CN1SS_IOS_METAL_COMMENT -->'
CN1SS_PREVIEW_SUBDIR: ios-metal
CN1SS_REPORT_TITLE: 'iOS Metal screenshot updates'
CN1SS_SUCCESS_MESSAGE: '✅ Native iOS Metal screenshot tests passed.'
CN1SS_COMMENT_LOG_PREFIX: '[run-ios-device-tests-metal]'
# Strict screenshot gating: fail on any pixel mismatch and on any
# missing screenshot. The Metal backend must produce the full
# baseline set; a test that hangs (e.g. the DialogTheme texture
# backdrop) drops every later capture and silently shrinks the
# suite from 122 to 107 - that now fails the job. No missing
# screenshots are tolerated. Enforced in scripts/lib/cn1ss.sh.
CN1SS_FAIL_ON_MISMATCH: '1'
# Enable Apple's Metal API Validation in the simulator. Catches
# render-pass / pipeline-state mismatches at the moment they
# happen (issue #5103: stencil pixel-format mismatch shipped in
# four consecutive 7.0.x releases because no test exercised the
# validation layer). assert mode crashes the app on the first
# validation error -- the missed CN1SS:SUITE:FINISHED marker
# then fails this step. run-ios-ui-tests.sh forwards both vars
# to the launched app via simctl --setenv.
MTL_DEBUG_LAYER: '1'
MTL_DEBUG_LAYER_ERROR_MODE: 'assert'
CN1SS_PORT_ID: ios-metal
run: |
set -euo pipefail
mkdir -p "${ARTIFACTS_DIR}"
echo "workspace='${{ steps.build-ios-app.outputs.workspace }}'"
echo "scheme='${{ steps.build-ios-app.outputs.scheme }}'"
echo "reference dir='${SCREENSHOT_REF_DIR}'"
./scripts/run-ios-ui-tests.sh \
"${{ steps.build-ios-app.outputs.workspace }}" \
"" \
"${{ steps.build-ios-app.outputs.scheme }}"
# Match the GL envelope; run-ios-ui-tests.sh still fails a stalled
# DeviceRunner after 12 minutes without CN1SS progress.
timeout-minutes: 65
- name: Upload iOS Metal port status
if: always()
uses: actions/upload-artifact@v7
with:
name: port-status-ios-metal
path: artifacts/ios-ui-tests-metal/port-status-ios-metal.json
if-no-files-found: warn
retention-days: 14
- name: Publish Metal screenshot summary
# Surfaces run-ios-ui-tests.sh's comparison result in the job's
# GitHub Actions summary page so the Metal port status is visible
# at a glance without digging into the artifact zip. Always runs
# so a failed or cancelled tests step still reports whatever got
# captured. The Python helper lives in scripts/ci/ because
# embedding Python heredocs inside a YAML "run: |" block is
# fragile -- unindented Python breaks the block scalar.
if: always()
env:
COMPARE_JSON: ${{ github.workspace }}/artifacts/ios-ui-tests-metal/screenshot-compare.json
COMMENT_MD: ${{ github.workspace }}/artifacts/ios-ui-tests-metal/screenshot-comment.md
run: |
set -eu
{
echo "## iOS Metal screenshot comparison"
echo
echo "Ran against \`scripts/hellocodenameone\` on the iOS simulator with \`codename1.arg.ios.metal=true\`."
echo "Golden images: \`scripts/ios/screenshots-metal/\` (Metal-specific baseline; see the README there for why it is separate from the GL set)."
echo
if [ -s "$COMPARE_JSON" ]; then
python3 scripts/ci/metal-screenshot-summary.py --markdown "$COMPARE_JSON"
elif [ -s "$COMMENT_MD" ]; then
cat "$COMMENT_MD"
else
echo "_No screenshot comparison artifact was produced. See the upload step output for details._"
fi
} >> "$GITHUB_STEP_SUMMARY"
if [ -s "$COMPARE_JSON" ]; then
NOTICE="$(python3 scripts/ci/metal-screenshot-summary.py --headline "$COMPARE_JSON" || true)"
if [ -n "$NOTICE" ]; then
echo "::notice title=Metal screenshot comparison::${NOTICE}"
fi
fi
- name: Upload iOS Metal artifacts
if: always()
uses: actions/upload-artifact@v7
with:
name: ios-ui-tests-metal
path: |
artifacts/ios-ui-tests-metal
artifacts/*.log
artifacts/*-stats.txt
artifacts/vm_time.txt
artifacts/xcodebuild-list.txt
if-no-files-found: warn
retention-days: 14
build-ios-watch:
# Native Apple Watch (watchOS) screenshot pipeline. The watch slice
# auto-enables from codename1.watchMain in the sample, so build-ios-app.sh
# generates the <Main>Watch target alongside the iOS app. This job renders
# the cn1ss suite on the watch simulator through the Core Graphics backend
# (no GL/Metal on watchOS), streams frames to the same Cn1ssScreenshotServer
# WS sink the iOS jobs use, and compares against scripts/ios/screenshots-watch.
# Isolated in its own job (like build-ios-metal) so the watch result is a
# distinct check and a regression there doesn't mask the iOS path.
needs: build-port
permissions:
contents: read
pull-requests: write
issues: write
runs-on: macos-15
timeout-minutes: 60
concurrency:
group: mac-ci-${{ github.workflow }}-watch-${{ github.ref_name }}
cancel-in-progress: true
env:
GITHUB_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
GH_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
# Optional: when set, build-ios-app.sh writes it as a bundled resource so
# the GoogleWebMap screenshot test renders a live Google map; absent (e.g.
# fork PRs with no access to the secret) the test skips.
GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }}
steps:
- uses: actions/checkout@v6
- name: Cache CocoaPods and user gems
uses: actions/cache@v5
with:
path: |
~/.gem
~/Library/Caches/CocoaPods
~/.cocoapods/repos
key: ${{ runner.os }}-pods-v1-${{ hashFiles('scripts/setup-workspace.sh') }}
restore-keys: |
${{ runner.os }}-pods-v1-
- name: Ensure CocoaPods tooling
run: |
mkdir -p ~/.codenameone
cp maven/UpdateCodenameOne.jar ~/.codenameone/
set -euo pipefail
if ! command -v ruby >/dev/null; then
echo "ruby not found"; exit 1
fi
GEM_USER_DIR="$(ruby -e 'print Gem.user_dir')"
export PATH="$GEM_USER_DIR/bin:$PATH"
if ! command -v pod >/dev/null 2>&1; then
gem install cocoapods xcodeproj --no-document --user-install
fi
pod --version
- name: Compute setup-workspace hash
id: setup_hash
run: |
set -euo pipefail
echo "hash=$(shasum -a 256 scripts/setup-workspace.sh | awk '{print $1}')" >> "$GITHUB_OUTPUT"
- name: Set TMPDIR
run: echo "TMPDIR=${{ runner.temp }}" >> $GITHUB_ENV
- name: Cache codenameone-tools
uses: actions/cache@v5
with:
path: ${{ runner.temp }}/codenameone-tools
key: ${{ runner.os }}-cn1-tools-${{ steps.setup_hash.outputs.hash }}
restore-keys: |
${{ runner.os }}-cn1-tools-
- name: Cache Maven repository
uses: actions/cache@v5
with:
path: ~/.m2/repository
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-m2-
- name: Restore cn1-binaries cache
uses: actions/cache@v5
with:
path: ../cn1-binaries
key: cn1-binaries-${{ runner.os }}-${{ steps.setup_hash.outputs.hash }}
restore-keys: |
cn1-binaries-${{ runner.os }}-
- name: Download built CN1 + iOS port bundle
uses: actions/download-artifact@v4
with:
name: cn1-built-bundle
path: ${{ runner.temp }}
- name: Restore built CN1 + iOS port tree
run: |
set -euo pipefail
# Replaces the evictable cn1-built cache restore with the artifact
# build-port handed off (not LRU-evicted, survives --failed re-runs).
tar -C / -xf "$RUNNER_TEMP/cn1-built-bundle.tar"
- name: Build sample iOS app (generates the watch target)
id: build-ios-app
run: ./scripts/build-ios-app.sh -q -DskipTests
timeout-minutes: 30
- name: Ensure watchOS simulator runtime
run: |
set -euo pipefail
if ! xcrun simctl list runtimes available 2>/dev/null | grep -qi watchOS; then
echo "No watchOS runtime found; attempting download"
xcodebuild -downloadPlatform watchOS || true
fi
xcrun simctl list runtimes available 2>/dev/null | grep -i watchOS || true
xcrun simctl list devices available 2>/dev/null | grep -i "Apple Watch" || true
- name: Run watchOS UI screenshot tests
env:
ARTIFACTS_DIR: ${{ github.workspace }}/artifacts/watch-ui-tests
SCREENSHOT_REF_DIR: ${{ github.workspace }}/scripts/ios/screenshots-watch
CN1SS_COMMENT_MARKER: '<!-- CN1SS_IOS_WATCH_COMMENT -->'
CN1SS_PREVIEW_SUBDIR: ios-watch
CN1SS_REPORT_TITLE: 'Apple Watch (watchOS) screenshot updates'
CN1SS_SUCCESS_MESSAGE: '✅ Native Apple Watch (watchOS, Core Graphics) screenshot tests passed.'
CN1SS_COMMENT_LOG_PREFIX: '[run-watch-ui-tests]'
CN1SS_FAIL_ON_MISMATCH: '1'
CN1SS_WATCH_TIMEOUT: '2400'
# A missing frame is an incomplete run, not an allowed result.
CN1SS_ALLOWED_MISSING: '0'
run: |
set -euo pipefail
mkdir -p "${ARTIFACTS_DIR}"
echo "workspace='${{ steps.build-ios-app.outputs.workspace }}'"
echo "scheme='${{ steps.build-ios-app.outputs.scheme }}'"
./scripts/run-watch-ui-tests.sh \
"${{ steps.build-ios-app.outputs.workspace }}" \
"${{ steps.build-ios-app.outputs.scheme }}"
timeout-minutes: 90
- name: Upload watchOS port status
if: always()
uses: actions/upload-artifact@v7
with:
name: port-status-watchos
path: artifacts/watch-ui-tests/port-status-watchos.json
if-no-files-found: warn
retention-days: 14
- name: Upload watch artifacts
if: always()
uses: actions/upload-artifact@v7
with:
name: watch-ui-tests
path: |
artifacts/watch-ui-tests
artifacts/*.log
artifacts/*-stats.txt
artifacts/vm_time.txt
artifacts/xcodebuild-list.txt
if-no-files-found: warn
retention-days: 14
build-ios-tv:
if: ${{ !inputs.watch_only }}
# Native Apple TV (tvOS) screenshot pipeline. The tvOS slice auto-enables
# from codename1.tvMain in the sample, so build-ios-app.sh generates the
# <Main>TV target alongside the iOS app. tvOS reuses the iOS UIApplicationMain
# entry and the Metal renderer (no OpenGL ES on tvOS); this job builds the TV
# target for the appletvsimulator, renders the cn1ss suite and streams frames
# to the same Cn1ssScreenshotServer the iOS/watch jobs use, comparing against
# scripts/ios/screenshots-tv.
#
# BLOCKING (a hard golden gate, like build-ios-watch): the tvOS slice
# compiles end-to-end and the golden set is seeded from a CI capture (see
# Ports/iOSPort/nativeSources/TVOS_PORT.md). A mismatch fails the job.
needs: build-port
permissions:
contents: read
pull-requests: write
issues: write
runs-on: macos-15
timeout-minutes: 60
concurrency:
group: mac-ci-${{ github.workflow }}-tv-${{ github.ref_name }}
cancel-in-progress: true
env:
GITHUB_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
GH_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
steps:
- uses: actions/checkout@v6
- name: Cache CocoaPods and user gems
uses: actions/cache@v5
with:
path: |
~/.gem
~/Library/Caches/CocoaPods
~/.cocoapods/repos
key: ${{ runner.os }}-pods-v1-${{ hashFiles('scripts/setup-workspace.sh') }}
restore-keys: |
${{ runner.os }}-pods-v1-
- name: Ensure CocoaPods tooling
run: |
mkdir -p ~/.codenameone
cp maven/UpdateCodenameOne.jar ~/.codenameone/
set -euo pipefail
if ! command -v ruby >/dev/null; then
echo "ruby not found"; exit 1
fi
GEM_USER_DIR="$(ruby -e 'print Gem.user_dir')"
export PATH="$GEM_USER_DIR/bin:$PATH"
if ! command -v pod >/dev/null 2>&1; then
gem install cocoapods xcodeproj --no-document --user-install
fi
pod --version
- name: Compute setup-workspace hash
id: setup_hash
run: |
set -euo pipefail
echo "hash=$(shasum -a 256 scripts/setup-workspace.sh | awk '{print $1}')" >> "$GITHUB_OUTPUT"
- name: Set TMPDIR
run: echo "TMPDIR=${{ runner.temp }}" >> $GITHUB_ENV
- name: Cache codenameone-tools
uses: actions/cache@v5
with:
path: ${{ runner.temp }}/codenameone-tools
key: ${{ runner.os }}-cn1-tools-${{ steps.setup_hash.outputs.hash }}
restore-keys: |
${{ runner.os }}-cn1-tools-
- name: Cache Maven repository
uses: actions/cache@v5
with:
path: ~/.m2/repository
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-m2-
- name: Restore cn1-binaries cache
uses: actions/cache@v5
with:
path: ../cn1-binaries
key: cn1-binaries-${{ runner.os }}-${{ steps.setup_hash.outputs.hash }}
restore-keys: |
cn1-binaries-${{ runner.os }}-
- name: Download built CN1 + iOS port bundle
uses: actions/download-artifact@v4
with:
name: cn1-built-bundle
path: ${{ runner.temp }}
- name: Restore built CN1 + iOS port tree
run: |
set -euo pipefail
# Replaces the evictable cn1-built cache restore with the artifact
# build-port handed off (not LRU-evicted, survives --failed re-runs).
tar -C / -xf "$RUNNER_TEMP/cn1-built-bundle.tar"
- name: Build sample iOS app (generates the tvOS target)
id: build-ios-app
run: ./scripts/build-ios-app.sh -q -DskipTests
timeout-minutes: 30
- name: Ensure tvOS simulator runtime
run: |
set -euo pipefail
if ! xcrun simctl list runtimes available 2>/dev/null | grep -qi tvOS; then
echo "No tvOS runtime found; attempting download"
xcodebuild -downloadPlatform tvOS || true
fi
xcrun simctl list runtimes available 2>/dev/null | grep -i tvOS || true
xcrun simctl list devices available 2>/dev/null | grep -i "Apple TV" || true
- name: Run tvOS UI screenshot tests
env:
ARTIFACTS_DIR: ${{ github.workspace }}/artifacts/tv-ui-tests
SCREENSHOT_REF_DIR: ${{ github.workspace }}/scripts/ios/screenshots-tv
CN1SS_COMMENT_MARKER: '<!-- CN1SS_IOS_TV_COMMENT -->'
CN1SS_PREVIEW_SUBDIR: ios-tv
CN1SS_REPORT_TITLE: 'Apple TV (tvOS) screenshot updates'
CN1SS_SUCCESS_MESSAGE: '✅ Native Apple TV (tvOS, Metal) screenshot tests passed.'
CN1SS_COMMENT_LOG_PREFIX: '[run-tv-ui-tests]'
CN1SS_FAIL_ON_MISMATCH: '1'
# A missing frame is an incomplete run, not an allowed result.
CN1SS_ALLOWED_MISSING: '0'
run: |
set -euo pipefail
mkdir -p "${ARTIFACTS_DIR}"
echo "workspace='${{ steps.build-ios-app.outputs.workspace }}'"
echo "scheme='${{ steps.build-ios-app.outputs.scheme }}'"
./scripts/run-tv-ui-tests.sh \
"${{ steps.build-ios-app.outputs.workspace }}" \
"${{ steps.build-ios-app.outputs.scheme }}"
timeout-minutes: 45
- name: Upload tvOS port status
if: always()
uses: actions/upload-artifact@v7
with:
name: port-status-tvos
path: artifacts/tv-ui-tests/port-status-tvos.json
if-no-files-found: warn
retention-days: 14
- name: Upload tv artifacts
if: always()
uses: actions/upload-artifact@v7
with:
name: tv-ui-tests
path: |
artifacts/tv-ui-tests
artifacts/*.log
artifacts/*-stats.txt
artifacts/vm_time.txt
artifacts/xcodebuild-list.txt
if-no-files-found: warn
retention-days: 14