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
20 changes: 20 additions & 0 deletions eng/pipelines/templates/prepare-winget-manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,26 @@ steps:
Write-Host "##vso[task.setvariable variable=WinGetIsPrereleaseInStablePackage]$isPrereleaseInStablePackage"
displayName: 🟣Set version ${{ parameters.version }}
# The standalone wingetcreate executable is framework-dependent. Exercise it
# in regular builds so an upstream target-framework change is caught before
# the release pipeline attempts a public submission.
- task: UseDotNet@2
displayName: '🟣Install .NET 9 runtime for wingetcreate'
inputs:
packageType: 'runtime'
version: '9.0.x'

- pwsh: |
$ErrorActionPreference = 'Stop'
Invoke-WebRequest -Uri "https://aka.ms/wingetcreate/latest" -OutFile "$(Build.StagingDirectory)/wingetcreate.exe"
& "$(Build.StagingDirectory)/wingetcreate.exe" info
if ($LASTEXITCODE -ne 0) {
Write-Error "wingetcreate info failed with exit code $LASTEXITCODE"
exit $LASTEXITCODE
}
displayName: '🟣Verify wingetcreate'
- pwsh: |
# Probe-only: do not attempt to install/repair winget here. The 1ES `1es-windows-2022`
# pool blocks outbound access to cdn.winget.microsoft.com, so
Expand Down
29 changes: 18 additions & 11 deletions eng/pipelines/templates/publish-winget.yml
Original file line number Diff line number Diff line change
Expand Up @@ -186,22 +186,29 @@ steps:
eq(variables['_IsProductionBranch'], 'true')
)

# The latest standalone wingetcreate executable targets .NET 9 and is not
# self-contained. Install its runtime whenever the WinGet job is selected so
# dry-run and non-production builds also verify that the CLI can start.
- task: UseDotNet@2
displayName: '🟣Install .NET 9 runtime for wingetcreate'
inputs:
packageType: 'runtime'
version: '9.0.x'
condition: succeeded()

- powershell: |
$ErrorActionPreference = 'Stop'
Write-Host "Downloading wingetcreate..."
Invoke-WebRequest -Uri "https://aka.ms/wingetcreate/latest" -OutFile "$(Build.StagingDirectory)/wingetcreate.exe"
Write-Host "wingetcreate downloaded successfully"
& "$(Build.StagingDirectory)/wingetcreate.exe" info
if ($LASTEXITCODE -ne 0) {
Write-Error "wingetcreate info failed with exit code $LASTEXITCODE"
exit $LASTEXITCODE
}
Write-Host "wingetcreate downloaded and verified successfully"
displayName: '🟣Install wingetcreate'
# Skip when no Submit step will run. wingetcreate is only used to submit to
# the upstream winget-pkgs repo, so dry-run and non-production-branch builds
# gain nothing from downloading it and instead pick up an aka.ms-redirect
# failure surface for free.
condition: |
and(
succeeded(),
eq('${{ parameters.dryRun }}', 'false'),
eq(variables['_IsProductionBranch'], 'true')
)
# This is side-effect free; the Submit step below retains the production gate.
condition: succeeded()

- powershell: |
$ErrorActionPreference = 'Stop'
Expand Down
7 changes: 7 additions & 0 deletions eng/winget/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ Where arch is `x64` or `arm64`.
| `release-publish-nuget.yml` (release) | — | Stable manifests only |

Publishing submits a PR to `microsoft/winget-pkgs` using `wingetcreate submit`.
The Azure DevOps prepare stage also installs the .NET 9 runtime, downloads the
latest standalone `wingetcreate`, and runs `wingetcreate info`. This
side-effect-free smoke catches runtime compatibility changes before the release
pipeline attempts a public submission; it does not exercise credentials or
submission. The release WinGet job repeats the same startup check whenever that
job is selected, including dry-run and non-production-branch builds, while the
submission step remains limited to non-dry-run production builds.

## Validation model

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,38 @@ public async Task ExtensionReleaseDryRunInstructionsDoNotOverstatePublisherRoleV
Assert.Contains("Separately confirm the service connection identity is a Contributor", workflow);
}

[Fact]
public async Task WinGetJobVerifiesWingetCreateBeforeConditionalSubmission()
{
var template = await ReadRepoFileAsync("eng/pipelines/templates/publish-winget.yml");
var runtimeInstallIndex = FindRequiredText(template, "- task: UseDotNet@2");
var wingetCreateInstallIndex = FindRequiredText(template, "Write-Host \"Downloading wingetcreate...\"");
var submitIndex = FindRequiredText(template, "Write-Host \"Submitting WinGet manifests");
var runtimeInstall = template[runtimeInstallIndex..wingetCreateInstallIndex];
var wingetCreateInstall = template[wingetCreateInstallIndex..submitIndex];
var submission = template[submitIndex..];

Assert.Contains("packageType: 'runtime'", runtimeInstall);
Assert.Contains("version: '9.0.x'", runtimeInstall);
Assert.Contains("condition: succeeded()", runtimeInstall);
Assert.Contains("wingetcreate.exe\" info", wingetCreateInstall);
Assert.Contains("condition: succeeded()", wingetCreateInstall);
Assert.Contains("eq('${{ parameters.dryRun }}', 'false')", submission);
Assert.Contains("eq(variables['_IsProductionBranch'], 'true')", submission);
}

[Fact]
public async Task WinGetPreparationExercisesWingetCreate()
{
var template = await ReadRepoFileAsync("eng/pipelines/templates/prepare-winget-manifest.yml");

Assert.Contains("- task: UseDotNet@2", template);
Assert.Contains("packageType: 'runtime'", template);
Assert.Contains("version: '9.0.x'", template);
Assert.Contains("https://aka.ms/wingetcreate/latest", template);
Assert.Contains("wingetcreate.exe\" info", template);
}

[Fact]
public async Task MarketplacePublishingDocumentationKeepsIdentityDetailsInternalAndRetiresPat()
{
Expand Down
Loading