-
Notifications
You must be signed in to change notification settings - Fork 466
357 lines (317 loc) · 13.9 KB
/
build.yaml
File metadata and controls
357 lines (317 loc) · 13.9 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
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
name: VFS for Git
run-name: ${{ inputs.run_name || 'VFS for Git' }}
on:
pull_request:
branches: [ master, releases/shipped ]
push:
branches: [ master, releases/shipped ]
workflow_dispatch:
inputs:
git_version:
description: 'Microsoft Git version tag to include in the build (leave empty for default)'
required: false
type: string
run_name:
description: 'Optional display name for this run (used for cross-repo automation)'
required: false
type: string
permissions:
contents: read
actions: read
checks: read
env:
GIT_VERSION: ${{ github.event.inputs.git_version || 'v2.53.0.vfs.0.7' }}
jobs:
validate:
runs-on: windows-2025
name: Validation
outputs:
skip: ${{ steps.check.outputs.result }}
steps:
- name: Look for prior successful runs
id: check
if: github.event.inputs.git_version == ''
uses: actions/github-script@v9
with:
github-token: ${{secrets.GITHUB_TOKEN}}
result-encoding: string
script: |
/*
* It would be nice if GitHub Actions offered a convenient way to avoid running
* successful workflow runs _again_ for the respective commit (or for a tree-same one):
* We would expect the same outcome in those cases, right?
*
* Let's check for such a scenario: Look for previous runs that have been successful
* and that correspond to the same commit, or at least a tree-same one. If there is
* one, skip running the build and tests _again_.
*
* There are challenges, though: We need to require those _jobs_ to succeed before PRs
* can be merged. You can mark workflow _jobs_ as required on GitHub, but not
* _workflows_. So if those jobs are now simply skipped, the requirement isn't met and
* the PR cannot be merged. We can't just skip the job. Instead, we need to run the job
* _but skip every single step_ so that the job can "succeed".
*/
try {
// Figure out workflow ID, commit and tree
const { data: run } = await github.rest.actions.getWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.runId,
});
const workflow_id = run.workflow_id;
const head_sha = run.head_sha;
const tree_id = run.head_commit.tree_id;
/*
* If the given workflow run was itself a "skip" run that references another
* run, follow the chain to find the deepest *actual* successful, non-skipped
* run, so we don't end up pointing at a chain of skip-runs.
*/
const SKIP_NOTICE_RE = /^Skipping: There already is a successful run: (.+)$/
const RUN_URL_RE = /\/actions\/runs\/(\d+)/
const MAX_CHAIN_LENGTH = 10
async function resolveSkipChain(initialRunId, initialRunUrl) {
let currentRunId = initialRunId
let currentRunUrl = initialRunUrl
console.log(`Resolving skip chain starting at ${currentRunUrl}`)
for (let i = 0; i < MAX_CHAIN_LENGTH; i++) {
let referencedUrl = null
try {
const { data: jobsData } = await github.rest.actions.listJobsForWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: currentRunId,
})
const validationJob = jobsData.jobs.find((j) => j.name === 'Validation')
if (!validationJob) {
console.log(`No 'Validation' job found on ${currentRunUrl}; treating as the real run`)
return currentRunUrl
}
const { data: annotations } = await github.rest.checks.listAnnotations({
owner: context.repo.owner,
repo: context.repo.repo,
check_run_id: validationJob.id,
})
for (const annotation of annotations) {
const match = annotation.message && annotation.message.match(SKIP_NOTICE_RE)
if (match) {
referencedUrl = match[1]
break
}
}
} catch (e) {
// If we cannot inspect this run, fall back to the URL we already have
console.log(`Failed to inspect ${currentRunUrl}: ${e.message}; stopping chain resolution`)
return currentRunUrl
}
if (!referencedUrl) {
console.log(`${currentRunUrl} is not a skip-run; using it as the resolved run`)
return currentRunUrl
}
console.log(`${currentRunUrl} is a skip-run referencing ${referencedUrl}; following the chain`)
const idMatch = referencedUrl.match(RUN_URL_RE)
if (!idMatch) {
console.log(`Could not parse a run ID from ${referencedUrl}; stopping chain resolution`)
return referencedUrl
}
const referencedId = Number(idMatch[1])
let referencedRun
try {
const response = await github.rest.actions.getWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: referencedId,
})
referencedRun = response.data
} catch (e) {
console.log(`Could not fetch referenced run ${referencedUrl}: ${e.message}; stopping chain resolution`)
return currentRunUrl
}
if (referencedRun.status !== 'completed' || referencedRun.conclusion !== 'success') {
console.log(`Referenced run ${referencedUrl} is no longer usable (status=${referencedRun.status}, conclusion=${referencedRun.conclusion}); stopping chain resolution`)
return currentRunUrl
}
currentRunId = referencedRun.id
currentRunUrl = referencedRun.html_url
}
console.log(`Reached MAX_CHAIN_LENGTH (${MAX_CHAIN_LENGTH}); stopping at ${currentRunUrl}`)
return currentRunUrl
}
// See whether there is a successful run for that commit or tree
const { data: runs } = await github.rest.actions.listWorkflowRuns({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 500,
workflow_id,
});
// first look at commit-same runs, then at finished ones, then at in-progress ones
const rank = (a) => (a.status === 'in_progress' ? 0 : (head_sha === a.head_sha ? 2 : 1))
const demoteInProgressToEnd = (a, b) => (rank(b) - rank(a))
for (const run of runs.workflow_runs.sort(demoteInProgressToEnd)) {
if (head_sha !== run.head_sha && tree_id !== run.head_commit?.tree_id) continue
if (context.runId === run.id) continue // do not wait for the current run to finish ;-)
if (run.event === 'workflow_dispatch') continue // skip runs that were started manually: they can override the Git version
if (run.status === 'in_progress') {
// poll until the run is done
const pollIntervalInSeconds = 30
let seconds = 0
for (;;) {
console.log(`Found existing, in-progress run at ${run.html_url}; Waiting for it to finish (waited ${seconds} seconds so far)...`)
await new Promise((resolve) => { setTimeout(resolve, pollIntervalInSeconds * 1000) })
seconds += pollIntervalInSeconds
const { data: polledRun } = await github.rest.actions.getWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: run.id
})
if (polledRun.status !== 'in_progress') break
}
}
if (run.status === 'completed' && run.conclusion === 'success') {
console.log(`Found previous successful run at ${run.html_url} (head_sha=${run.head_sha}, tree_id=${run.head_commit?.tree_id})`)
const resolvedUrl = await resolveSkipChain(run.id, run.html_url)
core.notice(`Skipping: There already is a successful run: ${resolvedUrl}`)
return resolvedUrl
}
}
return ''
} catch (e) {
core.error(e)
core.warning(e)
}
- name: Checkout source
if: steps.check.outputs.result == ''
uses: actions/checkout@v6
- name: Validate Microsoft Git version
if: steps.check.outputs.result == ''
shell: pwsh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
& "$env:GITHUB_WORKSPACE\.github\workflows\scripts\validate_release.ps1" `
-Repository microsoft/git `
-Tag $env:GIT_VERSION && `
Write-Host ::notice title=Validation::Using microsoft/git version $env:GIT_VERSION
- name: Download microsoft/git installers
if: steps.check.outputs.result == ''
shell: cmd
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release download %GIT_VERSION% --repo microsoft/git --pattern "Git*.exe" --dir MicrosoftGit
- name: Create Git install script
if: steps.check.outputs.result == ''
shell: cmd
run: |
>MicrosoftGit\install.bat (
echo @ECHO OFF
echo SETLOCAL
echo.
echo IF "%%PROCESSOR_ARCHITECTURE%%"=="AMD64" ^(
echo SET GIT_ARCH=64-bit
echo ^) ELSE IF "%%PROCESSOR_ARCHITECTURE%%"=="ARM64" ^(
echo SET GIT_ARCH=arm64
echo ^) ELSE ^(
echo ECHO Unknown architecture: %%PROCESSOR_ARCHITECTURE%%
echo exit 1
echo ^)
echo.
echo FOR /F "tokens=* USEBACKQ" %%%%F IN ^( `where /R %%~dp0 Git*-%%GIT_ARCH%%.exe` ^) DO SET GIT_INSTALLER=%%%%F
echo.
echo SET LOGDIR=%%~dp0\logs
echo IF EXIST %%LOGDIR%% ^( rmdir /S /Q %%LOGDIR%% ^)
echo mkdir %%LOGDIR%%
echo.
echo ECHO Installing Git ^(%%GIT_ARCH%%^)...
echo %%GIT_INSTALLER%% /LOG="%%LOGDIR%%\git.log" /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /ALLOWDOWNGRADE=1
)
- name: Upload microsoft/git installers
if: steps.check.outputs.result == ''
uses: actions/upload-artifact@v7
with:
name: MicrosoftGit
path: MicrosoftGit
build:
runs-on: windows-2025
name: Build and Unit Test
needs: validate
strategy:
matrix:
configuration: [ Debug, Release ]
fail-fast: false
steps:
- name: Skip this job if there is a previous successful run
if: needs.validate.outputs.skip != ''
id: skip
uses: actions/github-script@v9
with:
script: |
core.info(`Skipping: There already is a successful run: ${{ needs.validate.outputs.skip }}`)
return true
- name: Checkout source
if: steps.skip.outputs.result != 'true'
uses: actions/checkout@v6
with:
path: src
- name: Install .NET SDK
if: steps.skip.outputs.result != 'true'
uses: actions/setup-dotnet@v5
with:
global-json-file: src/global.json
- name: Add MSBuild to PATH
if: steps.skip.outputs.result != 'true'
uses: microsoft/setup-msbuild@v3.0.0
- name: Install vcpkg native dependencies
if: steps.skip.outputs.result != 'true'
shell: cmd
run: |
"%VCPKG_INSTALLATION_ROOT%\vcpkg.exe" install --triplet x64-windows-static-aot --x-install-root=out\vcpkg_installed\static --x-manifest-root=src || exit /b 1
"%VCPKG_INSTALLATION_ROOT%\vcpkg.exe" install --triplet x64-windows-dynamic --x-install-root=out\vcpkg_installed\dynamic --x-manifest-root=src || exit /b 1
- name: Build VFS for Git
if: steps.skip.outputs.result != 'true'
shell: cmd
run: src\scripts\Build.bat ${{ matrix.configuration }}
- name: Run unit tests
if: steps.skip.outputs.result != 'true'
shell: cmd
run: src\scripts\RunUnitTests.bat ${{ matrix.configuration }}
- name: Create build artifacts
if: steps.skip.outputs.result != 'true'
shell: cmd
run: src\scripts\CreateBuildArtifacts.bat ${{ matrix.configuration }} artifacts
- name: Upload functional tests drop
if: steps.skip.outputs.result != 'true'
uses: actions/upload-artifact@v7
with:
name: FunctionalTests_${{ matrix.configuration }}
path: artifacts\GVFS.FunctionalTests
- name: Upload FastFetch drop
if: steps.skip.outputs.result != 'true'
uses: actions/upload-artifact@v7
with:
name: FastFetch_${{ matrix.configuration }}
path: artifacts\FastFetch
- name: Upload GVFS installer
if: steps.skip.outputs.result != 'true'
uses: actions/upload-artifact@v7
with:
name: GVFS_${{ matrix.configuration }}
path: artifacts\GVFS.Installers
functional_tests:
name: Functional Tests
needs: [validate, build]
uses: ./.github/workflows/functional-tests.yaml
with:
skip: ${{ needs.validate.outputs.skip }}
upgrade_tests:
name: Upgrade Tests
needs: [validate, build]
uses: ./.github/workflows/upgrade-tests.yaml
with:
skip: ${{ needs.validate.outputs.skip }}
result:
runs-on: ubuntu-latest
name: Build, Unit and Functional Tests Successful
needs: [functional_tests, upgrade_tests]
steps:
- name: Success! # for easier identification of successful runs in the Checks Required for Pull Requests
run: echo "Workflow run is successful!"