From 1c9d80c06621274f3c3db9315678695de9c4514e Mon Sep 17 00:00:00 2001 From: 5an7y Date: Thu, 19 Mar 2026 15:36:29 -0700 Subject: Add Build-Samples.ps1, ListAllSamples.ps1, fix Build-Sample retry bug - ListAllSamples.ps1: Enumerates .sln files and writes Samples.txt - Build-Samples.ps1: Unified build script reading Samples.txt with exclusion support, parallel execution, and report generation - Build-Sample.ps1: Fix retry bug where all-fail exits 0 instead of 1 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Build-Sample.ps1 | 2 +- Build-Samples.ps1 | 407 +++++++++++++++++++++++++++++++++++++++++++++++++++++ ListAllSamples.ps1 | 51 +++++++ 3 files changed, 459 insertions(+), 1 deletion(-) create mode 100644 Build-Samples.ps1 create mode 100644 ListAllSamples.ps1 diff --git a/Build-Sample.ps1 b/Build-Sample.ps1 index 093ba1ed..c09b7694 100644 --- a/Build-Sample.ps1 +++ b/Build-Sample.ps1 @@ -139,7 +139,7 @@ if (-not $configurationIsSupported) Write-Verbose "Building Sample: $SampleName; Configuration: $Configuration; Platform: $Platform {" -$myexit=0 +$myexit=1 # # Let us build up to three times (0th, 1st, and 2nd attempt). diff --git a/Build-Samples.ps1 b/Build-Samples.ps1 new file mode 100644 index 00000000..c8ad72f5 --- /dev/null +++ b/Build-Samples.ps1 @@ -0,0 +1,407 @@ +<# +.SYNOPSIS + Builds driver samples from a sample list file with parallel execution and exclusion support. + +.DESCRIPTION + Reads sample names from Samples.txt (or a specified file), processes exclusions from + exclusions.csv, determines the build environment, and builds all non-excluded samples + in parallel using Build-Sample.ps1. Generates CSV and HTML overview reports. + + Requires PowerShell 7+ (uses ForEach-Object -Parallel and ternary operators). + +.PARAMETER SampleListPath + Path to the file containing sample names (one per line). Defaults to Samples.txt in the + current directory. + +.PARAMETER Samples + Optional array of specific sample names to build. When provided, overrides SampleListPath. + +.PARAMETER Configurations + Build configurations (e.g. 'Debug','Release'). Defaults to $env:WDS_Configuration or + ('Debug','Release'). + +.PARAMETER Platforms + Build platforms (e.g. 'x64','arm64'). Defaults to $env:WDS_Platform or ('x64','arm64'). + +.PARAMETER LogFilesDirectory + Directory for build log files. Defaults to _logs in the current directory. + +.PARAMETER ReportFileName + Base name for the report files (without extension). Defaults to $env:WDS_ReportFileName + or '_overview'. + +.PARAMETER InfOptions + Additional InfVerif options. If not provided, determined automatically based on build number. + +.PARAMETER ThrottleLimit + Maximum parallel build jobs. Defaults to 5 x logical processors. + +.EXAMPLE + .\Build-Samples + +.EXAMPLE + .\Build-Samples -Samples 'audio.acx.samples.audiocodec.driver','usb.kmdf_fx2' -Configurations 'Debug' -Platforms 'x64' + +.EXAMPLE + .\Build-Samples -SampleListPath .\MySamples.txt -ThrottleLimit 8 +#> + +[CmdletBinding()] +param( + [string]$SampleListPath = (Join-Path (Get-Location) "Samples.txt"), + [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 }), + [string]$LogFilesDirectory = (Join-Path (Get-Location) "_logs"), + [string]$ReportFileName = $(if ([string]::IsNullOrEmpty($env:WDS_ReportFileName)) { "_overview" } else { $env:WDS_ReportFileName }), + [string]$InfOptions, + [int]$ThrottleLimit = 0 +) + +$root = (Get-Location).Path + +# Launch developer PowerShell if necessary +if (-not $env:VSCMD_VER) { + Import-Module (Resolve-Path "$env:ProgramFiles\Microsoft Visual Studio\*\*\Common7\Tools\Microsoft.VisualStudio.DevShell.dll") + Enter-VsDevShell -VsInstallPath (Resolve-Path "$env:ProgramFiles\Microsoft Visual Studio\*\*") + Set-Location $root +} + +$ThrottleFactor = 5 +$LogicalProcessors = (Get-CIMInstance -Class 'CIM_Processor' -Verbose:$false).NumberOfLogicalProcessors + +if ($ThrottleLimit -eq 0) { + $ThrottleLimit = $ThrottleFactor * $LogicalProcessors +} + +$Verbose = $false +if ($PSBoundParameters.ContainsKey('Verbose')) { + $Verbose = $PsBoundParameters.Get_Item('Verbose') +} + +# Prepare log directory +Remove-Item -Recurse -Path $LogFilesDirectory 2>&1 | Out-Null +New-Item -ItemType Directory -Force -Path $LogFilesDirectory | Out-Null + +$reportFilePath = Join-Path $LogFilesDirectory "$ReportFileName.htm" +$reportCsvFilePath = Join-Path $LogFilesDirectory "$ReportFileName.csv" + +# Verify msbuild is available +$oldPreference = $ErrorActionPreference +$ErrorActionPreference = "stop" +try { + Get-Command "msbuild" | Out-Null +} +catch { + Write-Host "`u{274C} msbuild cannot be called from current environment. Check that msbuild is set in current path (for example, that it is called from a Visual Studio developer command)." + Write-Error "msbuild cannot be called from current environment." + exit 1 +} +finally { + $ErrorActionPreference = $oldPreference +} + +# +# Load sample list +# +if ($Samples) { + $sampleNames = $Samples +} +else { + if (-not (Test-Path $SampleListPath)) { + Write-Error "Sample list file not found: $SampleListPath. Run .\ListAllSamples.ps1 first to generate Samples.txt." + exit 1 + } + $sampleNames = Get-Content $SampleListPath | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } +} + +# Build the sampleSet hashtable: sampleName -> directory path +$sampleSet = @{} +foreach ($name in $sampleNames) { + $relativePath = $name.Replace('.', '\') + $fullPath = Join-Path $root $relativePath + $sampleSet[$name] = $fullPath +} + +# +# Determine build environment +# +$build_environment = "" +$build_number = 0 +$nuget_package_version = 0 + +if ($env:GITHUB_REPOSITORY) { + $build_environment = "GitHub" + $nuget_package_version = ([regex]'(?<=x64\.)(\d+\.)(\d+\.)(\d+\.)(\d+)').Matches((Get-ChildItem .\packages\*WDK.x64* -Name)).Value + $build_number = $nuget_package_version.split('.')[2] +} +elseif (Test-Path(".\packages\*")) { + $build_environment = "NuGet" + $nuget_package_version = ([regex]'(?<=x64\.)(\d+\.)(\d+\.)(\d+\.)(\d+)').Matches((Get-ChildItem .\packages\*WDK.x64* -Name)).Value + $build_number = $nuget_package_version.split('.')[2] +} +elseif ($env:BuildLab -match '(?[^.]*).(?[^.]*).(?[^.]*)') { + $build_environment = "EWDK." + $Matches.branch + "." + $Matches.build + "." + $Matches.qfe + $build_number = $Matches.build +} +elseif ($env:UCRTVersion -match '10.0.(?.*).0') { + $build_environment = "WDK" + $build_number = $Matches.build +} +else { + Write-Output "Environment variables {" + Get-ChildItem env:* | Sort-Object name + Write-Output "Environment variables }" + Write-Error "Could not determine build environment." + exit 1 +} + +# Determine WDK Visual Studio Component version +if ($build_environment -match '^EWDK') { + $wdk_vs_component_ver = "(WDK Visual Studio Component Version is not included for EWDK builds)" +} +else { + $wdk_vs_component_ver = Get-ChildItem "${env:ProgramData}\Microsoft\VisualStudio\Packages\Microsoft.Windows.DriverKit,version=*" -ErrorAction SilentlyContinue + if (-not $wdk_vs_component_ver) { + Write-Error "WDK Visual Studio Component version not found. Please ensure the WDK Component is installed." + exit 1 + } + $wdk_vs_component_ver = [regex]::Match($wdk_vs_component_ver.Name, '(\d+\.){3}\d+').Value +} + +# +# InfVerif_AdditionalOptions +# +if ($InfOptions) { + $InfVerif_AdditionalOptions = $InfOptions +} +else { + $InfVerif_AdditionalOptions = ($build_number -le 22621 ? "/sw1284 /sw1285 /sw1293 /sw2083 /sw2086" : "/samples") +} + +# +# Load exclusions from exclusions.csv +# +$exclusionConfigurations = @{} +$exclusionReasons = @{} +Import-Csv 'exclusions.csv' | ForEach-Object { + $excluded_driver = $_.Path.Replace($root, '').Trim('\').Replace('\', '.').ToLower() + $excluded_configurations = ($_.configurations -eq '' ? '*' : $_.configurations) + $excluded_minbuild = ($_.MinBuild -eq '' ? 00000 : $_.MinBuild) + $excluded_maxbuild = ($_.MaxBuild -eq '' ? 99999 : $_.MaxBuild) + if (($excluded_minbuild -le $build_number) -and ($build_number -le $excluded_maxbuild)) { + $exclusionConfigurations[$excluded_driver] = $excluded_configurations + $exclusionReasons[$excluded_driver] = $_.Reason + Write-Verbose "Exclusion.csv entry applied for '$excluded_driver' for configuration '$excluded_configurations'." + } + else { + Write-Verbose "Exclusion.csv entry not applied for '$excluded_driver' due to build number." + } +} + +# +# Build all samples +# +$jresult = @{ + SolutionsBuilt = 0 + SolutionsSucceeded = 0 + SolutionsExcluded = 0 + SolutionsUnsupported = 0 + SolutionsFailed = 0 + SolutionsSporadic = 0 + Results = @() + FailSet = @() + SporadicSet = @() + lock = [System.Threading.Mutex]::new($false) +} + +$SolutionsTotal = $sampleSet.Count * $Configurations.Count * $Platforms.Count + +Write-Output "WDK Build Environment: $build_environment" +Write-Output "WDK Build Number: $build_number" +if (($build_environment -eq "GitHub") -or ($build_environment -eq "NuGet")) { + Write-Output "WDK Nuget Version: $nuget_package_version" +} +Write-Output "WDK Visual Studio Component Version: $wdk_vs_component_ver" +Write-Output "Samples: $($sampleSet.Count)" +Write-Output "Configurations: $($Configurations.Count) ($Configurations)" +Write-Output "Platforms: $($Platforms.Count) ($Platforms)" +Write-Output "InfVerif_AdditionalOptions: $InfVerif_AdditionalOptions" +Write-Output "Combinations: $SolutionsTotal" +Write-Output "LogicalProcessors: $LogicalProcessors" +Write-Output "ThrottleFactor: $ThrottleFactor" +Write-Output "ThrottleLimit: $ThrottleLimit" +Write-Output "WDS_WipeOutputs: $env:WDS_WipeOutputs" +Write-Output "Disk Remaining (GB): $(((Get-Volume ((Get-Item ".").PSDrive.Name)).SizeRemaining) / 1GB)" +Write-Output "" +Write-Output "T: Combinations" +Write-Output "B: Built" +Write-Output "R: Build is running currently" +Write-Output "P: Build is pending an available build slot" +Write-Output "" +Write-Output "S: Built and result was 'Succeeded'" +Write-Output "E: Built and result was 'Excluded'" +Write-Output "U: Built and result was 'Unsupported' (Platform and Configuration combination)" +Write-Output "F: Built and result was 'Failed'" +Write-Output "O: Built and result was 'Sporadic'" +Write-Output "" +Write-Output "Building all combinations..." + +$sw = [Diagnostics.Stopwatch]::StartNew() + +$sampleSet.GetEnumerator() | ForEach-Object -ThrottleLimit $ThrottleLimit -Parallel { + $LogFilesDirectory = $using:LogFilesDirectory + $exclusionConfigurations = $using:exclusionConfigurations + $exclusionReasons = $using:exclusionReasons + $Configurations = $using:Configurations + $Platforms = $using:Platforms + $InfVerif_AdditionalOptions = $using:InfVerif_AdditionalOptions + $Verbose = $using:Verbose + + $sampleName = $_.Key + $directory = $_.Value + + $ResultElement = New-Object psobject + Add-Member -InputObject $ResultElement -MemberType NoteProperty -Name Sample -Value "$sampleName" + + foreach ($configuration in $Configurations) { + foreach ($platform in $Platforms) { + $thisunsupported = 0 + $thisfailed = 0 + $thissporadic = 0 + $thisexcluded = 0 + $thissucceeded = 0 + $thisresult = "Not run" + $thisfailset = @() + $thissporadicset = @() + + if ($exclusionConfigurations.ContainsKey($sampleName) -and ($exclusionConfigurations[$sampleName].Split(';') | Where-Object { "$configuration|$platform" -like $_ })) { + Write-Verbose "[$sampleName $configuration|$platform] `u{23E9} Excluded and skipped. Reason: $($exclusionReasons[$sampleName])" + $thisexcluded += 1 + $thisresult = "Excluded" + } + else { + .\Build-Sample -Directory $directory -SampleName $sampleName -LogFilesDirectory $LogFilesDirectory -Configuration $configuration -Platform $platform -InfVerif_AdditionalOptions $InfVerif_AdditionalOptions -Verbose:$Verbose + if ($LASTEXITCODE -eq 0) { + $thissucceeded += 1 + $thisresult = "Succeeded" + } + elseif ($LASTEXITCODE -eq 1) { + $thisfailset += "$sampleName $configuration|$platform" + $thisfailed += 1 + $thisresult = "Failed" + } + elseif ($LASTEXITCODE -eq 2) { + $thissporadicset += "$sampleName $configuration|$platform" + $thissporadic += 1 + $thisresult = "Sporadic" + } + else { + $thisunsupported += 1 + $thisresult = "Unsupported" + } + } + Add-Member -InputObject $ResultElement -MemberType NoteProperty -Name "$configuration|$platform" -Value "$thisresult" + + $null = ($using:jresult).lock.WaitOne() + try { + ($using:jresult).SolutionsBuilt += 1 + ($using:jresult).SolutionsSucceeded += $thissucceeded + ($using:jresult).SolutionsExcluded += $thisexcluded + ($using:jresult).SolutionsUnsupported += $thisunsupported + ($using:jresult).SolutionsFailed += $thisfailed + ($using:jresult).SolutionsSporadic += $thissporadic + ($using:jresult).FailSet += $thisfailset + ($using:jresult).SporadicSet += $thissporadicset + $SolutionsTotal = $using:SolutionsTotal + $ThrottleLimit = $using:ThrottleLimit + $SolutionsBuilt = ($using:jresult).SolutionsBuilt + $SolutionsRemaining = $SolutionsTotal - $SolutionsBuilt + $SolutionsRunning = if ($SolutionsRemaining -ge $ThrottleLimit) { $ThrottleLimit } else { $SolutionsRemaining } + $SolutionsPending = if ($SolutionsRemaining -ge $ThrottleLimit) { ($SolutionsRemaining - $ThrottleLimit) } else { 0 } + $SolutionsBuiltPercent = [Math]::Round(100 * ($SolutionsBuilt / $using:SolutionsTotal)) + $TBRP = "T:" + ($SolutionsTotal) + "; B:" + (($using:jresult).SolutionsBuilt) + "; R:" + ($SolutionsRunning) + "; P:" + ($SolutionsPending) + $rstr = "S:" + (($using:jresult).SolutionsSucceeded) + "; E:" + (($using:jresult).SolutionsExcluded) + "; U:" + (($using:jresult).SolutionsUnsupported) + "; F:" + (($using:jresult).SolutionsFailed) + "; O:" + (($using:jresult).SolutionsSporadic) + Write-Progress -Activity "Building combinations" -Status "$SolutionsBuilt of $using:SolutionsTotal combinations built ($SolutionsBuiltPercent%) | $TBRP | $rstr" -PercentComplete $SolutionsBuiltPercent + } + finally { + ($using:jresult).lock.ReleaseMutex() + } + } + } + $null = ($using:jresult).lock.WaitOne() + try { + ($using:jresult).Results += $ResultElement + } + finally { + ($using:jresult).lock.ReleaseMutex() + } +} + +$sw.Stop() + +Write-Output "" + +if ($jresult.FailSet.Count -gt 0) { + Write-Output "Some combinations were built with errors:" + $jresult.FailSet = $jresult.FailSet | Sort-Object + foreach ($failedSample in $jresult.FailSet) { + $failedSample -match "^(.*) (\w*)\|(\w*)$" | Out-Null + $failName = $Matches[1] + $failConfiguration = $Matches[2] + $failPlatform = $Matches[3] + Write-Output "Build errors in Sample $failName; Configuration: $failConfiguration; Platform: $failPlatform {" + Get-Content "$LogFilesDirectory\$failName.$failConfiguration.$failPlatform.0.err" | Write-Output + Write-Output "} $failedSample" + } + Write-Error "Some combinations were built with errors." + Write-Output "" +} + +if ($jresult.SporadicSet.Count -gt 0) { + Write-Output "Some combinations were built with sporadic error:" + $jresult.SporadicSet = $jresult.SporadicSet | Sort-Object + foreach ($sporadicSample in $jresult.SporadicSet) { + $sporadicSample -match "^(.*) (\w*)\|(\w*)$" | Out-Null + $sporadicName = $Matches[1] + $sporadicConfiguration = $Matches[2] + $sporadicPlatform = $Matches[3] + Write-Output "Build sporadic errors in Sample $sporadicName; Configuration: $sporadicConfiguration; Platform: $sporadicPlatform {" + Get-Content "$LogFilesDirectory\$sporadicName.$sporadicConfiguration.$sporadicPlatform.0.err" | Write-Output + Write-Output "} $sporadicSample" + } + Write-Error "Some combinations were built with sporadic errors." + Write-Output "" +} + +# Display timer statistics +$min = $sw.Elapsed.Minutes +$seconds = $sw.Elapsed.Seconds + +$SolutionsSucceeded = $jresult.SolutionsSucceeded +$SolutionsExcluded = $jresult.SolutionsExcluded +$SolutionsUnsupported = $jresult.SolutionsUnsupported +$SolutionsFailed = $jresult.SolutionsFailed +$SolutionsSporadic = $jresult.SolutionsSporadic +$Results = $jresult.Results + +Write-Output "Built all combinations." +Write-Output "" +Write-Output "Elapsed time: $min minutes, $seconds seconds." +Write-Output ("Disk Remaining (GB): " + (((Get-Volume (Get-Item ".").PSDrive.Name).SizeRemaining / 1GB))) +Write-Output ("Samples: " + $sampleSet.Count) +Write-Output ("Configurations: " + $Configurations.Count + " (" + $Configurations + ")") +Write-Output ("Platforms: " + $Platforms.Count + " (" + $Platforms + ")") +Write-Output "Combinations: $SolutionsTotal" +Write-Output "Succeeded: $SolutionsSucceeded" +Write-Output "Excluded: $SolutionsExcluded" +Write-Output "Unsupported: $SolutionsUnsupported" +Write-Output "Failed: $SolutionsFailed" +Write-Output "Sporadic: $SolutionsSporadic" +Write-Output "Log files directory: $LogFilesDirectory" +Write-Output "Overview report: $reportFilePath" +Write-Output "" + +$Results | Sort-Object { $_.Sample } | ConvertTo-Csv | Out-File $reportCsvFilePath +$Results | Sort-Object { $_.Sample } | ConvertTo-Html -Title "Overview" | Out-File $reportFilePath +Invoke-Item $reportFilePath diff --git a/ListAllSamples.ps1 b/ListAllSamples.ps1 new file mode 100644 index 00000000..2a387609 --- /dev/null +++ b/ListAllSamples.ps1 @@ -0,0 +1,51 @@ +<# +.SYNOPSIS + Enumerates all available sample solutions in the repository and writes them to Samples.txt. + +.DESCRIPTION + Searches for all .sln files recursively from the repo root, excludes NuGet package directories + (paths starting with 'packages.'), computes a normalized sample name for each, and writes + the sorted list to Samples.txt (one sample name per line). + + The sample name is derived from the relative directory path: backslashes are replaced with dots + and the result is lowercased. + +.EXAMPLE + .\ListAllSamples + + Discovers all samples and writes Samples.txt to the repo root. + +.OUTPUTS + Samples.txt in the current working directory. +#> + +[CmdletBinding()] +param() + +$root = (Get-Location).Path + +# Discover all .sln files +$solutionFiles = Get-ChildItem -Path $root -Recurse -Filter *.sln | Select-Object -ExpandProperty FullName + +$sampleNames = @{} + +foreach ($file in $solutionFiles) { + $dir = (Get-Item $file).DirectoryName + $dirNorm = $dir.Replace($root, '').Trim('\').Replace('\', '.').ToLower() + + if ($dirNorm -match '^packages\.') { + Write-Verbose "Ignored NuGet package directory: $dirNorm" + continue + } + + if (-not $sampleNames.ContainsKey($dirNorm)) { + $sampleNames[$dirNorm] = $true + } +} + +$sortedNames = $sampleNames.Keys | Sort-Object + +$outputPath = Join-Path $root "Samples.txt" +$sortedNames | Out-File -FilePath $outputPath -Encoding utf8 + +Write-Output "Found $($sortedNames.Count) samples. Written to Samples.txt" -- cgit v1.3.1 From 15e6a1f38da2fdcf22ae4a38ab38bd839df72987 Mon Sep 17 00:00:00 2001 From: 5an7y Date: Fri, 20 Mar 2026 14:11:50 -0700 Subject: Making the maxbuild bigger on exclusions for testint and adding the base samples.txt --- Samples.txt | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ exclusions.csv | 8 ++-- 2 files changed, 141 insertions(+), 4 deletions(-) create mode 100644 Samples.txt diff --git a/Samples.txt b/Samples.txt new file mode 100644 index 00000000..e649f7e2 --- /dev/null +++ b/Samples.txt @@ -0,0 +1,137 @@ +audio.acx.samples.audiocodec.driver +audio.simpleaudiosample +audio.soundwire.samples.sdcavad +audio.sysvad +avstream.avscamera +avstream.avshws +avstream.avssamp +avstream.sampledevicemft +avstream.samplemft0 +bluetooth.bthecho +bluetooth.bthecho.packages.microsoft.windows.wdk.x64.10.0.26100.3323.c.tools.10.0.26100.0.x64.acpigenfx.samples +bluetooth.bthecho.packages.microsoft.windows.wdk.x64.10.0.26100.6584-preview.ge-release-svc-prod1.c.tools.10.0.26100.0.x64.acpigenfx.samples +bluetooth.serialhcibus +filesys.cdfs +filesys.fastfat +filesys.minifilter.avscan +filesys.minifilter.cancelsafe +filesys.minifilter.cdo +filesys.minifilter.change +filesys.minifilter.ctx +filesys.minifilter.delete +filesys.minifilter.metadatamanager +filesys.minifilter.minispy +filesys.minifilter.namechanger +filesys.minifilter.nullfilter +filesys.minifilter.passthrough +filesys.minifilter.scanner +filesys.minifilter.simrep +filesys.minifilter.swapbuffers +general.cancel +general.dchu.osrfx2_dchu_base +general.dchu.osrfx2_dchu_extension_loose +general.dchu.osrfx2_dchu_extension_tight +general.echo.kmdf +general.echo.umdf2 +general.event +general.ioctl.kmdf +general.ioctl.wdm +general.obcallback +general.pcidrv +general.perfcounters.kcs +general.plx9x5x +general.registry.regfltr +general.simplemediasource +general.systemdma.wdm +general.toaster.toastdrv +general.toaster.toastpkg +general.toaster.umdf2 +general.tracing.evntdrv +general.tracing.systemtracecontrol +general.tracing.tracedriver +gnss +gpio.samples +hid.firefly +hid.hclient +hid.hidusbfx2 +hid.vhidmini2 +input.kbfiltr +input.layout +input.moufiltr +network.config.bindview +network.modem.fakemodem +network.ndis.extension +network.ndis.filter +network.ndis.mux +network.ndis.ndisprot_kmdf +network.ndis.ndisprot.6x +network.ndis.netvmini.6x +network.radio.radiomanagersample +network.trans.ddproxy +network.trans.inspect +network.trans.msnmntr +network.trans.stmedit +network.trans.wfpsampler +network.wlan.wdi +network.wsk.echosrv +network.wwan.cxwmbclass +nfc.nfccxsample +nfp.net +pofx.pep +pofx.umdf2 +pofx.wdf +pos.drivers.barcodescanner +pos.drivers.magneticstripereader +powerlimit.plclient +powerlimit.plpolicy +prm +sd.miniport.sdhc +security.elam +sensors.activity +sensors.adxl345acc +sensors.customsensors +sensors.fusion +sensors.pedometer +sensors.sensorscombodriver +sensors.simpledeviceorientationsensor +serial.serenum +serial.serenum.packages.microsoft.windows.wdk.x64.10.0.26100.3323.c.tools.10.0.26100.0.x64.acpigenfx.samples +serial.serial +serial.virtualserial2 +setup.devcon +simbatt.func +smartcrd +spb.skeletoni2c +spb.spbtesttool +storage.class.classpnp +storage.class.disk +storage.iscsi +storage.miniports.lsi_u3 +storage.miniports.storahci +storage.msdsm +storage.tools.spti +thermal.simsensor +thermal.thermalclient +tools.dv.samples.dv-faildriver-wdm.driver +tools.kasan.samples.kasandemo-wdm +tools.sdv.samples.sdv-faildriver-kmdf +tools.sdv.samples.sdv-faildriver-ndis +tools.sdv.samples.sdv-faildriver-storport +tools.sdv.samples.sdv-faildriver-wdm +tree +usb.kmdf_enumswitches +usb.kmdf_fx2 +usb.ucmcxucsi +usb.ucmtcpcicxclientsample +usb.ucmucsiacpisample +usb.ufxclientsample +usb.umdf2_fx2 +usb.usbsamp +usb.usbview +usb.wdf_osrfx2_lab +video.archive.pixlib +video.indirectdisplay +video.kmdod +wia +wmi.wmiacpi +wmi.wmisamp diff --git a/exclusions.csv b/exclusions.csv index 63d5e24a..fbfefa25 100644 --- a/exclusions.csv +++ b/exclusions.csv @@ -6,8 +6,8 @@ network\trans\WFPSampler,Debug|ARM64,,22621,Only NI: Only ARM: Fails to build on 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" +general\pcidrv,*,,27100,"failure introduced in VS17.14, suppressed until fix" +serial\serial,*,,27100,"failure introduced in VS17.14, suppressed until fix" +network\wlan\wdi,*,,27100,"failure introduced in VS17.14, suppressed until fix" +tools\kasan\samples\kasandemo-wdm,*|x64,,27100,"failure introduced in VS17.14, suppressed until fix" -- cgit v1.3.1 From 8e3b051532deef78849c15f96ce79b5f3b21b911 Mon Sep 17 00:00:00 2001 From: 5an7y Date: Fri, 20 Mar 2026 14:26:54 -0700 Subject: Remove NuGet packages from samples --- ListAllSamples.ps1 | 2 +- Samples.txt | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/ListAllSamples.ps1 b/ListAllSamples.ps1 index 2a387609..ed87fc83 100644 --- a/ListAllSamples.ps1 +++ b/ListAllSamples.ps1 @@ -33,7 +33,7 @@ foreach ($file in $solutionFiles) { $dir = (Get-Item $file).DirectoryName $dirNorm = $dir.Replace($root, '').Trim('\').Replace('\', '.').ToLower() - if ($dirNorm -match '^packages\.') { + if ($dirNorm -match '(^|\.|\b)packages(\.|$)') { Write-Verbose "Ignored NuGet package directory: $dirNorm" continue } diff --git a/Samples.txt b/Samples.txt index e649f7e2..7998f395 100644 --- a/Samples.txt +++ b/Samples.txt @@ -1,4 +1,4 @@ -audio.acx.samples.audiocodec.driver +audio.acx.samples.audiocodec.driver audio.simpleaudiosample audio.soundwire.samples.sdcavad audio.sysvad @@ -8,8 +8,6 @@ avstream.avssamp avstream.sampledevicemft avstream.samplemft0 bluetooth.bthecho -bluetooth.bthecho.packages.microsoft.windows.wdk.x64.10.0.26100.3323.c.tools.10.0.26100.0.x64.acpigenfx.samples -bluetooth.bthecho.packages.microsoft.windows.wdk.x64.10.0.26100.6584-preview.ge-release-svc-prod1.c.tools.10.0.26100.0.x64.acpigenfx.samples bluetooth.serialhcibus filesys.cdfs filesys.fastfat @@ -63,8 +61,8 @@ network.modem.fakemodem network.ndis.extension network.ndis.filter network.ndis.mux -network.ndis.ndisprot_kmdf network.ndis.ndisprot.6x +network.ndis.ndisprot_kmdf network.ndis.netvmini.6x network.radio.radiomanagersample network.trans.ddproxy @@ -95,7 +93,6 @@ sensors.pedometer sensors.sensorscombodriver sensors.simpledeviceorientationsensor serial.serenum -serial.serenum.packages.microsoft.windows.wdk.x64.10.0.26100.3323.c.tools.10.0.26100.0.x64.acpigenfx.samples serial.serial serial.virtualserial2 setup.devcon -- cgit v1.3.1 From 9cd8d031aa48669e9e3cc11886d856ce52d6ce20 Mon Sep 17 00:00:00 2001 From: 5an7y Date: Fri, 20 Mar 2026 15:09:27 -0700 Subject: Refactor Build-Samples.ps1 and fix ListAllSamples NuGet filter Build-Samples.ps1: - Extract helper functions for readability (Resolve-BuildEnvironment, Import-SampleExclusions, Initialize-DevShell, Assert-MsBuildAvailable, Get-DiskFreeGB) - Add #Requires -Version 7.0 - Fix build_number to explicit [int] cast (was string from regex) - Fix multi-socket CPU handling (sum LogicalProcessors) - Fix NuGet path detection to use absolute repo root path - Support wildcard patterns in exclusion paths (-like vs ContainsKey) - Guard Invoke-Item for CI/automation (skip in non-interactive sessions) - Validate sample directories exist before building - Wrap Get-Volume in try/catch for UNC/network drives - Use Write-Host with carriage return for progress (Write-Progress unreliable in -Parallel runspaces) - Clean up variable names and add section comments - Use switch instead of if/elseif for exit codes ListAllSamples.ps1: - Fix NuGet packages filter regex to catch packages anywhere in path (was only matching paths starting with packages) Samples.txt: - Regenerated with fixed filter, remove BOM Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Build-Samples.ps1 | 730 +++++++++++++++++++++++++++++++++-------------------- ListAllSamples.ps1 | 2 +- Samples.txt | 4 +- 3 files changed, 461 insertions(+), 275 deletions(-) diff --git a/Build-Samples.ps1 b/Build-Samples.ps1 index c8ad72f5..6fd86bd3 100644 --- a/Build-Samples.ps1 +++ b/Build-Samples.ps1 @@ -3,15 +3,24 @@ Builds driver samples from a sample list file with parallel execution and exclusion support. .DESCRIPTION - Reads sample names from Samples.txt (or a specified file), processes exclusions from - exclusions.csv, determines the build environment, and builds all non-excluded samples - in parallel using Build-Sample.ps1. Generates CSV and HTML overview reports. + This is the main build orchestrator for driver samples. It performs these steps: - Requires PowerShell 7+ (uses ForEach-Object -Parallel and ternary operators). + 1. Ensures a developer build environment (VS DevShell or EWDK) is active + 2. Reads sample names from Samples.txt (or a specified file/array) + 3. Detects the build environment (GitHub CI, NuGet, EWDK, or WDK) and build number + 4. Loads exclusions from exclusions.csv (supports wildcard paths) + 5. Builds all non-excluded sample/configuration/platform combinations in parallel + 6. Generates CSV and HTML overview reports + + Requires PowerShell 7+ (uses ForEach-Object -Parallel). + + Typical call chain: + Build-AllSamples.ps1 -> ListAllSamples.ps1 -> Build-Samples.ps1 -> Build-Sample.ps1 + (this script) .PARAMETER SampleListPath - Path to the file containing sample names (one per line). Defaults to Samples.txt in the - current directory. + Path to the file containing sample names (one per line, dot-separated). + Defaults to Samples.txt in the current directory. .PARAMETER Samples Optional array of specific sample names to build. When provided, overrides SampleListPath. @@ -31,7 +40,8 @@ or '_overview'. .PARAMETER InfOptions - Additional InfVerif options. If not provided, determined automatically based on build number. + Additional InfVerif options (e.g. '/samples', '/msft'). If not provided, determined + automatically based on the WDK build number. .PARAMETER ThrottleLimit Maximum parallel build jobs. Defaults to 5 x logical processors. @@ -39,13 +49,21 @@ .EXAMPLE .\Build-Samples + Builds all samples from Samples.txt with default settings. + .EXAMPLE .\Build-Samples -Samples 'audio.acx.samples.audiocodec.driver','usb.kmdf_fx2' -Configurations 'Debug' -Platforms 'x64' + Builds specific samples for a single configuration and platform. + .EXAMPLE .\Build-Samples -SampleListPath .\MySamples.txt -ThrottleLimit 8 + + Builds samples from a custom list with limited parallelism. #> +#Requires -Version 7.0 + [CmdletBinding()] param( [string]$SampleListPath = (Join-Path (Get-Location) "Samples.txt"), @@ -58,350 +76,518 @@ param( [int]$ThrottleLimit = 0 ) -$root = (Get-Location).Path +# ============================================================================= +# Helper Functions +# ============================================================================= -# Launch developer PowerShell if necessary -if (-not $env:VSCMD_VER) { - Import-Module (Resolve-Path "$env:ProgramFiles\Microsoft Visual Studio\*\*\Common7\Tools\Microsoft.VisualStudio.DevShell.dll") - Enter-VsDevShell -VsInstallPath (Resolve-Path "$env:ProgramFiles\Microsoft Visual Studio\*\*") - Set-Location $root -} +function Initialize-DevShell { + <# + .SYNOPSIS Imports the Visual Studio Developer PowerShell if not already active. + #> + param([string]$ReturnToDirectory) -$ThrottleFactor = 5 -$LogicalProcessors = (Get-CIMInstance -Class 'CIM_Processor' -Verbose:$false).NumberOfLogicalProcessors + if ($env:VSCMD_VER) { + Write-Verbose "VS Developer Shell already active (VSCMD_VER=$env:VSCMD_VER)." + return + } -if ($ThrottleLimit -eq 0) { - $ThrottleLimit = $ThrottleFactor * $LogicalProcessors + $devShellDll = Resolve-Path "$env:ProgramFiles\Microsoft Visual Studio\*\*\Common7\Tools\Microsoft.VisualStudio.DevShell.dll" ` + -ErrorAction SilentlyContinue | Select-Object -First 1 + if (-not $devShellDll) { + Write-Error "Visual Studio Developer Shell module not found." + exit 1 + } + + Import-Module $devShellDll.Path + $vsInstall = Resolve-Path "$env:ProgramFiles\Microsoft Visual Studio\*\*" | Select-Object -First 1 + Enter-VsDevShell -VsInstallPath $vsInstall.Path + Set-Location $ReturnToDirectory } -$Verbose = $false -if ($PSBoundParameters.ContainsKey('Verbose')) { - $Verbose = $PsBoundParameters.Get_Item('Verbose') +function Assert-MsBuildAvailable { + <# + .SYNOPSIS Verifies msbuild.exe is on PATH. Exits with error if not found. + #> + $savedPref = $ErrorActionPreference + $ErrorActionPreference = 'Stop' + try { + Get-Command 'msbuild' | Out-Null + } + catch { + Write-Error "msbuild cannot be called from current environment. Ensure it is on PATH (run from VS Developer Command Prompt or EWDK)." + exit 1 + } + finally { + $ErrorActionPreference = $savedPref + } } -# Prepare log directory -Remove-Item -Recurse -Path $LogFilesDirectory 2>&1 | Out-Null -New-Item -ItemType Directory -Force -Path $LogFilesDirectory | Out-Null +function Resolve-BuildEnvironment { + <# + .SYNOPSIS + Detects the active build environment and returns metadata. + .DESCRIPTION + Checks (in priority order) for GitHub CI, local NuGet packages, EWDK, or WDK. + Returns a hashtable: Name, BuildNumber (int), NuGetVersion, WdkVsComponentVersion. + #> + param([string]$RepoRoot) + + $result = @{ + Name = '' + BuildNumber = [int]0 + NuGetVersion = '' + WdkVsComponentVersion = '' + } -$reportFilePath = Join-Path $LogFilesDirectory "$ReportFileName.htm" -$reportCsvFilePath = Join-Path $LogFilesDirectory "$ReportFileName.csv" + if ($env:GITHUB_REPOSITORY) { + $result.Name = 'GitHub' + $wdkPackage = Get-ChildItem "$RepoRoot\packages\*WDK.x64*" -Name -ErrorAction SilentlyContinue + $result.NuGetVersion = ([regex]'(?<=x64\.)(\d+\.){3}\d+').Match($wdkPackage).Value + $result.BuildNumber = [int]($result.NuGetVersion.Split('.')[2]) + } + elseif (Test-Path "$RepoRoot\packages\*") { + $result.Name = 'NuGet' + $wdkPackage = Get-ChildItem "$RepoRoot\packages\*WDK.x64*" -Name -ErrorAction SilentlyContinue + $result.NuGetVersion = ([regex]'(?<=x64\.)(\d+\.){3}\d+').Match($wdkPackage).Value + $result.BuildNumber = [int]($result.NuGetVersion.Split('.')[2]) + } + elseif ($env:BuildLab -match '^(?[^.]+)\.(?\d+)\.(?[^.]+)$') { + $result.Name = "EWDK.$($Matches.branch).$($Matches.build).$($Matches.qfe)" + $result.BuildNumber = [int]$Matches.build + } + elseif ($env:UCRTVersion -match '10\.0\.(?\d+)\.0') { + $result.Name = 'WDK' + $result.BuildNumber = [int]$Matches.build + } + else { + Write-Output "Environment variables {" + Get-ChildItem env:* | Sort-Object Name + Write-Output "Environment variables }" + Write-Error "Could not determine build environment. Ensure EWDK, WDK, or NuGet packages are configured." + exit 1 + } -# Verify msbuild is available -$oldPreference = $ErrorActionPreference -$ErrorActionPreference = "stop" -try { - Get-Command "msbuild" | Out-Null + # WDK VS component version (EWDK does not ship this metadata) + if ($result.Name -match '^EWDK') { + $result.WdkVsComponentVersion = '(not available for EWDK builds)' + } + else { + $vsComponent = Get-ChildItem "${env:ProgramData}\Microsoft\VisualStudio\Packages\Microsoft.Windows.DriverKit,version=*" -ErrorAction SilentlyContinue + if (-not $vsComponent) { + Write-Error "WDK Visual Studio Component not found. Ensure the WDK Component is installed." + exit 1 + } + $result.WdkVsComponentVersion = [regex]::Match($vsComponent.Name, '(\d+\.){3}\d+').Value + } + + return $result } -catch { - Write-Host "`u{274C} msbuild cannot be called from current environment. Check that msbuild is set in current path (for example, that it is called from a Visual Studio developer command)." - Write-Error "msbuild cannot be called from current environment." - exit 1 + +function Import-SampleExclusions { + <# + .SYNOPSIS + Loads exclusions.csv and returns exclusion objects applicable to the current build. + .DESCRIPTION + Each returned exclusion has: + - Pattern: dot-separated path (may contain wildcards, e.g. 'general.*') + - 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. + .NOTES + CSV format: Path,Configurations,MinBuild,MaxBuild,Reason + Example row: network\wlan\wdi,*,,27100,"failure introduced in VS17.14" + #> + param( + [string]$CsvPath, + [int]$BuildNumber + ) + + if (-not (Test-Path $CsvPath)) { + Write-Warning "Exclusions file not found: $CsvPath. No exclusions will be applied." + return @() + } + + $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) { + [void]$exclusions.Add([PSCustomObject]@{ + Pattern = $pattern + Configurations = $configs + Reason = $_.Reason + }) + Write-Verbose "Exclusion applied: '$pattern' configs='$configs' reason='$($_.Reason)'" + } + else { + Write-Verbose "Exclusion skipped: '$pattern' - build $BuildNumber outside [$minBuild, $maxBuild]" + } + } + + return $exclusions.ToArray() +} + +function Get-DiskFreeGB { + <# + .SYNOPSIS Returns free disk space in GB for the current drive, or 'N/A' on error. + #> + try { + return [math]::Round((Get-Volume (Get-Item '.').PSDrive.Name).SizeRemaining / 1GB, 1) + } + catch { + return 'N/A' + } } -finally { - $ErrorActionPreference = $oldPreference + +# ============================================================================= +# Step 1 - Prepare Build Environment +# ============================================================================= + +$root = (Get-Location).Path + +Initialize-DevShell -ReturnToDirectory $root +Assert-MsBuildAvailable + +# ============================================================================= +# Step 2 - Calculate Parallelism +# ============================================================================= + +$throttleFactor = 5 +# Sum across all CPU sockets (Get-CimInstance returns an array on multi-socket systems) +$logicalProcessors = ((Get-CimInstance -Class CIM_Processor -Verbose:$false).NumberOfLogicalProcessors | Measure-Object -Sum).Sum + +if ($ThrottleLimit -eq 0) { + $ThrottleLimit = $throttleFactor * $logicalProcessors } -# -# Load sample list -# +$verbose = $PSBoundParameters.ContainsKey('Verbose') -and $PSBoundParameters['Verbose'] + +# ============================================================================= +# Step 3 - Prepare Log Directory +# ============================================================================= + +Remove-Item -Recurse -Path $LogFilesDirectory -ErrorAction SilentlyContinue +New-Item -ItemType Directory -Force -Path $LogFilesDirectory | Out-Null + +$reportHtmlPath = Join-Path $LogFilesDirectory "$ReportFileName.htm" +$reportCsvPath = Join-Path $LogFilesDirectory "$ReportFileName.csv" + +# ============================================================================= +# Step 4 - Load Sample List +# ============================================================================= + if ($Samples) { $sampleNames = $Samples } else { if (-not (Test-Path $SampleListPath)) { - Write-Error "Sample list file not found: $SampleListPath. Run .\ListAllSamples.ps1 first to generate Samples.txt." + Write-Error "Sample list not found: $SampleListPath. Run .\ListAllSamples.ps1 first." exit 1 } $sampleNames = Get-Content $SampleListPath | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } } -# Build the sampleSet hashtable: sampleName -> directory path -$sampleSet = @{} +# Map sample names to directory paths, validating each exists +$sampleSet = [ordered]@{} +$skippedCount = 0 foreach ($name in $sampleNames) { - $relativePath = $name.Replace('.', '\') - $fullPath = Join-Path $root $relativePath - $sampleSet[$name] = $fullPath + $fullPath = Join-Path $root ($name.Replace('.', '\')) + if (Test-Path $fullPath -PathType Container) { + $sampleSet[$name] = $fullPath + } + else { + Write-Warning "Sample directory not found, skipping: $name" + $skippedCount++ + } } -# -# Determine build environment -# -$build_environment = "" -$build_number = 0 -$nuget_package_version = 0 - -if ($env:GITHUB_REPOSITORY) { - $build_environment = "GitHub" - $nuget_package_version = ([regex]'(?<=x64\.)(\d+\.)(\d+\.)(\d+\.)(\d+)').Matches((Get-ChildItem .\packages\*WDK.x64* -Name)).Value - $build_number = $nuget_package_version.split('.')[2] -} -elseif (Test-Path(".\packages\*")) { - $build_environment = "NuGet" - $nuget_package_version = ([regex]'(?<=x64\.)(\d+\.)(\d+\.)(\d+\.)(\d+)').Matches((Get-ChildItem .\packages\*WDK.x64* -Name)).Value - $build_number = $nuget_package_version.split('.')[2] -} -elseif ($env:BuildLab -match '(?[^.]*).(?[^.]*).(?[^.]*)') { - $build_environment = "EWDK." + $Matches.branch + "." + $Matches.build + "." + $Matches.qfe - $build_number = $Matches.build -} -elseif ($env:UCRTVersion -match '10.0.(?.*).0') { - $build_environment = "WDK" - $build_number = $Matches.build -} -else { - Write-Output "Environment variables {" - Get-ChildItem env:* | Sort-Object name - Write-Output "Environment variables }" - Write-Error "Could not determine build environment." +if ($sampleSet.Count -eq 0) { + Write-Error "No valid sample directories found. Verify Samples.txt and current directory." exit 1 } -# Determine WDK Visual Studio Component version -if ($build_environment -match '^EWDK') { - $wdk_vs_component_ver = "(WDK Visual Studio Component Version is not included for EWDK builds)" -} -else { - $wdk_vs_component_ver = Get-ChildItem "${env:ProgramData}\Microsoft\VisualStudio\Packages\Microsoft.Windows.DriverKit,version=*" -ErrorAction SilentlyContinue - if (-not $wdk_vs_component_ver) { - Write-Error "WDK Visual Studio Component version not found. Please ensure the WDK Component is installed." - exit 1 - } - $wdk_vs_component_ver = [regex]::Match($wdk_vs_component_ver.Name, '(\d+\.){3}\d+').Value -} +# ============================================================================= +# Step 5 - Detect Build Environment +# ============================================================================= + +$buildEnv = Resolve-BuildEnvironment -RepoRoot $root +$buildNumber = $buildEnv.BuildNumber +# ============================================================================= +# Step 6 - Determine InfVerif Options +# ============================================================================= # -# InfVerif_AdditionalOptions +# Samples must build cleanly, but certain InfVerif warnings are acceptable because +# they flag issues intentionally present in samples (to be fixed when productizing). +# <= 22621: suppress individual warnings /sw1284 /sw1285 /sw1293 /sw2083 /sw2086 +# > 22621: these are grouped under /samples # if ($InfOptions) { - $InfVerif_AdditionalOptions = $InfOptions + $infVerifOptions = $InfOptions } else { - $InfVerif_AdditionalOptions = ($build_number -le 22621 ? "/sw1284 /sw1285 /sw1293 /sw2083 /sw2086" : "/samples") + $infVerifOptions = if ($buildNumber -le 22621) { '/sw1284 /sw1285 /sw1293 /sw2083 /sw2086' } else { '/samples' } } -# -# Load exclusions from exclusions.csv -# -$exclusionConfigurations = @{} -$exclusionReasons = @{} -Import-Csv 'exclusions.csv' | ForEach-Object { - $excluded_driver = $_.Path.Replace($root, '').Trim('\').Replace('\', '.').ToLower() - $excluded_configurations = ($_.configurations -eq '' ? '*' : $_.configurations) - $excluded_minbuild = ($_.MinBuild -eq '' ? 00000 : $_.MinBuild) - $excluded_maxbuild = ($_.MaxBuild -eq '' ? 99999 : $_.MaxBuild) - if (($excluded_minbuild -le $build_number) -and ($build_number -le $excluded_maxbuild)) { - $exclusionConfigurations[$excluded_driver] = $excluded_configurations - $exclusionReasons[$excluded_driver] = $_.Reason - Write-Verbose "Exclusion.csv entry applied for '$excluded_driver' for configuration '$excluded_configurations'." - } - else { - Write-Verbose "Exclusion.csv entry not applied for '$excluded_driver' due to build number." - } -} +# ============================================================================= +# Step 7 - Load Exclusions +# ============================================================================= -# -# Build all samples -# -$jresult = @{ - SolutionsBuilt = 0 - SolutionsSucceeded = 0 - SolutionsExcluded = 0 - SolutionsUnsupported = 0 - SolutionsFailed = 0 - SolutionsSporadic = 0 - Results = @() - FailSet = @() - SporadicSet = @() - lock = [System.Threading.Mutex]::new($false) -} +$exclusions = Import-SampleExclusions -CsvPath (Join-Path $root 'exclusions.csv') -BuildNumber $buildNumber -$SolutionsTotal = $sampleSet.Count * $Configurations.Count * $Platforms.Count +# ============================================================================= +# Step 8 - Print Build Plan +# ============================================================================= -Write-Output "WDK Build Environment: $build_environment" -Write-Output "WDK Build Number: $build_number" -if (($build_environment -eq "GitHub") -or ($build_environment -eq "NuGet")) { - Write-Output "WDK Nuget Version: $nuget_package_version" +$combinationsTotal = $sampleSet.Count * $Configurations.Count * $Platforms.Count + +Write-Output "" +Write-Output "--- WDK Sample Build Plan ------------------------------------------" +Write-Output " Environment: $($buildEnv.Name)" +Write-Output " Build Number: $buildNumber" +if ($buildEnv.NuGetVersion) { + Write-Output " NuGet Version: $($buildEnv.NuGetVersion)" } -Write-Output "WDK Visual Studio Component Version: $wdk_vs_component_ver" -Write-Output "Samples: $($sampleSet.Count)" -Write-Output "Configurations: $($Configurations.Count) ($Configurations)" -Write-Output "Platforms: $($Platforms.Count) ($Platforms)" -Write-Output "InfVerif_AdditionalOptions: $InfVerif_AdditionalOptions" -Write-Output "Combinations: $SolutionsTotal" -Write-Output "LogicalProcessors: $LogicalProcessors" -Write-Output "ThrottleFactor: $ThrottleFactor" -Write-Output "ThrottleLimit: $ThrottleLimit" -Write-Output "WDS_WipeOutputs: $env:WDS_WipeOutputs" -Write-Output "Disk Remaining (GB): $(((Get-Volume ((Get-Item ".").PSDrive.Name)).SizeRemaining) / 1GB)" +Write-Output " WDK VS Component: $($buildEnv.WdkVsComponentVersion)" +Write-Output " InfVerif Options: $infVerifOptions" Write-Output "" -Write-Output "T: Combinations" -Write-Output "B: Built" -Write-Output "R: Build is running currently" -Write-Output "P: Build is pending an available build slot" +Write-Output " Samples: $($sampleSet.Count) ($skippedCount skipped)" +Write-Output " Configurations: $($Configurations -join ', ')" +Write-Output " Platforms: $($Platforms -join ', ')" +Write-Output " Combinations: $combinationsTotal" +Write-Output " Exclusions: $($exclusions.Count)" Write-Output "" -Write-Output "S: Built and result was 'Succeeded'" -Write-Output "E: Built and result was 'Excluded'" -Write-Output "U: Built and result was 'Unsupported' (Platform and Configuration combination)" -Write-Output "F: Built and result was 'Failed'" -Write-Output "O: Built and result was 'Sporadic'" +Write-Output " Parallelism: $ThrottleLimit jobs ($logicalProcessors cores x $throttleFactor)" +Write-Output " Disk Free (GB): $(Get-DiskFreeGB)" +Write-Output " Wipe Outputs: $(-not [string]::IsNullOrEmpty($env:WDS_WipeOutputs))" +Write-Output "--------------------------------------------------------------------" +Write-Output "" +Write-Output "Progress legend:" +Write-Output " T=Total B=Built R=Running P=Pending" +Write-Output " S=Succeeded E=Excluded U=Unsupported F=Failed O=Sporadic" Write-Output "" Write-Output "Building all combinations..." -$sw = [Diagnostics.Stopwatch]::StartNew() +# ============================================================================= +# Step 9 - Execute Parallel Builds +# ============================================================================= + +# Shared mutable state protected by a Mutex. This is required because +# ForEach-Object -Parallel runs each iteration in a separate runspace, +# so standard .NET locks (Monitor/lock) do not work across runspaces. +$buildState = @{ + Built = 0 + Succeeded = 0 + Excluded = 0 + Unsupported = 0 + Failed = 0 + Sporadic = 0 + Results = @() + FailSet = @() + SporadicSet = @() + Lock = [System.Threading.Mutex]::new($false) +} + +$stopwatch = [Diagnostics.Stopwatch]::StartNew() $sampleSet.GetEnumerator() | ForEach-Object -ThrottleLimit $ThrottleLimit -Parallel { - $LogFilesDirectory = $using:LogFilesDirectory - $exclusionConfigurations = $using:exclusionConfigurations - $exclusionReasons = $using:exclusionReasons - $Configurations = $using:Configurations - $Platforms = $using:Platforms - $InfVerif_AdditionalOptions = $using:InfVerif_AdditionalOptions - $Verbose = $using:Verbose + # --- Import shared state from parent scope --- + $logDir = $using:LogFilesDirectory + $exclusions = $using:exclusions + $configs = $using:Configurations + $platforms = $using:Platforms + $infOpts = $using:infVerifOptions + $isVerbose = $using:verbose + $state = $using:buildState + $total = $using:combinationsTotal + $throttle = $using:ThrottleLimit $sampleName = $_.Key - $directory = $_.Value - - $ResultElement = New-Object psobject - Add-Member -InputObject $ResultElement -MemberType NoteProperty -Name Sample -Value "$sampleName" - - foreach ($configuration in $Configurations) { - foreach ($platform in $Platforms) { - $thisunsupported = 0 - $thisfailed = 0 - $thissporadic = 0 - $thisexcluded = 0 - $thissucceeded = 0 - $thisresult = "Not run" - $thisfailset = @() - $thissporadicset = @() - - if ($exclusionConfigurations.ContainsKey($sampleName) -and ($exclusionConfigurations[$sampleName].Split(';') | Where-Object { "$configuration|$platform" -like $_ })) { - Write-Verbose "[$sampleName $configuration|$platform] `u{23E9} Excluded and skipped. Reason: $($exclusionReasons[$sampleName])" - $thisexcluded += 1 - $thisresult = "Excluded" + $directory = $_.Value + + # Build a result row: one column per configuration|platform combination + $resultRow = [PSCustomObject]@{ Sample = $sampleName } + + foreach ($configuration in $configs) { + foreach ($platform in $platforms) { + $result = 'Not run' + $succeededDelta = 0 + $excludedDelta = 0 + $unsupportedDelta = 0 + $failedDelta = 0 + $sporadicDelta = 0 + $failEntry = $null + $sporadicEntry = $null + + # -- Check exclusions (supports wildcard paths like 'general.*') -- + $exclusionReason = $null + foreach ($excl in $exclusions) { + if ($sampleName -like $excl.Pattern) { + $configKey = "$configuration|$platform" + foreach ($cfgPattern in $excl.Configurations.Split(';')) { + if ($configKey -like $cfgPattern) { + $exclusionReason = $excl.Reason + break + } + } + if ($exclusionReason) { break } + } + } + + if ($exclusionReason) { + Write-Verbose "[$sampleName $configuration|$platform] Excluded: $exclusionReason" + $excludedDelta = 1 + $result = 'Excluded' } else { - .\Build-Sample -Directory $directory -SampleName $sampleName -LogFilesDirectory $LogFilesDirectory -Configuration $configuration -Platform $platform -InfVerif_AdditionalOptions $InfVerif_AdditionalOptions -Verbose:$Verbose - if ($LASTEXITCODE -eq 0) { - $thissucceeded += 1 - $thisresult = "Succeeded" - } - elseif ($LASTEXITCODE -eq 1) { - $thisfailset += "$sampleName $configuration|$platform" - $thisfailed += 1 - $thisresult = "Failed" - } - elseif ($LASTEXITCODE -eq 2) { - $thissporadicset += "$sampleName $configuration|$platform" - $thissporadic += 1 - $thisresult = "Sporadic" - } - else { - $thisunsupported += 1 - $thisresult = "Unsupported" + # -- Build the sample -- + .\Build-Sample -Directory $directory -SampleName $sampleName ` + -LogFilesDirectory $logDir -Configuration $configuration ` + -Platform $platform -InfVerif_AdditionalOptions $infOpts ` + -Verbose:$isVerbose + + # Exit codes from Build-Sample.ps1: + # 0 = succeeded on first attempt + # 1 = failed after all retries + # 2 = sporadic (failed first, succeeded on retry) + # 3 = unsupported configuration/platform + switch ($LASTEXITCODE) { + 0 { $succeededDelta = 1; $result = 'Succeeded' } + 1 { $failedDelta = 1; $result = 'Failed'; $failEntry = "$sampleName $configuration|$platform" } + 2 { $sporadicDelta = 1; $result = 'Sporadic'; $sporadicEntry = "$sampleName $configuration|$platform" } + default { $unsupportedDelta = 1; $result = 'Unsupported' } } } - Add-Member -InputObject $ResultElement -MemberType NoteProperty -Name "$configuration|$platform" -Value "$thisresult" - $null = ($using:jresult).lock.WaitOne() + $resultRow | Add-Member -MemberType NoteProperty -Name "$configuration|$platform" -Value $result + + # -- Update shared counters (under lock) -- + $null = $state.Lock.WaitOne() try { - ($using:jresult).SolutionsBuilt += 1 - ($using:jresult).SolutionsSucceeded += $thissucceeded - ($using:jresult).SolutionsExcluded += $thisexcluded - ($using:jresult).SolutionsUnsupported += $thisunsupported - ($using:jresult).SolutionsFailed += $thisfailed - ($using:jresult).SolutionsSporadic += $thissporadic - ($using:jresult).FailSet += $thisfailset - ($using:jresult).SporadicSet += $thissporadicset - $SolutionsTotal = $using:SolutionsTotal - $ThrottleLimit = $using:ThrottleLimit - $SolutionsBuilt = ($using:jresult).SolutionsBuilt - $SolutionsRemaining = $SolutionsTotal - $SolutionsBuilt - $SolutionsRunning = if ($SolutionsRemaining -ge $ThrottleLimit) { $ThrottleLimit } else { $SolutionsRemaining } - $SolutionsPending = if ($SolutionsRemaining -ge $ThrottleLimit) { ($SolutionsRemaining - $ThrottleLimit) } else { 0 } - $SolutionsBuiltPercent = [Math]::Round(100 * ($SolutionsBuilt / $using:SolutionsTotal)) - $TBRP = "T:" + ($SolutionsTotal) + "; B:" + (($using:jresult).SolutionsBuilt) + "; R:" + ($SolutionsRunning) + "; P:" + ($SolutionsPending) - $rstr = "S:" + (($using:jresult).SolutionsSucceeded) + "; E:" + (($using:jresult).SolutionsExcluded) + "; U:" + (($using:jresult).SolutionsUnsupported) + "; F:" + (($using:jresult).SolutionsFailed) + "; O:" + (($using:jresult).SolutionsSporadic) - Write-Progress -Activity "Building combinations" -Status "$SolutionsBuilt of $using:SolutionsTotal combinations built ($SolutionsBuiltPercent%) | $TBRP | $rstr" -PercentComplete $SolutionsBuiltPercent + $state.Built += 1 + $state.Succeeded += $succeededDelta + $state.Excluded += $excludedDelta + $state.Unsupported += $unsupportedDelta + $state.Failed += $failedDelta + $state.Sporadic += $sporadicDelta + if ($failEntry) { $state.FailSet += $failEntry } + if ($sporadicEntry) { $state.SporadicSet += $sporadicEntry } + + # Update progress bar + $built = $state.Built + $remaining = $total - $built + $running = [Math]::Min($remaining, $throttle) + $pending = [Math]::Max($remaining - $throttle, 0) + $pct = [Math]::Round(100 * $built / $total) + + $statusLine = "$built of $total combinations built ($pct%) | " + + "T:$total; B:$built; R:$running; P:$pending | " + + "S:$($state.Succeeded); E:$($state.Excluded); U:$($state.Unsupported); F:$($state.Failed); O:$($state.Sporadic)" + + # Write-Host with carriage return for a single-line progress indicator. + # Write-Progress does not reliably render from -Parallel runspaces. + Write-Host "`rBuilding combinations [$statusLine]" -NoNewline } finally { - ($using:jresult).lock.ReleaseMutex() + $state.Lock.ReleaseMutex() } } } - $null = ($using:jresult).lock.WaitOne() + + # Append the completed result row + $null = $state.Lock.WaitOne() try { - ($using:jresult).Results += $ResultElement + $state.Results += $resultRow } finally { - ($using:jresult).lock.ReleaseMutex() + $state.Lock.ReleaseMutex() } } -$sw.Stop() +$stopwatch.Stop() + +# End the progress line +Write-Host "" + +# ============================================================================= +# Step 10 - Report Failures +# ============================================================================= Write-Output "" -if ($jresult.FailSet.Count -gt 0) { - Write-Output "Some combinations were built with errors:" - $jresult.FailSet = $jresult.FailSet | Sort-Object - foreach ($failedSample in $jresult.FailSet) { - $failedSample -match "^(.*) (\w*)\|(\w*)$" | Out-Null - $failName = $Matches[1] - $failConfiguration = $Matches[2] - $failPlatform = $Matches[3] - Write-Output "Build errors in Sample $failName; Configuration: $failConfiguration; Platform: $failPlatform {" - Get-Content "$LogFilesDirectory\$failName.$failConfiguration.$failPlatform.0.err" | Write-Output - Write-Output "} $failedSample" +if ($buildState.FailSet.Count -gt 0) { + Write-Output "--- Build Failures -------------------------------------------------" + foreach ($entry in ($buildState.FailSet | Sort-Object)) { + if ($entry -match '^(?.*)\s+(?\w+)\|(?\w+)$') { + $errLog = Join-Path $LogFilesDirectory "$($Matches.name).$($Matches.config).$($Matches.platform).0.err" + Write-Output " [FAIL] $($Matches.name) ($($Matches.config)|$($Matches.platform)):" + if (Test-Path $errLog) { + Get-Content $errLog | ForEach-Object { Write-Output " $_" } + } + else { + Write-Output " (error log not found: $errLog)" + } + } } - Write-Error "Some combinations were built with errors." Write-Output "" + Write-Error "Some combinations were built with errors." } -if ($jresult.SporadicSet.Count -gt 0) { - Write-Output "Some combinations were built with sporadic error:" - $jresult.SporadicSet = $jresult.SporadicSet | Sort-Object - foreach ($sporadicSample in $jresult.SporadicSet) { - $sporadicSample -match "^(.*) (\w*)\|(\w*)$" | Out-Null - $sporadicName = $Matches[1] - $sporadicConfiguration = $Matches[2] - $sporadicPlatform = $Matches[3] - Write-Output "Build sporadic errors in Sample $sporadicName; Configuration: $sporadicConfiguration; Platform: $sporadicPlatform {" - Get-Content "$LogFilesDirectory\$sporadicName.$sporadicConfiguration.$sporadicPlatform.0.err" | Write-Output - Write-Output "} $sporadicSample" +if ($buildState.SporadicSet.Count -gt 0) { + Write-Output "--- Sporadic Failures (succeeded on retry) -------------------------" + foreach ($entry in ($buildState.SporadicSet | Sort-Object)) { + if ($entry -match '^(?.*)\s+(?\w+)\|(?\w+)$') { + $errLog = Join-Path $LogFilesDirectory "$($Matches.name).$($Matches.config).$($Matches.platform).0.err" + Write-Output " [SPORADIC] $($Matches.name) ($($Matches.config)|$($Matches.platform)):" + if (Test-Path $errLog) { + Get-Content $errLog | ForEach-Object { Write-Output " $_" } + } + } } - Write-Error "Some combinations were built with sporadic errors." Write-Output "" + Write-Error "Some combinations had sporadic build failures." } -# Display timer statistics -$min = $sw.Elapsed.Minutes -$seconds = $sw.Elapsed.Seconds +# ============================================================================= +# Step 11 - Final Summary +# ============================================================================= -$SolutionsSucceeded = $jresult.SolutionsSucceeded -$SolutionsExcluded = $jresult.SolutionsExcluded -$SolutionsUnsupported = $jresult.SolutionsUnsupported -$SolutionsFailed = $jresult.SolutionsFailed -$SolutionsSporadic = $jresult.SolutionsSporadic -$Results = $jresult.Results +$elapsed = $stopwatch.Elapsed -Write-Output "Built all combinations." +Write-Output "--- Build Complete -------------------------------------------------" +Write-Output " Elapsed: $($elapsed.Minutes)m $($elapsed.Seconds)s" +Write-Output " Disk Free (GB): $(Get-DiskFreeGB)" Write-Output "" -Write-Output "Elapsed time: $min minutes, $seconds seconds." -Write-Output ("Disk Remaining (GB): " + (((Get-Volume (Get-Item ".").PSDrive.Name).SizeRemaining / 1GB))) -Write-Output ("Samples: " + $sampleSet.Count) -Write-Output ("Configurations: " + $Configurations.Count + " (" + $Configurations + ")") -Write-Output ("Platforms: " + $Platforms.Count + " (" + $Platforms + ")") -Write-Output "Combinations: $SolutionsTotal" -Write-Output "Succeeded: $SolutionsSucceeded" -Write-Output "Excluded: $SolutionsExcluded" -Write-Output "Unsupported: $SolutionsUnsupported" -Write-Output "Failed: $SolutionsFailed" -Write-Output "Sporadic: $SolutionsSporadic" -Write-Output "Log files directory: $LogFilesDirectory" -Write-Output "Overview report: $reportFilePath" +Write-Output " Samples: $($sampleSet.Count)" +Write-Output " Configurations: $($Configurations -join ', ')" +Write-Output " Platforms: $($Platforms -join ', ')" +Write-Output " Combinations: $combinationsTotal" Write-Output "" - -$Results | Sort-Object { $_.Sample } | ConvertTo-Csv | Out-File $reportCsvFilePath -$Results | Sort-Object { $_.Sample } | ConvertTo-Html -Title "Overview" | Out-File $reportFilePath -Invoke-Item $reportFilePath +Write-Output " Succeeded: $($buildState.Succeeded)" +Write-Output " Excluded: $($buildState.Excluded)" +Write-Output " Unsupported: $($buildState.Unsupported)" +Write-Output " Failed: $($buildState.Failed)" +Write-Output " Sporadic: $($buildState.Sporadic)" +Write-Output "" +Write-Output " Log directory: $LogFilesDirectory" +Write-Output " CSV report: $reportCsvPath" +Write-Output " HTML report: $reportHtmlPath" +Write-Output "--------------------------------------------------------------------" + +# ============================================================================= +# Step 12 - Generate Reports +# ============================================================================= + +$sortedResults = $buildState.Results | Sort-Object { $_.Sample } +$sortedResults | ConvertTo-Csv | Out-File $reportCsvPath +$sortedResults | ConvertTo-Html -Title "WDK Sample Build Overview" | Out-File $reportHtmlPath + +# Only open the HTML report interactively (not in CI/automation) +if (-not $env:GITHUB_REPOSITORY -and -not $env:BUILD_BUILDID -and [Environment]::UserInteractive) { + Invoke-Item $reportHtmlPath +} diff --git a/ListAllSamples.ps1 b/ListAllSamples.ps1 index ed87fc83..f533c080 100644 --- a/ListAllSamples.ps1 +++ b/ListAllSamples.ps1 @@ -4,7 +4,7 @@ .DESCRIPTION Searches for all .sln files recursively from the repo root, excludes NuGet package directories - (paths starting with 'packages.'), computes a normalized sample name for each, and writes + (paths containing 'packages' as a segment), computes a normalized sample name for each, and writes the sorted list to Samples.txt (one sample name per line). The sample name is derived from the relative directory path: backslashes are replaced with dots diff --git a/Samples.txt b/Samples.txt index 7998f395..571b6b4c 100644 --- a/Samples.txt +++ b/Samples.txt @@ -1,4 +1,4 @@ -audio.acx.samples.audiocodec.driver +audio.acx.samples.audiocodec.driver audio.simpleaudiosample audio.soundwire.samples.sdcavad audio.sysvad @@ -61,8 +61,8 @@ network.modem.fakemodem network.ndis.extension network.ndis.filter network.ndis.mux -network.ndis.ndisprot.6x network.ndis.ndisprot_kmdf +network.ndis.ndisprot.6x network.ndis.netvmini.6x network.radio.radiomanagersample network.trans.ddproxy -- cgit v1.3.1 From 302d18984142a22d5195105fe0347eb5b145d859 Mon Sep 17 00:00:00 2001 From: 5an7y Date: Fri, 27 Mar 2026 16:04:47 -0700 Subject: Remove Samples.txt: discover samples dynamically via ListAllSamples - ListAllSamples.ps1: output sorted sample names to stdout instead of writing Samples.txt. The file output is replaced with Write-Output so callers can capture results via pipeline or command substitution. - Build-Samples.ps1: remove SampleListPath parameter. Default discovery now calls ListAllSamples.ps1 at runtime and consumes its stdout. Explicit -Samples array still works and is sorted alphabetically. Both paths guarantee a consistent alphabetical sample ordering. - Samples.txt: deleted. No longer needed as a pre-generated artifact. WDS_TestPass.cs is unaffected - it only passes -LogFilesDirectory and -InfOptions, neither of which changed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Build-Samples.ps1 | 33 ++++++------- ListAllSamples.ps1 | 14 +++--- Samples.txt | 134 ----------------------------------------------------- 3 files changed, 21 insertions(+), 160 deletions(-) delete mode 100644 Samples.txt diff --git a/Build-Samples.ps1 b/Build-Samples.ps1 index 6fd86bd3..aad2d3e5 100644 --- a/Build-Samples.ps1 +++ b/Build-Samples.ps1 @@ -6,7 +6,7 @@ This is the main build orchestrator for driver samples. It performs these steps: 1. Ensures a developer build environment (VS DevShell or EWDK) is active - 2. Reads sample names from Samples.txt (or a specified file/array) + 2. Discovers samples via ListAllSamples.ps1 (or uses the -Samples parameter if provided) 3. Detects the build environment (GitHub CI, NuGet, EWDK, or WDK) and build number 4. Loads exclusions from exclusions.csv (supports wildcard paths) 5. Builds all non-excluded sample/configuration/platform combinations in parallel @@ -15,15 +15,12 @@ Requires PowerShell 7+ (uses ForEach-Object -Parallel). Typical call chain: - Build-AllSamples.ps1 -> ListAllSamples.ps1 -> Build-Samples.ps1 -> Build-Sample.ps1 - (this script) + Build-AllSamples.ps1 -> Build-Samples.ps1 -> ListAllSamples.ps1 (for discovery) + (this script) -> Build-Sample.ps1 (per sample) -.PARAMETER SampleListPath - Path to the file containing sample names (one per line, dot-separated). - Defaults to Samples.txt in the current directory. .PARAMETER Samples - Optional array of specific sample names to build. When provided, overrides SampleListPath. + Optional array of specific sample names to build. When omitted, all samples are discovered dynamically via ListAllSamples.ps1. .PARAMETER Configurations Build configurations (e.g. 'Debug','Release'). Defaults to $env:WDS_Configuration or @@ -49,7 +46,7 @@ .EXAMPLE .\Build-Samples - Builds all samples from Samples.txt with default settings. + Discovers all samples via ListAllSamples.ps1 and builds them with default settings. .EXAMPLE .\Build-Samples -Samples 'audio.acx.samples.audiocodec.driver','usb.kmdf_fx2' -Configurations 'Debug' -Platforms 'x64' @@ -57,16 +54,15 @@ Builds specific samples for a single configuration and platform. .EXAMPLE - .\Build-Samples -SampleListPath .\MySamples.txt -ThrottleLimit 8 + .\Build-Samples -ThrottleLimit 8 - Builds samples from a custom list with limited parallelism. + Discovers all samples and builds them with limited parallelism. #> #Requires -Version 7.0 [CmdletBinding()] param( - [string]$SampleListPath = (Join-Path (Get-Location) "Samples.txt"), [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 }), @@ -282,14 +278,15 @@ $reportCsvPath = Join-Path $LogFilesDirectory "$ReportFileName.csv" # ============================================================================= if ($Samples) { - $sampleNames = $Samples + # Explicit list passed by the caller — use as-is, sorted alphabetically. + $sampleNames = $Samples | Sort-Object } else { - if (-not (Test-Path $SampleListPath)) { - Write-Error "Sample list not found: $SampleListPath. Run .\ListAllSamples.ps1 first." - exit 1 - } - $sampleNames = Get-Content $SampleListPath | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } + # Default: discover samples dynamically via ListAllSamples.ps1. + # ListAllSamples outputs one alphabetically-sorted sample name per line to stdout. + Write-Verbose "No -Samples provided. Discovering samples via ListAllSamples.ps1..." + $sampleNames = & (Join-Path $PSScriptRoot 'ListAllSamples.ps1') -Verbose:$verbose | + Where-Object { -not [string]::IsNullOrWhiteSpace($_) } } # Map sample names to directory paths, validating each exists @@ -307,7 +304,7 @@ foreach ($name in $sampleNames) { } if ($sampleSet.Count -eq 0) { - Write-Error "No valid sample directories found. Verify Samples.txt and current directory." + Write-Error "No valid sample directories found. Ensure ListAllSamples.ps1 is available in the repo root." exit 1 } diff --git a/ListAllSamples.ps1 b/ListAllSamples.ps1 index f533c080..1a1a4a78 100644 --- a/ListAllSamples.ps1 +++ b/ListAllSamples.ps1 @@ -1,11 +1,11 @@ <# .SYNOPSIS - Enumerates all available sample solutions in the repository and writes them to Samples.txt. + Enumerates all available sample solutions in the repository and writes them to the console. .DESCRIPTION Searches for all .sln files recursively from the repo root, excludes NuGet package directories (paths containing 'packages' as a segment), computes a normalized sample name for each, and writes - the sorted list to Samples.txt (one sample name per line). + the sorted list to stdout (one sample name per line). The sample name is derived from the relative directory path: backslashes are replaced with dots and the result is lowercased. @@ -13,10 +13,10 @@ .EXAMPLE .\ListAllSamples - Discovers all samples and writes Samples.txt to the repo root. + Discovers all samples and writes the sorted names to the console. .OUTPUTS - Samples.txt in the current working directory. + Sorted sample names written to stdout, one per line. #> [CmdletBinding()] @@ -45,7 +45,5 @@ foreach ($file in $solutionFiles) { $sortedNames = $sampleNames.Keys | Sort-Object -$outputPath = Join-Path $root "Samples.txt" -$sortedNames | Out-File -FilePath $outputPath -Encoding utf8 - -Write-Output "Found $($sortedNames.Count) samples. Written to Samples.txt" +Write-Verbose "Found $($sortedNames.Count) samples." +$sortedNames | Write-Output diff --git a/Samples.txt b/Samples.txt deleted file mode 100644 index 571b6b4c..00000000 --- a/Samples.txt +++ /dev/null @@ -1,134 +0,0 @@ -audio.acx.samples.audiocodec.driver -audio.simpleaudiosample -audio.soundwire.samples.sdcavad -audio.sysvad -avstream.avscamera -avstream.avshws -avstream.avssamp -avstream.sampledevicemft -avstream.samplemft0 -bluetooth.bthecho -bluetooth.serialhcibus -filesys.cdfs -filesys.fastfat -filesys.minifilter.avscan -filesys.minifilter.cancelsafe -filesys.minifilter.cdo -filesys.minifilter.change -filesys.minifilter.ctx -filesys.minifilter.delete -filesys.minifilter.metadatamanager -filesys.minifilter.minispy -filesys.minifilter.namechanger -filesys.minifilter.nullfilter -filesys.minifilter.passthrough -filesys.minifilter.scanner -filesys.minifilter.simrep -filesys.minifilter.swapbuffers -general.cancel -general.dchu.osrfx2_dchu_base -general.dchu.osrfx2_dchu_extension_loose -general.dchu.osrfx2_dchu_extension_tight -general.echo.kmdf -general.echo.umdf2 -general.event -general.ioctl.kmdf -general.ioctl.wdm -general.obcallback -general.pcidrv -general.perfcounters.kcs -general.plx9x5x -general.registry.regfltr -general.simplemediasource -general.systemdma.wdm -general.toaster.toastdrv -general.toaster.toastpkg -general.toaster.umdf2 -general.tracing.evntdrv -general.tracing.systemtracecontrol -general.tracing.tracedriver -gnss -gpio.samples -hid.firefly -hid.hclient -hid.hidusbfx2 -hid.vhidmini2 -input.kbfiltr -input.layout -input.moufiltr -network.config.bindview -network.modem.fakemodem -network.ndis.extension -network.ndis.filter -network.ndis.mux -network.ndis.ndisprot_kmdf -network.ndis.ndisprot.6x -network.ndis.netvmini.6x -network.radio.radiomanagersample -network.trans.ddproxy -network.trans.inspect -network.trans.msnmntr -network.trans.stmedit -network.trans.wfpsampler -network.wlan.wdi -network.wsk.echosrv -network.wwan.cxwmbclass -nfc.nfccxsample -nfp.net -pofx.pep -pofx.umdf2 -pofx.wdf -pos.drivers.barcodescanner -pos.drivers.magneticstripereader -powerlimit.plclient -powerlimit.plpolicy -prm -sd.miniport.sdhc -security.elam -sensors.activity -sensors.adxl345acc -sensors.customsensors -sensors.fusion -sensors.pedometer -sensors.sensorscombodriver -sensors.simpledeviceorientationsensor -serial.serenum -serial.serial -serial.virtualserial2 -setup.devcon -simbatt.func -smartcrd -spb.skeletoni2c -spb.spbtesttool -storage.class.classpnp -storage.class.disk -storage.iscsi -storage.miniports.lsi_u3 -storage.miniports.storahci -storage.msdsm -storage.tools.spti -thermal.simsensor -thermal.thermalclient -tools.dv.samples.dv-faildriver-wdm.driver -tools.kasan.samples.kasandemo-wdm -tools.sdv.samples.sdv-faildriver-kmdf -tools.sdv.samples.sdv-faildriver-ndis -tools.sdv.samples.sdv-faildriver-storport -tools.sdv.samples.sdv-faildriver-wdm -tree -usb.kmdf_enumswitches -usb.kmdf_fx2 -usb.ucmcxucsi -usb.ucmtcpcicxclientsample -usb.ucmucsiacpisample -usb.ufxclientsample -usb.umdf2_fx2 -usb.usbsamp -usb.usbview -usb.wdf_osrfx2_lab -video.archive.pixlib -video.indirectdisplay -video.kmdod -wia -wmi.wmiacpi -wmi.wmisamp -- cgit v1.3.1 From 4d94b8bf0d05c294b61087b395843a868789d911 Mon Sep 17 00:00:00 2001 From: 5an7y Date: Mon, 30 Mar 2026 12:00:47 -0700 Subject: Revert exclusions.csv to match main --- exclusions.csv | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/exclusions.csv b/exclusions.csv index fbfefa25..63d5e24a 100644 --- a/exclusions.csv +++ b/exclusions.csv @@ -6,8 +6,8 @@ network\trans\WFPSampler,Debug|ARM64,,22621,Only NI: Only ARM: Fails to build on 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,*,,27100,"failure introduced in VS17.14, suppressed until fix" -serial\serial,*,,27100,"failure introduced in VS17.14, suppressed until fix" -network\wlan\wdi,*,,27100,"failure introduced in VS17.14, suppressed until fix" -tools\kasan\samples\kasandemo-wdm,*|x64,,27100,"failure introduced in VS17.14, suppressed until fix" +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" -- cgit v1.3.1 From d92950792737971182298c34d7efa445fbb23728 Mon Sep 17 00:00:00 2001 From: 5an7y Date: Mon, 30 Mar 2026 15:26:00 -0700 Subject: Remove Build-AllSamples.ps1 and Build-SampleSet.ps1 in favor of Build-Samples.ps1 - Delete Build-AllSamples.ps1 and Build-SampleSet.ps1 (replaced by Build-Samples.ps1) - Update ci.yml workflow to call Build-Samples.ps1 - Update Build-ChangedSamples.ps1 to call Build-Samples.ps1 - Rewrite Building-Locally.md Step 6 with new script usage and output format - Update Step 7 NuGet notes to reference Build-Samples.ps1 --- .github/scripts/Build-ChangedSamples.ps1 | 7 +- .github/workflows/ci.yml | 2 +- Build-AllSamples.ps1 | 71 ------ Build-SampleSet.ps1 | 375 ------------------------------- Building-Locally.md | 104 ++++----- 5 files changed, 58 insertions(+), 501 deletions(-) delete mode 100644 Build-AllSamples.ps1 delete mode 100644 Build-SampleSet.ps1 diff --git a/.github/scripts/Build-ChangedSamples.ps1 b/.github/scripts/Build-ChangedSamples.ps1 index bdaee943..29f99061 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-AllSamples.ps1" -or $filename -eq "Build-Sample.ps1" -or $filename -eq "Build-SampleSet.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 "Build-Sample.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\*") { @@ -52,9 +52,10 @@ foreach ($file in $ChangedFiles) { } if ($buildAll) { - .\Build-AllSamples -Verbose:$Verbose -LogFilesDirectory (Join-Path $root "_logs") + .\Build-Samples -Verbose:$Verbose -LogFilesDirectory (Join-Path $root "_logs") } else { - .\Build-SampleSet -SampleSet $sampleSet -Verbose:$Verbose -LogFilesDirectory (Join-Path $root "_logs") + $sampleNames = $sampleSet.Keys | Sort-Object + .\Build-Samples -Samples $sampleNames -Verbose:$Verbose -LogFilesDirectory (Join-Path $root "_logs") } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1f3d7693..0e847b35 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: run: nuget restore .\packages.config -PackagesDirectory .\packages\ - name: Retrieve and build all available solutions - run: .\Build-AllSamples.ps1 -Verbose + run: .\Build-Samples.ps1 -Verbose env: WDS_Configuration: ${{ matrix.configuration }} WDS_Platform: ${{ matrix.platform }} diff --git a/Build-AllSamples.ps1 b/Build-AllSamples.ps1 deleted file mode 100644 index 6b3a1976..00000000 --- a/Build-AllSamples.ps1 +++ /dev/null @@ -1,71 +0,0 @@ -<# -.SYNOPSIS -Builds all available sample solutions in the repository (excluding specific solutions). - -.DESCRIPTION -This script searches for all available Visual Studio Solutions (.sln files) and attempts to run MSBuild to build them for the specified configurations and platforms. - -.PARAMETER Samples -A regular expression matching the samples to be built. Default is '' that matches all samples. Examples include '^tools.' or '.dchu'. - -.PARAMETER Configurations -A list of configurations to build samples under. Values available are 'Debug' and 'Release'. By default, $env:WDS_Configuration will be used as the sole configuration to build for. If this environment variable is not set the default is 'Debug' and 'Release'. - -.PARAMETER Platforms -A list of platforms to build samples under (e.g. 'x64', 'arm64'). By default, $env:WDS_Platform will be used as the sole platform to build for. If this environment variable is not set the default is 'x64' and'arm64'. - -.PARAMETER LogFilesDirectory -Path to a directory where the log files will be written to. If not provided, outputs will be logged to the '_logs' directory within the current working directory. - -.PARAMETER ThrottleLimit -An integer indicating how many combinations to build in parallel. If 0 or not provided this defaults to 5 x number of logical processors. - -.INPUTS -None. - -.OUTPUTS -None. - -.EXAMPLE -.\Build-AllSamples - -.EXAMPLE -.\Build-AllSamples -Samples '^tools.' -Configurations 'Debug','Release' -Platforms 'x64','arm64' - -#> - -[CmdletBinding()] -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 }), - [string]$LogFilesDirectory = (Join-Path (Get-Location) "_logs"), - [int]$ThrottleLimit -) - -$Verbose = $false -if ($PSBoundParameters.ContainsKey('Verbose')) { - $Verbose = $PsBoundParameters.Get_Item('Verbose') -} - -$root = Get-Location -$solutionFiles = Get-ChildItem -Path $root -Recurse -Filter *.sln | Select-Object -ExpandProperty FullName - -# To include in CI gate -$sampleSet = @{} -foreach ($file in $solutionFiles) { - $dir = (Get-Item $file).DirectoryName - $dir_norm = $dir.Replace($root, '').Trim('\').Replace('\', '.').ToLower() - if ($dir_norm -match ("^packages.")) { - Write-Verbose "`u{1F50E} Found and ignored non-sample [$dir_norm] at $dir" - } - elseif ($dir_norm -match ($Samples)) { - Write-Verbose "`u{1F50E} Found and filtered in sample [$dir_norm] at $dir" - $sampleSet[$dir_norm] = $dir - } - else { - Write-Verbose "`u{1F50E} Found and filtered out sample [$dir_norm] at $dir" - } -} - -.\Build-SampleSet -SampleSet $sampleSet -Configurations $Configurations -Platform $Platforms -LogFilesDirectory $LogFilesDirectory -Verbose:$Verbose -ThrottleLimit $ThrottleLimit diff --git a/Build-SampleSet.ps1 b/Build-SampleSet.ps1 deleted file mode 100644 index da016f05..00000000 --- a/Build-SampleSet.ps1 +++ /dev/null @@ -1,375 +0,0 @@ -[CmdletBinding()] -param( - [hashtable]$SampleSet, - [string[]]$Configurations = @(if ([string]::IsNullOrEmpty($env:WDS_Configuration)) { "Debug" } else { $env:WDS_Configuration }), - [string[]]$Platforms = @(if ([string]::IsNullOrEmpty($env:WDS_Platform)) { "x64" } else { $env:WDS_Platform }), - $LogFilesDirectory = (Get-Location), - [string]$ReportFileName = $(if ([string]::IsNullOrEmpty($env:WDS_ReportFileName)) { "_overview" } else { $env:WDS_ReportFileName }), - [int]$ThrottleLimit = 0 -) - -$root = Get-Location - -# launch developer powershell (if necessary to prevent multiple developer sessions) -if (-not $env:VSCMD_VER) { - Import-Module (Resolve-Path "$env:ProgramFiles\Microsoft Visual Studio\*\*\Common7\Tools\Microsoft.VisualStudio.DevShell.dll") - Enter-VsDevShell -VsInstallPath (Resolve-Path "$env:ProgramFiles\Microsoft Visual Studio\*\*") - Set-Location $root -} - -$ThrottleFactor = 5 -$LogicalProcessors = (Get-CIMInstance -Class 'CIM_Processor' -Verbose:$false).NumberOfLogicalProcessors - -if ($ThrottleLimit -eq 0) { - $ThrottleLimit = $ThrottleFactor * $LogicalProcessors -} - -$Verbose = $false -if ($PSBoundParameters.ContainsKey('Verbose')) { - $Verbose = $PsBoundParameters.Get_Item('Verbose') -} - -New-Item -ItemType Directory -Force -Path $LogFilesDirectory | Out-Null -$reportFilePath = Join-Path $LogFilesDirectory "$ReportFileName.htm" -$reportCsvFilePath = Join-Path $LogFilesDirectory "$ReportFileName.csv" - - -Remove-Item -Recurse -Path $LogFilesDirectory 2>&1 | Out-Null -New-Item -ItemType Directory -Force -Path $LogFilesDirectory | Out-Null - -$oldPreference = $ErrorActionPreference -$ErrorActionPreference = "stop" -try { - # Check that msbuild can be called before trying anything. - Get-Command "msbuild" | Out-Null -} -catch { - Write-Host "`u{274C} msbuild cannot be called from current environment. Check that msbuild is set in current path (for example, that it is called from a Visual Studio developer command)." - Write-Error "msbuild cannot be called from current environment." - exit 1 -} -finally { - $ErrorActionPreference = $oldPreference -} - -# -# Determine build environment: 'GitHub', 'NuGet', 'EWDK', or 'WDK'. -# Determine build number (used for exclusions based on build number). Five digits. Say, '22621'. -# Determine NuGet package version (if applicable). -# -$build_environment="" -$build_number=0 -$nuget_package_version=0 -# -# In Github we build using NuGet. -# -if ($env:GITHUB_REPOSITORY) { - $build_environment="GitHub" - $nuget_package_version=([regex]'(?<=x64\.)(\d+\.)(\d+\.)(\d+\.)(\d+)').Matches((Get-Childitem .\packages\*WDK.x64* -Name)).Value - $build_number=$nuget_package_version.split('.')[2] -} -# -# WDK NuGet will require presence of a folder 'packages'. The version is sourced from repo .\Env-Vars.ps1. -# -# Hack: If user has hydrated nuget packages, then use those. That will be indicated by presence of a folder named '.\packages'. -# Further, we need to test that the directory has been hydrated using '.\packages\*'. -# -elseif(Test-Path(".\packages\*")) { - $build_environment=("NuGet") - $nuget_package_version=([regex]'(?<=x64\.)(\d+\.)(\d+\.)(\d+\.)(\d+)').Matches((Get-Childitem .\packages\*WDK.x64* -Name)).Value - $build_number=$nuget_package_version.split('.')[2] -} -# -# EWDK sets environment variable BuildLab. For example 'ni_release_svc_prod1.22621.2428'. -# -elseif($env:BuildLab -match '(?[^.]*).(?[^.]*).(?[^.]*)') { - $build_environment=("EWDK."+$Matches.branch+"."+$Matches.build+"."+$Matches.qfe) - $build_number=$Matches.build -} -# -# WDK sets environment variable UCRTVersion. For example '10.0.22621.0'. -# -elseif ($env:UCRTVersion -match '10.0.(?.*).0') { - $build_environment="WDK" - $build_number=$Matches.build -} -else { - - # Dump all environment variables so as to help debug error: - Write-Output "Environment variables {" - Get-ChildItem env:* | Sort-Object name - Write-Output "Environment variables }" - - Write-Error "Could not determine build environment." - exit 1 -} - -# Determine WDK Visual Studio Component version -# -# Be lenient with EWDK builds that do not include the component information -if ($build_environment -match '^EWDK') { - $wdk_vs_component_ver = "(WDK Visual Studio Component Version is not included for EWDK builds)" -} else { - # Get the WDK extension version from installed packages - $wdk_vs_component_ver = Get-ChildItem "${env:ProgramData}\Microsoft\VisualStudio\Packages\Microsoft.Windows.DriverKit,version=*" -ErrorAction SilentlyContinue - if (-not $wdk_vs_component_ver) { - Write-Error "WDK Visual Studio Component version not found. Please ensure the WDK Component is installed." - exit 1 - } - $wdk_vs_component_ver = [regex]::Match($wdk_vs_component_ver.Name, '(\d+\.){3}\d+').Value -} - -# -# -# InfVerif_AdditionalOptions -# -# Samples must build cleanly and even without warnings. -# -# An exception is for infverif where specific warnings are acceptable. Those -# specific warnings indicates issues intentially present in the samples, that -# anyone that clones the samples must fix as part of productizing a driver. -# -# In 22621 those warnings are: /sw1284 /sw1285 /sw1293 /sw2083 /sw2086 -# -# After 22621 those warnings are put under a common flag: /samples -# -$InfVerif_AdditionalOptions=($build_number -le 22621 ? "/sw1284 /sw1285 /sw1293 /sw2083 /sw2086" : "/samples") - -# -# Determine exclusions. -# -# Exclusions are loaded from .\exclusions.csv. -# Each line has form: -# Path,Configurations,MinBuild,MaxBuild,Reason -# Where: -# Path: Is the path to folder containing solution(s) using backslashes. For example: 'audio\acx\samples\audiocodec\driver' . -# Configurations: Are the configurations to exclude. For example: '*|arm64' . -# MinBuild: Is the minimum WDK/EWDK build number the exclusion is applicable for. For example: '22621' . -# MaxBuild: Is the maximum WDK/EWDK build number the exclusion is applicable for. For example: '26031' . -# Reason: Is plain text documenting the reason for the exclusion. For example: 'error C1083: Cannot open include file: 'acx.h': No such file or directory' . -# -$exclusionConfigurations = @{} -$exclusionReasons = @{} -Import-Csv 'exclusions.csv' | ForEach-Object { - $excluded_driver=$_.Path.Replace($root, '').Trim('\').Replace('\', '.').ToLower() - $excluded_configurations=($_.configurations -eq '' ? '*' : $_.configurations) - $excluded_minbuild=($_.MinBuild -eq '' ? 00000 : $_.MinBuild) - $excluded_maxbuild=($_.MaxBuild -eq '' ? 99999 : $_.MaxBuild) - if (($excluded_minbuild -le $build_number) -and ($build_number -le $excluded_maxbuild) ) - { - $exclusionConfigurations[$excluded_driver] = $excluded_configurations - $exclusionReasons[$excluded_driver] = $_.Reason - Write-Verbose "Exclusion.csv entry applied for '$excluded_driver' for configuration '$excluded_configurations'." - } - else - { - Write-Verbose "Exclusion.csv entry not applied for '$excluded_driver' due to build number." - } -} - -$jresult = @{ - SolutionsBuilt = 0 - SolutionsSucceeded = 0 - SolutionsExcluded = 0 - SolutionsUnsupported = 0 - SolutionsFailed = 0 - SolutionsSporadic = 0 - Results = @() - FailSet = @() - lock = [System.Threading.Mutex]::new($false) -} - -$SolutionsTotal = $sampleSet.Count * $Configurations.Count * $Platforms.Count - -Write-Output "WDK Build Environment: $build_environment" -Write-Output "WDK Build Number: $build_number" -if (($build_environment -eq "GitHub") -or ($build_environment -eq "NuGet")) { -Write-Output "WDK Nuget Version: $nuget_package_version" -} -Write-Output "WDK Visual Studio Component Version: $wdk_vs_component_ver" -Write-Output "Samples: $($sampleSet.Count)" -Write-Output "Configurations: $($Configurations.Count) ($Configurations)" -Write-Output "Platforms: $($Platforms.Count) ($Platforms)" -Write-Output "InfVerif_AdditionalOptions: $InfVerif_AdditionalOptions" -Write-Output "Combinations: $SolutionsTotal" -Write-Output "LogicalProcessors: $LogicalProcessors" -Write-Output "ThrottleFactor: $ThrottleFactor" -Write-Output "ThrottleLimit: $ThrottleLimit" -Write-Output "WDS_WipeOutputs: $env:WDS_WipeOutputs" -Write-Output "Disk Remaining (GB): $(((Get-Volume ((Get-Item ".").PSDrive.Name)).SizeRemaining) / 1GB)" -Write-Output "" -Write-Output "T: Combinations" -Write-Output "B: Built" -Write-Output "R: Build is running currently" -Write-Output "P: Build is pending an available build slot" -Write-Output "" -Write-Output "S: Built and result was 'Succeeded'" -Write-Output "E: Built and result was 'Excluded'" -Write-Output "U: Built and result was 'Unsupported' (Platform and Configuration combination)" -Write-Output "F: Built and result was 'Failed'" -Write-Output "O: Built and result was 'Sporadic'" -Write-Output "" -Write-Output "Building all combinations..." - -$Results = @() - -$sw = [Diagnostics.Stopwatch]::StartNew() - -$SampleSet.GetEnumerator() | ForEach-Object -ThrottleLimit $ThrottleLimit -Parallel { - $LogFilesDirectory = $using:LogFilesDirectory - $exclusionConfigurations = $using:exclusionConfigurations - $exclusionReasons = $using:exclusionReasons - $Configurations = $using:Configurations - $Platforms = $using:Platforms - $InfVerif_AdditionalOptions = $using:InfVerif_AdditionalOptions - $Verbose = $using:Verbose - - $sampleName = $_.Key - $directory = $_.Value - - $ResultElement = new-object psobject - Add-Member -InputObject $ResultElement -MemberType NoteProperty -Name Sample -Value "$sampleName" - - foreach ($configuration in $Configurations) { - foreach ($platform in $Platforms) { - $thisunsupported = 0 - $thisfailed = 0 - $thissporadic = 0 - $thisexcluded = 0 - $thissucceeded = 0 - $thisresult = "Not run" - $thisfailset = @() - $thissporadicset = @() - - if ($exclusionConfigurations.ContainsKey($sampleName) -and ($exclusionConfigurations[$sampleName].Split(';') | Where-Object { "$configuration|$platform" -like $_ })) { - # Verbose - Write-Verbose "[$sampleName $configuration|$platform] `u{23E9} Excluded and skipped. Reason: $($exclusionReasons[$sampleName])" - $thisexcluded += 1 - $thisresult = "Excluded" - } - else { - .\Build-Sample -Directory $directory -SampleName $sampleName -LogFilesDirectory $LogFilesDirectory -Configuration $configuration -Platform $platform -InfVerif_AdditionalOptions $InfVerif_AdditionalOptions -Verbose:$Verbose - if ($LASTEXITCODE -eq 0) { - $thissucceeded += 1 - $thisresult = "Succeeded" - } - elseif ($LASTEXITCODE -eq 1) { - $thisfailset += "$sampleName $configuration|$platform" - $thisfailed += 1 - $thisresult = "Failed" - } - elseif ($LASTEXITCODE -eq 2) { - $thissporadicset += "$sampleName $configuration|$platform" - $thissporadic += 1 - $thisresult = "Sporadic" - } - else { - # ($LASTEXITCODE -eq 3) - $thisunsupported += 1 - $thisresult = "Unsupported" - } - } - Add-Member -InputObject $ResultElement -MemberType NoteProperty -Name "$configuration|$platform" -Value "$thisresult" - - $null = ($using:jresult).lock.WaitOne() - try { - ($using:jresult).SolutionsBuilt += 1 - ($using:jresult).SolutionsSucceeded += $thissucceeded - ($using:jresult).SolutionsExcluded += $thisexcluded - ($using:jresult).SolutionsUnsupported += $thisunsupported - ($using:jresult).SolutionsFailed += $thisfailed - ($using:jresult).SolutionsSporadic += $thissporadic - ($using:jresult).FailSet += $thisfailset - ($using:jresult).SporadicSet += $thissporadicset - $SolutionsTotal = $using:SolutionsTotal - $ThrottleLimit = $using:ThrottleLimit - $SolutionsBuilt = ($using:jresult).SolutionsBuilt - $SolutionsRemaining = $SolutionsTotal - $SolutionsBuilt - $SolutionsRunning = if ($SolutionsRemaining -ge $ThrottleLimit) { $ThrottleLimit } else { $SolutionsRemaining } - $SolutionsPending = if ($SolutionsRemaining -ge $ThrottleLimit) { ($SolutionsRemaining - $ThrottleLimit) } else { 0 } - $SolutionsBuiltPercent = [Math]::Round(100 * ($SolutionsBuilt / $using:SolutionsTotal)) - $TBRP = "T:" + ($SolutionsTotal) + "; B:" + (($using:jresult).SolutionsBuilt) + "; R:" + ($SolutionsRunning) + "; P:" + ($SolutionsPending) - $rstr = "S:" + (($using:jresult).SolutionsSucceeded) + "; E:" + (($using:jresult).SolutionsExcluded) + "; U:" + (($using:jresult).SolutionsUnsupported) + "; F:" + (($using:jresult).SolutionsFailed) + "; O:" + (($using:jresult).SolutionsSporadic) - Write-Progress -Activity "Building combinations" -Status "$SolutionsBuilt of $using:SolutionsTotal combinations built ($SolutionsBuiltPercent%) | $TBRP | $rstr" -PercentComplete $SolutionsBuiltPercent - } - finally { - ($using:jresult).lock.ReleaseMutex() - } - } - } - $null = ($using:jresult).lock.WaitOne() - try { - ($using:jresult).Results += $ResultElement - } - finally { - ($using:jresult).lock.ReleaseMutex() - } -} - -$sw.Stop() - -Write-Output "" - -if ($jresult.FailSet.Count -gt 0) { - Write-Output "Some combinations were built with errors:" - $jresult.FailSet = $jresult.FailSet | Sort-Object - foreach ($failedSample in $jresult.FailSet) { - $failedSample -match "^(.*) (\w*)\|(\w*)$" | Out-Null - $failName = $Matches[1] - $failConfiguration = $Matches[2] - $failPlatform = $Matches[3] - Write-Output "Build errors in Sample $failName; Configuration: $failConfiguration; Platform: $failPlatform {" - Get-Content "$LogFilesDirectory\$failName.$failConfiguration.$failPlatform.0.err" | Write-Output - Write-Output "} $failedSample" - } - Write-Error "Some combinations were built with errors." - Write-Output "" -} - -if ($jresult.SporadicSet.Count -gt 0) { - Write-Output "Some combinations were built with sporadic error:" - $jresult.SporadicSet = $jresult.SporadicSet | Sort-Object - foreach ($sporadicSample in $jresult.SporadicSet) { - $sporadicSample -match "^(.*) (\w*)\|(\w*)$" | Out-Null - $sporadicName = $Matches[1] - $sporadicConfiguration = $Matches[2] - $sporadicPlatform = $Matches[3] - Write-Output "Build sporadic errors in Sample $sporadicName; Configuration: $sporadicConfiguration; Platform: $sporadicPlatform {" - Get-Content "$LogFilesDirectory\$sporadicName.$sporadicConfiguration.$sporadicPlatform.0.err" | Write-Output - Write-Output "} $sporadicSample" - } - Write-Error "Some combinations were built with sporadic errors." - Write-Output "" -} - -# Display timer statistics to host -$min = $sw.Elapsed.Minutes -$seconds = $sw.Elapsed.Seconds - -$SolutionsSucceeded = $jresult.SolutionsSucceeded -$SolutionsExcluded = $jresult.SolutionsExcluded -$SolutionsUnsupported = $jresult.SolutionsUnsupported -$SolutionsFailed = $jresult.SolutionsFailed -$SolutionsSporadic = $jresult.SolutionsSporadic -$Results = $jresult.Results - -Write-Output "Built all combinations." -Write-Output "" -Write-Output "Elapsed time: $min minutes, $seconds seconds." -Write-Output ("Disk Remaining (GB): " + (((Get-Volume (Get-Item ".").PSDrive.Name).SizeRemaining / 1GB))) -Write-Output ("Samples: " + $sampleSet.Count) -Write-Output ("Configurations: " + $Configurations.Count + " (" + $Configurations + ")") -Write-Output ("Platforms: " + $Platforms.Count + " (" + $Platforms + ")") -Write-Output "Combinations: $SolutionsTotal" -Write-Output "Succeeded: $SolutionsSucceeded" -Write-Output "Excluded: $SolutionsExcluded" -Write-Output "Unsupported: $SolutionsUnsupported" -Write-Output "Failed: $SolutionsFailed" -Write-Output "Sporadic: $SolutionsSporadic" -Write-Output "Log files directory: $LogFilesDirectory" -Write-Output "Overview report: $reportFilePath" -Write-Output "" - -$Results | Sort-Object { $_.Sample } | ConvertTo-Csv | Out-File $reportCsvFilePath -$Results | Sort-Object { $_.Sample } | ConvertTo-Html -Title "Overview" | Out-File $reportFilePath -Invoke-Item $reportFilePath diff --git a/Building-Locally.md b/Building-Locally.md index 312c5967..d988a330 100644 --- a/Building-Locally.md +++ b/Building-Locally.md @@ -129,80 +129,82 @@ Microsoft.Windows.WDK.arm64.10.0.26000.1 --- -## Step 6: Check all samples builds with expected results for all flavors +## Step 6: Build all samples ```powershell pwsh -.\Build-AllSamples +.\Build-Samples ``` -Above builds all samples for all configurations and platforms. +Above builds all samples for all configurations and platforms. -You can refine what exact samples to build, what configurations, and platforms to build. build Here are a few examples: +You can refine what exact samples to build, what configurations, and platforms to build. Here are a few examples: ```powershell # Get Help: -Get-Help .\Build-AllSamples +Get-Help .\Build-Samples # Build all solutions for all flavors with builds running in parallel: -.\Build-AllSamples +.\Build-Samples # Build with Verbose output (print start and finish of each sample): -.\Build-AllSamples -Verbose +.\Build-Samples -Verbose -# Build without massive parallism (slow, but good debugging): -.\Build-AllSamples -ThrottleLimit 1 +# Build without massive parallelism (slow, but good for debugging): +.\Build-Samples -ThrottleLimit 1 -# Build the solutions in the tools folder for all flavors: -.\Build-AllSamples -Samples '^tools.' -Configurations 'Debug','Release' -Platforms 'x64','arm64' +# Build specific samples for all flavors: +.\Build-Samples -Samples 'tools.sdv.samples.sampledriver','usb.kmdf_fx2' -# Build the solutions in the tools folder for only 'Debug|x64': -.\Build-AllSamples -Samples '^tools.' -Configurations 'Debug' -Platforms 'x64' +# Build specific samples for only 'Debug|x64': +.\Build-Samples -Samples 'tools.sdv.samples.sampledriver' -Configurations 'Debug' -Platforms 'x64' ``` Example of expected output: ``` -Build Environment: NuGet -Build Number: 26100 -Samples: 132 -Configurations: 2 (Debug Release) -Platforms: 2 (x64 arm64) -InfVerif_AdditionalOptions: /samples -Combinations: 528 -LogicalProcessors: 12 -ThrottleFactor: 5 -ThrottleLimit: 60 -WDS_WipeOutputs: -Disk Remaining (GB): ... - -T: Combinations -B: Built -R: Build is running currently -P: Build is pending an available build slot - -S: Built and result was 'Succeeded' -E: Built and result was 'Excluded' -U: Built and result was 'Unsupported' (Platform and Configuration combination) -F: Built and result was 'Failed' -O: Built and result was 'Sporadic' +--- WDK Sample Build Plan ------------------------------------------ + Environment: NuGet + Build Number: 26100 + NuGet Version: 10.0.26100.1 + WDK VS Component: 10.0.26100.1882 + InfVerif Options: /samples + + Samples: 132 (0 skipped) + Configurations: Debug, Release + Platforms: x64, arm64 + Combinations: 528 + Exclusions: 4 + + Parallelism: 60 jobs (12 cores x 5) + Disk Free (GB): ... + Wipe Outputs: False +-------------------------------------------------------------------- + +Progress legend: + T=Total B=Built R=Running P=Pending + S=Succeeded E=Excluded U=Unsupported F=Failed O=Sporadic Building all combinations... -Built all combinations. +--- Build Complete ------------------------------------------------- + Elapsed: 12m 42s + Disk Free (GB): ... + + Samples: 132 + Configurations: Debug, Release + Platforms: x64, arm64 + Combinations: 528 + + Succeeded: 526 + Excluded: 0 + Unsupported: 2 + Failed: 0 + Sporadic: 0 -Elapsed time: 12 minutes, 42 seconds. -Disk Remaining (GB): ... -Samples: 132 -Configurations: 2 (Debug Release) -Platforms: 2 (x64 arm64) -Combinations: 528 -Succeeded: 526 -Excluded: 0 -Unsupported: 2 -Failed: 0 -Sporadic: 0 -Log files directory: .\_logs -Overview report: .\_overview.htm + Log directory: .\_logs + CSV report: .\_logs\_overview.csv + HTML report: .\_logs\_overview.htm +-------------------------------------------------------------------- ``` --- @@ -214,7 +216,7 @@ To restore a specific version of our WDK NuGet packages: Follow these steps before running "nuget restore" command: * Open the .\packages.config file and update the full version (including the branch if required) in all three entries. * Open the .\Directory.build.props file and update the version and build of the package with the same values as in previous step. -* Open .\Build-SampleSet and change the NuGet build number (used by .\exclusions.csv and for determining infverif flags) +* Open .\Build-Samples.ps1 and check the NuGet build number logic (used by .\exclusions.csv and for determining infverif flags) * Now you can run "nuget restore" A few examples of how to interact with nuget: -- cgit v1.3.1 From bc6b2708d9d762dde716a2af7c8fa08534c1afcc Mon Sep 17 00:00:00 2001 From: 5an7y Date: Mon, 30 Mar 2026 15:32:35 -0700 Subject: Merge Build-Sample.ps1 into Build-Samples.ps1 as internal function - Inline Build-Sample.ps1 logic as Build-SingleSample function in Build-Samples.ps1 - Pass function definition into parallel runspaces via $using: pattern - Replace exit codes with return values for in-process execution - Delete Build-Sample.ps1 (no longer needed as a separate script) - Update Build-ChangedSamples.ps1 trigger file list --- .github/scripts/Build-ChangedSamples.ps1 | 2 +- Build-Sample.ps1 | 211 ------------------------------- Build-Samples.ps1 | 152 +++++++++++++++++++++- 3 files changed, 146 insertions(+), 219 deletions(-) delete mode 100644 Build-Sample.ps1 diff --git a/.github/scripts/Build-ChangedSamples.ps1 b/.github/scripts/Build-ChangedSamples.ps1 index 29f99061..3e91eb88 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 "Build-Sample.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 "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/Build-Sample.ps1 b/Build-Sample.ps1 deleted file mode 100644 index c09b7694..00000000 --- a/Build-Sample.ps1 +++ /dev/null @@ -1,211 +0,0 @@ -<# -.SYNOPSIS -Builds an specific directory containing a sample solution. - -.DESCRIPTION -This script attempts to build a directory containing a driver sample Solution for the specified configurations and platforms. - -.PARAMETER Directory -Path to a directory containing a valid Visual Studio Solution (.sln file). This is the solution that will be built. - -.PARAMETER SampleName -A friendly name to refer to the sample. Is unspecified, a name will be automatically generated one from the sample path. - -.PARAMETER Configuration -Configuration name that will be used to build the solution. Common available values are "Debug" and "Release". - -.PARAMETER Platform -Platform to build the solution for (e.g. "x64", "arm64"). - -.PARAMETER InfVerif_AdditionalOptions -Additional options for infverif (e.g. "/samples"). - -.PARAMETER LogFilesDirectoy -Path to a directory where the log files will be written to. If not provided, outputs will be logged to the current working directory. - -.INPUTS -None. - -.OUTPUTS -Verbose output about the execution of this script will be provided only if -Verbose is provided. Otherwise, no output will be generated. - -.EXAMPLE -.\Build-Sample -Directory .\usb\kmdf_fx2 - -.EXAMPLE -.\Build-Sample -Directory .\usb\kmdf_fx2 -Configuration 'Release' -Platform 'x64' -Verbose -LogFilesDirectory .\_logs - -#> - -[CmdletBinding()] -param( - [Parameter(Mandatory = $true, - HelpMessage = 'Enter one directory path', - Position = 0)] - [string]$Directory, - [string]$SampleName, - [string]$Configuration = "Debug", - [string]$Platform = "x64", - [string]$InfVerif_AdditionalOptions = "/samples", - $LogFilesDirectory = (Get-Location) -) - -$Verbose = $false -if ($PSBoundParameters.ContainsKey('Verbose')) { - $Verbose = $PsBoundParameters.Get_Item('Verbose') -} - -$oldPreference = $ErrorActionPreference -$ErrorActionPreference = "stop" -try -{ - # Check that msbuild can be called before trying anything. - Get-Command "msbuild" | Out-Null -} -catch -{ - Write-Verbose "`u{274C} msbuild cannot be called from current environment. Check that msbuild is set in current path (for example, that it is called from a Visual Studio developer command)." - Write-Error "msbuild cannot be called from current environment." - exit 1 -} -finally -{ - $ErrorActionPreference = $oldPreference -} - -if (-not (Test-Path -Path $Directory -PathType Container)) -{ - Write-Warning "`u{274C} A valid directory could not be found under $Directory" - exit 1 -} - -New-Item -ItemType Directory -Force -Path $LogFilesDirectory | Out-Null - -if (-not (Test-Path -Path $LogFilesDirectory -PathType Container)) -{ - Write-Warning "`u{274C} A valid directory for storing log files could not be created under $LogFilesDirectory" - # No exit here: process will continue but logs won't be available. -} - -if ([string]::IsNullOrWhitespace($SampleName)) -{ - $SampleName = (Resolve-Path $Directory).Path.Replace((Get-Location), '').Replace('\', '.').Trim('.').ToLower() -} - -$solutionFile = Get-ChildItem -Path $Directory -Filter *.sln | Select-Object -ExpandProperty FullName -First 1 - -if ($null -eq $solutionFile) -{ - Write-Warning "`u{274C} A solution could not be found under $Directory" - exit 1 -} - -$configurationIsSupported = $false -$inSolutionConfigurationPlatformsSection = $false -foreach ($line in Get-Content -Path $solutionFile) -{ - if (-not $inSolutionConfigurationPlatformsSection -and $line -match "\s*GlobalSection\(SolutionConfigurationPlatforms\).*") - { - $inSolutionConfigurationPlatformsSection = $true; - continue; - } - elseif ($line -match "\s*EndGlobalSection.*") - { - $inSolutionConfigurationPlatformsSection = $false; - continue; - } - - if ($inSolutionConfigurationPlatformsSection) - { - [regex]$regex = ".*=\s*(?(?.*)\|(?.*))\s*" - $match = $regex.Match($line) - if ([string]::IsNullOrWhiteSpace($match.Groups["ConfigString"].Value) -or [string]::IsNullOrWhiteSpace($match.Groups["Platform"].Value)) - { - Write-Warning "Could not parse configuration entry $line from file $solutionFile." - continue; - } - if ($match.Groups["Configuration"].Value.Trim() -eq $Configuration -and $match.Groups["Platform"].Value.Trim() -eq $Platform) - { - $configurationIsSupported = $true; - } - } -} - -if (-not $configurationIsSupported) -{ - Write-Verbose "[$SampleName] `u{23E9} Skipped. Configuration $Configuration|$Platform not supported." - exit 3 -} - -Write-Verbose "Building Sample: $SampleName; Configuration: $Configuration; Platform: $Platform {" - -$myexit=1 - -# -# Let us build up to three times (0th, 1st, and 2nd attempt). -# If we succeed at first, then it is a success. -# If we fail at first, but succeed at either of next two attempts, then it is a sporadic failure. -# If we even at third attempt fail, then it is a true failure. -# -for ($i = 0; $i -lt 3; $i++) -{ - $binLogFilePath = "$LogFilesDirectory\$SampleName.$Configuration.$Platform.$i.binlog" - $errorLogFilePath = "$LogFilesDirectory\$SampleName.$Configuration.$Platform.$i.err" - $warnLogFilePath = "$LogFilesDirectory\$SampleName.$Configuration.$Platform.$i.wrn" - $OutLogFilePath = "$LogFilesDirectory\$SampleName.$Configuration.$Platform.$i.out" - - msbuild $solutionFile -clp:Verbosity=m -t:rebuild -property:Configuration=$Configuration -property:Platform=$Platform -p:TargetVersion=Windows10 -p:InfVerif_AdditionalOptions="$InfVerif_AdditionalOptions" -warnaserror -binaryLogger:LogFile=$binLogFilePath`;ProjectImports=None -flp1:errorsonly`;logfile=$errorLogFilePath -flp2:WarningsOnly`;logfile=$warnLogFilePath -noLogo > $OutLogFilePath - if ($null -ne $env:WDS_WipeOutputs) - { - Write-Verbose ("WipeOutputs: " + $Directory + " " + (((Get-Volume (Get-Item ".").PSDrive.Name).SizeRemaining / 1GB))) - Get-ChildItem -path $Directory -Recurse -Include x64 | Remove-Item -Recurse - Get-ChildItem -path $Directory -Recurse -Include arm64 | Remove-Item -Recurse - } - if ($LASTEXITCODE -eq 0) - { - # We succeeded building. - # If it was at a later attempt, let the caller know with a different exit code. - if ($i -eq 0) - { - $myexit = 0 - } - else - { - $myexit = 2 - } - # Remove binlog on success to save space; keep otherwise to diagnose issues. - Remove-Item $binLogFilePath - break; - } - else - { - # We failed building. - # Let us sleep for a bit. - # Then let the while loop do its thing and re-run. - Start-Sleep 1 - if ($Verbose) - { - Write-Warning "`u{274C} Build failed. Retrying to see if sporadic..." - } - } -} - -if ($myexit -eq 1) -{ - if ($Verbose) - { - Write-Warning "`u{274C} Build failed. Log available at $errorLogFilePath" - } - exit 1 -} - -if ($myexit -eq 2) -{ - if ($Verbose) - { - Write-Warning "`u{274C} Build sporadically failed. Log available at $errorLogFilePath" - } - exit 2 -} - -Write-Verbose "Building Sample: $SampleName; Configuration: $Configuration; Platform: $Platform }" diff --git a/Build-Samples.ps1 b/Build-Samples.ps1 index aad2d3e5..4242d428 100644 --- a/Build-Samples.ps1 +++ b/Build-Samples.ps1 @@ -14,10 +14,6 @@ Requires PowerShell 7+ (uses ForEach-Object -Parallel). - Typical call chain: - Build-AllSamples.ps1 -> Build-Samples.ps1 -> ListAllSamples.ps1 (for discovery) - (this script) -> Build-Sample.ps1 (per sample) - .PARAMETER Samples Optional array of specific sample names to build. When omitted, all samples are discovered dynamically via ListAllSamples.ps1. @@ -240,6 +236,141 @@ function Get-DiskFreeGB { } } +function Build-SingleSample { + <# + .SYNOPSIS + Builds a single sample directory for one configuration/platform combination. + .DESCRIPTION + Locates the .sln in the given directory, verifies the configuration|platform is + supported, then invokes msbuild with up to 3 attempts (to detect sporadic failures). + .OUTPUTS + Returns an integer exit code: + 0 = succeeded on first attempt + 1 = failed after all retries + 2 = sporadic (failed first, succeeded on retry) + 3 = unsupported configuration/platform + #> + param( + [string]$Directory, + [string]$SampleName, + [string]$Configuration = 'Debug', + [string]$Platform = 'x64', + [string]$InfVerif_AdditionalOptions = '/samples', + [string]$LogFilesDirectory = (Get-Location), + [bool]$Verbose = $false + ) + + if (-not (Test-Path -Path $Directory -PathType Container)) { + Write-Warning "`u{274C} A valid directory could not be found under $Directory" + return 1 + } + + New-Item -ItemType Directory -Force -Path $LogFilesDirectory | Out-Null + + if ([string]::IsNullOrWhiteSpace($SampleName)) { + $SampleName = (Resolve-Path $Directory).Path.Replace((Get-Location), '').Replace('\', '.').Trim('.').ToLower() + } + + $solutionFile = Get-ChildItem -Path $Directory -Filter *.sln | + Select-Object -ExpandProperty FullName -First 1 + + if ($null -eq $solutionFile) { + Write-Warning "`u{274C} A solution could not be found under $Directory" + return 1 + } + + # --- Check whether the solution supports the requested configuration|platform --- + $configurationIsSupported = $false + $inSolutionConfigurationPlatformsSection = $false + foreach ($line in Get-Content -Path $solutionFile) { + if (-not $inSolutionConfigurationPlatformsSection -and + $line -match '\s*GlobalSection\(SolutionConfigurationPlatforms\).*') { + $inSolutionConfigurationPlatformsSection = $true + continue + } + elseif ($line -match '\s*EndGlobalSection.*') { + $inSolutionConfigurationPlatformsSection = $false + continue + } + + if ($inSolutionConfigurationPlatformsSection) { + [regex]$regex = '.*=\s*(?(?.*)\|(?.*))\s*' + $match = $regex.Match($line) + if ([string]::IsNullOrWhiteSpace($match.Groups['ConfigString'].Value) -or + [string]::IsNullOrWhiteSpace($match.Groups['Platform'].Value)) { + Write-Warning "Could not parse configuration entry $line from file $solutionFile." + continue + } + if ($match.Groups['Configuration'].Value.Trim() -eq $Configuration -and + $match.Groups['Platform'].Value.Trim() -eq $Platform) { + $configurationIsSupported = $true + } + } + } + + if (-not $configurationIsSupported) { + Write-Verbose "[$SampleName] `u{23E9} Skipped. Configuration $Configuration|$Platform not supported." + return 3 + } + + Write-Verbose "Building Sample: $SampleName; Configuration: $Configuration; Platform: $Platform {" + + $myexit = 1 + + # Build up to three times (0th, 1st, and 2nd attempt). + # Succeed on 1st -> success (0) + # Fail 1st, succeed on retry -> sporadic (2) + # Fail all three -> failure (1) + for ($i = 0; $i -lt 3; $i++) { + $binLogFilePath = "$LogFilesDirectory\$SampleName.$Configuration.$Platform.$i.binlog" + $errorLogFilePath = "$LogFilesDirectory\$SampleName.$Configuration.$Platform.$i.err" + $warnLogFilePath = "$LogFilesDirectory\$SampleName.$Configuration.$Platform.$i.wrn" + $outLogFilePath = "$LogFilesDirectory\$SampleName.$Configuration.$Platform.$i.out" + + msbuild $solutionFile ` + -clp:Verbosity=m -t:rebuild ` + -property:Configuration=$Configuration ` + -property:Platform=$Platform ` + -p:TargetVersion=Windows10 ` + -p:InfVerif_AdditionalOptions="$InfVerif_AdditionalOptions" ` + -warnaserror ` + -binaryLogger:LogFile=$binLogFilePath`;ProjectImports=None ` + -flp1:errorsonly`;logfile=$errorLogFilePath ` + -flp2:WarningsOnly`;logfile=$warnLogFilePath ` + -noLogo > $outLogFilePath + + if ($null -ne $env:WDS_WipeOutputs) { + Write-Verbose ("WipeOutputs: $Directory " + (((Get-Volume (Get-Item '.').PSDrive.Name).SizeRemaining / 1GB))) + Get-ChildItem -Path $Directory -Recurse -Include x64 | Remove-Item -Recurse + Get-ChildItem -Path $Directory -Recurse -Include arm64 | Remove-Item -Recurse + } + + if ($LASTEXITCODE -eq 0) { + $myexit = if ($i -eq 0) { 0 } else { 2 } + # Remove binlog on success to save space; keep otherwise to diagnose issues. + Remove-Item $binLogFilePath + break + } + else { + Start-Sleep 1 + if ($Verbose) { + Write-Warning "`u{274C} Build failed. Retrying to see if sporadic..." + } + } + } + + if ($myexit -eq 1 -and $Verbose) { + Write-Warning "`u{274C} Build failed. Log available at $errorLogFilePath" + } + if ($myexit -eq 2 -and $Verbose) { + Write-Warning "`u{274C} Build sporadically failed. Log available at $errorLogFilePath" + } + + Write-Verbose "Building Sample: $SampleName; Configuration: $Configuration; Platform: $Platform }" + + return $myexit +} + # ============================================================================= # Step 1 - Prepare Build Environment # ============================================================================= @@ -392,6 +523,9 @@ $buildState = @{ $stopwatch = [Diagnostics.Stopwatch]::StartNew() +# Capture function definition so it can be reconstructed inside each parallel runspace. +$buildSingleSampleDef = ${function:Build-SingleSample}.ToString() + $sampleSet.GetEnumerator() | ForEach-Object -ThrottleLimit $ThrottleLimit -Parallel { # --- Import shared state from parent scope --- $logDir = $using:LogFilesDirectory @@ -404,6 +538,9 @@ $sampleSet.GetEnumerator() | ForEach-Object -ThrottleLimit $ThrottleLimit -Paral $total = $using:combinationsTotal $throttle = $using:ThrottleLimit + # Reconstruct the function inside this parallel runspace + ${function:Build-SingleSample} = $using:buildSingleSampleDef + $sampleName = $_.Key $directory = $_.Value @@ -443,17 +580,18 @@ $sampleSet.GetEnumerator() | ForEach-Object -ThrottleLimit $ThrottleLimit -Paral } else { # -- Build the sample -- - .\Build-Sample -Directory $directory -SampleName $sampleName ` + $buildResult = Build-SingleSample ` + -Directory $directory -SampleName $sampleName ` -LogFilesDirectory $logDir -Configuration $configuration ` -Platform $platform -InfVerif_AdditionalOptions $infOpts ` -Verbose:$isVerbose - # Exit codes from Build-Sample.ps1: + # Return codes from Build-SingleSample: # 0 = succeeded on first attempt # 1 = failed after all retries # 2 = sporadic (failed first, succeeded on retry) # 3 = unsupported configuration/platform - switch ($LASTEXITCODE) { + switch ($buildResult) { 0 { $succeededDelta = 1; $result = 'Succeeded' } 1 { $failedDelta = 1; $result = 'Failed'; $failEntry = "$sampleName $configuration|$platform" } 2 { $sporadicDelta = 1; $result = 'Sporadic'; $sporadicEntry = "$sampleName $configuration|$platform" } -- cgit v1.3.1 From 83cfa72cc265ab052100563cfc2e8c9d9b6020ba Mon Sep 17 00:00:00 2001 From: 5an7y Date: Mon, 30 Mar 2026 15:43:16 -0700 Subject: Support wildcard patterns in -Samples parameter - When -Samples contains wildcards (* or ?), discover all samples first then filter with -like (same pattern style as exclusions.csv) - Exact sample names still work as before without discovery overhead - Update parameter docs, examples, and Building-Locally.md --- Build-Samples.ps1 | 43 +++++++++++++++++++++++++++++++++++-------- Building-Locally.md | 3 +++ 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/Build-Samples.ps1 b/Build-Samples.ps1 index 4242d428..7eafb54d 100644 --- a/Build-Samples.ps1 +++ b/Build-Samples.ps1 @@ -16,7 +16,9 @@ .PARAMETER Samples - Optional array of specific sample names to build. When omitted, all samples are discovered dynamically via ListAllSamples.ps1. + Optional array of specific sample names or wildcard patterns to build. Supports + wildcards (e.g. 'tools.*', 'audio.*') which are matched against all discovered + samples. When omitted, all samples are discovered dynamically via ListAllSamples.ps1. .PARAMETER Configurations Build configurations (e.g. 'Debug','Release'). Defaults to $env:WDS_Configuration or @@ -49,6 +51,11 @@ Builds specific samples for a single configuration and platform. +.EXAMPLE + .\Build-Samples -Samples 'tools.*' + + Builds all samples whose name matches the wildcard pattern 'tools.*'. + .EXAMPLE .\Build-Samples -ThrottleLimit 8 @@ -408,17 +415,37 @@ $reportCsvPath = Join-Path $LogFilesDirectory "$ReportFileName.csv" # Step 4 - Load Sample List # ============================================================================= -if ($Samples) { - # Explicit list passed by the caller — use as-is, sorted alphabetically. - $sampleNames = $Samples | Sort-Object -} -else { - # Default: discover samples dynamically via ListAllSamples.ps1. - # ListAllSamples outputs one alphabetically-sorted sample name per line to stdout. +# Always discover the full sample list when patterns contain wildcards, +# or when no -Samples were provided at all. +$hasWildcards = $Samples | Where-Object { $_ -match '[*?]' } + +if (-not $Samples) { + # No filter: discover and build everything. Write-Verbose "No -Samples provided. Discovering samples via ListAllSamples.ps1..." $sampleNames = & (Join-Path $PSScriptRoot 'ListAllSamples.ps1') -Verbose:$verbose | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } } +elseif ($hasWildcards) { + # One or more entries contain wildcards — discover all, then filter with -like. + Write-Verbose "Wildcard detected in -Samples. Discovering all samples to match patterns..." + $allSamples = & (Join-Path $PSScriptRoot 'ListAllSamples.ps1') -Verbose:$verbose | + Where-Object { -not [string]::IsNullOrWhiteSpace($_) } + $sampleNames = @() + foreach ($pattern in $Samples) { + $matched = $allSamples | Where-Object { $_ -like $pattern } + if ($matched) { + $sampleNames += $matched + } + else { + Write-Warning "Pattern '$pattern' did not match any samples." + } + } + $sampleNames = $sampleNames | Sort-Object -Unique +} +else { + # Exact list passed by the caller — use as-is, sorted alphabetically. + $sampleNames = $Samples | Sort-Object +} # Map sample names to directory paths, validating each exists $sampleSet = [ordered]@{} diff --git a/Building-Locally.md b/Building-Locally.md index d988a330..45110098 100644 --- a/Building-Locally.md +++ b/Building-Locally.md @@ -155,6 +155,9 @@ Get-Help .\Build-Samples # Build specific samples for all flavors: .\Build-Samples -Samples 'tools.sdv.samples.sampledriver','usb.kmdf_fx2' +# Build all samples inside the 'tools' folder using wildcards: +.\Build-Samples -Samples 'tools.*' + # Build specific samples for only 'Debug|x64': .\Build-Samples -Samples 'tools.sdv.samples.sampledriver' -Configurations 'Debug' -Platforms 'x64' ``` -- cgit v1.3.1 From 8056a2723c732809b4ca684666ac08385f6e835f Mon Sep 17 00:00:00 2001 From: 5an7y Date: Mon, 30 Mar 2026 15:45:36 -0700 Subject: Remove redundant specific-samples example from Building-Locally.md --- Building-Locally.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/Building-Locally.md b/Building-Locally.md index 45110098..645e9a61 100644 --- a/Building-Locally.md +++ b/Building-Locally.md @@ -152,9 +152,6 @@ Get-Help .\Build-Samples # Build without massive parallelism (slow, but good for debugging): .\Build-Samples -ThrottleLimit 1 -# Build specific samples for all flavors: -.\Build-Samples -Samples 'tools.sdv.samples.sampledriver','usb.kmdf_fx2' - # Build all samples inside the 'tools' folder using wildcards: .\Build-Samples -Samples 'tools.*' -- cgit v1.3.1