Skip to content

Commit 7d236a1

Browse files
authored
Merge pull request #8 from nutthead/fix/workflows
ci: Improve and fix various issues in workflows
2 parents c6b8267 + ec267f1 commit 7d236a1

21 files changed

+8587
-145
lines changed

.github/scripts/coverage-comment/package-lock.json

Lines changed: 60 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "coverage-comment",
3+
"version": "1.0.0",
4+
"description": "Dependencies for CI coverage PR comment script",
5+
"private": true,
6+
"dependencies": {
7+
"fast-xml-parser": "5.2.5",
8+
"dedent": "1.7.0"
9+
}
10+
}

.github/workflows/ci.yml

Lines changed: 86 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,17 @@ env:
4646
CARGO_TERM_COLOR: always # Enable colored output in CI logs
4747
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse # Use sparse registry protocol
4848
RUST_BACKTRACE: short # Provide backtraces without excessive noise
49-
RUSTFLAGS: "-D warnings" # Treat warnings as errors
5049

5150
permissions:
5251
contents: read
53-
pull-requests: write
5452
actions: read
55-
checks: write
5653

5754
jobs:
5855
# Fast-fail checks run before the expensive test matrix
5956
quick-check:
6057
name: Quick Checks
6158
runs-on: ubuntu-latest
59+
timeout-minutes: 15
6260
steps:
6361
- name: Checkout repository
6462
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
@@ -89,11 +87,26 @@ jobs:
8987
env:
9088
RUSTDOCFLAGS: "-D warnings"
9189

90+
- name: Validate changelog format
91+
run: |
92+
if [ ! -f "CHANGELOG.md" ]; then
93+
echo "⚠️ CHANGELOG.md not found (will be created by release-plz)"
94+
exit 0
95+
fi
96+
97+
if ! grep -q "^# Changelog" CHANGELOG.md; then
98+
echo "❌ Invalid changelog format - missing '# Changelog' header"
99+
exit 1
100+
fi
101+
102+
echo "✅ Changelog format is valid"
103+
92104
# Security audit runs in parallel with quick-check
93105
# Continues on error since advisory warnings shouldn't block PRs
94106
security:
95107
name: Security Audit
96108
runs-on: ubuntu-latest
109+
timeout-minutes: 20
97110
continue-on-error: true # Advisory warnings are informational only
98111
steps:
99112
- name: Checkout repository
@@ -130,12 +143,13 @@ jobs:
130143
with:
131144
name: security-audit
132145
path: audit.json
133-
retention-days: 30
146+
retention-days: 90 # Long-term retention for compliance/security artifacts
134147

135148
# Unit tests run across multiple platforms
136149
unit-tests:
137150
name: Unit Tests ${{ matrix.name }}
138151
needs: [quick-check]
152+
timeout-minutes: 30
139153
strategy:
140154
fail-fast: false
141155
matrix:
@@ -187,14 +201,18 @@ jobs:
187201
sudo apt-get install -y musl-tools
188202
189203
- name: Run unit tests
190-
run: cargo test --locked --target ${{ matrix.target }} ${{ matrix.test_args }} -- --test-threads=1
204+
run: cargo test --locked --target ${{ matrix.target }} ${{ matrix.test_args }}
191205

192206
# Generates code coverage report using tarpaulin
193207
# Uploads to Codecov and posts PR comment with coverage summary
194208
coverage:
195209
name: Code Coverage
196210
needs: [unit-tests]
197211
runs-on: ubuntu-latest
212+
timeout-minutes: 45
213+
permissions:
214+
contents: read
215+
pull-requests: write # Required to post coverage comments on PRs
198216
steps:
199217
- name: Checkout repository
200218
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
@@ -214,6 +232,7 @@ jobs:
214232
uses: cargo-bins/cargo-binstall@38e8f5e4c386b611d51e8aa997b9a06a3c8eb67a # v1.15.6
215233

