diff --git a/.github/scripts/Build-ChangedSamples.ps1 b/.github/scripts/Build-ChangedSamples.ps1 index 3e91eb883..f03f93f3b 100644 --- a/.github/scripts/Build-ChangedSamples.ps1 +++ b/.github/scripts/Build-ChangedSamples.ps1 @@ -22,7 +22,7 @@ foreach ($file in $ChangedFiles) { $filename = Split-Path $file -Leaf # Files that can affect how every sample is built should trigger a full build - if ($filename -eq "Build-Samples.ps1" -or $filename -eq "exclusions.csv" -or $filename -eq "Directory.Build.props" -or $filename -eq "packages.config") { + if ($filename -eq "Build-Samples.ps1" -or $filename -eq "Get-NtTargetVersions.ps1" -or $filename -eq "exclusions.csv" -or $filename -eq "Directory.Build.props" -or $filename -eq "packages.config") { $buildAll = $true } if ($dir -like "$root\.github\scripts" -or $dir -like "$root\.github\scripts\*") { diff --git a/.github/scripts/Join-CsvReports.ps1 b/.github/scripts/Join-CsvReports.ps1 index 32cd9d63e..43e0c826b 100644 --- a/.github/scripts/Join-CsvReports.ps1 +++ b/.github/scripts/Join-CsvReports.ps1 @@ -1,35 +1,201 @@ -$logsPath = Join-Path (Get-Location).Path "_logs" +<# +.SYNOPSIS + Joins the per-job Build-Samples CSV reports (one per _NT_TARGET_VERSION x configuration x + platform) into a single overview, and writes an easy-to-scan summary to the GitHub Actions + run page ($GITHUB_STEP_SUMMARY). + +.DESCRIPTION + Each build job uploads a "_logs" folder containing a report named + _overview....csv + with columns: Sample, (one combination per file). This script: + * parses the _NT_TARGET_VERSION tag and combination from every report, + * collapses each sample's combinations into one status per version, + * writes _overview.all.csv / _overview.all.htm (a colour-coded sample x version matrix), and + * appends a Markdown summary (per-version totals + a failures table) to $GITHUB_STEP_SUMMARY + so failures are obvious from the run page without opening any logs. + + The older 2-part name (_overview...csv, no version) is still + understood and bucketed under the "latest" column. +#> + +$logsPath = Join-Path (Get-Location).Path "_logs" $reportFileName = '_overview.all' -$idProperty = 'Sample' -$results = $null -Get-ChildItem -Path $logsPath -Filter '*.csv' | ForEach-Object { - $csv = Import-Csv -Path $_ - if ($results -eq $null) { - $results = $csv +if (-not (Test-Path $logsPath)) { + Write-Warning "No _logs directory found at $logsPath - nothing to report." + return +} + +# --- Load every per-job CSV --------------------------------------------------- +# data[sample][tag][combo] = status +$data = @{} +$allSamples = [System.Collections.Generic.SortedSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) +$tagSet = [System.Collections.Generic.HashSet[string]]::new() + +Get-ChildItem -Path $logsPath -Filter '_overview.*.csv' | + Where-Object { $_.Name -notlike '_overview.all.*' } | + ForEach-Object { + # _overview... -> drop _overview; last two are config/platform. + $parts = [IO.Path]::GetFileNameWithoutExtension($_.Name).Split('.') + $parts = $parts[1..($parts.Count - 1)] # drop the leading "_overview" + if ($parts.Count -ge 3) { $tag = ($parts[0..($parts.Count - 3)] -join '.') } + else { $tag = 'latest' } + [void]$tagSet.Add($tag) + + Import-Csv -Path $_.FullName | ForEach-Object { + $sample = $_.Sample + if (-not $sample) { return } + [void]$allSamples.Add($sample) + if (-not $data.ContainsKey($sample)) { $data[$sample] = @{} } + if (-not $data[$sample].ContainsKey($tag)) { $data[$sample][$tag] = @{} } + foreach ($col in ($_.PSObject.Properties.Name | Where-Object { $_ -ne 'Sample' })) { + $data[$sample][$tag][$col] = "$($_.$col)".Trim() + } + } } - else { - $results = $csv | ForEach-Object { - $id = $_.$idProperty - $match = $results | Where-Object { $_.$idProperty -eq $id } - if ($match) { - $properties = $_ | Get-Member -MemberType NoteProperty | Where-Object { $_.Name -ne $idProperty } | Select-Object -ExpandProperty Name - $newObject = New-Object PSObject - # Add ID property separately to ensure it appears first - $newObject | Add-Member -MemberType NoteProperty -Name $idProperty -Value $_.$idProperty - foreach ($property in $properties) { - $newObject | Add-Member -MemberType NoteProperty -Name $property -Value $_.$property - } - foreach ($property in ($match | Get-Member -MemberType NoteProperty | Where-Object { $_.Name -ne $idProperty } | Select-Object -ExpandProperty Name)) { - if ($properties -notcontains $property) { - $newObject | Add-Member -MemberType NoteProperty -Name $property -Value $match.$property - } - } - $newObject + +if ($tagSet.Count -eq 0) { + Write-Warning "No per-job '_overview.*.csv' reports were found in $logsPath." + return +} + +# Order versions newest-first (numeric tags descending; non-numeric last) +$tags = $tagSet | Sort-Object @{ Expression = { if ($_ -match '^\d+$') { [int]$_ } else { 0 } }; Descending = $true }, @{ Expression = { $_ } } + +function Get-Status { + # Collapse one sample/version's combinations into a single status object. + param([hashtable]$Combos) + $c = @{ Succeeded = 0; Failed = 0; Sporadic = 0; Unsupported = 0; Excluded = 0 } + $details = @() + if ($Combos) { + foreach ($k in ($Combos.Keys | Sort-Object)) { + switch ($Combos[$k]) { + 'Succeeded' { $c.Succeeded++ } 'Failed' { $c.Failed++ } 'Sporadic' { $c.Sporadic++ } + 'Unsupported' { $c.Unsupported++ } 'Excluded' { $c.Excluded++ } + } + $details += "$k = $($Combos[$k])" + } + } + $buildable = $c.Succeeded + $c.Failed + $c.Sporadic + if (-not $Combos -or $Combos.Count -eq 0) { $label = 'n/a'; $klass = 'na' } + elseif ($buildable -eq 0) { $label = '--'; $klass = 'na' } + elseif ($c.Failed -eq 0 -and $c.Sporadic -eq 0) { $label = "PASS ($($c.Succeeded)/$buildable)"; $klass = 'pass' } + elseif ($c.Failed -eq 0) { $label = "PASS* ($($c.Succeeded + $c.Sporadic)/$buildable)"; $klass = 'flaky' } + elseif ($c.Failed -eq $buildable) { $label = "FAIL ($($c.Failed)/$buildable)"; $klass = 'fail' } + else { $label = "PARTIAL ($($c.Failed) failed / $buildable)"; $klass = 'partial' } + [pscustomobject]@{ Label = $label; Class = $klass; Tooltip = ($details -join ' | '); Counts = $c; Buildable = $buildable } +} + +# --- Build per-version totals + the matrix ------------------------------------ +$totals = @{}; foreach ($t in $tags) { $totals[$t] = [pscustomobject]@{ S = 0; F = 0; O = 0; U = 0; E = 0; pass = 0; flaky = 0; partial = 0; fail = 0; na = 0 } } +$failuresList = [System.Collections.ArrayList]::new() +$csvRows = @() +$bodyRows = New-Object System.Text.StringBuilder + +foreach ($sample in $allSamples) { + $csvRow = [ordered]@{ Sample = $sample } + $cells = '' + foreach ($t in $tags) { + $combos = $null + if ($data[$sample].ContainsKey($t)) { $combos = $data[$sample][$t] } + $st = Get-Status -Combos $combos + $tt = $totals[$t] + $tt.S += $st.Counts.Succeeded; $tt.F += $st.Counts.Failed; $tt.O += $st.Counts.Sporadic + $tt.U += $st.Counts.Unsupported; $tt.E += $st.Counts.Excluded + switch ($st.Class) { 'pass' { $tt.pass++ } 'flaky' { $tt.flaky++ } 'partial' { $tt.partial++ } 'fail' { $tt.fail++ } 'na' { $tt.na++ } } + $csvRow["$t"] = $st.Label + $tip = [System.Web.HttpUtility]::HtmlEncode($st.Tooltip) + $cells += "$($st.Label)" + if ($combos) { + foreach ($k in ($combos.Keys | Sort-Object)) { + if ($combos[$k] -eq 'Failed') { [void]$failuresList.Add([pscustomobject]@{ Sample = $sample; Version = $t; Combo = $k }) } } } } + $csvRows += [pscustomobject]$csvRow + $enc = [System.Web.HttpUtility]::HtmlEncode($sample) + [void]$bodyRows.Append("$enc$cells`n") } -$results | ConvertTo-Csv | Out-File (Join-Path $logsPath "$reportFileName.csv") -$results | ConvertTo-Html -Title "Overview" | Out-File (Join-Path $logsPath "$reportFileName.htm") +Add-Type -AssemblyName System.Web -ErrorAction SilentlyContinue + +# --- CSV ---------------------------------------------------------------------- +$csvRows | Export-Csv -Path (Join-Path $logsPath "$reportFileName.csv") -NoTypeInformation + +# --- HTML (colour-coded sample x version matrix) ------------------------------ +$generated = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' +$sumHead = "_NT_TARGET_VERSIONPassFlakyPartialFailn/aCombos OKSporadicFailedExcludedPass rate" +$sumRows = '' +foreach ($t in $tags) { + $x = $totals[$t]; $tot = $x.pass + $x.flaky + $x.partial + $x.fail + $x.na; $elig = $tot - $x.na + $rate = if ($elig -gt 0) { '{0:N0}%' -f (100.0 * ($x.pass + $x.flaky) / $elig) } else { 'n/a' } + $sumRows += "$t$($x.pass)$($x.flaky)$($x.partial)$($x.fail)$($x.na)$($x.S)$($x.O)$($x.F)$($x.E)$rate`n" +} +$matHead = "Sample" +foreach ($t in $tags) { $matHead += "$t" } +$matHead += "" + +$html = @" + +WDK Driver Samples - Build Overview + +

WDK Driver Samples — Build Overview

+
Generated: $generated  |  columns are _NT_TARGET_VERSION (library link version); hover a cell for the per-combination breakdown.
+
PASSPASS* (retry)PARTIALFAIL-- n/a
+

Summary by _NT_TARGET_VERSION

+$sumHead +$sumRows
+

Sample × _NT_TARGET_VERSION

+$matHead +$($bodyRows.ToString())
+ +"@ +$html | Out-File -FilePath (Join-Path $logsPath "$reportFileName.htm") -Encoding UTF8 + +# --- GitHub Actions run summary (Markdown) ------------------------------------ +if ($env:GITHUB_STEP_SUMMARY) { + $totalFailed = ($totals.Values | Measure-Object -Property fail -Sum).Sum + ($totals.Values | Measure-Object -Property partial -Sum).Sum + $icon = if ($failuresList.Count -gt 0) { ':x:' } else { ':white_check_mark:' } + + $md = [System.Text.StringBuilder]::new() + [void]$md.AppendLine("# $icon WDK Driver Samples — Build Overview") + [void]$md.AppendLine() + [void]$md.AppendLine("Columns are **_NT_TARGET_VERSION** (the WDK library version drivers link against). Each version was built for Debug/Release x x64/arm64.") + [void]$md.AppendLine() + [void]$md.AppendLine("## Summary by _NT_TARGET_VERSION") + [void]$md.AppendLine("| _NT_TARGET_VERSION | :white_check_mark: Pass | :warning: Flaky | :large_orange_diamond: Partial | :x: Fail | :heavy_minus_sign: n/a | Pass rate |") + [void]$md.AppendLine("|---|---:|---:|---:|---:|---:|---:|") + foreach ($t in $tags) { + $x = $totals[$t]; $tot = $x.pass + $x.flaky + $x.partial + $x.fail + $x.na; $elig = $tot - $x.na + $rate = if ($elig -gt 0) { '{0:N0}%' -f (100.0 * ($x.pass + $x.flaky) / $elig) } else { 'n/a' } + [void]$md.AppendLine("| ``$t`` | $($x.pass) | $($x.flaky) | $($x.partial) | $($x.fail) | $($x.na) | **$rate** |") + } + [void]$md.AppendLine() + + if ($failuresList.Count -gt 0) { + [void]$md.AppendLine("## :x: Failures ($($failuresList.Count))") + [void]$md.AppendLine("| Sample | _NT_TARGET_VERSION | Config/Platform |") + [void]$md.AppendLine("|---|---|---|") + foreach ($f in ($failuresList | Sort-Object Sample, Version, Combo)) { + [void]$md.AppendLine("| ``$($f.Sample)`` | $($f.Version) | $($f.Combo.Replace('|','/')) |") + } + [void]$md.AppendLine() + [void]$md.AppendLine("> Open the matching **build** job's summary (or the ``logs-*`` artifact) for the exact compiler error.") + } + else { + [void]$md.AppendLine(":tada: **All combinations built successfully.**") + } + + $md.ToString() | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8 +} diff --git a/.github/workflows/ci-pr.yml b/.github/workflows/ci-pr.yml index 6719270e7..8119c94c4 100644 --- a/.github/workflows/ci-pr.yml +++ b/.github/workflows/ci-pr.yml @@ -8,13 +8,39 @@ on: - '**.md' - 'LICENSE' jobs: + # Auto-discover the available _NT_TARGET_VERSION values from the active WDK so the build + # matrix never needs a hand-maintained version list. Change -Newest to build more/fewer. + discover: + name: discover _NT_TARGET_VERSIONs + runs-on: windows-2025-vs2026 + outputs: + versions: ${{ steps.nt.outputs.versions }} + steps: + - name: Check out repository code + uses: actions/checkout@v4 + + - name: Install Nuget Packages + run: nuget restore .\packages.config -PackagesDirectory .\packages\ + + - name: Discover the newest _NT_TARGET_VERSION values + id: nt + shell: pwsh + run: | + $json = .\Get-NtTargetVersions.ps1 -Newest 4 -AsMatrixJson + "versions=$json" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 + Write-Host "Discovered _NT_TARGET_VERSION matrix: $json" + build: - name: Build driver samples + name: build ${{ matrix.nt.tag }} ${{ matrix.configuration }} ${{ matrix.platform }} + needs: discover strategy: fail-fast: false matrix: configuration: [Debug, Release] platform: [x64, arm64] + # _NT_TARGET_VERSION values are auto-discovered by the 'discover' job from the active + # WDK, so there is no version list to maintain here. + nt: ${{ fromJSON(needs.discover.outputs.versions) }} runs-on: windows-2025-vs2026 steps: - name: Check out repository code @@ -38,13 +64,14 @@ jobs: env: WDS_Configuration: ${{ matrix.configuration }} WDS_Platform: ${{ matrix.platform }} - WDS_ReportFileName: _overview.${{ matrix.configuration }}.${{ matrix.platform }} + WDS_NtTargetVersion: ${{ matrix.nt.version }} + WDS_ReportFileName: _overview.${{ matrix.nt.tag }}.${{ matrix.configuration }}.${{ matrix.platform }} - name: Archive build logs and overview build reports uses: actions/upload-artifact@v4 if: always() with: - name: logs-${{ matrix.configuration }}-${{ matrix.platform }} + name: logs-${{ matrix.nt.tag }}-${{ matrix.configuration }}-${{ matrix.platform }} path: _logs report: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca2696d89..de856d556 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,13 +11,39 @@ on: # Runs every Saturday at 00:00 PST (08:00 UTC) - cron: '0 8 * * 6' jobs: + # Auto-discover the available _NT_TARGET_VERSION values from the active WDK so the build + # matrix never needs a hand-maintained version list. Change -Newest to build more/fewer. + discover: + name: discover _NT_TARGET_VERSIONs + runs-on: windows-2025-vs2026 + outputs: + versions: ${{ steps.nt.outputs.versions }} + steps: + - name: Check out repository code + uses: actions/checkout@v4 + + - name: Install Nuget Packages + run: nuget restore .\packages.config -PackagesDirectory .\packages\ + + - name: Discover the newest _NT_TARGET_VERSION values + id: nt + shell: pwsh + run: | + $json = .\Get-NtTargetVersions.ps1 -Newest 4 -AsMatrixJson + "versions=$json" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 + Write-Host "Discovered _NT_TARGET_VERSION matrix: $json" + build: - name: Build driver samples + name: build ${{ matrix.nt.tag }} ${{ matrix.configuration }} ${{ matrix.platform }} + needs: discover strategy: fail-fast: false matrix: configuration: [Debug, Release] platform: [x64, arm64] + # _NT_TARGET_VERSION values are auto-discovered by the 'discover' job from the active + # WDK, so there is no version list to maintain here. + nt: ${{ fromJSON(needs.discover.outputs.versions) }} runs-on: windows-2025-vs2026 steps: - name: Check out repository code @@ -33,13 +59,14 @@ jobs: env: WDS_Configuration: ${{ matrix.configuration }} WDS_Platform: ${{ matrix.platform }} - WDS_ReportFileName: _overview.${{ matrix.configuration }}.${{ matrix.platform }} + WDS_NtTargetVersion: ${{ matrix.nt.version }} + WDS_ReportFileName: _overview.${{ matrix.nt.tag }}.${{ matrix.configuration }}.${{ matrix.platform }} - name: Archive build logs and overview build reports uses: actions/upload-artifact@v4 if: always() with: - name: logs-${{ matrix.configuration }}-${{ matrix.platform }} + name: logs-${{ matrix.nt.tag }}-${{ matrix.configuration }}-${{ matrix.platform }} path: _logs report: diff --git a/Build-Samples.ps1 b/Build-Samples.ps1 index a73a2afa1..e0b1a6b35 100644 --- a/Build-Samples.ps1 +++ b/Build-Samples.ps1 @@ -27,6 +27,14 @@ .PARAMETER Platforms Build platforms (e.g. 'x64','arm64'). Defaults to $env:WDS_Platform or ('x64','arm64'). +.PARAMETER NtTargetVersion + The _NT_TARGET_VERSION value - the WDK library version the driver links against + ("OS version of libraries"). Accepts the Windows build-number form '10.0.' or the + short '' tag (e.g. '10.0.28000' or '28000'). The valid values are auto-discovered + from the active WDK's DriverGeneral.xml rule (see Get-NtTargetVersions.ps1), so a new WDK + version is picked up with no script change. Defaults to $env:WDS_NtTargetVersion, or the + latest discovered version when unset. + .PARAMETER LogFilesDirectory Directory for build log files. Defaults to _logs in the current directory. @@ -69,6 +77,11 @@ .\Build-Samples -RunMode WDK Forces WDK mode regardless of environment variables. + +.EXAMPLE + .\Build-Samples -NtTargetVersion 10.0.22000 + + Builds all samples linking against the 10.0.22000 library set instead of the latest. #> #Requires -Version 7.0 @@ -78,6 +91,9 @@ param( [string[]]$Samples, [string[]]$Configurations = @(if ([string]::IsNullOrEmpty($env:WDS_Configuration)) { ('Debug', 'Release') } else { $env:WDS_Configuration }), [string[]]$Platforms = @(if ([string]::IsNullOrEmpty($env:WDS_Platform)) { ('x64', 'arm64') } else { $env:WDS_Platform }), + # _NT_TARGET_VERSION = the WDK library version the driver links against. Valid values are + # auto-discovered from the WDK (Get-NtTargetVersions.ps1); empty = the latest discovered. + [string]$NtTargetVersion = $env:WDS_NtTargetVersion, [string]$LogFilesDirectory = (Join-Path (Get-Location) "_logs"), [string]$ReportFileName = $(if ([string]::IsNullOrEmpty($env:WDS_ReportFileName)) { "_overview" } else { $env:WDS_ReportFileName }), [string]$InfOptions, @@ -102,15 +118,20 @@ function Import-SampleExclusions { - Configurations: semicolon-separated config|platform patterns (or '*' for all) - Reason: human-readable explanation - Only exclusions whose [MinBuild, MaxBuild] range includes the given build number - are returned. Exclusions outside the range are silently skipped. + A row is only returned when ALL of the following match the current build: + - its [MinBuild, MaxBuild] range includes the given build number, and + - its [MinNtTargetVersion, MaxNtTargetVersion] range includes the current + _NT_TARGET_VERSION build number (e.g. 22000 parsed from '10.0.22000'). + Rows outside either range are skipped; blank range bounds mean unbounded. .NOTES - CSV format: Path,Configurations,MinBuild,MaxBuild,Reason - Example row: network\wlan\wdi,*,,27100,"failure introduced in VS17.14" + CSV format: Path,Configurations,MinBuild,MaxBuild,MinNtTargetVersion,MaxNtTargetVersion,Reason + Example row: network\wlan\wdi,*,26100,,,,"failure introduced in VS17.14" + NT-version-specific: somepath,*,,,,22621,"needs an API newer than the 10.0.22621 library" #> param( [string]$CsvPath, - [int]$BuildNumber + [int]$BuildNumber, + [string]$NtTargetVersion ) if (-not (Test-Path $CsvPath)) { @@ -118,20 +139,38 @@ function Import-SampleExclusions { return @() } + # The _NT_TARGET_VERSION param is the friendly build-number form (e.g. '10.0.22000'); + # take its last dotted component for numeric range comparisons. An empty value (or + # 'latest') means the newest libraries, so no NT-version-scoped row applies. + $ntBuild = if ([string]::IsNullOrWhiteSpace($NtTargetVersion) -or $NtTargetVersion -eq 'latest') { + [int]::MaxValue + } + else { + [int]($NtTargetVersion -replace '.*\.', '') + } + $exclusions = [System.Collections.ArrayList]::new() Import-Csv $CsvPath | ForEach-Object { $pattern = $_.Path.Trim('\').Replace('\', '.').ToLower() $configs = if ([string]::IsNullOrWhiteSpace($_.Configurations)) { '*' } else { $_.Configurations } $minBuild = if ([string]::IsNullOrWhiteSpace($_.MinBuild)) { 0 } else { [int]$_.MinBuild } $maxBuild = if ([string]::IsNullOrWhiteSpace($_.MaxBuild)) { 99999 } else { [int]$_.MaxBuild } - - if ($minBuild -le $BuildNumber -and $BuildNumber -le $maxBuild) { + # Min/MaxNtTargetVersion columns are optional; blank or missing means "all NT versions". + $minNt = if ([string]::IsNullOrWhiteSpace($_.MinNtTargetVersion)) { 0 } else { [int]$_.MinNtTargetVersion } + $maxNt = if ([string]::IsNullOrWhiteSpace($_.MaxNtTargetVersion)) { [int]::MaxValue } else { [int]$_.MaxNtTargetVersion } + + # _NT_TARGET_VERSION is constant for the whole run, so (like the build number) filter + # these rows out here at load time. + if ($ntBuild -lt $minNt -or $ntBuild -gt $maxNt) { + Write-Verbose "Exclusion skipped: '$pattern' - _NT_TARGET_VERSION $ntBuild outside [$minNt, $maxNt]" + } + elseif ($minBuild -le $BuildNumber -and $BuildNumber -le $maxBuild) { [void]$exclusions.Add([PSCustomObject]@{ Pattern = $pattern Configurations = $configs Reason = $_.Reason }) - Write-Verbose "Exclusion applied: '$pattern' configs='$configs' reason='$($_.Reason)'" + Write-Verbose "Exclusion applied: '$pattern' configs='$configs' ntRange=[$minNt,$maxNt] reason='$($_.Reason)'" } else { Write-Verbose "Exclusion skipped: '$pattern' - build $BuildNumber outside [$minBuild, $maxBuild]" @@ -172,6 +211,7 @@ function Build-SingleSample { [string]$SampleName, [string]$Configuration = 'Debug', [string]$Platform = 'x64', + [string]$NtTargetVersionCode, [string]$InfVerif_AdditionalOptions = '/samples', [string]$LogFilesDirectory = (Get-Location), [bool]$Verbose = $false @@ -249,6 +289,7 @@ function Build-SingleSample { -property:Configuration=$Configuration ` -property:Platform=$Platform ` -p:TargetVersion=Windows10 ` + -p:_NT_TARGET_VERSION=$NtTargetVersionCode ` -p:InfVerif_AdditionalOptions="$InfVerif_AdditionalOptions" ` -warnaserror ` -binaryLogger:LogFile=$binLogFilePath`;ProjectImports=None ` @@ -392,11 +433,37 @@ else { $infVerifOptions = if ($buildNumber -le 22621) { '/sw1284 /sw1285 /sw1293 /sw2083 /sw2086' } else { '/samples' } } +# ============================================================================= +# Step 5b - Resolve _NT_TARGET_VERSION +# ============================================================================= +# +# _NT_TARGET_VERSION selects the WDK library version the driver links against. The valid +# values (and their NTDDI codes) are auto-discovered from the active WDK by +# Get-NtTargetVersions.ps1, so nothing here needs updating when a new WDK version ships. +# msbuild takes the NTDDI code. +$ntVersions = & (Join-Path $PSScriptRoot 'Get-NtTargetVersions.ps1') +if (-not $ntVersions) { + Write-Error "Could not discover any _NT_TARGET_VERSION values from the active WDK." + exit 1 +} +if ([string]::IsNullOrWhiteSpace($NtTargetVersion) -or $NtTargetVersion -eq 'latest') { + $ntSelected = $ntVersions[0] # newest +} +else { + $ntSelected = $ntVersions | Where-Object { $_.Version -eq $NtTargetVersion -or $_.Tag -eq $NtTargetVersion } | Select-Object -First 1 + if (-not $ntSelected) { + Write-Error "Invalid -NtTargetVersion '$NtTargetVersion'. Valid values: $(($ntVersions.Version) -join ', ')" + exit 1 + } +} +$NtTargetVersion = $ntSelected.Version +$ntTargetVersionCode = $ntSelected.Code + # ============================================================================= # Step 6 - Load Exclusions # ============================================================================= -$exclusions = Import-SampleExclusions -CsvPath (Join-Path $root 'exclusions.csv') -BuildNumber $buildNumber +$exclusions = Import-SampleExclusions -CsvPath (Join-Path $root 'exclusions.csv') -BuildNumber $buildNumber -NtTargetVersion $NtTargetVersion # ============================================================================= # Step 7 - Print Build Plan @@ -417,6 +484,7 @@ Write-Output "" Write-Output " Samples: $($sampleSet.Count) ($skippedCount skipped)" Write-Output " Configurations: $($Configurations -join ', ')" Write-Output " Platforms: $($Platforms -join ', ')" +Write-Output " NT Target Ver: $NtTargetVersion ($ntTargetVersionCode)" Write-Output " Combinations: $combinationsTotal" Write-Output " Exclusions: $($exclusions.Count)" Write-Output "" @@ -463,6 +531,7 @@ $sampleSet.GetEnumerator() | ForEach-Object -ThrottleLimit $ThrottleLimit -Paral $configs = $using:Configurations $platforms = $using:Platforms $infOpts = $using:infVerifOptions + $ntCode = $using:ntTargetVersionCode $isVerbose = $using:verbose $state = $using:buildState $total = $using:combinationsTotal @@ -513,7 +582,8 @@ $sampleSet.GetEnumerator() | ForEach-Object -ThrottleLimit $ThrottleLimit -Paral $buildResult = Build-SingleSample ` -Directory $directory -SampleName $sampleName ` -LogFilesDirectory $logDir -Configuration $configuration ` - -Platform $platform -InfVerif_AdditionalOptions $infOpts ` + -Platform $platform -NtTargetVersionCode $ntCode ` + -InfVerif_AdditionalOptions $infOpts ` -Verbose:$isVerbose # Return codes from Build-SingleSample: @@ -631,6 +701,7 @@ Write-Output "" Write-Output " Samples: $($sampleSet.Count)" Write-Output " Configurations: $($Configurations -join ', ')" Write-Output " Platforms: $($Platforms -join ', ')" +Write-Output " NT Target Ver: $NtTargetVersion ($ntTargetVersionCode)" Write-Output " Combinations: $combinationsTotal" Write-Output "" Write-Output " Succeeded: $($buildState.Succeeded)" @@ -650,9 +721,61 @@ Write-Output "------------------------------------------------------------------ $sortedResults = $buildState.Results | Sort-Object { $_.Sample } $sortedResults | ConvertTo-Csv | Out-File $reportCsvPath -$sortedResults | ConvertTo-Html -Title "WDK Sample Build Overview" | Out-File $reportHtmlPath +$sortedResults | ConvertTo-Html -Title "WDK Sample Build Overview - _NT_TARGET_VERSION $NtTargetVersion" | Out-File $reportHtmlPath # Only open the HTML report interactively (not in CI/automation) if (-not $env:BUILD_BUILDID -and [Environment]::UserInteractive) { Invoke-Item $reportHtmlPath } + +# ============================================================================= +# Step 12 - GitHub Actions job summary (CI only; no-op when run locally) +# ============================================================================= +# When $GITHUB_STEP_SUMMARY is set, emit an easy-to-scan markdown summary for the run +# page: a status header, a counts table, and (if any) a table of failures with the first +# compiler/linker error so problems are obvious without opening the logs. +if ($env:GITHUB_STEP_SUMMARY) { + $icon = if ($buildState.Failed -gt 0) { ':x:' } elseif ($buildState.Sporadic -gt 0) { ':warning:' } else { ':white_check_mark:' } + $cfgLabel = "$($Configurations -join ',')|$($Platforms -join ',')" + + $md = [System.Text.StringBuilder]::new() + [void]$md.AppendLine("## $icon ``$cfgLabel``  ·  _NT_TARGET_VERSION ``$NtTargetVersion``") + [void]$md.AppendLine() + [void]$md.AppendLine("Environment **$($buildEnv.Name)** · WDK build **$buildNumber** · **$($sampleSet.Count)** samples · $($elapsed.Minutes)m $($elapsed.Seconds)s") + [void]$md.AppendLine() + [void]$md.AppendLine("| :white_check_mark: Succeeded | :x: Failed | :warning: Sporadic | :heavy_minus_sign: Excluded | :grey_question: Unsupported |") + [void]$md.AppendLine("|---:|---:|---:|---:|---:|") + [void]$md.AppendLine("| $($buildState.Succeeded) | $($buildState.Failed) | $($buildState.Sporadic) | $($buildState.Excluded) | $($buildState.Unsupported) |") + [void]$md.AppendLine() + + if ($buildState.FailSet.Count -gt 0) { + [void]$md.AppendLine("
:x: $($buildState.FailSet.Count) failed") + [void]$md.AppendLine() + [void]$md.AppendLine("| Sample | Config/Platform | First error |") + [void]$md.AppendLine("|---|---|---|") + foreach ($entry in ($buildState.FailSet | Sort-Object)) { + if ($entry -match '^(?.*)\s+(?\w+)\|(?\w+)$') { + $fName = $Matches.name; $fConfig = $Matches.config; $fPlatform = $Matches.platform + $errLog = Join-Path $LogFilesDirectory "$fName.$fConfig.$fPlatform.0.err" + $msg = '' + if (Test-Path $errLog) { + $line = Get-Content $errLog | Where-Object { $_ -match ': (error|fatal error) ' } | Select-Object -First 1 + if ($line -match ':\s*((?:fatal )?error\s.+?)\s*\[[^\[]*\]\s*$') { $msg = $Matches[1] } else { $msg = $line } + } + $msg = ("$msg" -replace '\|', '\|').Trim() + if ($msg.Length -gt 180) { $msg = $msg.Substring(0, 177) + '...' } + [void]$md.AppendLine("| ``$fName`` | $fConfig/$fPlatform | $msg |") + } + } + [void]$md.AppendLine("
") + [void]$md.AppendLine() + } + + if ($buildState.SporadicSet.Count -gt 0) { + $sp = ($buildState.SporadicSet | Sort-Object | ForEach-Object { "``$_``" }) -join ', ' + [void]$md.AppendLine(":warning: **Sporadic** (passed on retry): $sp") + [void]$md.AppendLine() + } + + $md.ToString() | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8 +} diff --git a/Building-Locally.md b/Building-Locally.md index 9d683a86a..ebddbfbad 100644 --- a/Building-Locally.md +++ b/Building-Locally.md @@ -120,6 +120,52 @@ Get-Help .\Build-Samples.ps1 -Detailed # Build a specific sample for Debug|x64 only: .\Build-Samples.ps1 -Samples 'tools.sdv.samples.sampledriver' -Configurations 'Debug' -Platforms 'x64' + +# Build every sample linking against an older WDK library set (default is the latest): +.\Build-Samples.ps1 -NtTargetVersion 10.0.22000 +``` + +`-NtTargetVersion` selects the WDK **`_NT_TARGET_VERSION`** — the OS version of the libraries +the driver links against. It accepts the Windows build number (`10.0.`) or the short +`` tag (e.g. `10.0.22000` or `22000`); when omitted it uses the latest. The valid +values are **auto-discovered from the active WDK** — `Get-NtTargetVersions.ps1` parses the +WDK's `DriverGeneral.xml` rule — so a new WDK version is picked up automatically with no edits. +List what's available with: + +```powershell +.\Get-NtTargetVersions.ps1 +``` + +--- + +## Excluding samples from the build + +Samples that are known not to build for a given environment are listed in `exclusions.csv` +at the repo root. Each row excludes a path (with wildcards) for specific +configuration/platform combinations, an optional WDK build-number range, and an optional +`_NT_TARGET_VERSION` range: + +``` +Path,Configurations,MinBuild,MaxBuild,MinNtTargetVersion,MaxNtTargetVersion,Reason +``` + +| Column | Meaning | +| ---------------- | ---------------------------------------------------------------------------------------- | +| `Path` | Sample path (backslashes); supports `*`/`?` wildcards. | +| `Configurations` | `;`-separated `Config\|Platform` patterns, or `*` for all (e.g. `*\|ARM64`, `Debug\|x64`). | +| `MinBuild`/`MaxBuild` | Inclusive WDK build-number range; blank = unbounded. | +| `MinNtTargetVersion`/`MaxNtTargetVersion` | Inclusive `-NtTargetVersion` build-number range (e.g. `22621` matches `10.0.22621`); blank = unbounded. Use this for samples that fail only when linking against older libraries. | +| `Reason` | Human-readable explanation (keep this column last; quote it if it contains commas). | + +A row is applied only when every populated condition matches the current run (path, +configuration/platform, WDK build-number range, and NT target-version range are AND-ed +together). Leave a column blank to ignore that dimension (the default for most rows). + +For example, to exclude a sample (Debug builds only) when linking against the `10.0.22621` +library set or older, because it uses a newer API: + +``` +somepath,Debug|*,,,,22621,uses an API newer than the 10.0.22621 library ``` --- diff --git a/Get-NtTargetVersions.ps1 b/Get-NtTargetVersions.ps1 new file mode 100644 index 000000000..680882777 --- /dev/null +++ b/Get-NtTargetVersions.ps1 @@ -0,0 +1,100 @@ +<# +.SYNOPSIS + Auto-discovers the valid _NT_TARGET_VERSION values from the active WDK. + +.DESCRIPTION + The _NT_TARGET_VERSION property (the OS version of the libraries a driver links against) + is an enumeration defined by the WDK in its 'DriverGeneral.xml' rule file. This script + locates that rule file (from the restored NuGet packages, or the installed WDK) and parses + the enumeration so that nothing in the build needs a hard-coded version list: when a new + WDK adds a new _NT_TARGET_VERSION it is picked up automatically. + + Returns one object per Windows 10/11 entry, newest-first: + Version e.g. 10.0.28000 (use with -NtTargetVersion) + Tag e.g. 28000 (short, filename/CI-friendly) + Code e.g. 0xA000012 (the NTDDI value passed to msbuild) + Build e.g. 28000 (numeric, for sorting/ranges) + +.PARAMETER XmlPath + Optional explicit path to a DriverGeneral.xml. When omitted the newest available rule file + is auto-located. + +.PARAMETER Newest + Return only the newest N versions (0 = all). Useful for bounding the CI build matrix. + +.PARAMETER AsMatrixJson + Emit a compact JSON array of { version, tag } objects for a GitHub Actions matrix + (consumed via fromJSON). Implies a single-line output. + +.EXAMPLE + .\Get-NtTargetVersions.ps1 # all discovered versions (objects) + +.EXAMPLE + .\Get-NtTargetVersions.ps1 -Newest 4 -AsMatrixJson +#> +[CmdletBinding()] +param( + [string]$XmlPath, + [int]$Newest = 0, + [switch]$AsMatrixJson +) + +function Find-DriverGeneralXml { + # Prefer the restored NuGet WDK package (matches what the build actually uses), then the + # installed WDK. Within each source, pick the highest build version. + $candidates = @() + $candidates += Get-ChildItem -Path (Join-Path $PSScriptRoot 'packages') -Recurse -Filter 'DriverGeneral.xml' -ErrorAction SilentlyContinue + foreach ($kitsRoot in @("${env:ProgramFiles(x86)}\Windows Kits\10\build", "${env:ProgramFiles}\Windows Kits\10\build")) { + if ($kitsRoot -and (Test-Path $kitsRoot)) { + $candidates += Get-ChildItem -Path $kitsRoot -Recurse -Filter 'DriverGeneral.xml' -ErrorAction SilentlyContinue + } + } + # EWDK / arbitrary build environments expose the build tree via these variables. + foreach ($envRoot in @($env:WDKContentRoot, $env:WindowsSdkDir)) { + if ($envRoot -and (Test-Path $envRoot)) { + $buildDir = Join-Path $envRoot 'build' + if (Test-Path $buildDir) { + $candidates += Get-ChildItem -Path $buildDir -Recurse -Filter 'DriverGeneral.xml' -ErrorAction SilentlyContinue + } + } + } + if (-not $candidates) { return $null } + # Order by the build version embedded in the path (e.g. ...\10.0.28000.0\...), highest first. + return ($candidates | Sort-Object { + if ($_.FullName -match '10\.0\.(\d+)\.\d') { [int]$Matches[1] } else { 0 } + } -Descending | Select-Object -First 1).FullName +} + +if (-not $XmlPath) { $XmlPath = Find-DriverGeneralXml } +if (-not $XmlPath -or -not (Test-Path $XmlPath)) { + throw "Could not locate DriverGeneral.xml. Restore the WDK NuGet packages or install the WDK, or pass -XmlPath." +} + +[xml]$xml = Get-Content -Path $XmlPath -Raw +$enum = $xml.ProjectSchemaDefinitions.Rule.EnumProperty | Where-Object { $_.Name -eq '_NT_TARGET_VERSION' } +if (-not $enum) { throw "No _NT_TARGET_VERSION enumeration found in '$XmlPath'." } + +$versions = + $enum.EnumValue | + ForEach-Object { + # DisplayName is e.g. "Windows 10.0.28000"; Name is the NTDDI code e.g. "0xA000012". + if ("$($_.DisplayName)" -match 'Windows\s+(?10\.0\.(?\d+))\s*$') { + [pscustomobject]@{ + Version = $Matches.v + Tag = $Matches.b + Code = $_.Name + Build = [int]$Matches.b + } + } + } | + Sort-Object Build -Descending + +if ($Newest -gt 0) { $versions = $versions | Select-Object -First $Newest } + +if ($AsMatrixJson) { + # Compact, single-line JSON for a GitHub Actions matrix: [{ "version": "...", "tag": "..." }, ...] + $matrix = @($versions | ForEach-Object { [ordered]@{ version = $_.Version; tag = $_.Tag } }) + return ($matrix | ConvertTo-Json -Compress -Depth 3) +} + +return $versions diff --git a/exclusions.csv b/exclusions.csv index df12335dc..0cae0380f 100644 --- a/exclusions.csv +++ b/exclusions.csv @@ -1,12 +1,20 @@ -Path,Configurations,MinBuild,MaxBuild,Reason -audio\acx\samples\audiocodec\driver,*,,22621,Only NI: error C1083: Cannot open include file: 'acx.h': No such file or directory -general\dchu\osrfx2_dchu_extension_loose,*|x64,,22621,Only NI: Only x64: Fails to build -general\dchu\osrfx2_dchu_extension_tight,*|x64,,22621,Only NI: Only x64: Fails to build -network\trans\WFPSampler,Debug|ARM64,,22621,Only NI: Only ARM: Fails to build on EWDK 22621 with VS 17.1.5 - CallingConvention=StdCall not supported -prm,*,,22621,Only NI: Not supported on NI. -powerlimit\plclient,*,,22621,Only NI: Not supported on NI. -powerlimit\plpolicy,*,,22621,Only NI: Not supported on NI. -general\pcidrv,*,26100,,"failure introduced in VS17.14, suppressed until fix" -serial\serial,*,26100,,"failure introduced in VS17.14, suppressed until fix" -network\wlan\wdi,*,26100,,"failure introduced in VS17.14, suppressed until fix" -tools\kasan\samples\kasandemo-wdm,*|x64,26100,,"failure introduced in VS17.14, suppressed until fix" +Path,Configurations,MinBuild,MaxBuild,MinNtTargetVersion,MaxNtTargetVersion,Reason +audio\acx\samples\audiocodec\driver,*,,22621,,,Only NI: error C1083: Cannot open include file: 'acx.h': No such file or directory +general\dchu\osrfx2_dchu_extension_loose,*|x64,,22621,,,Only NI: Only x64: Fails to build +general\dchu\osrfx2_dchu_extension_tight,*|x64,,22621,,,Only NI: Only x64: Fails to build +network\trans\WFPSampler,Debug|ARM64,,22621,,,Only NI: Only ARM: Fails to build on EWDK 22621 with VS 17.1.5 - CallingConvention=StdCall not supported +prm,*,,22621,,,Only NI: Not supported on NI. +powerlimit\plclient,*,,22621,,,Only NI: Not supported on NI. +powerlimit\plpolicy,*,,22621,,,Only NI: Not supported on NI. +general\pcidrv,*,26100,,,,"failure introduced in VS17.14, suppressed until fix" +serial\serial,*,26100,,,,"failure introduced in VS17.14, suppressed until fix" +network\wlan\wdi,*,26100,,,,"failure introduced in VS17.14, suppressed until fix" +tools\kasan\samples\kasandemo-wdm,*|x64,26100,,,,"failure introduced in VS17.14, suppressed until fix" +audio\sysvad,*,,,,22000,_NT_TARGET_VERSION: KSJACK_DESCRIPTION3 undeclared; audio jack descriptor v3 was added in 22H2 (10.0.22621) +network\netadaptercx\netvadapter,*,,,,22621,_NT_TARGET_VERSION: requests an NDIS/DDI version newer than the linked library (C1189 wrong NDIS or DDI version) +network\wlan\wificx,*,,,,22621,_NT_TARGET_VERSION: requests an NDIS/DDI version newer than the linked library (C1189 wrong NDIS or DDI version) +powerlimit\plclient,*,,,,22621,_NT_TARGET_VERSION: POWER_LIMIT_ATTRIBUTES not declared in the older library (C2061) +powerlimit\plpolicy,*,,,,22621,_NT_TARGET_VERSION: POWER_LIMIT_ATTRIBUTES not declared in the older library (C2061) +storage\class\classpnp,Debug|*,,,,22621,_NT_TARGET_VERSION: STOR_ADDRESS_TYPE_NVME undeclared in the older library (C2065); Debug only +storage\miniports\storahci,Debug|*,,,,22621,_NT_TARGET_VERSION: STOR_ADDRESS_TYPE_NVME undeclared in the older library (C2065); Debug only +storage\msdsm,Debug|x64,,,,22621,_NT_TARGET_VERSION: STOR_ADDRESS_TYPE_NVME undeclared in the older library (C2065); Debug|x64 only