Skip to content

Commit 774a4e7

Browse files
authored
[0.81] Build pipeline improvements (#15965)
* Combine dependabot PRs and add catch-all grouping (#15856) * Fix download issues in pipelines (#15901) * Harden Release publishing (#15902) * Reduce dependabot noise (#15938)
1 parent b15aab6 commit 774a4e7

File tree

10 files changed

+313
-40
lines changed

10 files changed

+313
-40
lines changed

.ado/build-template.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ extends:
231231
- template: .ado/templates/set-version-vars.yml@self
232232
parameters:
233233
buildEnvironment: Continuous
234+
isReleaseBuild: $(isReleaseBuild)
234235

235236
# 8. Include npmPack.js for Release pipeline (CI only)
236237
- script: copy ".ado\scripts\npmPack.js" "$(Build.StagingDirectory)\versionEnvVars\npmPack.js"

.ado/jobs/desktop-single.yml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,29 @@ steps:
2727
displayName: Install IIS
2828
2929
- pwsh: |
30-
Invoke-WebRequest `
30+
function Invoke-WebRequestWithRetry($Uri, $OutFile, $MaxRetries = 3) {
31+
for ($i = 1; $i -le $MaxRetries; $i++) {
32+
try {
33+
Write-Host "Downloading $OutFile (attempt $i of $MaxRetries)"
34+
Invoke-WebRequest -Uri $Uri -OutFile $OutFile
35+
return
36+
} catch {
37+
Write-Host "Attempt $i failed: $_"
38+
if ($i -eq $MaxRetries) { throw }
39+
Start-Sleep -Seconds (5 * $i)
40+
}
41+
}
42+
}
43+
44+
Invoke-WebRequestWithRetry `
3145
-Uri 'https://download.visualstudio.microsoft.com/download/pr/20598243-c38f-4538-b2aa-af33bc232f80/ea9b2ca232f59a6fdc84b7a31da88464/dotnet-hosting-8.0.3-win.exe' `
3246
-OutFile dotnet-hosting-8.0.3-win.exe
3347
3448
Write-Host 'Installing .NET hosting bundle'
3549
Start-Process -Wait -FilePath .\dotnet-hosting-8.0.3-win.exe -ArgumentList '/INSTALL', '/QUIET', '/NORESTART'
3650
Write-Host 'Installed .NET hosting bundle'
3751
38-
Invoke-WebRequest `
52+
Invoke-WebRequestWithRetry `
3953
-Uri 'https://download.visualstudio.microsoft.com/download/pr/f2ec926e-0d98-4a8b-8c70-722ccc2ca0e5/b59941b0c60f16421679baafdb7e9338/dotnet-sdk-7.0.407-win-x64.exe' `
4054
-OutFile dotnet-sdk-7.0.407-win-x64.exe
4155

.ado/jobs/linting.yml

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,29 @@ jobs:
1616
variables: [template: ../variables/windows.yml]
1717
pool: ${{ parameters.AgentPool.Medium }}
1818
steps:
19-
- template: ../templates/checkout-shallow.yml
19+
- ${{ if eq(parameters.buildEnvironment, 'Continuous') }}:
20+
- template: ../templates/checkout-shallow.yml
21+
parameters:
22+
persistCredentials: true
23+
24+
# Extract the GitHub OAuth token that ADO uses to clone the repo.
25+
# Any authenticated token raises the GitHub API rate limit from 60 to
26+
# 5,000 req/hr, which prevents validate-overrides from being throttled.
27+
- pwsh: |
28+
$headerLine = git config --get-regexp "http.*\.extraheader" 2>$null | Select-Object -First 1
29+
if (-not $headerLine) {
30+
Write-Host "##vso[task.logissue type=warning]No HTTP extraheader found — validate-overrides will run without GitHub auth"
31+
exit 0
32+
}
33+
$encoded = ($headerLine.Split(' ')[-1]).Trim()
34+
$decoded = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($encoded))
35+
$token = $decoded.Split(':')[-1]
36+
Write-Host "Extracted GitHub OAuth token (length=$($token.Length))"
37+
Write-Host "##vso[task.setvariable variable=GitHubOAuthToken;issecret=true]$token"
38+
displayName: Extract GitHub OAuth token
39+
40+
- ${{ else }}:
41+
- template: ../templates/checkout-shallow.yml
2042

2143
- template: ../templates/prepare-js-env.yml
2244

@@ -28,6 +50,9 @@ jobs:
2850

2951
- script: yarn validate-overrides
3052
displayName: yarn validate-overrides
53+
${{ if eq(parameters.buildEnvironment, 'Continuous') }}:
54+
env:
55+
PLATFORM_OVERRIDE_GITHUB_TOKEN: $(GitHubOAuthToken)
3156

3257
- script: npx unbroken -q --local-only --allow-local-line-sections
3358
displayName: check local links in .md files

.ado/release-pipeline.yml

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ name: RNW Release $(Date:yyyyMMdd).$(Rev:r)
1919
trigger: none
2020
pr: none
2121

22+
parameters:
23+
- name: forceRelease
24+
displayName: 'Force release (override CI flag — emergency use only)'
25+
type: boolean
26+
default: false
27+
2228
resources:
2329
pipelines:
2430
- pipeline: 'CI'
@@ -57,6 +63,12 @@ extends:
5763
jobs:
5864
- job: Evaluate
5965
displayName: Check if release should proceed
66+
templateContext:
67+
inputs:
68+
- input: pipelineArtifact
69+
pipeline: 'CI'
70+
artifactName: 'VersionEnvVars'
71+
targetPath: '$(Pipeline.Workspace)/VersionEnvVars'
6072
steps:
6173
- checkout: none
6274

@@ -99,10 +111,15 @@ extends:
99111
CI_REQUESTEDFOR: $(resources.pipeline.CI.requestedFor)
100112
CI_REQUESTEDFORID: $(resources.pipeline.CI.requestedForID)
101113
114+
# Apply version variables from the CI artifact (sets IsReleaseBuild, npmVersion, etc.)
115+
- task: CmdLine@2
116+
displayName: Apply CI version variables
117+
inputs:
118+
script: node $(Pipeline.Workspace)/VersionEnvVars/versionEnvVars.js
119+
102120
- pwsh: |
103-
$buildReason = $env:BUILD_REASON
104-
# Use only the first line of the commit message
105-
$sourceMessage = ($env:SOURCE_MESSAGE -split "`n")[0].Trim()
121+
$forceRelease = '${{ parameters.forceRelease }}'
122+
$isReleaseBuild = $env:IS_RELEASE_BUILD
106123
$ciRunName = $env:CI_RUN_NAME
107124
$sourceBranch = $env:SOURCE_BRANCH -replace '^refs/heads/', ''
108125
@@ -111,25 +128,42 @@ extends:
111128
$originalBuildNumber = $env:BUILD_BUILDNUMBER
112129
$dateStamp = if ($originalBuildNumber -match '(\d{8}\.\d+)$') { $Matches[1] } else { "" }
113130
131+
# Fallback: if IsReleaseBuild is not in the CI artifact (old branches),
132+
# fall back to commit message detection for backward compatibility.
133+
if (-not $isReleaseBuild) {
134+
$sourceMessage = ($env:SOURCE_MESSAGE -split "`n")[0].Trim()
135+
$isReleaseBuild = if ($sourceMessage.StartsWith("RELEASE:")) { 'True' } else { 'False' }
136+
Write-Host "##[warning]IsReleaseBuild not found in CI artifact, fell back to commit message detection: $isReleaseBuild"
137+
}
138+
139+
Write-Host "isReleaseBuild (from CI): $isReleaseBuild"
140+
Write-Host "forceRelease (param): $forceRelease"
141+
114142
$shouldRelease = $false
115143
$buildNumber = ""
116144
117-
if ($buildReason -eq "Manual") {
145+
if ($forceRelease -eq 'True') {
146+
# Emergency override — allow publishing regardless of CI flag
118147
$shouldRelease = $true
119148
if ($ciRunName) {
120-
$buildNumber = "$ciRunName ($sourceBranch) - $dateStamp"
149+
$buildNumber = "$ciRunName [FORCED] ($sourceBranch) - $dateStamp"
121150
} else {
122-
$buildNumber = "Release ($sourceBranch) - $dateStamp"
151+
$buildNumber = "Release [FORCED] ($sourceBranch) - $dateStamp"
123152
}
153+
Write-Host "##[warning]Release forced by parameter override"
124154
}
125-
elseif ($sourceMessage.StartsWith("RELEASE:")) {
155+
elseif ($isReleaseBuild -eq 'True') {
126156
$shouldRelease = $true
127-
$buildNumber = "$ciRunName ($sourceBranch) - $dateStamp"
157+
if ($ciRunName) {
158+
$buildNumber = "$ciRunName ($sourceBranch) - $dateStamp"
159+
} else {
160+
$buildNumber = "Release ($sourceBranch) - $dateStamp"
161+
}
128162
}
129163
else {
130164
$shouldRelease = $false
131165
# Truncate commit message for readability
132-
$shortMsg = $sourceMessage
166+
$shortMsg = ($env:SOURCE_MESSAGE -split "`n")[0].Trim()
133167
if ($shortMsg.Length -gt 60) {
134168
$shortMsg = $shortMsg.Substring(0, 57) + "..."
135169
}
@@ -147,20 +181,20 @@ extends:
147181
Write-Host "##vso[build.updatebuildnumber]$buildNumber"
148182
Write-Host "##vso[task.setvariable variable=shouldRelease;isOutput=true]$shouldRelease"
149183
name: gate
150-
displayName: Determine release eligibility and set build number
184+
displayName: Determine release eligibility from CI artifact
151185
env:
152-
BUILD_REASON: $(Build.Reason)
153186
BUILD_BUILDNUMBER: $(Build.BuildNumber)
154-
SOURCE_MESSAGE: $(Build.SourceVersionMessage)
187+
IS_RELEASE_BUILD: $(IsReleaseBuild)
155188
CI_RUN_NAME: $(resources.pipeline.CI.runName)
156189
SOURCE_BRANCH: $(resources.pipeline.CI.sourceBranch)
190+
SOURCE_MESSAGE: $(Build.SourceVersionMessage)
157191
158192
- script: echo Proceeding with release
159193
displayName: RELEASING - proceeding to publish
160194
condition: eq(variables['gate.shouldRelease'], 'True')
161195

162196
- script: echo Skipping release
163-
displayName: SKIPPED - not a RELEASE commit
197+
displayName: SKIPPED - not a release build
164198
condition: eq(variables['gate.shouldRelease'], 'False')
165199

166200
- stage: Release

.ado/release.yml

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ name: RNW NuGet Release $(Date:yyyyMMdd).$(Rev:r)
1414
trigger: none
1515
pr: none
1616

17+
parameters:
18+
- name: forceRelease
19+
displayName: 'Force release (override CI flag — emergency use only)'
20+
type: boolean
21+
default: false
22+
1723
resources:
1824
pipelines:
1925
- pipeline: 'Publish'
@@ -52,6 +58,12 @@ extends:
5258
jobs:
5359
- job: Evaluate
5460
displayName: Check if release should proceed
61+
templateContext:
62+
inputs:
63+
- input: pipelineArtifact
64+
pipeline: 'Publish'
65+
artifactName: 'VersionEnvVars'
66+
targetPath: '$(Pipeline.Workspace)/VersionEnvVars'
5567
steps:
5668
- checkout: none
5769

@@ -94,10 +106,15 @@ extends:
94106
PUBLISH_REQUESTEDFOR: $(resources.pipeline.Publish.requestedFor)
95107
PUBLISH_REQUESTEDFORID: $(resources.pipeline.Publish.requestedForID)
96108
109+
# Apply version variables from the CI artifact (sets IsReleaseBuild, npmVersion, etc.)
110+
- task: CmdLine@2
111+
displayName: Apply CI version variables
112+
inputs:
113+
script: node $(Pipeline.Workspace)/VersionEnvVars/versionEnvVars.js
114+
97115
- pwsh: |
98-
$buildReason = $env:BUILD_REASON
99-
# Use only the first line of the commit message
100-
$sourceMessage = ($env:SOURCE_MESSAGE -split "`n")[0].Trim()
116+
$forceRelease = '${{ parameters.forceRelease }}'
117+
$isReleaseBuild = $env:IS_RELEASE_BUILD
101118
$publishRunName = $env:PUBLISH_RUN_NAME
102119
$sourceBranch = $env:SOURCE_BRANCH -replace '^refs/heads/', ''
103120
@@ -106,25 +123,42 @@ extends:
106123
$originalBuildNumber = $env:BUILD_BUILDNUMBER
107124
$dateStamp = if ($originalBuildNumber -match '(\d{8}\.\d+)$') { $Matches[1] } else { "" }
108125
126+
# Fallback: if IsReleaseBuild is not in the CI artifact (old branches),
127+
# fall back to commit message detection for backward compatibility.
128+
if (-not $isReleaseBuild) {
129+
$sourceMessage = ($env:SOURCE_MESSAGE -split "`n")[0].Trim()
130+
$isReleaseBuild = if ($sourceMessage.StartsWith("RELEASE:")) { 'True' } else { 'False' }
131+
Write-Host "##[warning]IsReleaseBuild not found in CI artifact, fell back to commit message detection: $isReleaseBuild"
132+
}
133+
134+
Write-Host "isReleaseBuild (from CI): $isReleaseBuild"
135+
Write-Host "forceRelease (param): $forceRelease"
136+
109137
$shouldRelease = $false
110138
$buildNumber = ""
111139
112-
if ($buildReason -eq "Manual") {
140+
if ($forceRelease -eq 'True') {
141+
# Emergency override — allow publishing regardless of CI flag
113142
$shouldRelease = $true
114143
if ($publishRunName) {
115-
$buildNumber = "$publishRunName ($sourceBranch) - $dateStamp"
144+
$buildNumber = "$publishRunName [FORCED] ($sourceBranch) - $dateStamp"
116145
} else {
117-
$buildNumber = "Release ($sourceBranch) - $dateStamp"
146+
$buildNumber = "Release [FORCED] ($sourceBranch) - $dateStamp"
118147
}
148+
Write-Host "##[warning]Release forced by parameter override"
119149
}
120-
elseif ($sourceMessage.StartsWith("RELEASE:")) {
150+
elseif ($isReleaseBuild -eq 'True') {
121151
$shouldRelease = $true
122-
$buildNumber = "$publishRunName ($sourceBranch) - $dateStamp"
152+
if ($publishRunName) {
153+
$buildNumber = "$publishRunName ($sourceBranch) - $dateStamp"
154+
} else {
155+
$buildNumber = "Release ($sourceBranch) - $dateStamp"
156+
}
123157
}
124158
else {
125159
$shouldRelease = $false
126160
# Truncate commit message for readability
127-
$shortMsg = $sourceMessage
161+
$shortMsg = ($env:SOURCE_MESSAGE -split "`n")[0].Trim()
128162
if ($shortMsg.Length -gt 60) {
129163
$shortMsg = $shortMsg.Substring(0, 57) + "..."
130164
}
@@ -142,20 +176,20 @@ extends:
142176
Write-Host "##vso[build.updatebuildnumber]$buildNumber"
143177
Write-Host "##vso[task.setvariable variable=shouldRelease;isOutput=true]$shouldRelease"
144178
name: gate
145-
displayName: Determine release eligibility and set build number
179+
displayName: Determine release eligibility from CI artifact
146180
env:
147-
BUILD_REASON: $(Build.Reason)
148181
BUILD_BUILDNUMBER: $(Build.BuildNumber)
149-
SOURCE_MESSAGE: $(Build.SourceVersionMessage)
182+
IS_RELEASE_BUILD: $(IsReleaseBuild)
150183
PUBLISH_RUN_NAME: $(resources.pipeline.Publish.runName)
151184
SOURCE_BRANCH: $(resources.pipeline.Publish.sourceBranch)
185+
SOURCE_MESSAGE: $(Build.SourceVersionMessage)
152186
153187
- script: echo Proceeding with release
154188
displayName: RELEASING - proceeding to publish
155189
condition: eq(variables['gate.shouldRelease'], 'True')
156190

157191
- script: echo Skipping release
158-
displayName: SKIPPED - not a RELEASE commit
192+
displayName: SKIPPED - not a release build
159193
condition: eq(variables['gate.shouldRelease'], 'False')
160194

161195
- stage: Release

.ado/scripts/setVersionEnvVars.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ if (buildId == undefined) {
2121
throw new Error("Missing argument for the buildid")
2222
}
2323

24+
// Optional: isReleaseBuild flag from CI setup detection step.
25+
// When present, it is included in the VersionEnvVars artifact so the Release
26+
// pipeline can read the authoritative release/developer classification without
27+
// re-deriving it from commit messages.
28+
const isReleaseBuild = process.argv[4] === 'True' ? 'True' : 'False';
29+
if (process.argv[4] == null) {
30+
console.log("##[warning]isReleaseBuild argument not provided, defaulting to False");
31+
}
32+
2433
let adoBuildVersion=pkgJson.version;
2534
if (isPr) {
2635
adoBuildVersion += `.pr${buildId}`;
@@ -38,6 +47,7 @@ const versionEnvVars = {
3847
reactDevDependency: pkgJson.devDependencies['react'],
3948
reactNativeDevDependency: pkgJson.devDependencies['react-native'],
4049
npmDistTag: pkgJson?.beachball?.defaultNpmTag?.trim(),
50+
IsReleaseBuild: isReleaseBuild,
4151
}
4252

4353
if (!versionEnvVars.npmDistTag) {
@@ -46,7 +56,12 @@ if (!versionEnvVars.npmDistTag) {
4656

4757
// Set the build number so the build in the publish pipeline and the release pipeline are named with the convenient version
4858
// See: https://docs.microsoft.com/en-us/azure/devops/pipelines/scripts/logging-commands?view=azure-devops&tabs=bash#updatebuildnumber-override-the-automatically-generated-build-number
49-
console.log(`##vso[build.updatebuildnumber]RNW_${adoBuildVersion}`)
59+
// CI builds include [Release] or [Dev] tag for at-a-glance identification in pipeline history.
60+
let buildNumber = `RNW_${adoBuildVersion}`;
61+
if (!isPr) {
62+
buildNumber += isReleaseBuild === 'True' ? ' [Release]' : ' [Dev]';
63+
}
64+
console.log(`##vso[build.updatebuildnumber]${buildNumber}`)
5065

5166
// Set env variable to allow VS to build dll with correct version information
5267
console.log(`##vso[task.setvariable variable=RNW_PKG_VERSION_STR]${versionEnvVars.RNW_PKG_VERSION_STR}`);
@@ -60,6 +75,7 @@ console.log(`##vso[task.setvariable variable=publishCommitId]${versionEnvVars.pu
6075
console.log(`##vso[task.setvariable variable=reactDevDependency]${versionEnvVars.reactDevDependency}`);
6176
console.log(`##vso[task.setvariable variable=reactNativeDevDependency]${versionEnvVars.reactNativeDevDependency}`);
6277
console.log(`##vso[task.setvariable variable=NpmDistTag]${versionEnvVars.npmDistTag}`);
78+
console.log(`##vso[task.setvariable variable=IsReleaseBuild]${versionEnvVars.IsReleaseBuild}`);
6379

6480
const runnerTemp = process.env.RUNNER_TEMP;
6581
if (!runnerTemp) {
@@ -80,4 +96,5 @@ console.log("##vso[task.setvariable variable=publishCommitId]${versionEnvVars.pu
8096
console.log("##vso[task.setvariable variable=reactDevDependency]${versionEnvVars.reactDevDependency}");
8197
console.log("##vso[task.setvariable variable=reactNativeDevDependency]${versionEnvVars.reactNativeDevDependency}");
8298
console.log("##vso[task.setvariable variable=NpmDistTag]${versionEnvVars.npmDistTag}");
99+
console.log("##vso[task.setvariable variable=IsReleaseBuild]${versionEnvVars.IsReleaseBuild}");
83100
`);
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
# Checkout the state of the repo at the time of the commit being tested,
22
# without full history.
3+
parameters:
4+
- name: persistCredentials
5+
type: boolean
6+
default: false
7+
38
steps:
49
- checkout: self
510
fetchDepth: 1
611
clean: false
712
submodules: false
813
lfs: false
14+
persistCredentials: ${{ parameters.persistCredentials }}

.ado/templates/set-version-vars.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ parameters:
66
values:
77
- PullRequest
88
- Continuous
9+
- name: isReleaseBuild
10+
type: string
11+
default: 'False'
912

1013
steps:
11-
- script: node ./.ado/scripts/setVersionEnvVars.js ${{ parameters.buildEnvironment }} $(Build.BuildId)
14+
- script: node ./.ado/scripts/setVersionEnvVars.js ${{ parameters.buildEnvironment }} $(Build.BuildId) ${{ parameters.isReleaseBuild }}
1215
displayName: Set version variables
1316
name: setVersionEnvVars
1417
env:

0 commit comments

Comments
 (0)