Skip to content

Missing CanReturnMultipleSuccessValues for IShellLinkA and IShellLinkW for GetPath and GetIDList. Fixed microsoft#2279. #82

Missing CanReturnMultipleSuccessValues for IShellLinkA and IShellLinkW for GetPath and GetIDList. Fixed microsoft#2279.

Missing CanReturnMultipleSuccessValues for IShellLinkA and IShellLinkW for GetPath and GetIDList. Fixed microsoft#2279. #82

Workflow file for this run

# PR Validation — Full cross-architecture build and test
#
# Mirrors the Azure Pipelines CI pipeline (azure-pipelines.yml) but runs as a
# GitHub Actions workflow so PR checks work without Azure DevOps integration.
#
# Pipeline structure:
# 1. Three parallel scrape jobs (x64, x86, arm64) generate metadata source
# 2. One build job assembles the winmd, packages, samples, and runs tests
name: PR Validation
on:
pull_request:
branches: [main]
paths-ignore:
- 'apidocs/**'
- 'docs/**'
push:
branches: [main]
paths-ignore:
- 'apidocs/**'
- 'docs/**'
workflow_dispatch:
permissions:
contents: read
pull-requests: write
concurrency:
group: pr-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
BUILD_CONFIGURATION: Release
jobs:
# ──────────────────────────────────────────────────────────────────────
# Stage 1: Parallel header scraping (one job per architecture)
# ──────────────────────────────────────────────────────────────────────
scrape:
name: 'Scrape headers: ${{ matrix.arch }}'
runs-on: windows-2022
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
arch: [x64, x86, arm64]
include:
- arch: x64
extra_args: ''
set_version: true
- arch: x86
extra_args: '-scrapeConstants'
set_version: false
- arch: arm64
extra_args: ''
set_version: false
steps:
- uses: actions/checkout@v4
with:
lfs: true
fetch-depth: 0
- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- name: Install nbgv and set version
if: matrix.set_version
shell: pwsh
run: |
.\scripts\Install-DotNetTool.ps1 -Name nbgv -NuGetConfigFile "${{ github.workspace }}\nuget.config"
nbgv cloud
- name: Generate metadata source (${{ matrix.arch }})
shell: pwsh
run: .\scripts\GenerateMetadataSource.ps1 -arch ${{ matrix.arch }} ${{ matrix.extra_args }}
- name: Upload generated assets
uses: actions/upload-artifact@v4
with:
name: generated_${{ matrix.arch }}
path: generation/WinSDK/obj
retention-days: 1
- name: Upload build logs
uses: actions/upload-artifact@v4
if: always()
with:
name: scrape_logs_${{ matrix.arch }}
path: bin/logs
retention-days: 7
# ──────────────────────────────────────────────────────────────────────
# Stage 2: Build, test, and package (depends on all scrape jobs)
# ──────────────────────────────────────────────────────────────────────
build-test:
name: 'Build, test, package'
needs: [scrape]
runs-on: windows-2022
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
with:
lfs: true
fetch-depth: 0
- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- uses: microsoft/setup-msbuild@v2
- name: Download all generated assets
uses: actions/download-artifact@v4
with:
pattern: generated_*
path: generation/WinSDK/obj
merge-multiple: true
- name: Build metadata binary
shell: pwsh
run: .\scripts\BuildMetadataBin.ps1 -assetsScrapedSeparately
- name: Package
shell: pwsh
run: .\scripts\DoPackages.ps1
- name: Build samples
shell: pwsh
run: .\scripts\DoSamples.ps1
- name: Run tests
shell: pwsh
run: .\scripts\DoTests.ps1
- name: Download baseline winmd from target branch
if: github.event_name == 'pull_request'
id: baseline
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const path = require('path');
// Find the latest successful run on the target branch
const baseBranch = context.payload.pull_request.base.ref;
const runs = await github.rest.actions.listWorkflowRuns({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'pr-validation.yml',
branch: baseBranch,
status: 'success',
per_page: 1
});
if (runs.data.workflow_runs.length === 0) {
core.warning(`No successful runs found on ${baseBranch}. Will fall back to NuGet baseline.`);
return;
}
const runId = runs.data.workflow_runs[0].id;
core.info(`Found baseline run ${runId} on ${baseBranch}`);
// Find the winmd artifact
const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: runId
});
const winmdArtifact = artifacts.data.artifacts.find(a => a.name === 'winmd');
if (!winmdArtifact) {
core.warning('No winmd artifact found in baseline run. Will fall back to NuGet baseline.');
return;
}
// Download and extract
const download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: winmdArtifact.id,
archive_format: 'zip'
});
const outputDir = 'bin/baseline-artifact';
fs.mkdirSync(outputDir, { recursive: true });
const zipPath = path.join(outputDir, 'artifact.zip');
fs.writeFileSync(zipPath, Buffer.from(download.data));
// Extract using PowerShell
const { execSync } = require('child_process');
execSync(`Expand-Archive -Path "${zipPath}" -DestinationPath "${outputDir}" -Force`, { shell: 'pwsh' });
fs.unlinkSync(zipPath);
core.info(`Extracted baseline winmd to ${outputDir}`);
- name: Generate API surface diff
if: github.event_name == 'pull_request'
id: apidump
shell: pwsh
run: |
$ErrorActionPreference = 'Continue'
$PSNativeCommandUseErrorActionPreference = $false
$winmdUtils = "bin\Release\net8.0\WinmdUtils.dll"
$currentWinmd = "bin\Windows.Win32.winmd"
# Determine baseline: prefer main branch artifact, fall back to last release
$baselineWinmd = "bin\baseline-artifact\Windows.Win32.winmd"
if (-not (Test-Path $baselineWinmd)) {
Write-Host "No main branch artifact found, falling back to last NuGet release..."
. .\scripts\CommonUtils.ps1
$baselineWinmd = Get-Win32MetadataLastReleaseWinmdPath
}
Write-Host "Baseline: $baselineWinmd"
Write-Host "Current: $currentWinmd"
# Dump baseline
Write-Host "Dumping baseline..."
& dotnet $winmdUtils dump --winmd $baselineWinmd --output bin\baseline.apidump.cs
if ($LASTEXITCODE -ne 0) { throw "Failed to dump baseline" }
# Dump current build
Write-Host "Dumping current build..."
& dotnet $winmdUtils dump --winmd $currentWinmd --output bin\current.apidump.cs
if ($LASTEXITCODE -ne 0) { throw "Failed to dump current" }
# Generate diff — git diff exits 1 when differences exist, which is expected
& cmd /c "git diff --no-index --unified=3 bin\baseline.apidump.cs bin\current.apidump.cs > bin\api-diff.patch 2>&1"
$diffExitCode = $LASTEXITCODE
if ($diffExitCode -eq 0) {
Write-Host "No API differences found."
"has_diff=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
} else {
Write-Host "API differences detected."
"has_diff=true" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
# Count additions/deletions from the patch file
$diffLines = Get-Content bin\api-diff.patch
$additions = ($diffLines | Where-Object { $_ -match '^\+[^+]' }).Count
$deletions = ($diffLines | Where-Object { $_ -match '^\-[^-]' }).Count
"additions=$additions" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
"deletions=$deletions" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
Write-Host "+$additions additions / -$deletions deletions"
# Truncate if too large for a GH comment
$diffText = $diffLines -join "`n"
$maxLen = 60000
if ($diffText.Length -gt $maxLen) {
$diffText = $diffText.Substring(0, $maxLen) + "`n`n... (diff truncated - see full artifact)"
Set-Content -Path bin\api-diff.patch -Value $diffText -Encoding UTF8
}
}
# Reset LASTEXITCODE so GitHub Actions doesn't treat this step as failed
$global:LASTEXITCODE = 0
- name: Post API diff comment on PR
if: github.event_name == 'pull_request' && steps.apidump.outputs.has_diff == 'true'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const diff = fs.readFileSync('bin/api-diff.patch', 'utf8');
const prNumber = context.payload.pull_request.number;
const additions = ${{ steps.apidump.outputs.additions || 0 }};
const deletions = ${{ steps.apidump.outputs.deletions || 0 }};
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
const marker = '<!-- win32metadata-api-diff -->';
const body = `${marker}
## 📋 API Surface Diff
**+${additions} additions / -${deletions} deletions** vs main branch ([full build log](${runUrl}))
<details>
<summary>Click to expand API diff</summary>
\`\`\`diff
${diff}
\`\`\`
</details>
> This comment is automatically updated on each push.`;
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
per_page: 100
});
const existing = comments.data.find(c => c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body: body
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: body
});
}
- name: Post no-diff comment on PR
if: github.event_name == 'pull_request' && steps.apidump.outputs.has_diff == 'false'
uses: actions/github-script@v7
with:
script: |
const prNumber = context.payload.pull_request.number;
const marker = '<!-- win32metadata-api-diff -->';
const body = `${marker}\n## 📋 API Surface Diff\n\n✅ No API differences vs last release.\n\n> This comment is automatically updated on each push.`;
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
per_page: 100
});
const existing = comments.data.find(c => c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body: body
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: body
});
}
- name: Upload winmd and API dump
uses: actions/upload-artifact@v4
if: always()
with:
name: winmd
path: |
bin/Windows.Win32.winmd
bin/current.apidump.cs
bin/baseline.apidump.cs
bin/api-diff.patch
retention-days: 30
- name: Upload build logs
uses: actions/upload-artifact@v4
if: always()
with:
name: build_logs
path: bin/logs
retention-days: 7
- name: Upload NuGet packages
uses: actions/upload-artifact@v4
with:
name: NuGetPackages
path: bin/Packages/Release/NuGet
retention-days: 7