forked from microsoft/aspire
-
Notifications
You must be signed in to change notification settings - Fork 0
250 lines (229 loc) · 12.1 KB
/
Copy pathci.yml
File metadata and controls
250 lines (229 loc) · 12.1 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
name: CI
on:
pull_request:
branches:
- main
- 'release/**'
push:
branches:
- main
- 'release/**'
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
prepare_for_ci:
runs-on: ubuntu-latest
name: Prepare for CI
if: ${{ github.repository_owner == 'microsoft' }}
outputs:
skip_workflow: ${{ (steps.check_for_changes.outputs.no_changes == 'true' || steps.check_for_changes.outputs.only_changed == 'true') && 'true' || 'false' }}
VERSION_SUFFIX_OVERRIDE: ${{ steps.compute_version_suffix.outputs.VERSION_SUFFIX_OVERRIDE }}
EXTENSION_VERSION_OVERRIDE: ${{ steps.compute_version_suffix.outputs.EXTENSION_VERSION_OVERRIDE }}
steps:
- name: Checkout code
if: ${{ github.event_name == 'pull_request' }}
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Check for any changes that require CI
id: check_for_changes
if: ${{ github.event_name == 'pull_request' }}
uses: ./.github/actions/check-changed-files
with:
patterns_file: eng/github-ci/ci-skip-entirely-patterns.txt
- id: compute_version_suffix
name: Compute version suffix for PRs
if: ${{ github.event_name == 'pull_request' }}
shell: pwsh
env:
# Use the pull request head SHA instead of GITHUB_SHA (which can be a merge commit)
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PR_NUMBER: ${{ github.event.number }}
run: |
Write-Host "Determining VERSION_SUFFIX_OVERRIDE (PR only step)..."
if ([string]::IsNullOrWhiteSpace($Env:PR_HEAD_SHA)) {
Write-Error "PR_HEAD_SHA not set; cannot compute version suffix."
exit 1
}
$SHORT_SHA = $Env:PR_HEAD_SHA.Substring(0,8)
$VERSION_SUFFIX = "/p:VersionSuffix=pr.$($Env:PR_NUMBER).g$SHORT_SHA /p:DotNetFinalVersionKind=prerelease"
Write-Host "Computed VERSION_SUFFIX_OVERRIDE=$VERSION_SUFFIX"
"VERSION_SUFFIX_OVERRIDE=$VERSION_SUFFIX" | Out-File -FilePath $Env:GITHUB_OUTPUT -Append -Encoding utf8
$EXTENSION_VERSION = "99.0.$($Env:PR_NUMBER)"
Write-Host "Computed EXTENSION_VERSION_OVERRIDE=$EXTENSION_VERSION"
"EXTENSION_VERSION_OVERRIDE=$EXTENSION_VERSION" | Out-File -FilePath $Env:GITHUB_OUTPUT -Append -Encoding utf8
tests:
uses: ./.github/workflows/tests.yml
name: Tests
needs: [prepare_for_ci]
# issues:write + pull-requests:write are used only by tests.yml's isolated comment_selection job
# (which runs no PR code) to post the "selected tests" issue comment; every job that runs PR code
# stays at contents:read. Reusable-workflow tokens are capped by the caller, so this is where the
# elevated scope must be granted.
permissions:
contents: read
issues: write
pull-requests: write
if: ${{ github.repository_owner == 'microsoft' && needs.prepare_for_ci.outputs.skip_workflow != 'true' }}
with:
versionOverrideArg: ${{ needs.prepare_for_ci.outputs.VERSION_SUFFIX_OVERRIDE }}
extensionVersionOverride: ${{ needs.prepare_for_ci.outputs.EXTENSION_VERSION_OVERRIDE }}
# Stabilization gate.
#
# PR CI runs unstable (DotNetFinalVersionKind=prerelease above) so that dogfood artifacts
# have unique versions and dynamic Helix versions line up cleanly. To compensate for the
# fact that we never normally exercise the "everything is stable" build shape on PRs, this
# job runs a parallel build with StabilizePackageVersion=true that:
#
# 1. Packs all shipping packages, which catches NU5104 (stable package depending on a
# preview package whose csproj sets SuppressFinalPackageVersion=true). This is the
# check that used to fire only on the official stabilization PR.
# 2. Runs the subset of Aspire.Cli.Tests classes that exercise version/channel/template
# version pinning logic against the stably-built CLI assemblies. These are the code
# paths most likely to behave differently when the CLI's own version label is stable.
# 3. Runs a small `aspire new aspire-empty` + `aspire restore` smoke script that
# exercises the user-visible CLI flow against the locally-built stable feed,
# which is the scenario that has historically broken on stabilization branches.
#
# The job is gated to PRs only. Push CI (including push to release branches with
# StabilizePackageVersion=true) doesn't need this check because that *is* the stabilized
# build shape already.
stabilization_check:
name: Stabilization Check
runs-on: 8-core-ubuntu-latest
needs: [prepare_for_ci]
if: ${{ github.repository_owner == 'microsoft' && needs.prepare_for_ci.outputs.skip_workflow != 'true' }}
steps:
# The actual stabilization work only makes sense for pull_request events. On push
# (including push to release branches with StabilizePackageVersion=true) the build is
# already running in the shape this job is meant to validate, so there's nothing to
# add. We still let the job *run* on push so the GitHub Actions result is 'success'
# rather than 'skipped' — otherwise the `results:` job's `contains(needs.*.result,
# 'skipped')` check would fail every push to main / release/**.
- name: Skip on push events
if: ${{ github.event_name != 'pull_request' }}
run: echo "Stabilization Check only runs on pull_request events. Skipping (job remains success)."
- name: Checkout code
if: ${{ github.event_name == 'pull_request' }}
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Build + pack with StabilizePackageVersion=true
if: ${{ github.event_name == 'pull_request' }}
# SkipNativeBuild=true keeps this job fast (~5min): pack-time NU5104 detection
# doesn't need NativeAOT CLI binaries, and the moderate smoke below invokes the
# CLI via `dotnet run --project src/Aspire.Cli/Aspire.Cli.csproj`.
run: |
./build.sh -restore -build -ci -pack \
-p:StabilizePackageVersion=true \
-p:SkipTestProjects=false \
-p:SkipPlaygroundProjects=true \
-p:SkipNativeBuild=true \
-p:InstallBrowsersForPlaywright=false
- name: Run version-sensitive Aspire.Cli.Tests classes under stabilization
if: ${{ github.event_name == 'pull_request' }}
# Classes that touch IsPrerelease / channel detection / template version pinning /
# update-notification logic. These are the code paths most likely to behave
# differently when the CLI's own assembly version label is stable.
# NOTE: we intentionally do NOT pass --no-build / --no-restore here. SDK 10's
# `dotnet test` driver needs to perform its own MSBuild evaluation pass to detect
# the Microsoft.Testing.Platform runner (which Aspire.Cli.Tests uses), and without
# restore that evaluation reports "All projects must use that test runner ... using
# VSTest test runner" and exits non-zero. The previous pack step's restore is keyed
# by a different global-property fingerprint and can't be reused. The cost of
# letting `dotnet test` re-restore here is small because nothing on disk actually
# changes — it's just an in-memory evaluation pass.
run: |
./dotnet.sh test --project tests/Aspire.Cli.Tests/Aspire.Cli.Tests.csproj \
--no-launch-profile \
-p:StabilizePackageVersion=true \
-- \
--filter-class "*.AssemblyMetadataChannelTests" \
--filter-class "*.IdentityChannelReaderTests" \
--filter-class "*.PackageChannelTests" \
--filter-class "*.PrebuiltAppHostServerChannelResolutionTests" \
--filter-class "*.DotNetBasedAppHostServerChannelResolutionTests" \
--filter-class "*.NewCommandChannelResolutionTests" \
--filter-class "*.ChannelReseedTests" \
--filter-class "*.VersionHelperTests" \
--filter-class "*.CliUpdateNotificationServiceTests" \
--filter-class "*.UpdateCommandTests" \
--filter-class "*.DotNetTemplateFactoryTests" \
--filter-not-trait "quarantined=true" \
--filter-not-trait "outerloop=true"
- name: aspire init + restore smoke against stable local feed
if: ${{ github.event_name == 'pull_request' }}
run: ./eng/scripts/stabilization-smoke-init-restore.sh
# Always upload logs, even if the build fails, to enable debugging
- name: Upload logs
if: ${{ always() && github.event_name == 'pull_request' }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: stabilization_check_logs
path: artifacts/log
retention-days: 5
# This job is used for branch protection. It fails if any of the dependent jobs failed
results:
if: ${{ always() && github.repository_owner == 'microsoft' }}
runs-on: ubuntu-latest
name: Final Results
needs: [prepare_for_ci, tests, stabilization_check]
steps:
- name: Fail if any of the dependent jobs failed
# Don't fail if the workflow is being skipped.
# Check skip_workflow on all declared dependencies. Workflows without
# skip_workflow outputs (e.g., tests, build_cli_archives) evaluate to
# empty string, so the check still works ('!= true' is true for empty).
#
# For others 'skipped' can be when a transitive dependency fails and the dependent job gets
# 'skipped'. For example, one of setup_* jobs failing and the Integration test jobs getting
# 'skipped'
if: >-
${{ always() &&
needs.prepare_for_ci.outputs.skip_workflow != 'true' &&
needs.tests.outputs.skip_workflow != 'true' &&
(contains(needs.*.result, 'failure') ||
contains(needs.*.result, 'cancelled') ||
contains(needs.*.result, 'skipped')) }}
run: |
echo "One or more dependent jobs failed."
exit 1
# Track red-main: file/update one deduplicated issue per branch when a push to a
# protected branch (main, release/**) is red, and close it when a later push to
# the same branch is green. One job handles both — it runs on every push
# (if: always()) and the script opens or closes based on the aggregate CI result
# passed in CI_RED / CI_GREEN. Runs only on push; PR failures live in the PR's own
# checks and are owned by the author.
#
# CI_RED / CI_GREEN are derived from explicit needs.*.result checks rather than
# failure()/success() because tests/stabilization_check are *skipped* on the
# no-relevant-changes path (skip_workflow): a skipped run is neither red nor green,
# so the script leaves any open issue untouched (no CI signal). Requiring all
# three to have *succeeded* for CI_GREEN means a docs-only push never closes a
# still-red issue without actually re-running CI.
ci_failure_tracker:
name: Track CI Failure
needs: [prepare_for_ci, tests, stabilization_check]
if: ${{ always() && github.event_name == 'push' && github.repository_owner == 'microsoft' }}
runs-on: ubuntu-latest
permissions:
contents: read # checkout to require the local .js modules
issues: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: File or close the red-main issue
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
REF: ${{ github.ref_name }}
CI_RED: ${{ contains(needs.*.result, 'failure') }}
CI_GREEN: >-
${{ needs.prepare_for_ci.result == 'success' &&
needs.tests.result == 'success' &&
needs.stabilization_check.result == 'success' }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
await require('./.github/workflows/report-ci-failure.js').track({ github, context, core });