compat-check #5
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
| # Verifies pinned combinations of CLI version, Node.js major, runtime version | |
| # and toolchain versions by building a freshly created app. The matrix comes | |
| # from public feeds (npm, nodejs.org, xcodereleases.com, the Android SDK | |
| # repository, Adoptium) minus every combination that already has a file under | |
| # data/verified/. The first run is large; later runs only contain what new | |
| # releases create. Results land in a PR that adds result files. | |
| name: compat-check | |
| on: | |
| schedule: | |
| - cron: "0 6 * * 1" | |
| workflow_dispatch: | |
| # Leave every field empty for a normal feed-driven run. Fill them in to | |
| # verify one specific combination by hand. | |
| inputs: | |
| platform: | |
| description: "feed-driven: normal run. ios / android: verify one combination from the fields below." | |
| type: choice | |
| options: ["feed-driven", "ios", "android"] | |
| default: "feed-driven" | |
| cli: | |
| description: "nativescript CLI version, e.g. 9.1.1" | |
| type: string | |
| node: | |
| description: "Node.js major, e.g. 24" | |
| type: string | |
| runtime: | |
| description: "Runtime version, e.g. 9.1.0" | |
| type: string | |
| xcode: | |
| description: "Xcode line for iOS, e.g. 26.3" | |
| type: string | |
| compileSdk: | |
| description: "Android API level, e.g. 36" | |
| type: string | |
| jdk: | |
| description: "JDK major for Android, e.g. 21" | |
| type: string | |
| force: | |
| description: "Rebuild even if this combination is already recorded" | |
| type: boolean | |
| default: false | |
| concurrency: | |
| group: compat-check | |
| cancel-in-progress: false | |
| permissions: | |
| contents: read | |
| jobs: | |
| matrix: | |
| name: Build matrix | |
| runs-on: ubuntu-latest | |
| outputs: | |
| ios: ${{ steps.matrix.outputs.ios }} | |
| android: ${{ steps.matrix.outputs.android }} | |
| has_ios: ${{ steps.matrix.outputs.has_ios }} | |
| has_android: ${{ steps.matrix.outputs.has_android }} | |
| steps: | |
| # The default branch as it is when this job starts, so results committed | |
| # by a run that finished while this one was queued are already recorded. | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event.repository.default_branch }} | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| - id: matrix | |
| env: | |
| MANUAL_PLATFORM: ${{ inputs.platform }} | |
| MANUAL_CLI: ${{ inputs.cli }} | |
| MANUAL_NODE: ${{ inputs.node }} | |
| MANUAL_RUNTIME: ${{ inputs.runtime }} | |
| MANUAL_XCODE: ${{ inputs.xcode }} | |
| MANUAL_COMPILE_SDK: ${{ inputs.compileSdk }} | |
| MANUAL_JDK: ${{ inputs.jdk }} | |
| MANUAL_FORCE: ${{ inputs.force }} | |
| run: node scripts/build-matrix.mjs --github | |
| ios: | |
| name: iOS ${{ matrix.runtime }} · Xcode ${{ matrix.xcode }} · CLI ${{ matrix.cli }} · Node ${{ matrix.node }} | |
| needs: matrix | |
| if: needs.matrix.outputs.has_ios == 'true' | |
| # Chosen per Xcode line by the matrix job from the runner images' readmes. | |
| runs-on: ${{ matrix.runner }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: ${{ fromJson(needs.matrix.outputs.ios) }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ matrix.node }} | |
| - name: Select Xcode ${{ matrix.xcode }} | |
| run: | | |
| app=$(ls -d /Applications/Xcode_${{ matrix.xcode }}*.app 2>/dev/null | sort -V | tail -1) | |
| if [ -z "$app" ]; then | |
| echo "::warning::Xcode ${{ matrix.xcode }} is not installed on ${{ matrix.runner }}" | |
| exit 1 | |
| fi | |
| sudo xcode-select -s "$app/Contents/Developer" | |
| xcodebuild -version | |
| - name: Build the compat app | |
| id: build | |
| env: | |
| NS_SKIP_ENV_CHECK: "1" | |
| run: | | |
| npm install -g nativescript@${{ matrix.cli }} | |
| ns create compat --tsc --disableAnalytics | |
| cd compat | |
| npm install @nativescript/ios@${{ matrix.runtime }} --save-dev | |
| # One retry before a failure is recorded: a runner hiccup must not become a permanent verdict. | |
| set -o pipefail | |
| (ns build ios --disableAnalytics || ns build ios --disableAnalytics) 2>&1 | tee ../build.log | |
| - name: Record result | |
| # A pinned combination that failed is as final as one that passed: record it so it is never rebuilt. | |
| if: always() && steps.build.outcome != 'skipped' | |
| run: | | |
| node scripts/report-verification.mjs --package @nativescript/ios --version ${{ matrix.runtime }} \ | |
| --toolchain xcode=${{ matrix.xcode }} --toolchain cocoapods="$(pod --version)" \ | |
| --with nativescript=${{ matrix.cli }} --with node=${{ matrix.node }} \ | |
| --resolved node="$(node --version | tr -d v)" --resolved xcode="$(xcodebuild -version | head -1 | cut -d' ' -f2)" \ | |
| --outcome ${{ steps.build.outcome }} --signature "$(grep -aE 'error|FAILURE|failed' build.log | grep -viE 'warning|deprecat' | tail -1)" \ | |
| --evidence "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| - uses: actions/upload-artifact@v4 | |
| if: always() && steps.build.outcome != 'skipped' | |
| with: | |
| name: result-ios-${{ matrix.runtime }}-xcode-${{ matrix.xcode }}-cli-${{ matrix.cli }}-node-${{ matrix.node }} | |
| path: data/verified/ | |
| android: | |
| name: Android ${{ matrix.runtime }} · SDK ${{ matrix.compileSdk }} · JDK ${{ matrix.jdk }} · CLI ${{ matrix.cli }} · Node ${{ matrix.node }} | |
| needs: matrix | |
| if: needs.matrix.outputs.has_android == 'true' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: ${{ fromJson(needs.matrix.outputs.android) }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: ${{ matrix.jdk }} | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ matrix.node }} | |
| - name: Install ${{ matrix.platform }} and build-tools ${{ matrix.buildTools }} | |
| run: | | |
| yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" \ | |
| "platforms;${{ matrix.platform }}" "build-tools;${{ matrix.buildTools }}" >/dev/null | |
| - name: Build the compat app | |
| id: build | |
| env: | |
| NS_SKIP_ENV_CHECK: "1" | |
| run: | | |
| npm install -g nativescript@${{ matrix.cli }} | |
| ns create compat --tsc --disableAnalytics | |
| cd compat | |
| npm install @nativescript/android@${{ matrix.runtime }} --save-dev | |
| set -o pipefail | |
| (ns build android --compileSdk ${{ matrix.compileSdk }} --disableAnalytics || ns build android --compileSdk ${{ matrix.compileSdk }} --disableAnalytics) 2>&1 | tee ../build.log | |
| - name: Record result | |
| if: always() && steps.build.outcome != 'skipped' | |
| run: | | |
| node scripts/report-verification.mjs --package @nativescript/android --version ${{ matrix.runtime }} \ | |
| --toolchain compileSdk=${{ matrix.compileSdk }} --toolchain buildTools=${{ matrix.buildTools }} --toolchain jdk=${{ matrix.jdk }} \ | |
| --with nativescript=${{ matrix.cli }} --with node=${{ matrix.node }} \ | |
| --resolved node="$(node --version | tr -d v)" --resolved jdk="$(java -version 2>&1 | head -1 | cut -d'"' -f2)" \ | |
| --outcome ${{ steps.build.outcome }} --signature "$(grep -aE 'error|FAILURE|failed' build.log | grep -viE 'warning|deprecat' | tail -1)" \ | |
| --evidence "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| - uses: actions/upload-artifact@v4 | |
| if: always() && steps.build.outcome != 'skipped' | |
| with: | |
| name: result-android-${{ matrix.runtime }}-sdk-${{ matrix.compileSdk }}-jdk-${{ matrix.jdk }}-cli-${{ matrix.cli }}-node-${{ matrix.node }} | |
| path: data/verified/ | |
| collect: | |
| name: Record results | |
| needs: [matrix, ios, android] | |
| # Runs even when some builds failed: every outcome is a recorded cell. | |
| if: always() && needs.matrix.result == 'success' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event.repository.default_branch }} | |
| # Each artifact is a data/verified/ tree holding that job's result file, | |
| # so dropping them all onto the checkout is the whole merge. | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| pattern: result-* | |
| path: data/verified | |
| merge-multiple: true | |
| - name: Commit results | |
| run: | | |
| git add data/verified | |
| if git diff --cached --quiet; then | |
| echo "no new results" | |
| exit 0 | |
| fi | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| count=$(git diff --cached --name-only | wc -l | tr -d ' ') | |
| git commit -m "data: record $count compatibility results" \ | |
| -m "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| # Another run or a maintainer may have pushed meanwhile; results only add files, so a rebase never conflicts. | |
| for attempt in 1 2 3; do | |
| git push && exit 0 | |
| git pull --rebase | |
| done | |
| exit 1 | |
| # Commits made with the Actions token do not trigger the deploy | |
| # workflow, so the results are deployed from here. | |
| - name: Deploy | |
| if: env.CLOUDFLARE_API_TOKEN != '' | |
| env: | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| run: | | |
| npm ci | |
| npx vite build | |
| - uses: cloudflare/wrangler-action@v3 | |
| if: env.CLOUDFLARE_API_TOKEN != '' | |
| env: | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| with: | |
| apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| command: deploy |