216234
- name: Install and run tarpaulin
235+
id: tarpaulin
217236
run: |
218237
cargo binstall --force --no-confirm --locked cargo-tarpaulin || cargo install --locked cargo-tarpaulin
219238
@@ -222,14 +241,25 @@ jobs:
222241
cargo install --locked cargo-tarpaulin
223242
fi
224243
225-
# Run tarpaulin and capture exit code
226-
# Use --no-fail-fast to ensure XML is generated even if coverage is low
227-
cargo tarpaulin --timeout 120 --avoid-cfg-tarpaulin || {
228-
EXIT_CODE=$?
229-
echo "⚠️ Tarpaulin exited with code $EXIT_CODE (possibly due to coverage threshold)"
230-
echo "Continuing to upload coverage data..."
231-
exit 0 # Don't fail the step
232-
}
244+
# Run tarpaulin - differentiate between test failures and coverage threshold
245+
set +e # Don't exit script on error
246+
cargo tarpaulin --timeout 120 --avoid-cfg-tarpaulin
247+
EXIT_CODE=$?
248+
set -e
249+
250+
# Store exit code for later evaluation
251+
echo "exit_code=$EXIT_CODE" >> "$GITHUB_OUTPUT"
252+
253+
if [ $EXIT_CODE -eq 0 ]; then
254+
echo "✅ Coverage passed (above threshold)"
255+
elif [ $EXIT_CODE -eq 2 ]; then
256+
echo "⚠️ Coverage below threshold - will fail after uploading report"
257+
echo "Uploading report for analysis..."
258+
else
259+
echo "❌ Tarpaulin failed (exit code: $EXIT_CODE)"
260+
echo "This indicates test failures or compilation errors"
261+
exit $EXIT_CODE # Fail immediately on real errors
262+
fi
233263
234264
- name: Upload coverage to Codecov
235265
if: always()
@@ -240,18 +270,35 @@ jobs:
240270
name: codecov-umbrella
241271
fail_ci_if_error: false
242272

273+
- name: Determine if PR comment should be posted
274+
id: check-pr
275+
if: always()
276+
run: |
277+
SHOULD_COMMENT="false"
278+
279+
# Check all required conditions
280+
if [[ "${{ github.event_name }}" == "pull_request" ]] && \
281+
[[ "${{ github.event.pull_request.head.repo.fork }}" == "false" ]] && \
282+
[[ -f "target/tarpaulin/cobertura.xml" ]]; then
283+
SHOULD_COMMENT="true"
284+
fi
285+
286+
echo "should_comment=${SHOULD_COMMENT}" >> "$GITHUB_OUTPUT"
287+
echo "PR comment needed: ${SHOULD_COMMENT}"
288+
243289
- name: Install coverage parser
244-
if: always() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false && hashFiles('target/tarpaulin/cobertura.xml') != ''
245-
run: npm install fast-xml-parser@5.2.5 dedent@1.7.0 --no-save
290+
if: always() && steps.check-pr.outputs.should_comment == 'true'
291+
working-directory: .github/scripts/coverage-comment
292+
run: npm ci
246293

247294
- name: Comment coverage on PR
248-
if: always() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false && hashFiles('target/tarpaulin/cobertura.xml') != ''
295+
if: always() && steps.check-pr.outputs.should_comment == 'true'
249296
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
250297
with:
251298
script: |
252299
const fs = require('fs');
253-
const { XMLParser } = require('fast-xml-parser');
254-
const dedent = require('dedent');
300+
const { XMLParser } = require('.github/scripts/coverage-comment/node_modules/fast-xml-parser');
301+
const dedent = require('.github/scripts/coverage-comment/node_modules/dedent');
255302
256303
const parser = new XMLParser({
257304
ignoreAttributes: false,
@@ -342,23 +389,33 @@ jobs:
342389
body
343390
});
344391
392+
- name: Enforce coverage threshold
393+
if: always() && steps.tarpaulin.outputs.exit_code == '2'
394+
run: |
395+
echo "❌ Coverage is below the required threshold (70%)"
396+
echo "Review the coverage report uploaded to Codecov for details"
397+
exit 1
398+
345399
# Final status check required by branch protection
346400
ci-success:
347401
name: CI Success
348-
if: always()
402+
# Use GitHub's native conditional syntax for clearer intent
403+
# Job runs only if all critical jobs pass and security doesn't fail
404+
if: |
405+
always() &&
406+
needs.quick-check.result == 'success' &&
407+
needs.unit-tests.result == 'success' &&
408+
needs.coverage.result == 'success' &&
409+
(needs.security.result == 'success' || needs.security.result == 'skipped')
349410
needs: [quick-check, security, unit-tests, coverage]
350411
runs-on: ubuntu-latest
412+
timeout-minutes: 5
413+
permissions:
414+
contents: read
415+
checks: write # Needed for merge queue status checks
351416
steps:
352-
- name: Check status
353-
run: |
354-
# Check all job results
355-
if [[ "${{ needs.quick-check.result }}" != "success" ]] ||
356-
[[ "${{ needs.unit-tests.result }}" != "success" ]] ||
357-
[[ "${{ needs.coverage.result }}" != "success" ]]; then
358-
echo "❌ CI failed"
359-
exit 1
360-
fi
361-
echo "✅ All CI checks passed"
417+
- name: Report success
418+
run: echo "✅ All CI checks passed"
362419

