From c349fb0addb1526c53dbcc2f814d8031bc72b870 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Thu, 30 Apr 2026 17:32:39 +0300 Subject: [PATCH] CI: share iOS port build across iOS workflows via reusable workflow Introduces a reusable workflow .github/workflows/_build-ios-port.yml (workflow_call) that runs scripts/setup-workspace.sh and scripts/build-ios-port.sh once per workflow run. The three iOS workflows (scripts-ios.yml, scripts-ios-native.yml, ios-packaging.yml) now have two jobs each: a build-port job that calls the reusable workflow, and the existing test job which "needs: build-port" and restores the populated caches. A new "cn1-built" cache stores the built CN1 + iOS port artifacts (~/.m2/repository/com/codenameone, Themes, Ports/iOSPort/nativeSources) keyed on a composite hash of: - CN1 / iOS port / VM Java/native source files - All pom.xml files - setup-workspace.sh / build-ios-port.sh / build-native-themes.sh / the reusable workflow itself There are no restore-keys on this cache (exact match only) so a stale artifact set never satisfies a different source state. On cache hit the build-port job skips both setup-workspace and build-ios-port and finishes in ~1-2 min instead of ~30 min. Cross-workflow benefit: the cn1-built cache is keyed only on the composite source hash, so the first iOS workflow that runs on a PR populates it and the other two skip their builds. On the same PR this typically saves ~2x ~30 min macOS minutes. Within ios-packaging.yml the matrix entries are now 3 small jobs that just restore caches and run build-ios-app + tests, replacing the prior 3 full duplicate pipelines. Also fixes a self-trigger bug in parparvm-tests.yml: changes to the workflow file itself didn't trigger a CI run because the paths filter omitted .github/workflows/parparvm-tests.yml. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/_build-ios-port.yml | 134 +++++++++++++++++++++++ .github/workflows/ios-packaging.yml | 49 ++++++--- .github/workflows/parparvm-tests.yml | 2 + .github/workflows/scripts-ios-native.yml | 41 +++++-- .github/workflows/scripts-ios.yml | 51 ++++++--- 5 files changed, 237 insertions(+), 40 deletions(-) create mode 100644 .github/workflows/_build-ios-port.yml diff --git a/.github/workflows/_build-ios-port.yml b/.github/workflows/_build-ios-port.yml new file mode 100644 index 0000000000..577e7aa8d2 --- /dev/null +++ b/.github/workflows/_build-ios-port.yml @@ -0,0 +1,134 @@ +name: _Build iOS port (reusable) + +# Reusable workflow that runs scripts/setup-workspace.sh + scripts/build-ios-port.sh +# once per workflow run, populating shared caches that downstream test jobs (in +# scripts-ios.yml, scripts-ios-native.yml, ios-packaging.yml) restore via the +# same cache keys. +# +# A separate "cn1-built" cache is keyed on the CN1 source hash with no restore- +# keys (exact match only). On hit, the entire setup-workspace + build-ios-port +# sequence is skipped — the iOS port artifact in ~/.m2/repository/com/codenameone +# is already correct for the current source state. +# +# This cache is shared across all three iOS workflows on the same branch, so +# whichever workflow runs first populates it and the others skip the rebuild. + +on: + workflow_call: + +jobs: + build: + runs-on: macos-15 + timeout-minutes: 60 + concurrency: + group: mac-ios-port-${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: true + steps: + - uses: actions/checkout@v4 + + - name: Cache CocoaPods and user gems + uses: actions/cache@v4 + 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 \ + -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@v4 + 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@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-m2- + + - name: Restore cn1-binaries cache + uses: actions/cache@v4 + with: + path: ../cn1-binaries + key: cn1-binaries-${{ runner.os }}-${{ steps.setup_hash.outputs.hash }} + restore-keys: | + cn1-binaries-${{ runner.os }}- + + # Built CN1 + iOS port artifacts. Restored after the m2 cache so it + # overwrites any stale com/codenameone subtree pulled in by the broader + # m2 cache (whose key is keyed on POMs, not source). + - name: Cache built CN1 + iOS port artifacts + id: cn1_built + uses: actions/cache@v4 + with: + path: | + ~/.m2/repository/com/codenameone + Themes + Ports/iOSPort/nativeSources + key: cn1-built-${{ runner.os }}-${{ steps.src_hash.outputs.hash }} + + - name: Setup workspace + if: steps.cn1_built.outputs.cache-hit != 'true' + run: ./scripts/setup-workspace.sh -q -DskipTests + timeout-minutes: 40 + + - name: Build iOS port + if: steps.cn1_built.outputs.cache-hit != 'true' + run: ./scripts/build-ios-port.sh -q -DskipTests + timeout-minutes: 40 + + - name: Report cache outcome + run: | + if [ "${{ steps.cn1_built.outputs.cache-hit }}" = "true" ]; then + echo "cn1-built cache HIT for key cn1-built-${{ runner.os }}-${{ steps.src_hash.outputs.hash }} — setup-workspace and build-ios-port were skipped." + else + echo "cn1-built cache MISS for key cn1-built-${{ runner.os }}-${{ steps.src_hash.outputs.hash }} — built fresh." + fi diff --git a/.github/workflows/ios-packaging.yml b/.github/workflows/ios-packaging.yml index eb4a1b90da..4b35730fc3 100644 --- a/.github/workflows/ios-packaging.yml +++ b/.github/workflows/ios-packaging.yml @@ -4,6 +4,7 @@ on: pull_request: paths: - '.github/workflows/ios-packaging.yml' + - '.github/workflows/_build-ios-port.yml' - 'maven/codenameone-maven-plugin/**' - 'vm/ByteCodeTranslator/**' - 'Ports/iOSPort/**' @@ -17,6 +18,7 @@ on: branches: [ master ] paths: - '.github/workflows/ios-packaging.yml' + - '.github/workflows/_build-ios-port.yml' - 'maven/codenameone-maven-plugin/**' - 'vm/ByteCodeTranslator/**' - 'Ports/iOSPort/**' @@ -28,16 +30,18 @@ on: - '!docs/**' jobs: + build-port: + uses: ./.github/workflows/_build-ios-port.yml + packaging: + needs: build-port permissions: contents: read runs-on: macos-15 - timeout-minutes: 75 + timeout-minutes: 45 # Exercises both CocoaPods and SPM in a single Xcode project. Catches # regressions in either dependency manager (each pathway runs end to end) - # and additionally validates that they coexist correctly. The pods-only - # and spm-only matrix entries previously here were proper subsets of this - # configuration, so they were dropped to halve macOS runner consumption. + # and additionally validates that they coexist correctly. env: IOS_DEPENDENCY_ARGS: >- -Dcodename1.arg.ios.dependencyManager=both @@ -75,9 +79,24 @@ jobs: id: setup_hash run: | set -euo pipefail - SETUP_HASH=$(shasum -a 256 scripts/setup-workspace.sh | awk '{print $1}') - IOS_PORT_HASH=$(find Ports/iOSPort/src -type f -name '*.java' | sort | xargs shasum -a 256 | shasum -a 256 | awk '{print $1}') - echo "hash=${SETUP_HASH}-${IOS_PORT_HASH}" >> "$GITHUB_OUTPUT" + 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 \ + -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 @@ -106,13 +125,15 @@ jobs: restore-keys: | cn1-binaries-${{ runner.os }}- - - name: Setup workspace - run: ./scripts/setup-workspace.sh -q -DskipTests - timeout-minutes: 40 - - - name: Build iOS port - run: ./scripts/build-ios-port.sh -q -DskipTests - timeout-minutes: 40 + - name: Restore built CN1 + iOS port artifacts + uses: actions/cache/restore@v4 + with: + path: | + ~/.m2/repository/com/codenameone + Themes + Ports/iOSPort/nativeSources + key: cn1-built-${{ runner.os }}-${{ steps.src_hash.outputs.hash }} + fail-on-cache-miss: true - name: Build sample iOS app id: build_ios_app diff --git a/.github/workflows/parparvm-tests.yml b/.github/workflows/parparvm-tests.yml index eac387504c..79974ac5eb 100644 --- a/.github/workflows/parparvm-tests.yml +++ b/.github/workflows/parparvm-tests.yml @@ -3,6 +3,7 @@ name: ParparVM Java Tests on: pull_request: paths: + - '.github/workflows/parparvm-tests.yml' - 'vm/**' - 'Ports/JavaScriptPort/**' - '!vm/**/README.md' @@ -11,6 +12,7 @@ on: push: branches: [ master, main ] paths: + - '.github/workflows/parparvm-tests.yml' - 'vm/**' - 'Ports/JavaScriptPort/**' - '!vm/**/README.md' diff --git a/.github/workflows/scripts-ios-native.yml b/.github/workflows/scripts-ios-native.yml index e188200b5c..5fc0b56aac 100644 --- a/.github/workflows/scripts-ios-native.yml +++ b/.github/workflows/scripts-ios-native.yml @@ -4,6 +4,7 @@ on: pull_request: paths: - '.github/workflows/scripts-ios-native.yml' + - '.github/workflows/_build-ios-port.yml' - 'scripts/setup-workspace.sh' - 'scripts/build-ios-port.sh' - 'scripts/build-ios-app.sh' @@ -30,6 +31,7 @@ on: branches: [ master ] paths: - '.github/workflows/scripts-ios-native.yml' + - '.github/workflows/_build-ios-port.yml' - 'scripts/setup-workspace.sh' - 'scripts/build-ios-port.sh' - 'scripts/build-ios-app.sh' @@ -54,11 +56,15 @@ on: - '!maven/core-unittests/**' jobs: + build-port: + uses: ./.github/workflows/_build-ios-port.yml + native-ios: + needs: build-port permissions: contents: read runs-on: macos-15 - timeout-minutes: 65 + timeout-minutes: 45 concurrency: group: mac-ci-${{ github.workflow }}-${{ github.ref_name }} cancel-in-progress: true @@ -98,6 +104,23 @@ jobs: 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 \ + -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 @@ -125,13 +148,15 @@ jobs: restore-keys: | cn1-binaries-${{ runner.os }}- - - name: Setup workspace - run: ./scripts/setup-workspace.sh -q -DskipTests - timeout-minutes: 40 - - - name: Build iOS port - run: ./scripts/build-ios-port.sh -q -DskipTests - timeout-minutes: 40 + - name: Restore built CN1 + iOS port artifacts + uses: actions/cache/restore@v4 + with: + path: | + ~/.m2/repository/com/codenameone + Themes + Ports/iOSPort/nativeSources + key: cn1-built-${{ runner.os }}-${{ steps.src_hash.outputs.hash }} + fail-on-cache-miss: true - name: Build sample iOS app id: build_ios_app diff --git a/.github/workflows/scripts-ios.yml b/.github/workflows/scripts-ios.yml index f7665b4180..718f24b681 100644 --- a/.github/workflows/scripts-ios.yml +++ b/.github/workflows/scripts-ios.yml @@ -4,6 +4,7 @@ on: 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' @@ -32,6 +33,7 @@ on: 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' @@ -58,13 +60,17 @@ on: - '!maven/core-unittests/**' jobs: + build-port: + uses: ./.github/workflows/_build-ios-port.yml + build-ios: + 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: 60 # allow enough time for dependency installs and full build + timeout-minutes: 45 # only sample app build + UI tests now; iOS port build runs in build-port concurrency: # ensure only one mac build runs at once group: mac-ci-${{ github.workflow }}-${{ github.ref_name }} cancel-in-progress: true @@ -108,6 +114,23 @@ jobs: 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 \ + -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 @@ -135,23 +158,15 @@ jobs: restore-keys: | cn1-binaries-${{ runner.os }}- -# Temporary disabled due to github issue: https://github.com/actions/runner/issues/4134 -# - name: Restore cn1-binaries cache -# uses: actions/cache@v4 -# with: -# path: ../cn1-binaries -# key: cn1-binaries-${{ runner.os }}-${{ hashFiles('scripts/setup-workspace.sh') }} -# restore-keys: | -# cn1-binaries-${{ runner.os }}- - - - name: Setup workspace - run: ./scripts/setup-workspace.sh -q -DskipTests - # per-step timeout - timeout-minutes: 40 - - - name: Build iOS port - run: ./scripts/build-ios-port.sh -q -DskipTests - timeout-minutes: 40 + - name: Restore built CN1 + iOS port artifacts + uses: actions/cache/restore@v4 + with: + path: | + ~/.m2/repository/com/codenameone + Themes + Ports/iOSPort/nativeSources + key: cn1-built-${{ runner.os }}-${{ steps.src_hash.outputs.hash }} + fail-on-cache-miss: true - name: Build sample iOS app and compile workspace id: build-ios-app