Skip to content

Commit 4068463

Browse files
authored
[CI] Add a top-level ci.yml which runs tests and build cli archives (#10810)
* [CI] Add a top level ci.yml which runs tests and build cli archives - Move job for building cli archives to a new file as a reusable workflow - Make `tests.yml` a reusable workflow - Add a top level `ci.yml` which uses the above two workflows * Move the repo owner == dotnet check to top level ci.yml - feedback from copilot * Add OS to the test job name * Add a new Final Results job on ci.yml * cleanup * Skip workflow if PR has only markdown changes - to do this add a new action `check-changed-files` - And update conditions in `ci.yml` to skip the workflow if only md file changes were detected - Doing it like this instead of using `paths-ignore` is better because `Final results` job can still run. And we have branch protection rules that depend on `Final results`. * remove debug code
1 parent 57cc0dc commit 4068463

File tree

5 files changed

+322
-91
lines changed

5 files changed

+322
-91
lines changed
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
name: 'Check Changed Files'
2+
description: |
3+
Check if all changed files in a PR match provided regex patterns.
4+
5+
This action compares changed files in a pull request against one or more regex patterns
6+
and determines if all changed files match at least one of the provided patterns.
7+
8+
Inputs:
9+
- patterns: List of regex patterns (multiline string) to match against changed file paths
10+
11+
Outputs:
12+
- only_changed: Boolean indicating if all changed files matched the patterns
13+
- no_changes: Boolean indicating if there were no changed files in the PR
14+
- changed_files: JSON array of all changed files in the PR
15+
- matched_files: JSON array of files that matched at least one pattern
16+
- unmatched_files: JSON array of files that didn't match any pattern
17+
inputs:
18+
patterns:
19+
description: 'List of regex patterns to match against changed files'
20+
required: true
21+
22+
outputs:
23+
only_changed:
24+
description: 'True if all changed files match the provided patterns, false otherwise'
25+
value: ${{ steps.check_files.outputs.only_changed }}
26+
no_changes:
27+
description: 'True if there were no changed files in the PR, false otherwise'
28+
value: ${{ steps.check_files.outputs.no_changes }}
29+
changed_files:
30+
description: 'List of changed files'
31+
value: ${{ steps.check_files.outputs.changed_files }}
32+
matched_files:
33+
description: 'List of changed files that matched the patterns'
34+
value: ${{ steps.check_files.outputs.matched_files }}
35+
unmatched_files:
36+
description: 'List of changed files that did not match any pattern'
37+
value: ${{ steps.check_files.outputs.unmatched_files }}
38+
39+
runs:
40+
using: "composite"
41+
steps:
42+
- name: Check changed files against patterns
43+
id: check_files
44+
shell: bash
45+
run: |
46+
set -ex
47+
48+
# Only support pull request events
49+
if [ "${{ github.event_name }}" != "pull_request" ]; then
50+
echo "Error: This action only supports pull_request events, got: ${{ github.event_name }}"
51+
exit 1
52+
fi
53+
54+
# Check if jq is available
55+
if ! command -v jq >/dev/null 2>&1; then
56+
echo "Error: jq is required but not installed"
57+
exit 1
58+
fi
59+
60+
# Read patterns from input (multiline string)
61+
PATTERNS_INPUT="${{ inputs.patterns }}"
62+
63+
# Validate patterns input
64+
if [ -z "$PATTERNS_INPUT" ]; then
65+
echo "Error: patterns input is required"
66+
exit 1
67+
fi
68+
69+
# Get list of changed files for pull requests
70+
BASE_REF="${{ github.event.pull_request.base.sha }}"
71+
HEAD_REF="${{ github.event.pull_request.head.sha }}"
72+
if ! CHANGED_FILES=$(git diff --name-only "$BASE_REF".."$HEAD_REF" 2>/dev/null); then
73+
echo "Error: Failed to get changed files. Base ref: $BASE_REF HEAD ref: $HEAD_REF"
74+
exit 1
75+
fi
76+
77+
echo "Changed files:"
78+
echo "$CHANGED_FILES"
79+
80+
# Convert patterns to array and filter out empty lines
81+
PATTERNS=()
82+
while IFS= read -r pattern; do
83+
# Remove leading/trailing whitespace
84+
pattern=$(echo "$pattern" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
85+
# Skip empty patterns
86+
if [ -n "$pattern" ]; then
87+
PATTERNS+=("$pattern")
88+
fi
89+
done <<< "$PATTERNS_INPUT"
90+
91+
# Check if we have any valid patterns
92+
if [ ${#PATTERNS[@]} -eq 0 ]; then
93+
echo "Error: No valid patterns provided"
94+
exit 1
95+
fi
96+
97+
# Initialize arrays
98+
MATCHED_FILES=()
99+
UNMATCHED_FILES=()
100+
101+
# Handle the case where there are no changed files
102+
if [ -z "$CHANGED_FILES" ]; then
103+
echo "No files changed in this PR"
104+
ONLY_CHANGED="true" # No files changed - treat as success
105+
NO_CHANGES="true"
106+
else
107+
# Check each changed file against patterns
108+
while IFS= read -r file; do
109+
if [ -z "$file" ]; then
110+
continue
111+
fi
112+
113+
MATCHED=false
114+
for pattern in "${PATTERNS[@]}"; do
115+
# Use safe regex matching with error handling
116+
if [[ "$file" =~ $pattern ]] 2>/dev/null; then
117+
MATCHED=true
118+
break
119+
fi
120+
done
121+
122+
if [ "$MATCHED" = true ]; then
123+
MATCHED_FILES+=("$file")
124+
else
125+
UNMATCHED_FILES+=("$file")
126+
fi
127+
done <<< "$CHANGED_FILES"
128+
129+
# Determine if only matched files changed
130+
if [ ${#UNMATCHED_FILES[@]} -eq 0 ]; then
131+
ONLY_CHANGED="true" # All changed files matched
132+
else
133+
ONLY_CHANGED="false" # Some files didn't match
134+
fi
135+
NO_CHANGES="false"
136+
fi
137+
138+
# Convert arrays to JSON for output (handle empty arrays safely)
139+
if [ -z "$CHANGED_FILES" ]; then
140+
CHANGED_FILES_JSON="[]"
141+
else
142+
CHANGED_FILES_JSON=$(printf '%s\n' "$CHANGED_FILES" | jq -R . | jq -s .)
143+
fi
144+
145+
if [ ${#MATCHED_FILES[@]} -eq 0 ]; then
146+
MATCHED_FILES_JSON="[]"
147+
else
148+
MATCHED_FILES_JSON=$(printf '%s\n' "${MATCHED_FILES[@]}" | jq -R . | jq -s .)
149+
fi
150+
151+
if [ ${#UNMATCHED_FILES[@]} -eq 0 ]; then
152+
UNMATCHED_FILES_JSON="[]"
153+
else
154+
UNMATCHED_FILES_JSON=$(printf '%s\n' "${UNMATCHED_FILES[@]}" | jq -R . | jq -s .)
155+
fi
156+
157+
# Set outputs with proper escaping
158+
{
159+
echo "only_changed=$ONLY_CHANGED"
160+
echo "no_changes=$NO_CHANGES"
161+
echo "changed_files<<EOF"
162+
echo "$CHANGED_FILES_JSON"
163+
echo "EOF"
164+
echo "matched_files<<EOF"
165+
echo "$MATCHED_FILES_JSON"
166+
echo "EOF"
167+
echo "unmatched_files<<EOF"
168+
echo "$UNMATCHED_FILES_JSON"
169+
echo "EOF"
170+
} >> $GITHUB_OUTPUT
171+
172+
echo "Only changed matching files: $ONLY_CHANGED"
173+
echo "Matched files: ${MATCHED_FILES[*]}"
174+
echo "Unmatched files: ${UNMATCHED_FILES[*]}"
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
name: Build native CLI archives
2+
3+
on:
4+
workflow_call:
5+
6+
jobs:
7+
8+
build_cli_archives:
9+
name: Build CLI (${{ matrix.targets.os }})
10+
runs-on: ${{ matrix.targets.os }}
11+
strategy:
12+
matrix:
13+
targets:
14+
- os: ubuntu-latest
15+
rids: linux-x64
16+
- os: windows-latest
17+
rids: win-x64
18+
- os: macos-latest
19+
rids: osx-arm64
20+
21+
steps:
22+
- name: Checkout code
23+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
24+
25+
- name: Build CLI packages (Windows)
26+
env:
27+
CI: false
28+
if: ${{ matrix.targets.os == 'windows-latest' }}
29+
shell: pwsh
30+
run: >
31+
.\build.cmd
32+
-ci
33+
-build
34+
-restore
35+
/bl:${{ github.workspace }}/artifacts/log/Debug/BuildCli.binlog
36+
/p:ContinuousIntegrationBuild=true
37+
/p:SkipManagedBuild=true
38+
/p:TargetRids=${{ matrix.targets.rids }}
39+
40+
- name: Build CLI packages (Unix)
41+
env:
42+
CI: false
43+
if: ${{ matrix.targets.os != 'windows-latest' }}
44+
shell: bash
45+
run: >
46+
./build.sh
47+
--ci
48+
--build
49+
--restore
50+
/bl:${{ github.workspace }}/artifacts/log/Debug/BuildCli.binlog
51+
/p:ContinuousIntegrationBuild=true
52+
/p:SkipManagedBuild=true
53+
/p:TargetRids=${{ matrix.targets.rids }}
54+
55+
- name: Upload logs
56+
if: always()
57+
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
58+
with:
59+
name: cli-native-logs-${{ matrix.targets.rids }}
60+
path: artifacts/log/**
61+
62+
- name: Upload CLI archives
63+
if: success()
64+
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
65+
with:
66+
name: cli-native-archives-${{ matrix.targets.rids }}
67+
path: artifacts/packages/**/aspire-cli*
68+
retention-days: 15
69+
if-no-files-found: error

.github/workflows/ci.yml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
- 'release/**'
8+
9+
push:
10+
branches:
11+
- main
12+
- 'release/**'
13+
14+
concurrency:
15+
group: ${{ github.workflow }}-${{ github.ref }}
16+
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
17+
18+
jobs:
19+
20+
check_changes:
21+
runs-on: ubuntu-latest
22+
name: Check Changed Files
23+
if: ${{ github.repository_owner == 'dotnet' }}
24+
outputs:
25+
skip_workflow: ${{ (steps.check_docs.outputs.no_changes == 'true' || steps.check_docs.outputs.only_changed == 'true') && 'true' || 'false' }}
26+
steps:
27+
- name: Checkout code
28+
if: ${{ github.event_name == 'pull_request' }}
29+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
30+
with:
31+
fetch-depth: 0
32+
33+
- name: Check if only documentation changed
34+
id: check_docs
35+
if: ${{ github.event_name == 'pull_request' }}
36+
uses: ./.github/actions/check-changed-files
37+
with:
38+
patterns: |
39+
\.md$
40+
41+
tests:
42+
uses: ./.github/workflows/tests.yml
43+
name: Tests
44+
needs: [check_changes]
45+
if: ${{ github.repository_owner == 'dotnet' && needs.check_changes.outputs.skip_workflow != 'true' }}
46+
47+
build_cli_archives:
48+
uses: ./.github/workflows/build-cli-native-archives.yml
49+
name: Build native CLI archives
50+
needs: [check_changes]
51+
if: ${{ github.repository_owner == 'dotnet' && needs.check_changes.outputs.skip_workflow != 'true' }}
52+
53+
# This job is used for branch protection. It fails if any of the dependent jobs failed
54+
results:
55+
if: ${{ always() && github.repository_owner == 'dotnet' }}
56+
runs-on: ubuntu-latest
57+
name: Final Results
58+
needs: [check_changes, tests, build_cli_archives]
59+
60+
steps:
61+
- name: Fail if any of the dependent jobs failed
62+
# Don't fail if the workflow is being skipped.
63+
#
64+
# For others 'skipped' can be when a transitive dependency fails and the dependent job gets
65+
# 'skipped'. For example, one of setup_* jobs failing and the Integration test jobs getting
66+
# 'skipped'
67+
if: >-
68+
${{ always() &&
69+
needs.check_changes.outputs.skip_workflow != 'true' &&
70+
(contains(needs.*.result, 'failure') ||
71+
contains(needs.*.result, 'cancelled') ||
72+
contains(needs.*.result, 'skipped')) }}
73+
run: |
74+
echo "One or more dependent jobs failed."
75+
exit 1

.github/workflows/run-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
test:
4747
runs-on: ${{ inputs.os }}
4848
timeout-minutes: 60
49-
name: ${{ inputs.testShortName }}
49+
name: ${{ inputs.testShortName }} (${{ inputs.os }})
5050
env:
5151
DOTNET_ROOT: ${{ github.workspace }}/.dotnet
5252
steps:

0 commit comments

Comments
 (0)