From ba9a5c50e79057d723dc8a8ef4e6c9e626db8e44 Mon Sep 17 00:00:00 2001 From: leogdion Date: Thu, 2 Apr 2026 12:11:43 -0400 Subject: [PATCH 01/11] Optimize CI with dynamic matrix, concurrency groups, and cache cleanup (#128) - Add concurrency groups to cancel in-progress runs on same branch - Add paths-ignore to skip CI on doc-only changes - Add pull_request trigger for PRs to main - Add configure job with dynamic matrix (minimal on feature branches, full on main/PRs) - Gate Windows builds to full matrix only - Split macOS into core (always) and full (main/PRs only) jobs - Update lint/docs conditions to run when optional jobs are skipped - Update all GitHub Actions to latest versions (checkout v6, cache v5, codecov v6, etc.) - Add cache cleanup workflows for deleted branches and closed PRs - Update CodeQL workflow action versions Closes #128, closes #126, closes #127 Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/SyntaxKit.yml | 168 ++++++++++++++++-------- .github/workflows/cleanup-caches.yml | 38 ++++++ .github/workflows/cleanup-pr-caches.yml | 39 ++++++ .github/workflows/codeql.yml | 82 ++++++++++++ 4 files changed, 274 insertions(+), 53 deletions(-) create mode 100644 .github/workflows/cleanup-caches.yml create mode 100644 .github/workflows/cleanup-pr-caches.yml create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/SyntaxKit.yml b/.github/workflows/SyntaxKit.yml index 5ca13c2..8dfa2f2 100644 --- a/.github/workflows/SyntaxKit.yml +++ b/.github/workflows/SyntaxKit.yml @@ -3,41 +3,71 @@ on: push: branches-ignore: - '*WIP' + paths-ignore: + - '**.md' + - 'Docs/**' + - 'LICENSE' + - '.github/ISSUE_TEMPLATE/**' + pull_request: + branches: [main] + paths-ignore: + - '**.md' + - 'Docs/**' + - 'LICENSE' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: - PACKAGE_NAME: SyntaxKit + PACKAGE_NAME: SyntaxKit jobs: + configure: + name: Configure Build Matrix + runs-on: ubuntu-latest + if: ${{ !contains(github.event.head_commit.message, 'ci skip') }} + outputs: + full-matrix: ${{ steps.set-matrix.outputs.full-matrix }} + ubuntu-matrix: ${{ steps.set-matrix.outputs.ubuntu-matrix }} + steps: + - name: Determine build matrix + id: set-matrix + run: | + if [[ "${{ github.ref }}" == "refs/heads/main" || "${{ github.event_name }}" == "pull_request" ]]; then + echo "full-matrix=true" >> "$GITHUB_OUTPUT" + echo 'ubuntu-matrix={"os":["noble","jammy"],"swift":[{"version":"6.0"},{"version":"6.1"},{"version":"6.1","nightly":true},{"version":"6.2","nightly":true}]}' >> "$GITHUB_OUTPUT" + else + echo "full-matrix=false" >> "$GITHUB_OUTPUT" + echo 'ubuntu-matrix={"os":["noble"],"swift":[{"version":"6.1"}]}' >> "$GITHUB_OUTPUT" + fi + build-ubuntu: name: Build on Ubuntu + needs: [configure] runs-on: ubuntu-latest container: ${{ matrix.swift.nightly && format('swiftlang/swift:nightly-{0}-{1}', matrix.swift.version, matrix.os) || format('swift:{0}-{1}', matrix.swift.version, matrix.os) }} - if: ${{ !contains(github.event.head_commit.message, 'ci skip') }} strategy: - matrix: - os: [noble, jammy] - swift: - - version: "6.0" - - version: "6.1" - - version: "6.1" - nightly: true - - version: "6.2" - nightly: true + matrix: ${{ fromJSON(needs.configure.outputs.ubuntu-matrix) }} steps: - - uses: actions/checkout@v4 - - uses: brightdigit/swift-build@v1.3.1 - - uses: sersoft-gmbh/swift-coverage-action@v4 + - uses: actions/checkout@v6 + - uses: brightdigit/swift-build@v1.5.2 + - uses: sersoft-gmbh/swift-coverage-action@v5 id: coverage-files - with: + with: fail-on-empty-output: true - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v6 with: fail_ci_if_error: true - flags: swift-${{ matrix.swift-version }},ubuntu - verbose: true - token: ${{ secrets.CODECOV_TOKEN }} - files: ${{ join(fromJSON(steps.coverage-files.outputs.files), ',') }} + flags: swift-${{ matrix.swift.version }},ubuntu + verbose: true + token: ${{ secrets.CODECOV_TOKEN }} + files: ${{ join(fromJSON(steps.coverage-files.outputs.files), ',') }} + build-windows: name: Build on Windows + needs: [configure] + if: needs.configure.outputs.full-matrix == 'true' runs-on: ${{ matrix.runs-on }} strategy: fail-fast: false @@ -49,51 +79,83 @@ jobs: - swift-version: swift-6.1-release swift-build: 6.1-RELEASE steps: - - uses: actions/checkout@v4 - - uses: brightdigit/swift-build@v1.3.1 + - uses: actions/checkout@v6 + - uses: brightdigit/swift-build@v1.5.2 with: windows-swift-version: ${{ matrix.swift-version }} windows-swift-build: ${{ matrix.swift-build }} - name: Upload coverage to Codecov - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@v6 with: fail_ci_if_error: true flags: swift-${{ matrix.swift-version }},windows - verbose: true + verbose: true token: ${{ secrets.CODECOV_TOKEN }} os: windows swift_project: SyntaxKit - # files: ${{ join(fromJSON(steps.coverage-files.outputs.files), ',') }} + build-macos: name: Build on macOS env: PACKAGE_NAME: SyntaxKit runs-on: ${{ matrix.runs-on }} - if: "!contains(github.event.head_commit.message, 'ci skip')" + if: ${{ !contains(github.event.head_commit.message, 'ci skip') }} strategy: fail-fast: false matrix: include: - # SPM Build Matrix + # SPM Build - runs-on: macos-15 xcode: "/Applications/Xcode_26.0.app" - - runs-on: macos-15 - xcode: "/Applications/Xcode_16.4.app" - - # macOS Build Matrix + # macOS Build - type: macos runs-on: macos-15 xcode: "/Applications/Xcode_26.0.app" - - type: macos - runs-on: macos-15 + steps: + - uses: actions/checkout@v6 + + - name: Build and Test + uses: brightdigit/swift-build@v1.5.2 + with: + scheme: ${{ env.PACKAGE_NAME }}-Package + type: ${{ matrix.type }} + xcode: ${{ matrix.xcode }} + deviceName: ${{ matrix.deviceName }} + osVersion: ${{ matrix.osVersion }} + download-platform: ${{ matrix.download-platform }} + + # Common Coverage Steps + - name: Process Coverage + uses: sersoft-gmbh/swift-coverage-action@v5 + + - name: Upload Coverage + uses: codecov/codecov-action@v6 + with: + token: ${{ secrets.CODECOV_TOKEN }} + flags: ${{ matrix.type && format('{0}{1}', matrix.type, matrix.osVersion) || 'spm' }} + + build-macos-full: + name: Build on macOS (Full) + needs: [configure] + if: needs.configure.outputs.full-matrix == 'true' + env: + PACKAGE_NAME: SyntaxKit + runs-on: ${{ matrix.runs-on }} + strategy: + fail-fast: false + matrix: + include: + # SPM Build (older Xcode) + - runs-on: macos-15 xcode: "/Applications/Xcode_16.4.app" + # macOS Build (older Xcode) - type: macos runs-on: macos-15 - xcode: "/Applications/Xcode_26.0.app" - + xcode: "/Applications/Xcode_16.4.app" + # iOS Build Matrix - type: ios runs-on: macos-15 @@ -101,7 +163,7 @@ jobs: deviceName: "iPhone 17 Pro" osVersion: "26.0" download-platform: true - + # watchOS Build Matrix - type: watchos runs-on: macos-15 @@ -118,19 +180,19 @@ jobs: osVersion: "26.0" download-platform: true - # visionOS Build Matrix + # visionOS Build Matrix - type: visionos runs-on: macos-15 xcode: "/Applications/Xcode_26.0.app" deviceName: "Apple Vision Pro" osVersion: "26.0" download-platform: true - + steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Build and Test - uses: brightdigit/swift-build@v1.3.1 + uses: brightdigit/swift-build@v1.5.2 with: scheme: ${{ env.PACKAGE_NAME }}-Package type: ${{ matrix.type }} @@ -138,39 +200,39 @@ jobs: deviceName: ${{ matrix.deviceName }} osVersion: ${{ matrix.osVersion }} download-platform: ${{ matrix.download-platform }} - + # Common Coverage Steps - name: Process Coverage - uses: sersoft-gmbh/swift-coverage-action@v4 - + uses: sersoft-gmbh/swift-coverage-action@v5 + - name: Upload Coverage - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: ${{ matrix.type && format('{0}{1}', matrix.type, matrix.osVersion) || 'spm' }} lint: name: Linting - if: "!contains(github.event.head_commit.message, 'ci skip')" + if: ${{ !cancelled() && !failure() && !contains(github.event.head_commit.message, 'ci skip') }} runs-on: ubuntu-latest - needs: [build-ubuntu, build-macos, build-windows] + needs: [build-ubuntu, build-macos, build-windows, build-macos-full] env: MINT_PATH: .mint/lib MINT_LINK_PATH: .mint/bin steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Cache mint id: cache-mint - uses: actions/cache@v4 + uses: actions/cache@v5 env: cache-name: cache with: path: | .mint - Mint + Mint key: ${{ runner.os }}-mint-${{ hashFiles('**/Mintfile') }} restore-keys: | - ${{ runner.os }}-mint- + ${{ runner.os }}-mint- - name: Install mint if: steps.cache-mint.outputs.cache-hit == '' run: | @@ -182,10 +244,10 @@ jobs: docs: name: Documentation Validation - if: "!contains(github.event.head_commit.message, 'ci skip')" - needs: [build-ubuntu, build-macos, build-windows] + if: ${{ !cancelled() && !failure() && !contains(github.event.head_commit.message, 'ci skip') }} + needs: [build-ubuntu, build-macos, build-windows, build-macos-full] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Validate Documentation run: ./Scripts/validate-docs.sh diff --git a/.github/workflows/cleanup-caches.yml b/.github/workflows/cleanup-caches.yml new file mode 100644 index 0000000..80a0655 --- /dev/null +++ b/.github/workflows/cleanup-caches.yml @@ -0,0 +1,38 @@ +name: Cleanup Branch Caches +on: + delete: + +jobs: + cleanup: + name: Cleanup Caches for Deleted Branch + runs-on: ubuntu-latest + permissions: + actions: write + steps: + - uses: actions/checkout@v6 + - name: Delete branch caches + uses: actions/github-script@v8 + with: + script: | + const ref = `refs/heads/${context.payload.ref}`; + console.log(`Cleaning up caches for branch: ${ref}`); + + const caches = await github.paginate( + github.rest.actions.getActionsCacheList, + { + owner: context.repo.owner, + repo: context.repo.repo, + ref: ref, + } + ); + + for (const cache of caches) { + console.log(`Deleting cache: ${cache.key} (${cache.id})`); + await github.rest.actions.deleteActionsCacheById({ + owner: context.repo.owner, + repo: context.repo.repo, + cache_id: cache.id, + }); + } + + console.log(`Deleted ${caches.length} cache(s) for ${ref}`); diff --git a/.github/workflows/cleanup-pr-caches.yml b/.github/workflows/cleanup-pr-caches.yml new file mode 100644 index 0000000..a180205 --- /dev/null +++ b/.github/workflows/cleanup-pr-caches.yml @@ -0,0 +1,39 @@ +name: Cleanup PR Caches +on: + pull_request: + types: [closed] + +jobs: + cleanup: + name: Cleanup Caches for PR + runs-on: ubuntu-latest + permissions: + actions: write + steps: + - uses: actions/checkout@v6 + - name: Delete PR branch caches + uses: actions/github-script@v8 + with: + script: | + const ref = `refs/pull/${context.payload.pull_request.number}/merge`; + console.log(`Cleaning up caches for PR: ${ref}`); + + const caches = await github.paginate( + github.rest.actions.getActionsCacheList, + { + owner: context.repo.owner, + repo: context.repo.repo, + ref: ref, + } + ); + + for (const cache of caches) { + console.log(`Deleting cache: ${cache.key} (${cache.id})`); + await github.rest.actions.deleteActionsCacheById({ + owner: context.repo.owner, + repo: context.repo.repo, + cache_id: cache.id, + }); + } + + console.log(`Deleted ${caches.length} cache(s) for ${ref}`); diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..e4375fd --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,82 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches-ignore: + - '*WIP' + pull_request: + # The branches below must be a subset of the branches above + branches: [ "main" ] + schedule: + - cron: '20 11 * * 3' + +jobs: + analyze: + name: Analyze + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners + # Consider using larger runners for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-15') || 'ubuntu-latest' }} + timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'swift' ] + # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ] + # Use only 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Setup Xcode + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer + + - name: Verify Swift Version + run: | + swift --version + swift package --version + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). + # If this step fails, then you should remove it and run the build manually (see below) + - run: | + echo "Run, Build Application using script" + swift build + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: "/language:${{matrix.language}}" From a21698e3481dc48806ad42a3264bd5d649a916e8 Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Thu, 2 Apr 2026 12:55:00 -0400 Subject: [PATCH 02/11] Address PR review comments: update CI matrix and CodeQL workflow - Ubuntu matrix: add Swift 6.2 and 6.3, remove nightly versions; use 6.3 as single-version for feature branches - build-macos: keep SPM-only; move macOS Build to build-macos-full - build-macos-full: add macOS Build with Xcode 26.4; update iOS/watchOS/tvOS/visionOS to Xcode 26.4 / osVersion 26.4 - Add build-android and build-wasm jobs (full-matrix only) - Update lint and docs needs to include new jobs - codeql.yml: update runner to macos-26, Xcode to 26.4 Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/SyntaxKit.yml | 56 +++++++++++++++++++++++---------- .github/workflows/codeql.yml | 4 +-- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/.github/workflows/SyntaxKit.yml b/.github/workflows/SyntaxKit.yml index 8dfa2f2..7ae31d4 100644 --- a/.github/workflows/SyntaxKit.yml +++ b/.github/workflows/SyntaxKit.yml @@ -35,10 +35,10 @@ jobs: run: | if [[ "${{ github.ref }}" == "refs/heads/main" || "${{ github.event_name }}" == "pull_request" ]]; then echo "full-matrix=true" >> "$GITHUB_OUTPUT" - echo 'ubuntu-matrix={"os":["noble","jammy"],"swift":[{"version":"6.0"},{"version":"6.1"},{"version":"6.1","nightly":true},{"version":"6.2","nightly":true}]}' >> "$GITHUB_OUTPUT" + echo 'ubuntu-matrix={"os":["noble","jammy"],"swift":[{"version":"6.0"},{"version":"6.1"},{"version":"6.2"},{"version":"6.3"}]}' >> "$GITHUB_OUTPUT" else echo "full-matrix=false" >> "$GITHUB_OUTPUT" - echo 'ubuntu-matrix={"os":["noble"],"swift":[{"version":"6.1"}]}' >> "$GITHUB_OUTPUT" + echo 'ubuntu-matrix={"os":["noble"],"swift":[{"version":"6.3"}]}' >> "$GITHUB_OUTPUT" fi build-ubuntu: @@ -108,11 +108,6 @@ jobs: - runs-on: macos-15 xcode: "/Applications/Xcode_26.0.app" - # macOS Build - - type: macos - runs-on: macos-15 - xcode: "/Applications/Xcode_26.0.app" - steps: - uses: actions/checkout@v6 @@ -156,36 +151,41 @@ jobs: runs-on: macos-15 xcode: "/Applications/Xcode_16.4.app" + # macOS Build + - type: macos + runs-on: macos-15 + xcode: "/Applications/Xcode_26.4.app" + # iOS Build Matrix - type: ios runs-on: macos-15 - xcode: "/Applications/Xcode_26.0.app" + xcode: "/Applications/Xcode_26.4.app" deviceName: "iPhone 17 Pro" - osVersion: "26.0" + osVersion: "26.4" download-platform: true # watchOS Build Matrix - type: watchos runs-on: macos-15 - xcode: "/Applications/Xcode_26.0.app" + xcode: "/Applications/Xcode_26.4.app" deviceName: "Apple Watch Ultra 3 (49mm)" - osVersion: "26.0" + osVersion: "26.4" download-platform: true # tvOS Build Matrix - type: tvos runs-on: macos-15 - xcode: "/Applications/Xcode_26.0.app" + xcode: "/Applications/Xcode_26.4.app" deviceName: "Apple TV" - osVersion: "26.0" + osVersion: "26.4" download-platform: true # visionOS Build Matrix - type: visionos runs-on: macos-15 - xcode: "/Applications/Xcode_26.0.app" + xcode: "/Applications/Xcode_26.4.app" deviceName: "Apple Vision Pro" - osVersion: "26.0" + osVersion: "26.4" download-platform: true steps: @@ -211,11 +211,33 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} flags: ${{ matrix.type && format('{0}{1}', matrix.type, matrix.osVersion) || 'spm' }} + build-android: + name: Build for Android + needs: [configure] + if: needs.configure.outputs.full-matrix == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: brightdigit/swift-build@v1.5.2 + with: + type: android + + build-wasm: + name: Build for WASM + needs: [configure] + if: needs.configure.outputs.full-matrix == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: brightdigit/swift-build@v1.5.2 + with: + type: wasm + lint: name: Linting if: ${{ !cancelled() && !failure() && !contains(github.event.head_commit.message, 'ci skip') }} runs-on: ubuntu-latest - needs: [build-ubuntu, build-macos, build-windows, build-macos-full] + needs: [build-ubuntu, build-macos, build-windows, build-macos-full, build-android, build-wasm] env: MINT_PATH: .mint/lib MINT_LINK_PATH: .mint/bin @@ -245,7 +267,7 @@ jobs: docs: name: Documentation Validation if: ${{ !cancelled() && !failure() && !contains(github.event.head_commit.message, 'ci skip') }} - needs: [build-ubuntu, build-macos, build-windows, build-macos-full] + needs: [build-ubuntu, build-macos, build-windows, build-macos-full, build-android, build-wasm] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index e4375fd..e0ed2f5 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -29,7 +29,7 @@ jobs: # - https://gh.io/supported-runners-and-hardware-resources # - https://gh.io/using-larger-runners # Consider using larger runners for possible analysis time improvements. - runs-on: ${{ (matrix.language == 'swift' && 'macos-15') || 'ubuntu-latest' }} + runs-on: ${{ (matrix.language == 'swift' && 'macos-26') || 'ubuntu-latest' }} timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} permissions: actions: read @@ -50,7 +50,7 @@ jobs: uses: actions/checkout@v6 - name: Setup Xcode - run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_26.4.app/Contents/Developer - name: Verify Swift Version run: | From 9efa95043ea22fb587f674219ee4e9a4d6ae8c36 Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Thu, 2 Apr 2026 13:10:15 -0400 Subject: [PATCH 03/11] Restructure ubuntu matrix to include WASM types; fix Android matrix - configure outputs separate ubuntu-os/swift/type arrays (matching MonthBar pattern) - build-ubuntu composes matrix from all three outputs; wasm/wasm-embedded types use swift:6.3-noble container; type and wasmtime-version: 41.0.3 passed to swift-build - Remove separate build-wasm job - Android: add swift 6.3 alongside 6.2, add free-disk-space step, android-swift-version/android-api-level/android-run-tests parameters, coverage upload Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/SyntaxKit.yml | 84 +++++++++++++++++++++++---------- 1 file changed, 58 insertions(+), 26 deletions(-) diff --git a/.github/workflows/SyntaxKit.yml b/.github/workflows/SyntaxKit.yml index 7ae31d4..ce930ce 100644 --- a/.github/workflows/SyntaxKit.yml +++ b/.github/workflows/SyntaxKit.yml @@ -28,29 +28,41 @@ jobs: if: ${{ !contains(github.event.head_commit.message, 'ci skip') }} outputs: full-matrix: ${{ steps.set-matrix.outputs.full-matrix }} - ubuntu-matrix: ${{ steps.set-matrix.outputs.ubuntu-matrix }} + ubuntu-os: ${{ steps.set-matrix.outputs.ubuntu-os }} + ubuntu-swift: ${{ steps.set-matrix.outputs.ubuntu-swift }} + ubuntu-type: ${{ steps.set-matrix.outputs.ubuntu-type }} steps: - name: Determine build matrix id: set-matrix run: | if [[ "${{ github.ref }}" == "refs/heads/main" || "${{ github.event_name }}" == "pull_request" ]]; then echo "full-matrix=true" >> "$GITHUB_OUTPUT" - echo 'ubuntu-matrix={"os":["noble","jammy"],"swift":[{"version":"6.0"},{"version":"6.1"},{"version":"6.2"},{"version":"6.3"}]}' >> "$GITHUB_OUTPUT" + echo 'ubuntu-os=["noble","jammy"]' >> "$GITHUB_OUTPUT" + echo 'ubuntu-swift=[{"version":"6.0"},{"version":"6.1"},{"version":"6.2"},{"version":"6.3"}]' >> "$GITHUB_OUTPUT" + echo 'ubuntu-type=["","wasm","wasm-embedded"]' >> "$GITHUB_OUTPUT" else echo "full-matrix=false" >> "$GITHUB_OUTPUT" - echo 'ubuntu-matrix={"os":["noble"],"swift":[{"version":"6.3"}]}' >> "$GITHUB_OUTPUT" + echo 'ubuntu-os=["noble"]' >> "$GITHUB_OUTPUT" + echo 'ubuntu-swift=[{"version":"6.3"}]' >> "$GITHUB_OUTPUT" + echo 'ubuntu-type=[""]' >> "$GITHUB_OUTPUT" fi build-ubuntu: name: Build on Ubuntu needs: [configure] runs-on: ubuntu-latest - container: ${{ matrix.swift.nightly && format('swiftlang/swift:nightly-{0}-{1}', matrix.swift.version, matrix.os) || format('swift:{0}-{1}', matrix.swift.version, matrix.os) }} + container: ${{ (matrix.type == 'wasm' || matrix.type == 'wasm-embedded') && 'swift:6.3-noble' || matrix.swift.nightly && format('swiftlang/swift:nightly-{0}-{1}', matrix.swift.version, matrix.os) || format('swift:{0}-{1}', matrix.swift.version, matrix.os) }} strategy: - matrix: ${{ fromJSON(needs.configure.outputs.ubuntu-matrix) }} + matrix: + os: ${{ fromJSON(needs.configure.outputs.ubuntu-os) }} + swift: ${{ fromJSON(needs.configure.outputs.ubuntu-swift) }} + type: ${{ fromJSON(needs.configure.outputs.ubuntu-type) }} steps: - uses: actions/checkout@v6 - uses: brightdigit/swift-build@v1.5.2 + with: + type: ${{ matrix.type }} + wasmtime-version: 41.0.3 - uses: sersoft-gmbh/swift-coverage-action@v5 id: coverage-files with: @@ -74,8 +86,10 @@ jobs: matrix: runs-on: [windows-2022, windows-2025] include: - - swift-version: swift-6.2-branch - swift-build: 6.2-DEVELOPMENT-SNAPSHOT-2025-09-06-a + - swift-version: swift-6.3-release + swift-build: 6.3-RELEASE + - swift-version: swift-6.2-release + swift-build: 6.2-RELEASE - swift-version: swift-6.1-release swift-build: 6.1-RELEASE steps: @@ -105,8 +119,8 @@ jobs: matrix: include: # SPM Build - - runs-on: macos-15 - xcode: "/Applications/Xcode_26.0.app" + - runs-on: macos-26 + xcode: "/Applications/Xcode_26.4.app" steps: - uses: actions/checkout@v6 @@ -153,12 +167,12 @@ jobs: # macOS Build - type: macos - runs-on: macos-15 + runs-on: macos-26 xcode: "/Applications/Xcode_26.4.app" # iOS Build Matrix - type: ios - runs-on: macos-15 + runs-on: macos-26 xcode: "/Applications/Xcode_26.4.app" deviceName: "iPhone 17 Pro" osVersion: "26.4" @@ -166,7 +180,7 @@ jobs: # watchOS Build Matrix - type: watchos - runs-on: macos-15 + runs-on: macos-26 xcode: "/Applications/Xcode_26.4.app" deviceName: "Apple Watch Ultra 3 (49mm)" osVersion: "26.4" @@ -174,7 +188,7 @@ jobs: # tvOS Build Matrix - type: tvos - runs-on: macos-15 + runs-on: macos-26 xcode: "/Applications/Xcode_26.4.app" deviceName: "Apple TV" osVersion: "26.4" @@ -182,7 +196,7 @@ jobs: # visionOS Build Matrix - type: visionos - runs-on: macos-15 + runs-on: macos-26 xcode: "/Applications/Xcode_26.4.app" deviceName: "Apple Vision Pro" osVersion: "26.4" @@ -216,28 +230,46 @@ jobs: needs: [configure] if: needs.configure.outputs.full-matrix == 'true' runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + swift: + - version: "6.2" + - version: "6.3" + android-api-level: [33, 34] steps: - uses: actions/checkout@v6 + - name: Free disk space + uses: jlumbroso/free-disk-space@v1.3.1 + with: + tool-cache: false + android: false + dotnet: true + haskell: true + large-packages: true + docker-images: true + swap-storage: true - uses: brightdigit/swift-build@v1.5.2 + id: build with: type: android - - build-wasm: - name: Build for WASM - needs: [configure] - if: needs.configure.outputs.full-matrix == 'true' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: brightdigit/swift-build@v1.5.2 + android-swift-version: ${{ matrix.swift.version }} + android-api-level: ${{ matrix.android-api-level }} + android-run-tests: true + - name: Upload coverage to Codecov + if: steps.build.outputs.contains-code-coverage == 'true' + uses: codecov/codecov-action@v6 with: - type: wasm + fail_ci_if_error: true + flags: android-api${{ matrix.android-api-level }}-swift${{ matrix.swift.version }} + verbose: true + token: ${{ secrets.CODECOV_TOKEN }} lint: name: Linting if: ${{ !cancelled() && !failure() && !contains(github.event.head_commit.message, 'ci skip') }} runs-on: ubuntu-latest - needs: [build-ubuntu, build-macos, build-windows, build-macos-full, build-android, build-wasm] + needs: [build-ubuntu, build-macos, build-windows, build-macos-full, build-android] env: MINT_PATH: .mint/lib MINT_LINK_PATH: .mint/bin @@ -267,7 +299,7 @@ jobs: docs: name: Documentation Validation if: ${{ !cancelled() && !failure() && !contains(github.event.head_commit.message, 'ci skip') }} - needs: [build-ubuntu, build-macos, build-windows, build-macos-full, build-android, build-wasm] + needs: [build-ubuntu, build-macos, build-windows, build-macos-full, build-android] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 From 9f4f4870996635dd8acaf4551f2a384483b02bbb Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Thu, 2 Apr 2026 13:23:56 -0400 Subject: [PATCH 04/11] Fix full matrix not running on PRs - configure: guard ci-skip check with event_name check so it always runs on pull_request events (github.event.head_commit is null on PRs) - build-macos: add needs: [configure], gate on configure success so ci-skip propagates via dependency chain instead of re-checking head_commit.message Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/SyntaxKit.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/SyntaxKit.yml b/.github/workflows/SyntaxKit.yml index ce930ce..8e9e1d6 100644 --- a/.github/workflows/SyntaxKit.yml +++ b/.github/workflows/SyntaxKit.yml @@ -25,7 +25,7 @@ jobs: configure: name: Configure Build Matrix runs-on: ubuntu-latest - if: ${{ !contains(github.event.head_commit.message, 'ci skip') }} + if: ${{ github.event_name == 'pull_request' || !contains(github.event.head_commit.message, 'ci skip') }} outputs: full-matrix: ${{ steps.set-matrix.outputs.full-matrix }} ubuntu-os: ${{ steps.set-matrix.outputs.ubuntu-os }} @@ -110,10 +110,11 @@ jobs: build-macos: name: Build on macOS + needs: [configure] env: PACKAGE_NAME: SyntaxKit runs-on: ${{ matrix.runs-on }} - if: ${{ !contains(github.event.head_commit.message, 'ci skip') }} + if: ${{ !cancelled() && needs.configure.result == 'success' }} strategy: fail-fast: false matrix: From 68455ac58a221588ee076fdd24dcaa3a46f89795 Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Thu, 2 Apr 2026 13:25:08 -0400 Subject: [PATCH 05/11] Install curl in Swift 6.3 Ubuntu container before coverage upload The swift:6.3 Docker image does not include curl, which is required by the Codecov uploader. Applies to all 6.3 builds including wasm types. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/SyntaxKit.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/SyntaxKit.yml b/.github/workflows/SyntaxKit.yml index 8e9e1d6..75fe4b4 100644 --- a/.github/workflows/SyntaxKit.yml +++ b/.github/workflows/SyntaxKit.yml @@ -63,6 +63,11 @@ jobs: with: type: ${{ matrix.type }} wasmtime-version: 41.0.3 + - name: Install curl + if: matrix.swift.version == '6.3' + run: | + apt-get update -q + apt-get install -y curl - uses: sersoft-gmbh/swift-coverage-action@v5 id: coverage-files with: From 9ef6549c74d99d5846e67901b60a43b69686b3a5 Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Thu, 2 Apr 2026 13:31:18 -0400 Subject: [PATCH 06/11] Fix PR trigger for version branches and prevent duplicate push/PR runs - Add version branch pattern to pull_request trigger so PRs targeting v0.0.4 (and similar) fire the full matrix CI - Use github.head_ref || github.ref_name for concurrency group so push and pull_request events for the same branch share a group; PR run (arrives second) cancels the concurrent push run Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/SyntaxKit.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/SyntaxKit.yml b/.github/workflows/SyntaxKit.yml index 75fe4b4..360795a 100644 --- a/.github/workflows/SyntaxKit.yml +++ b/.github/workflows/SyntaxKit.yml @@ -9,14 +9,16 @@ on: - 'LICENSE' - '.github/ISSUE_TEMPLATE/**' pull_request: - branches: [main] + branches: + - main + - 'v[0-9]*.[0-9]*.[0-9]*' paths-ignore: - '**.md' - 'Docs/**' - 'LICENSE' concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.head_ref || github.ref_name }} cancel-in-progress: true env: From c90daf87040d94f2dffc23929a8b6384ec86f678 Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Thu, 2 Apr 2026 13:49:11 -0400 Subject: [PATCH 07/11] Fix test file path resolution for WASM/Android/Linux CI Settings.projectRoot now uses a 3-strategy fallback matching the SyndiKit pattern: working directory first (reliable for WASM/Android/SPM), then #filePath-relative (macOS/Linux CI), then walking up parent directories. Add android-copy-files to copy Documentation.docc to the Android emulator's working directory so the path resolution finds the files via Strategy 1. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/SyntaxKit.yml | 1 + Tests/SyntaxDocTests/Settings.swift | 30 +++++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/.github/workflows/SyntaxKit.yml b/.github/workflows/SyntaxKit.yml index 360795a..ffe1a84 100644 --- a/.github/workflows/SyntaxKit.yml +++ b/.github/workflows/SyntaxKit.yml @@ -264,6 +264,7 @@ jobs: android-swift-version: ${{ matrix.swift.version }} android-api-level: ${{ matrix.android-api-level }} android-run-tests: true + android-copy-files: Sources/SyntaxKit/Documentation.docc - name: Upload coverage to Codecov if: steps.build.outputs.contains-code-coverage == 'true' uses: codecov/codecov-action@v6 diff --git a/Tests/SyntaxDocTests/Settings.swift b/Tests/SyntaxDocTests/Settings.swift index ab5bda9..205fe6a 100644 --- a/Tests/SyntaxDocTests/Settings.swift +++ b/Tests/SyntaxDocTests/Settings.swift @@ -8,14 +8,36 @@ import Foundation internal enum Settings { - /// Project root directory calculated from the current file location + /// Project root directory calculated with a 3-strategy fallback for cross-platform support internal static let projectRoot: URL = { - let currentFileURL = URL(fileURLWithPath: #filePath) - return - currentFileURL + // Strategy 1: Working directory (most reliable for SPM/WASM/Android) + let workingDir = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) + if FileManager.default.fileExists(atPath: workingDir.appendingPathComponent("Sources").path) { + return workingDir + } + + // Strategy 2: Source-relative via #filePath (macOS/Linux CI) + let sourceRelative = URL(fileURLWithPath: #filePath) .deletingLastPathComponent() // Tests/SyntaxDocTests .deletingLastPathComponent() // Tests .deletingLastPathComponent() // Project root + if FileManager.default.fileExists( + atPath: sourceRelative.appendingPathComponent("Sources").path) + { + return sourceRelative + } + + // Strategy 3: Walk up from working directory (nested execution contexts) + var search = workingDir + for _ in 0..<4 { + if FileManager.default.fileExists(atPath: search.appendingPathComponent("Sources").path) { + return search + } + search = search.deletingLastPathComponent() + } + + // Fallback — will produce a clear error if Sources/ is still not found + return sourceRelative }() /// Document paths to search for documentation files From b1414b69f42b6823be47942c22c7469cbf1b4158 Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Thu, 2 Apr 2026 14:44:02 -0400 Subject: [PATCH 08/11] Limit doc test paths on WASM to tutorial files only Examples/ (2.1MB) and full Documentation.docc (with images) exceed WASM memory constraints. On WASI, use only the two lightweight tutorial .md files (~36KB total). Matches SyndiKit's #if os(WASI) subset pattern. Co-Authored-By: Claude Sonnet 4.6 --- Tests/SyntaxDocTests/Settings.swift | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/Tests/SyntaxDocTests/Settings.swift b/Tests/SyntaxDocTests/Settings.swift index 205fe6a..864212c 100644 --- a/Tests/SyntaxDocTests/Settings.swift +++ b/Tests/SyntaxDocTests/Settings.swift @@ -41,11 +41,22 @@ internal enum Settings { }() /// Document paths to search for documentation files - internal static let docPaths = [ - "Sources/SyntaxKit/Documentation.docc", - "README.md", - "Examples", - ] + /// On WASM, limited to lightweight tutorial files only (no images, no Examples) + /// due to WASM memory constraints (~144KB practical limit) + internal static let docPaths: [String] = { + #if os(WASI) + return [ + "Sources/SyntaxKit/Documentation.docc/Tutorials/Quick-Start-Guide.md", + "Sources/SyntaxKit/Documentation.docc/Tutorials/Creating-Macros-with-SyntaxKit.md", + ] + #else + return [ + "Sources/SyntaxKit/Documentation.docc", + "README.md", + "Examples", + ] + #endif + }() /// Resolves a relative file path to absolute path internal static func resolveFilePath(_ filePath: String) throws -> URL { From 9c8c1037acad2f4644fb0730b823bd08c4331a18 Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Thu, 2 Apr 2026 14:55:52 -0400 Subject: [PATCH 09/11] Fix Android/WASM doc path resolution and coverage conditions - Add Strategy 1b to Settings.projectRoot: detect Documentation.docc/ in working dir (android-copy-files copies last component only) - Add #if os(Android) branch to docPaths with prefix-less paths matching the flat copy layout on Android devices - Add id: build to swift-build steps and gate coverage upload/processing on steps.build.outputs.contains-code-coverage == 'true' across all jobs Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/SyntaxKit.yml | 11 +++++++++++ Tests/SyntaxDocTests/Settings.swift | 18 ++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/.github/workflows/SyntaxKit.yml b/.github/workflows/SyntaxKit.yml index ffe1a84..1bbc6c0 100644 --- a/.github/workflows/SyntaxKit.yml +++ b/.github/workflows/SyntaxKit.yml @@ -62,6 +62,7 @@ jobs: steps: - uses: actions/checkout@v6 - uses: brightdigit/swift-build@v1.5.2 + id: build with: type: ${{ matrix.type }} wasmtime-version: 41.0.3 @@ -72,9 +73,11 @@ jobs: apt-get install -y curl - uses: sersoft-gmbh/swift-coverage-action@v5 id: coverage-files + if: steps.build.outputs.contains-code-coverage == 'true' with: fail-on-empty-output: true - name: Upload coverage to Codecov + if: steps.build.outputs.contains-code-coverage == 'true' uses: codecov/codecov-action@v6 with: fail_ci_if_error: true @@ -102,10 +105,12 @@ jobs: steps: - uses: actions/checkout@v6 - uses: brightdigit/swift-build@v1.5.2 + id: build with: windows-swift-version: ${{ matrix.swift-version }} windows-swift-build: ${{ matrix.swift-build }} - name: Upload coverage to Codecov + if: steps.build.outputs.contains-code-coverage == 'true' uses: codecov/codecov-action@v6 with: fail_ci_if_error: true @@ -134,6 +139,7 @@ jobs: - uses: actions/checkout@v6 - name: Build and Test + id: build uses: brightdigit/swift-build@v1.5.2 with: scheme: ${{ env.PACKAGE_NAME }}-Package @@ -145,9 +151,11 @@ jobs: # Common Coverage Steps - name: Process Coverage + if: steps.build.outputs.contains-code-coverage == 'true' uses: sersoft-gmbh/swift-coverage-action@v5 - name: Upload Coverage + if: steps.build.outputs.contains-code-coverage == 'true' uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} @@ -214,6 +222,7 @@ jobs: - uses: actions/checkout@v6 - name: Build and Test + id: build uses: brightdigit/swift-build@v1.5.2 with: scheme: ${{ env.PACKAGE_NAME }}-Package @@ -225,9 +234,11 @@ jobs: # Common Coverage Steps - name: Process Coverage + if: steps.build.outputs.contains-code-coverage == 'true' uses: sersoft-gmbh/swift-coverage-action@v5 - name: Upload Coverage + if: steps.build.outputs.contains-code-coverage == 'true' uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} diff --git a/Tests/SyntaxDocTests/Settings.swift b/Tests/SyntaxDocTests/Settings.swift index 864212c..b9825e4 100644 --- a/Tests/SyntaxDocTests/Settings.swift +++ b/Tests/SyntaxDocTests/Settings.swift @@ -10,12 +10,20 @@ import Foundation internal enum Settings { /// Project root directory calculated with a 3-strategy fallback for cross-platform support internal static let projectRoot: URL = { - // Strategy 1: Working directory (most reliable for SPM/WASM/Android) let workingDir = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) + + // Strategy 1a: Sources/ in working directory (SPM/WASM/Linux) if FileManager.default.fileExists(atPath: workingDir.appendingPathComponent("Sources").path) { return workingDir } + // Strategy 1b: Documentation.docc copied as last component (Android flat copy via android-copy-files) + if FileManager.default.fileExists( + atPath: workingDir.appendingPathComponent("Documentation.docc").path) + { + return workingDir + } + // Strategy 2: Source-relative via #filePath (macOS/Linux CI) let sourceRelative = URL(fileURLWithPath: #filePath) .deletingLastPathComponent() // Tests/SyntaxDocTests @@ -44,7 +52,13 @@ internal enum Settings { /// On WASM, limited to lightweight tutorial files only (no images, no Examples) /// due to WASM memory constraints (~144KB practical limit) internal static let docPaths: [String] = { - #if os(WASI) + #if os(Android) + // android-copy-files copies Documentation.docc/ as last component to working dir + return [ + "Documentation.docc/Tutorials/Quick-Start-Guide.md", + "Documentation.docc/Tutorials/Creating-Macros-with-SyntaxKit.md", + ] + #elseif os(WASI) return [ "Sources/SyntaxKit/Documentation.docc/Tutorials/Quick-Start-Guide.md", "Sources/SyntaxKit/Documentation.docc/Tutorials/Creating-Macros-with-SyntaxKit.md", From 426285a0e2cee4781d30f6659c7bcb682fb939d3 Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Thu, 2 Apr 2026 15:05:56 -0400 Subject: [PATCH 10/11] Install curl only when code coverage is present on Ubuntu Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/SyntaxKit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/SyntaxKit.yml b/.github/workflows/SyntaxKit.yml index 1bbc6c0..a010891 100644 --- a/.github/workflows/SyntaxKit.yml +++ b/.github/workflows/SyntaxKit.yml @@ -67,7 +67,7 @@ jobs: type: ${{ matrix.type }} wasmtime-version: 41.0.3 - name: Install curl - if: matrix.swift.version == '6.3' + if: steps.build.outputs.contains-code-coverage == 'true' run: | apt-get update -q apt-get install -y curl From 99edc26aa670116bbcfb04c54a6da6a1ce121dcf Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Thu, 2 Apr 2026 15:19:06 -0400 Subject: [PATCH 11/11] Update swift-docc-plugin to 1.4.6 to fix Windows CI 1.4.6 adds .gitattributes for symlinks on Windows, fixing build failures where symlinked plugin source files were checked out as plain text, causing 'cannot find Lock in scope' and related errors. Co-Authored-By: Claude Sonnet 4.6 --- Package.resolved | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Package.resolved b/Package.resolved index e355d45..24cfefa 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,13 +1,13 @@ { - "originHash" : "3a9631966aed0489b1d0415c39c345f4083602bf6a2ddfdc7954cd286093542f", + "originHash" : "482d43aff5bb5c075d237e0ea17c12ee2c043b2642e459260752aa1848a20593", "pins" : [ { "identity" : "swift-docc-plugin", "kind" : "remoteSourceControl", "location" : "https://github.com/swiftlang/swift-docc-plugin", "state" : { - "revision" : "3e4f133a77e644a5812911a0513aeb7288b07d06", - "version" : "1.4.5" + "revision" : "e977f65879f82b375a044c8837597f690c067da6", + "version" : "1.4.6" } }, {