mirror of
https://github.com/Homebrew/BrewUI.git
synced 2026-10-02 00:24:33 +08:00
Add baseline-gated Periphery dead-code check to CI
Runs Periphery after the existing Xcode build in pr_build_test, reusing that build's index store (--index-store-path + --skip-build) so it adds no second build. Scans the Xcode project (.periphery.yml) so the Homebrew app counts as a consumer of the SwiftPM modules — reporting unused code across the whole program, including dead public API, which a package-only scan cannot. Gating is baseline-driven (--strict --baseline): a PR fails only on dead code not already in .periphery-baseline.json, so the existing tail is grandfathered and only newly introduced dead code blocks a merge. Implicit-usage classes are retained in config (SwiftUI previews, Codable properties, assign-only/Hashable key structs) to keep false positives near zero. The baseline can't be generated in the agent sandbox (needs a working xcodebuild app build), so the CI step self-seeds: when .periphery-baseline.json is absent it writes one and uploads it as the 'periphery-baseline' artifact without gating. Commit that artifact to activate the gate. Periphery pinned in Mintfile (3.7.4); usage documented in AGENTS.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
3f2d5fb25b
commit
f8c2b6c9a5
@@ -16,6 +16,9 @@ on:
|
||||
- "Package.resolved"
|
||||
- "Tools/BrewUILint/**"
|
||||
- "scripts/test"
|
||||
- "Mintfile"
|
||||
- ".periphery.yml"
|
||||
- ".periphery-baseline.json"
|
||||
- ".github/workflows/pr_build_test.yml"
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
@@ -31,6 +34,9 @@ on:
|
||||
- "Package.resolved"
|
||||
- "Tools/BrewUILint/**"
|
||||
- "scripts/test"
|
||||
- "Mintfile"
|
||||
- ".periphery.yml"
|
||||
- ".periphery-baseline.json"
|
||||
- ".github/workflows/pr_build_test.yml"
|
||||
|
||||
permissions:
|
||||
@@ -44,7 +50,9 @@ jobs:
|
||||
build-and-test:
|
||||
name: Build + unit tests
|
||||
runs-on: macos-26
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 25
|
||||
env:
|
||||
MINT_PATH: ${{ github.workspace }}/mint
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
@@ -58,6 +66,7 @@ jobs:
|
||||
xcodebuild test \
|
||||
-project Homebrew.xcodeproj \
|
||||
-scheme Brew-Unit \
|
||||
-derivedDataPath DerivedData \
|
||||
-destination "platform=macOS" \
|
||||
-skipPackagePluginValidation \
|
||||
-skipMacroValidation \
|
||||
@@ -97,6 +106,50 @@ jobs:
|
||||
set -o pipefail
|
||||
xcrun swift test --package-path Tools/BrewUILint | tee swift-test-brewuilint.log
|
||||
|
||||
- name: Cache Mint packages
|
||||
id: mint-cache
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ${{ github.workspace }}/mint
|
||||
key: ${{ runner.os }}-mint-${{ hashFiles('**/Mintfile') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-mint-
|
||||
|
||||
- name: Install Mintfile packages
|
||||
if: steps.mint-cache.outputs.cache-hit != 'true'
|
||||
run: mint bootstrap --mintfile Mintfile
|
||||
|
||||
- name: Periphery dead-code scan
|
||||
run: |
|
||||
set -o pipefail
|
||||
# Reuse the index store produced by the xcodebuild test step (no second build).
|
||||
index="DerivedData/Index.noindex/DataStore"
|
||||
if [ ! -d "$index" ]; then
|
||||
index="$(find DerivedData -type d -name DataStore -path '*Index*' | head -n 1)"
|
||||
fi
|
||||
echo "Using index store: $index"
|
||||
if [ -f .periphery-baseline.json ]; then
|
||||
mint run periphery scan \
|
||||
--index-store-path "$index" --skip-build \
|
||||
--baseline .periphery-baseline.json --strict \
|
||||
--relative-results --format github-actions
|
||||
else
|
||||
echo "::warning::No .periphery-baseline.json committed yet — writing a seed baseline (this run does NOT gate). Download the 'periphery-baseline' artifact from this run, commit it as .periphery-baseline.json, and subsequent PRs will fail only on newly introduced dead code."
|
||||
mint run periphery scan \
|
||||
--index-store-path "$index" --skip-build \
|
||||
--write-baseline .periphery-baseline.json \
|
||||
--relative-results
|
||||
fi
|
||||
|
||||
- name: Upload periphery seed baseline
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: periphery-baseline
|
||||
path: .periphery-baseline.json
|
||||
if-no-files-found: ignore
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload logs on failure
|
||||
if: failure() || cancelled()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# Periphery — dead-code (unused declaration) analysis.
|
||||
# Run in CI by .github/workflows/pr_build_test.yml; see AGENTS.md for the workflow.
|
||||
#
|
||||
# We scan the Xcode PROJECT (not the bare SwiftPM package) so the Homebrew app is included as a
|
||||
# consumer of the SwiftPM library modules. That means `retain_public` stays FALSE: public API that
|
||||
# not even the app uses is genuinely dead and should be reported. (Scanning the package alone would
|
||||
# force retain_public and blind us to dead public API, while also flagging the whole app-only UI tree
|
||||
# as a false positive.)
|
||||
project: Homebrew.xcodeproj
|
||||
schemes:
|
||||
- Brew-Unit # host-app unit-test scheme — builds the app + every SPM module + unit tests
|
||||
|
||||
# Retain the declaration classes that are used implicitly (Periphery can't see these as "reads"):
|
||||
retain_swift_ui_previews: true # `#Preview` bodies aren't indexed as usage
|
||||
retain_codable_properties: true # decoded/encoded stored properties on Codable types
|
||||
retain_assign_only_properties: true # e.g. Hashable/Equatable key structs read only via synthesis
|
||||
@@ -100,6 +100,15 @@ The pre-commit hook formats and lints **staged** Swift files (SwiftFormat/SwiftL
|
||||
|
||||
If a test failure surfaces a real regression that's out of scope for the current turn, surface it to the user rather than silently skipping it — never paper over a red test with `.disabled` or `--filter` exclusions without flagging.
|
||||
|
||||
### Dead-code analysis (Periphery)
|
||||
|
||||
`.github/workflows/pr_build_test.yml` runs [Periphery](https://github.com/peripheryapp/periphery) (pinned in `Mintfile`) after the Xcode build, reusing that build's index store (`--index-store-path DerivedData/Index.noindex/DataStore --skip-build`) so it adds no second build. It scans the **Xcode project** (config in `.periphery.yml`) so the `Homebrew/` app counts as a consumer of the SwiftPM modules — this reports unused code across the whole program, including dead `public` API, which a package-only scan cannot.
|
||||
|
||||
The check is **baseline-gated**: it fails a PR only on dead code **not** already recorded in `.periphery-baseline.json` (via `--strict --baseline`). This grandfathers the existing tail so only newly introduced dead code blocks a merge.
|
||||
|
||||
- **Seeding / regenerating the baseline:** it can't be generated in the agent sandbox (needs a working `xcodebuild` app build). If `.periphery-baseline.json` is absent, the CI step writes one and uploads it as the `periphery-baseline` artifact without gating — download it, commit it, and the gate activates. To refresh it intentionally (after a deliberate change to the unused set), regenerate on a machine/CI where the app builds: build `Brew-Unit` with `-derivedDataPath DerivedData`, then `mint run periphery scan --index-store-path DerivedData/Index.noindex/DataStore --skip-build --write-baseline .periphery-baseline.json`.
|
||||
- **Known-implicit usage is already retained** via `.periphery.yml` (`retain_swift_ui_previews`, `retain_codable_properties`, `retain_assign_only_properties`). For a genuine one-off that Periphery still can't see, annotate the declaration with `// periphery:ignore` (or `// periphery:ignore:all` for a type and its members) rather than widening the baseline.
|
||||
|
||||
---
|
||||
|
||||
## What Lives Where
|
||||
|
||||
Reference in New Issue
Block a user