-
Notifications
You must be signed in to change notification settings - Fork 0
327 lines (290 loc) · 13.3 KB
/
Copy pathci.yml
File metadata and controls
327 lines (290 loc) · 13.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
name: KeyPath CI
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches: [ main, master ]
paths-ignore:
- 'docs/**'
- '*.md'
- '.github/workflows/claude.yml'
- '.github/workflows/claude-code-review.yml'
- 'Sources/KeyPathAppKit/Resources/*.md'
- 'Sources/KeyPathAppKit/Resources/*.css'
- 'Sources/KeyPathAppKit/Resources/*.png'
concurrency:
group: ci-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build-and-test:
runs-on: [self-hosted, macOS, keypath]
timeout-minutes: 10
steps:
- name: Clean stale git credentials
run: |
git config --global --unset-all http.https://github.com/.extraheader 2>/dev/null || true
git config --global --unset-all url.https://github.com/.insteadof 2>/dev/null || true
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: true
persist-credentials: false
- name: Add Homebrew and Cargo to PATH
run: |
echo "/opt/homebrew/bin" >> $GITHUB_PATH
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Compute kanata cache key
id: kanata-cache-key
run: |
KANATA_SHA=$(git rev-parse HEAD:External/kanata)
CARGO_LOCK_HASH=$(shasum -a 256 External/kanata/Cargo.lock | awk '{print $1}')
RUSTC_HASH=$(rustc --version | shasum -a 256 | awk '{print $1}')
echo "key=kanata-${{ runner.os }}-${{ runner.arch }}-${KANATA_SHA}-${CARGO_LOCK_HASH}-${RUSTC_HASH}" >> "$GITHUB_OUTPUT"
echo "Kanata submodule: $KANATA_SHA"
- name: Restore kanata binary cache
id: kanata-cache
uses: actions/cache@v4
with:
path: build/ci-kanata-cache
key: ${{ steps.kanata-cache-key.outputs.key }}
- name: Build and install kanata fork
run: |
mkdir -p build/ci-kanata-cache
# On the self-hosted runner the workspace persists between runs, so
# binaries from a previous run can survive even when actions/cache
# missed. Gate the "hit" on a stamp matching the checked-out submodule
# SHA — otherwise a stale-pin run poisons every later run with an old
# engine (stale kanata-simulator broke RemapEndToEndTests; see #891).
EXPECTED_ENGINE_SHA=$(git rev-parse HEAD:External/kanata)
CACHED_ENGINE_SHA=$(cat build/ci-kanata-cache/engine-sha 2>/dev/null || echo "none")
if [[ -x build/ci-kanata-cache/kanata && -x build/ci-kanata-cache/kanata-simulator && "$CACHED_ENGINE_SHA" == "$EXPECTED_ENGINE_SHA" ]]; then
echo "✅ Restored kanata artifacts from cache (engine $CACHED_ENGINE_SHA)"
echo "KANATA_CACHE_STATUS=hit" >> "$GITHUB_ENV"
else
echo "🔨 Building kanata artifacts (cache miss: cached=$CACHED_ENGINE_SHA expected=$EXPECTED_ENGINE_SHA)"
cd External/kanata
cargo build --release --target aarch64-apple-darwin 2>&1 | tail -20
cargo build --release --target aarch64-apple-darwin -p kanata-sim 2>&1 | tail -20
cd ../..
cp External/kanata/target/aarch64-apple-darwin/release/kanata build/ci-kanata-cache/kanata
cp External/kanata/target/aarch64-apple-darwin/release/kanata_simulated_input build/ci-kanata-cache/kanata-simulator
chmod 755 build/ci-kanata-cache/kanata build/ci-kanata-cache/kanata-simulator
echo "$EXPECTED_ENGINE_SHA" > build/ci-kanata-cache/engine-sha
echo "KANATA_CACHE_STATUS=miss" >> "$GITHUB_ENV"
fi
cp build/ci-kanata-cache/kanata /opt/homebrew/bin/kanata
cp build/ci-kanata-cache/kanata-simulator /opt/homebrew/bin/kanata-simulator
kanata --version
kanata-simulator --version
- name: Lint WizardAutoFixer for forbidden subprocess usage
run: |
chmod +x ./Scripts/lint-no-subprocess-in-autofixer.sh
./Scripts/lint-no-subprocess-in-autofixer.sh
- name: Run Test Lane - full
env:
KP_SIGN_DRY_RUN: "1"
KEYPATH_BUNDLED_SIMULATOR_OVERRIDE: /opt/homebrew/bin/kanata-simulator
# SwiftPM can wrap XCTest post-run SIGABRT as exit 1 on the self-hosted runner;
# run-tests-safe.sh still fails if it parses any test failures and emits a warning when this path is used.
KEYPATH_ALLOW_TEST_RUNNER_CRASH_SUCCESS: "1"
KEYPATH_TEST_ENFORCE_CLEAN_SUMMARY: "1"
KEYPATH_TEST_RESET_MODULE_CACHE: "0"
run: |
echo "Running full named test lane..."
export CI_ENVIRONMENT=true
export SKIP_EVENT_TAP_TESTS=1
export TIMEOUT_SECONDS=300
chmod +x ./Scripts/test-lane.sh
chmod +x ./Scripts/run-tests-safe.sh
if ./Scripts/test-lane.sh full; then
echo "TEST_STATUS=passed" >> $GITHUB_ENV
else
echo "TEST_STATUS=failed" >> $GITHUB_ENV
exit 1
fi
- name: Parse Installer Reliability Matrix from Test Log
if: always()
run: |
echo "Parsing installer reliability matrix from test output..."
chmod +x ./Scripts/parse-installer-matrix.sh
./Scripts/parse-installer-matrix.sh test_output.safe.txt
- name: Verify SMAppService plist wrapper
run: |
echo "Verifying com.keypath.kanata.plist points at kanata-launcher"
./Scripts/verify-kanata-plist.sh Sources/KeyPathApp/com.keypath.kanata.plist
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: |
test_output.safe.txt
test-results/installer-reliability/
if-no-files-found: ignore
retention-days: 7
- name: Generate Test Summary
if: always()
run: |
echo "## Test Results" >> $GITHUB_STEP_SUMMARY
if [ "${TEST_STATUS:-unknown}" = "failed" ]; then
echo "**Tests Failed** - Check the test-results artifact for details." >> $GITHUB_STEP_SUMMARY
elif [ "${TEST_STATUS:-unknown}" = "passed" ]; then
echo "**All Tests Passed**" >> $GITHUB_STEP_SUMMARY
else
echo "**Test Status Unknown** (${TEST_STATUS:-unknown})" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Fast Smoke Lane" >> $GITHUB_STEP_SUMMARY
if [ "${SMOKE_STATUS:-unknown}" = "failed" ]; then
echo "- \`smoke\`: failed" >> $GITHUB_STEP_SUMMARY
elif [ "${SMOKE_STATUS:-unknown}" = "passed" ]; then
echo "- \`smoke\`: passed" >> $GITHUB_STEP_SUMMARY
else
echo "- \`smoke\`: status unknown" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Installer Reliability Matrix" >> $GITHUB_STEP_SUMMARY
if [ -f "test-results/installer-reliability/latest/matrix-summary.md" ]; then
cat test-results/installer-reliability/latest/matrix-summary.md >> $GITHUB_STEP_SUMMARY
else
echo "- Installer matrix summary not found." >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Kanata Artifacts" >> $GITHUB_STEP_SUMMARY
echo "- Cache status: \`${KANATA_CACHE_STATUS:-unknown}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Coverage" >> $GITHUB_STEP_SUMMARY
echo "- Narrow coverage enforcement moved to the scheduled/manual \`KeyPath Coverage\` workflow." >> $GITHUB_STEP_SUMMARY
code-quality:
runs-on: [self-hosted, macOS, keypath]
timeout-minutes: 5
steps:
- name: Clean stale git credentials
run: |
git config --global --unset-all http.https://github.com/.extraheader 2>/dev/null || true
git config --global --unset-all url.https://github.com/.insteadof 2>/dev/null || true
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- name: Add Homebrew to PATH
run: echo "/opt/homebrew/bin" >> $GITHUB_PATH
- name: Get changed Swift files
id: changed
run: |
BASE=${{ github.event.pull_request.base.sha }}
CHANGED=$(git diff --name-only --diff-filter=d "$BASE"...HEAD -- '*.swift' || true)
if [ -z "$CHANGED" ]; then
echo "swift_files=" >> $GITHUB_OUTPUT
echo "No Swift files changed"
else
echo "swift_files<<EOF" >> $GITHUB_OUTPUT
echo "$CHANGED" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "Changed Swift files:"
echo "$CHANGED"
fi
- name: SwiftLint Analysis
if: steps.changed.outputs.swift_files != ''
run: |
echo "Linting changed Swift files only..."
echo "${{ steps.changed.outputs.swift_files }}" | xargs swiftlint --reporter github-actions-logging
echo "SWIFTLINT_STATUS=passed" >> $GITHUB_ENV
- name: Verify SwiftFormat version matches pin
if: steps.changed.outputs.swift_files != ''
run: |
PINNED=$(sed -nE 's/^[[:space:]]*swiftformat[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/p' mise.toml)
ACTUAL=$(swiftformat --version | tr -d '[:space:]')
echo "SwiftFormat — pinned (mise.toml): '$PINNED', installed: '$ACTUAL'"
if [ -z "$PINNED" ]; then
echo "::error::Could not read the swiftformat pin from mise.toml"
exit 1
fi
if [ "$PINNED" != "$ACTUAL" ]; then
echo "::error::SwiftFormat version drift — runner has '$ACTUAL' but mise.toml pins '$PINNED'."
echo "::error::master is a formatted fixed-point for the pinned version; a different version produces spurious lint results. Install the pinned version on the runner (mise install / brew) or update mise.toml + reformat."
exit 1
fi
- name: SwiftFormat Check
if: steps.changed.outputs.swift_files != ''
run: |
echo "Checking formatting of changed Swift files (must match the pinned fixed-point)..."
echo "${{ steps.changed.outputs.swift_files }}" | xargs swiftformat --lint || {
echo "::error::SwiftFormat found formatting issues. Run 'swiftformat Sources Tests' (at the pinned version) and commit."
exit 1
}
- name: Wizard Sleep Lint
run: |
chmod +x ./Scripts/lint-no-sleep.sh
./Scripts/lint-no-sleep.sh
- name: Accessibility Check
run: |
python3 Scripts/check-accessibility.py
- name: Critical Pattern Analysis
run: |
if grep -r 'TODO.*CRITICAL\|FIXME.*URGENT' Sources/ --include='*.swift'; then
echo "::error::CRITICAL TODO/FIXME markers found. Resolve them or downgrade the marker before merging."
echo "CRITICAL_ISSUES=found" >> $GITHUB_ENV
exit 1
else
echo "No critical TODOs/FIXMEs found"
echo "CRITICAL_ISSUES=none" >> $GITHUB_ENV
fi
- name: Code Smell Detection
run: |
FORCE_UNWRAPS=0
PRINT_STATEMENTS=0
python3 - <<'PY' > /tmp/keypath-force-unwraps.txt
from pathlib import Path
import re
pattern = re.compile(r"(?:\btry\s*!|(?:[\w\)\]\}\"'`])\s*!(?=\s*(?:[\)\]\}\.,;:]|$)))")
for path in Path("Sources/KeyPathAppKit/Managers").rglob("*.swift"):
try:
lines = path.read_text(encoding="utf-8").splitlines()
except UnicodeDecodeError:
continue
for idx, line in enumerate(lines, start=1):
stripped = line.strip()
if stripped.startswith("//") or "// swiftlint:disable" in line:
continue
if pattern.search(line):
print(f"{path}:{idx}: {stripped}")
PY
if [ -s /tmp/keypath-force-unwraps.txt ]; then
echo "::warning::Force unwraps found in manager files"
head -10 /tmp/keypath-force-unwraps.txt
FORCE_UNWRAPS=1
fi
if grep -r "print(" Sources/ --include="*.swift" | grep -v "// debug" >/dev/null 2>&1; then
echo "::warning::print() statements found (consider using Logger)"
grep -r "print(" Sources/ --include="*.swift" | grep -v "// debug" | head -10
PRINT_STATEMENTS=1
fi
echo "FORCE_UNWRAPS=$FORCE_UNWRAPS" >> $GITHUB_ENV
echo "PRINT_STATEMENTS=$PRINT_STATEMENTS" >> $GITHUB_ENV
- name: Generate Code Quality Summary
if: always()
run: |
echo "## Code Quality Analysis" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${SWIFTLINT_STATUS:-unknown}" = "passed" ]; then
echo "- SwiftLint: **No violations**" >> $GITHUB_STEP_SUMMARY
elif [ "${SWIFTLINT_STATUS:-unknown}" = "issues_found" ]; then
echo "- SwiftLint: **Issues detected** - See job logs" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${CRITICAL_ISSUES:-unknown}" = "found" ]; then
echo "- **CRITICAL issues found** - Review urgently" >> $GITHUB_STEP_SUMMARY
fi
SMELLS_FOUND=0
if [ "${FORCE_UNWRAPS:-0}" = "1" ]; then
echo "- Force unwraps found in manager files" >> $GITHUB_STEP_SUMMARY
SMELLS_FOUND=1
fi
if [ "${PRINT_STATEMENTS:-0}" = "1" ]; then
echo "- print() statements found (consider using Logger)" >> $GITHUB_STEP_SUMMARY
SMELLS_FOUND=1
fi
if [ "$SMELLS_FOUND" = "0" ] && [ "${CRITICAL_ISSUES:-unknown}" = "none" ]; then
echo "- No code smells detected" >> $GITHUB_STEP_SUMMARY
fi