Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 67 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ permissions:
# No paths-ignore here: "Build Solution" and "Test Suite" below are
# required status checks on main's ruleset. A docs-only PR that this
# workflow never runs against would leave them permanently missing,
# blocking the PR from merging.
# blocking the PR from merging. The changes job skips the build and tests
# for such a PR instead; a skipped required check counts as passing.
pull_request:
types: [opened, synchronize, reopened, ready_for_review]

Expand All @@ -54,8 +55,62 @@ env:
DOTNET_CLI_TELEMETRY_OPTOUT: true

jobs:
# Whether the PR changes anything the build or tests depend on. A PR that
# changes only Markdown and files under docs/ (such as a release blog PR)
# skips the build, the test matrix and coverage. Everything else counts as
# code, including workflows and scripts, so an unexpected path runs the full
# suite. Pushes and manual runs always run it.
changes:
name: Detect Changes
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
code: ${{ steps.diff.outputs.code }}
steps:
- name: Checkout code
if: github.event_name == 'pull_request'
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
fetch-depth: 0

- name: Classify changed files
id: diff
env:
EVENT_NAME: ${{ github.event_name }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
if [ "$EVENT_NAME" != "pull_request" ]; then
echo "code=true" >> "$GITHUB_OUTPUT"
exit 0
fi

# HEAD is the PR merged into its base. Diffing against the base the
# event names can also pick up commits that reached main since,
# which only ever adds paths, so it errs toward running the suite.
# --no-renames lists a renamed file under both paths, so moving code
# into docs/ or to a .md name still counts as a code change.
mapfile -t files < <(git diff --name-only --no-renames "$BASE_SHA" HEAD)
code=false
for file in "${files[@]}"; do
case "$file" in
docs/*|*.md) ;;
*) code=true; echo "Code change: $file"; break ;;
esac
done
if [ "${#files[@]}" -eq 0 ]; then
code=true
fi

echo "code=$code" >> "$GITHUB_OUTPUT"
if [ "$code" = "false" ]; then
echo "::notice::Docs-only change (${#files[@]} files); the build, tests and coverage are skipped."
fi

build:
name: Build Solution
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15

Expand Down Expand Up @@ -464,6 +519,7 @@ jobs:
pull-requests: write
issues: read
needs:
- changes
- build
- discover-tests
- test
Expand Down Expand Up @@ -503,6 +559,8 @@ jobs:
# PR-controlled project paths: read from the environment, not
# interpolated into the script.
TEST_MATRIX: ${{ needs.discover-tests.outputs.matrix }}
CHANGES_STATUS: ${{ needs.changes.result }}
CODE_CHANGED: ${{ needs.changes.outputs.code }}
BUILD_STATUS: ${{ needs.build.result }}
DISCOVER_STATUS: ${{ needs.discover-tests.result }}
TEST_STATUS: ${{ needs.test.result }}
Expand All @@ -512,6 +570,9 @@ jobs:
echo "## Test Execution Summary"
echo ""
echo "### Job Status"
if [ "$CHANGES_STATUS" = "success" ] && [ "$CODE_CHANGED" = "false" ]; then
echo "- **Docs-only change:** the build, tests and coverage were skipped."
fi
echo "- **Build:** ${BUILD_STATUS}"
echo "- **Discovered Tests:** ${DISCOVER_STATUS}"
echo "- **Test Matrix:** ${TEST_STATUS}"
Expand All @@ -530,8 +591,11 @@ jobs:
# This job is the required status check that stands in for the
# dynamically named "Tests: …" matrix jobs and the 60% coverage
# gate, so it must fail when any of them does. A skipped test
# matrix (no test projects) still passes.
if [[ "$BUILD_STATUS" =~ ^(failure|cancelled)$ \
# matrix (no test projects, or a docs-only change) still passes, but
# only when the changes job itself succeeded: if it failed, the build
# was skipped for the wrong reason.
if [[ "$CHANGES_STATUS" != "success" \
|| "$BUILD_STATUS" =~ ^(failure|cancelled)$ \
|| "$DISCOVER_STATUS" =~ ^(failure|cancelled)$ \
|| "$TEST_STATUS" =~ ^(failure|cancelled)$ \
|| "$COVERAGE_STATUS" =~ ^(failure|cancelled)$ ]]; then
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,14 @@ name: "CodeQL"
- "**.csproj"
- ".github/workflows/**"

# Not a required check, and main's push and weekly runs still cover the
# code, so a docs-only PR (such as a release blog PR) skips the analysis.
pull_request:
branches:
- "**"
paths-ignore:
- "docs/**"
- "**/*.md"

workflow_dispatch:
inputs:
Expand Down
Loading