Repository navigation
Size CSS border-radius by the CSS box model (#5454) #5758
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: 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 "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 "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 |