363420
- name: Set merge queue status
364421
if: github.event_name == 'merge_group'

.github/workflows/publish-crate.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ jobs:
3434
publish:
3535
name: Publish to crates.io
3636
runs-on: ubuntu-latest
37+
timeout-minutes: 25
3738
steps:
3839
- name: Normalize version
3940
id: version
@@ -52,6 +53,15 @@ jobs:
5253
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
5354
echo "Publishing version: ${VERSION}"
5455
56+
- name: Install dependencies
57+
run: |
58+
# Ensure jq is installed (used for JSON parsing)
59+
if ! command -v jq >/dev/null 2>&1; then
60+
echo "Installing jq..."
61+
sudo apt-get update && sudo apt-get install -y jq
62+
fi
63+
jq --version
64+
5565
- name: Check if release exists
5666
run: |
5767
VERSION="${{ steps.version.outputs.version }}"
@@ -105,7 +115,8 @@ jobs:
105115
- name: Verify Cargo.toml version matches
106116
run: |
107117
VERSION="${{ steps.version.outputs.version }}"
108-
CARGO_VERSION=$(grep '^version' Cargo.toml | head -1 | cut -d'"' -f2)
118+
# Use cargo metadata for robust version parsing
119+
CARGO_VERSION=$(cargo metadata --format-version 1 --no-deps | jq -r '.packages[0].version')
109120
110121
if [ "$VERSION" != "$CARGO_VERSION" ]; then
111122
echo "❌ Version mismatch!"

.github/workflows/release-plz.yml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ jobs:
3030
release-tag:
3131
name: Create Release Tag
3232
runs-on: ubuntu-latest
33+
timeout-minutes: 15
3334
# Prevent infinite loops from github-actions bot or [skip ci]
3435
if: |
3536
github.event.pusher.name != 'github-actions[bot]' &&
@@ -46,9 +47,18 @@ jobs:
4647
- name: Check for version change
4748
id: check
4849
run: |
50+
# Check if HEAD~1 exists (handles first commit case)
51+
if ! git rev-parse HEAD~1 >/dev/null 2>&1; then
52+
echo "First commit detected - no previous version to compare"
53+
echo "should_release=false" >> "$GITHUB_OUTPUT"
54+
exit 0
55+
fi
56+
4957
if git diff HEAD~1 HEAD --name-only | grep -q "^Cargo.toml$"; then
58+
# Previous version: use grep on git show output (historical file)
5059
PREV_VERSION=$(git show HEAD~1:Cargo.toml | grep '^version' | head -1 | cut -d'"' -f2)
51-
CURR_VERSION=$(grep '^version' Cargo.toml | head -1 | cut -d'"' -f2)
60+
# Current version: use cargo metadata for robust parsing
61+
CURR_VERSION=$(cargo metadata --format-version 1 --no-deps | jq -r '.packages[0].version')
5262
5363
if [[ "$PREV_VERSION" != "$CURR_VERSION" ]]; then
5464
echo "Version changed from $PREV_VERSION to $CURR_VERSION"
@@ -84,6 +94,5 @@ jobs:
8494
uses: MarcoIeni/release-plz-action@acb9246af4d59a270d1d4058a8b9af8c3f3a2559 # v0.5.117
8595
env:
8696
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
87-
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
8897
with:
8998
command: release

.github/workflows/release-pr.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ on:
1717
- 'Cargo.toml'
1818
- 'Cargo.lock'
1919
- '.github/workflows/release-pr.yml'
20-
- '.tarpaulin.toml'
2120

2221
permissions:
2322
contents: write
@@ -32,6 +31,7 @@ jobs:
3231
create-release-pr:
3332
name: Create Release PR
3433
runs-on: ubuntu-latest
34+
timeout-minutes: 20
3535
# Skip if commit message contains [skip ci] to prevent unnecessary runs
3636
if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }}
3737
steps:

0 commit comments

Comments
 (0)