Repository navigation
test: use the quinaryLabel spelling CI's Xcode accepts (#3397) #2075
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: Repo Hygiene | |
| # Lints the parts of the repo the macOS suite cannot reach. 7,500 lines of shell build, sign, | |
| # notarize and publish this app, and nothing checked any of it: the six findings actionlint reported | |
| # the first time it ran here were all inside `run:` blocks in the release and plugin workflows. | |
| # | |
| # Runs on ubuntu, where minutes are free on a public repo, and finishes in well under a minute. | |
| on: | |
| # No paths filter on pull_request: a workflow skipped by one never reports, so its gate could not | |
| # be a required check. The changes job below does the filtering instead. | |
| pull_request: | |
| paths: | |
| - "scripts/**" | |
| - ".claude/skills/**/*.sh" | |
| - ".github/workflows/**" | |
| - ".github/actions/**" | |
| - ".github/scripts/**" | |
| - ".github/plugin-registry.json" | |
| - ".github/duplicate-contract-baseline.txt" | |
| - "Plugins/**/*.swift" | |
| - "TablePro/**/*.swift" | |
| - "Packages/**/*.swift" | |
| - "TableProTests/**/*.swift" | |
| - "project.yml" | |
| - "TablePro.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved" | |
| - "TablePro/Resources/ThirdPartyLicenses/licenses.yml" | |
| - ".swiftlint.yml" | |
| push: | |
| branches: [main] | |
| paths: | |
| - "scripts/**" | |
| - ".claude/skills/**/*.sh" | |
| - ".github/workflows/**" | |
| - ".github/actions/**" | |
| - ".github/scripts/**" | |
| - ".github/plugin-registry.json" | |
| - ".github/duplicate-contract-baseline.txt" | |
| - ".github/hub-ratchet-baseline.json" | |
| - ".claude/skills/**/*.sh" | |
| - "Plugins/**/*.swift" | |
| - "TablePro/**/*.swift" | |
| - "Packages/**/*.swift" | |
| - "TableProTests/**/*.swift" | |
| - "project.yml" | |
| - "TablePro.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved" | |
| - "TablePro/Resources/ThirdPartyLicenses/licenses.yml" | |
| - ".swiftlint.yml" | |
| workflow_dispatch: | |
| concurrency: | |
| group: repo-hygiene-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| # Keep this list and the push paths above identical. | |
| changes: | |
| name: Detect relevant changes | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| outputs: | |
| run: ${{ steps.decide.outputs.run }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| - name: Decide whether the checks need to run | |
| id: decide | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| run: | | |
| set -euo pipefail | |
| RUN=$(scripts/ci/detect-changed-paths.sh \ | |
| --event "$EVENT_NAME" \ | |
| --base "$BASE_SHA" \ | |
| 'scripts/' '\.github/workflows/' '\.github/actions/' '\.github/scripts/' \ | |
| '\.github/plugin-registry\.json$' '\.github/duplicate-contract-baseline\.txt$' \ | |
| '\.github/hub-ratchet-baseline\.json$' '\.claude/skills/.*\.sh$' \ | |
| 'Plugins/.*\.swift$' 'TablePro/.*\.swift$' 'Packages/.*\.swift$' 'TableProTests/.*\.swift$' \ | |
| 'project\.yml$' \ | |
| 'TablePro\.xcodeproj/project\.xcworkspace/xcshareddata/swiftpm/Package\.resolved$' \ | |
| 'TablePro/Resources/ThirdPartyLicenses/licenses\.yml$' '\.swiftlint\.yml$') | |
| echo "run=$RUN" >> "$GITHUB_OUTPUT" | |
| lint: | |
| name: Lint workflows and scripts | |
| needs: changes | |
| if: needs.changes.outputs.run == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Install shellcheck and actionlint | |
| run: | | |
| sudo apt-get update -qq | |
| sudo apt-get install -y -qq shellcheck | |
| bash <(curl -sSL https://raw.githubusercontent.com/rhysd/actionlint/v1.7.12/scripts/download-actionlint.bash) 1.7.12 | |
| ./actionlint --version | |
| # actionlint shells out to shellcheck for every inline `run:` block, which is the only way | |
| # those get checked at all: they are not files, so a plain shellcheck run never sees them. | |
| - name: Lint workflows | |
| run: ./actionlint -color | |
| # --severity=warning is the floor the repo currently holds at zero. The remaining | |
| # informational findings are almost all SC2012 (`ls` where `find` would be sturdier) in | |
| # message strings, and gating on them today would only add noise. | |
| - name: Lint scripts | |
| run: | | |
| set -euo pipefail | |
| mapfile -t SCRIPTS < <(find scripts .claude/skills -name '*.sh' | sort) | |
| echo "checking ${#SCRIPTS[@]} scripts" | |
| shellcheck --severity=warning "${SCRIPTS[@]}" | |
| # shellcheck cannot tell a shell function from a command on PATH, so a script that calls a | |
| # scripts/lib function without sourcing the library reads as correct everywhere and dies at | |
| # run time with exit 127. That is how v0.67.1's DMG job failed: create-dmg.sh called | |
| # notarize_and_staple and never sourced lib/notarize.sh, and a release was the first thing | |
| # to run it. | |
| - name: Check every script sources the libraries it calls into | |
| run: python3 scripts/ci/check-lib-sourcing.py | |
| - name: Check the plugin manifest against the plugin classes | |
| run: python3 scripts/ci/check-plugin-manifest.py | |
| # A build script that writes the deployment target down itself goes stale the next time the | |
| # app's floor moves, and the result is an archive built for a newer macOS than the app that | |
| # links it. That shipped: #2874 moved the app to macOS 13 and five copies of "14.0" stayed. | |
| - name: Check no build script hardcodes the deployment target | |
| run: bash scripts/ci/check-deployment-target.sh | |
| # The iOS app compiles source files that belong to macOS plugin targets, and it alone sets | |
| # SWIFT_DEFAULT_ACTOR_ISOLATION: MainActor, so one unmarked declaration in a shared file is | |
| # @MainActor on iOS and nonisolated in the plugin. The macOS suite cannot see it and the iOS | |
| # job reports it one symbol at a time, which took main red three times over one keyword. | |
| - name: Check shared plugin sources are explicitly isolated for iOS | |
| run: python3 scripts/ci/check-ios-shared-isolation.py | |
| # The plugin targets build with MemberImportVisibility, so a file that logs needs the import | |
| # that defines the log methods. Compiling every plugin catches it, but only once both sides | |
| # are merged: the pull request that turned the feature on and the pull request that added the | |
| # file were each green alone, and main broke on the merge of the second. | |
| - name: Check plugin sources that log import the module that defines it | |
| run: python3 scripts/ci/check-plugin-os-import.py | |
| # SwiftLint's included: does not reach Plugins/, so this applies its log privacy rule there, | |
| # with the regex read from .swiftlint.yml. | |
| - name: Check no source publishes an error's text to the system log | |
| run: python3 scripts/ci/check-log-privacy.py | |
| - name: Validate the log privacy check | |
| run: python3 scripts/ci/test_check_log_privacy.py | |
| # Every top-level @Suite is a peer macro the compiler files in module-scope lookup, and each | |
| # @Suite expansion walks all of them, so the test module's compile time grows with the square | |
| # of their count. 2,336 of them held TableProTests' emit-module job at 544 to 626 seconds on the | |
| # build runner, and 2,200 of those only renamed their type. Only a @Suite with a trait is kept. | |
| - name: Check no top-level test type carries a @Suite without a trait | |
| run: python3 scripts/ci/check-test-suite-attributes.py | |
| - name: Validate the test suite attribute check | |
| run: python3 scripts/ci/test_check_test_suite_attributes.py | |
| # A plugin's or package's String(localized:) resolves against Bundle.main, which is the host | |
| # app, so its strings live in the app's catalog. Xcode never finds them in the app's sources, | |
| # so each one must be there and managed manually, or Xcode's next sync deletes it. | |
| - name: Check plugin strings reach the string catalog | |
| run: python3 scripts/localization.py plugins | |
| - name: Validate the localization script | |
| run: python3 scripts/test_localization.py | |
| - name: Validate the registry update script | |
| run: python3 .github/scripts/test_update_registry.py | |
| # Only the kit version a release tags ever ships, and the registry keeps binaries for three, | |
| # so a second bump in one release cycle drops an older release out of that window for nothing. | |
| # The checkout is shallow and carries no tags, so the check lists them on origin itself. | |
| - name: Check currentPluginKitVersion moved at most once since the last release | |
| run: python3 scripts/ci/check-pluginkit-bump-cadence.py --remote origin | |
| - name: Validate the PluginKit bump cadence check | |
| run: python3 scripts/ci/test_check_pluginkit_bump_cadence.py | |
| # The macOS suite cannot catch a fault here: the lister runs before any test does. | |
| - name: Validate the test lister and quarantine | |
| run: python3 scripts/ci/test_list_tests.py | |
| - name: Validate the pull request title check | |
| run: python3 scripts/ci/test_check_pr_title.py | |
| - name: Validate release note extraction | |
| run: python3 scripts/ci/test_release_notes.py | |
| - name: Validate the DMG script's arguments | |
| run: bash scripts/ci/test_create_dmg_args.sh | |
| - name: Validate the release draft script | |
| run: bash scripts/ci/test_draft_release.sh | |
| - name: Validate changelog contributor credits | |
| run: python3 scripts/ci/test_changelog_credits.py | |
| # Only a real release runs sign-and-appcast.sh, so nothing on a pull request ever exercised | |
| # the code that edits the file every install polls. These two cover the parts that can be | |
| # tested without a signing key: which bytes move, and which releases are refused. | |
| - name: Validate the appcast text primitives | |
| run: python3 scripts/ci/test_appcast_feed.py | |
| - name: Validate the appcast merge | |
| run: python3 scripts/ci/test_merge_appcast.py | |
| - name: Validate the delta naming | |
| run: python3 scripts/ci/test_name_deltas.py | |
| - name: Validate release withdrawal | |
| run: python3 scripts/ci/test_pull_release.py | |
| # The framework the app links and the generate_appcast that signs its feed are the same | |
| # project, and nothing compared them. project.yml declares majorVersion, which XcodeGen | |
| # writes as upToNextMajorVersion, so a resolve can move the framework alone. | |
| - name: Check the Sparkle version agrees everywhere | |
| run: python3 scripts/ci/check-sparkle-version.py | |
| # A check that guarded a real invariant and ran nowhere. Pure grep over Swift sources, so it | |
| # belongs on the free Linux runner. The MongoDB filter-shape check is the other one that was | |
| # orphaned, but it compiles a C probe against Libs/libbson, so it lives in the macOS build | |
| # job in macos-tests.yml where the toolchain and the libraries already are. | |
| - name: Check the shared-contract drift gates | |
| run: scripts/audit-refactor-health.sh --check | |
| # The modularization moves features out of the main window's hubs. Each count may only go down. | |
| - name: Check no hub grew past its recorded size | |
| run: python3 scripts/ci/check-hub-ratchet.py | |
| - name: Validate the hub ratchet | |
| run: python3 scripts/ci/test_check_hub_ratchet.py | |
| # The one check to mark required on main. | |
| gate: | |
| name: Repo Hygiene Gate | |
| if: always() | |
| needs: [changes, lint] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Require the checks to have passed or been skipped | |
| env: | |
| RESULTS: ${{ join(needs.*.result, ' ') }} | |
| run: | | |
| set -euo pipefail | |
| echo "job results: $RESULTS" | |
| for result in $RESULTS; do | |
| case "$result" in | |
| success|skipped) ;; | |
| *) | |
| echo "A repo hygiene job reported '$result'." >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| done |