summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdonais Romero González <[email protected]>2023-01-30 15:00:49 -0800
committerGitHub <[email protected]>2023-01-30 15:00:49 -0800
commit07779ee84973f2f63a1f2ced6377463b1c01baaf (patch)
treed28fc10efc8f5dee97ec5df162bb533f6e801585
parent2c0c8763300988d92ddd139cadf012f5ab1b12e1 (diff)
Build scripts update (#844)
--------- Co-authored-by: Jakob Lichtenberg (170957) <[email protected]>
-rw-r--r--.github/README.md35
-rw-r--r--.github/scripts/Build-ChangedProjects.ps134
-rw-r--r--.github/scripts/Build-ChangedSamples.ps140
-rw-r--r--.github/workflows/Code-Scanning.yml4
-rw-r--r--.github/workflows/ci-pr.yml10
-rw-r--r--.github/workflows/ci.yml6
-rw-r--r--Build-AllProjects.ps115
-rw-r--r--Build-AllSamples.ps157
-rw-r--r--Build-Project.ps164
-rw-r--r--Build-ProjectSet.ps165
-rw-r--r--Build-Sample.ps1153
-rw-r--r--Build-SampleSet.ps1190
-rw-r--r--Building-Locally.md57
13 files changed, 541 insertions, 189 deletions
diff --git a/.github/README.md b/.github/README.md
new file mode 100644
index 00000000..b7569139
--- /dev/null
+++ b/.github/README.md
@@ -0,0 +1,35 @@
+# Using GitHub actions for building drivers
+
+If you use GitHub to host your code, you can leverage [GitHub Actions](https://docs.github.com/en/actions) to create automated workflows to build your driver projects.
+
+`windows-2022` runner (provided by `windows-latest`) is configured with Windows Driver Kit version 22H2 and Visual Studio 2022 off the box, so most solutions can be built directly running `msbuild` directly.
+
+```yaml
+name: Build all driver samples
+on:
+ push:
+ branches:
+ - main
+jobs:
+ build:
+ strategy:
+ matrix:
+ configuration: [Debug, Release]
+ platform: [x64]
+ runs-on: windows-2022
+ env:
+ Solution_Path: path\to\driver\solution.sln
+ steps:
+ - name: Check out repository code
+ uses: actions/checkout@v3
+
+ - name: Add MSBuild to PATH
+ uses: microsoft/[email protected]
+
+ - name: Build solution
+ run: |
+ msbuild $${{ env.Solution_Path }} -p:Configuration:${{ env.Configuration }} -p:Platform:${{ env.Platform }}
+ env:
+ Configuration: ${{ matrix.configuration }}
+ Platform: ${{ matrix.platform }}
+```
diff --git a/.github/scripts/Build-ChangedProjects.ps1 b/.github/scripts/Build-ChangedProjects.ps1
deleted file mode 100644
index a13e3501..00000000
--- a/.github/scripts/Build-ChangedProjects.ps1
+++ /dev/null
@@ -1,34 +0,0 @@
-param (
- [array]$ChangedFiles
-)
-
-$root = (Get-Location).Path
-
-# To include in CI gate
-$projectSet = @{}
-foreach ($file in $ChangedFiles)
-{
- if (-not (Test-Path $file)) {
- Write-Output "`u{2754} Changed file $file cannot be found"
- continue
- }
- $dir = (Get-Item $file).DirectoryName
- while ((-not ($slnItems = (Get-ChildItem $dir '*.sln'))) -and ($dir -ne $root))
- {
- $dir = (Get-Item $dir).Parent.FullName
- }
- if ($dir -eq $root)
- {
- Write-Output "`u{2754} Changed file $file does not match a project."
- continue
- }
- $projectName = $dir.Replace($root, '').Trim('\').Replace('\', '.').ToLower()
- Write-Output "`u{1F50E} Found project [$projectName] at $dir from changed file $file"
- if (-not ($projectSet.ContainsKey($projectName)))
- {
- $projectSet[$projectName] = $dir
- }
-}
-
-.\Build-ProjectSet -ProjectSet $projectSet
-
diff --git a/.github/scripts/Build-ChangedSamples.ps1 b/.github/scripts/Build-ChangedSamples.ps1
new file mode 100644
index 00000000..82096cd6
--- /dev/null
+++ b/.github/scripts/Build-ChangedSamples.ps1
@@ -0,0 +1,40 @@
+[CmdletBinding()]
+param (
+ [array]$ChangedFiles
+)
+
+$Verbose = $false
+if ($PSBoundParameters.ContainsKey('Verbose')) {
+ $Verbose = $PsBoundParameters.Get_Item('Verbose')
+}
+
+$root = (Get-Location).Path
+
+# To include in CI gate
+$sampleSet = @{}
+foreach ($file in $ChangedFiles)
+{
+ if (-not (Test-Path $file)) {
+ Write-Verbose "`u{2754} Changed file $file cannot be found"
+ continue
+ }
+ $dir = (Get-Item $file).DirectoryName
+ while ((-not ($slnItems = (Get-ChildItem $dir '*.sln'))) -and ($dir -ne $root))
+ {
+ $dir = (Get-Item $dir).Parent.FullName
+ }
+ if ($dir -eq $root)
+ {
+ Write-Verbose "`u{2754} Changed file $file does not match a sample."
+ continue
+ }
+ $sampleName = $dir.Replace($root, '').Trim('\').Replace('\', '.').ToLower()
+ Write-Verbose "`u{1F50E} Found sample [$sampleName] at $dir from changed file $file"
+ if (-not ($sampleSet.ContainsKey($sampleName)))
+ {
+ $sampleSet[$sampleName] = $dir
+ }
+}
+
+.\Build-SampleSet -SampleSet $sampleSet -Verbose:$Verbose -LogFilesDirectory (Join-Path $root "_logs")
+
diff --git a/.github/workflows/Code-Scanning.yml b/.github/workflows/Code-Scanning.yml
index 2993c393..b2c851b6 100644
--- a/.github/workflows/Code-Scanning.yml
+++ b/.github/workflows/Code-Scanning.yml
@@ -46,9 +46,9 @@ jobs:
uses: microsoft/[email protected]
- name: Retrieve and build all available solutions
- id: build-all-projects
+ id: build-all-samples
run: |
- .\Build-AllProjects.ps1
+ .\Build-AllSamples.ps1
env:
Configuration: ${{ matrix.configuration }}
Platform: ${{ matrix.platform }}
diff --git a/.github/workflows/ci-pr.yml b/.github/workflows/ci-pr.yml
index 931df9e4..c701bd23 100644
--- a/.github/workflows/ci-pr.yml
+++ b/.github/workflows/ci-pr.yml
@@ -15,8 +15,6 @@ jobs:
configuration: [Debug, Release]
platform: [x64, arm64]
runs-on: windows-2022
- env:
- Solution_Path: general\echo\kmdf\kmdfecho.sln
steps:
- name: Check out repository code
uses: actions/checkout@v3
@@ -29,12 +27,14 @@ jobs:
- name: Get changed files
id: get-changed-files
uses: tj-actions/changed-files@v27
+ with:
+ separator: ","
- name: Retrieve and build solutions from changed files
- id: build-changed-projects
+ id: build-changed-samples
run: |
- $changedFiles = "${{ steps.get-changed-files.outputs.all_changed_files }}".Split(' ')
- .\.github\scripts\Build-ChangedProjects.ps1 -ChangedFiles $changedFiles
+ $changedFiles = "${{ steps.get-changed-files.outputs.all_changed_files }}".Split(',')
+ .\.github\scripts\Build-ChangedSamples.ps1 -ChangedFiles $changedFiles -Verbose
env:
Configuration: ${{ matrix.configuration }}
Platform: ${{ matrix.platform }}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4c07460a..a8063d6e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,8 +15,6 @@ jobs:
configuration: [Debug, Release]
platform: [x64, arm64]
runs-on: windows-2022
- env:
- Solution_Path: general\echo\kmdf\kmdfecho.sln
steps:
- name: Check out repository code
uses: actions/checkout@v3
@@ -27,9 +25,9 @@ jobs:
uses: microsoft/[email protected]
- name: Retrieve and build all available solutions
- id: build-all-projects
+ id: build-all-samples
run: |
- .\Build-AllProjects.ps1
+ .\Build-AllSamples.ps1 -Verbose
env:
Configuration: ${{ matrix.configuration }}
Platform: ${{ matrix.platform }}
diff --git a/Build-AllProjects.ps1 b/Build-AllProjects.ps1
deleted file mode 100644
index 1c35d884..00000000
--- a/Build-AllProjects.ps1
+++ /dev/null
@@ -1,15 +0,0 @@
-$root = Get-Location
-$solutionFiles = Get-ChildItem -Path $root -Recurse -Filter *.sln | Select-Object -ExpandProperty FullName
-
-# To include in CI gate
-$projectSet = @{}
-foreach ($file in $solutionFiles)
-{
- $dir = (Get-Item $file).DirectoryName
- $dir_norm = $dir.Replace($root, '').Trim('\').Replace('\', '.').ToLower()
- Write-Output "`u{1F50E} Found project [$dir_norm] at $dir"
- $projectSet[$dir_norm] = $dir
-}
-
-.\Build-ProjectSet -ProjectSet $projectSet
-
diff --git a/Build-AllSamples.ps1 b/Build-AllSamples.ps1
new file mode 100644
index 00000000..d857c641
--- /dev/null
+++ b/Build-AllSamples.ps1
@@ -0,0 +1,57 @@
+<#
+.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 Configurations
+A list of configurations to build samples under. Values available are "Debug" and "Release". By default, $env:Configuration will be used as the sole configuration to build for. If this value doesn't exist, "Debug" will be used instead.
+
+.PARAMETER Platforms
+A list of platforms to build samples under (e.g. "x64", "arm64"). By default, $env:Platform will be used as the sole platform to build for. If this value doesn't exist, "x64" will be used instead.
+
+.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.
+
+.INPUTS
+None.
+
+.OUTPUTS
+None.
+
+.EXAMPLE
+.\Build-AllSamples
+
+.EXAMPLE
+.\Build-AllSamples -Configurations 'Debug','Release' -Platforms 'x64','arm64' -LogFilesDirectory .\_logs
+
+#>
+
+[CmdletBinding()]
+param(
+ [string[]]$Configurations = @([string]::IsNullOrEmpty($env:Configuration) ? "Debug" : $env:Configuration),
+ [string[]]$Platforms = @([string]::IsNullOrEmpty($env:Platform) ? "x64" : $env:Platform),
+ [string]$LogFilesDirectory = (Join-Path (Get-Location) "_logs")
+)
+
+$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()
+ Write-Verbose "`u{1F50E} Found sample [$dir_norm] at $dir"
+ $sampleSet[$dir_norm] = $dir
+}
+
+
+
+.\Build-SampleSet -SampleSet $sampleSet -Configurations $Configurations -Platform $Platforms -LogFilesDirectory $LogFilesDirectory -Verbose:$Verbose
diff --git a/Build-Project.ps1 b/Build-Project.ps1
deleted file mode 100644
index b01d13a8..00000000
--- a/Build-Project.ps1
+++ /dev/null
@@ -1,64 +0,0 @@
-param(
- $Directory,
- [string]$ProjectName,
- $LogFilesDirectory = "_logfiles",
- [string]$Configuration = "Debug",
- [string]$Platform = "x64"
-)
-
-# TODO Validate $Directory and $LogFilesDirectory
-New-Item -ItemType Directory -Force -Path $LogFilesDirectory | Out-Null
-
-if ([string]::IsNullOrWhitespace($ProjectName))
-{
- $ProjectName = (Resolve-Path $Directory).Path.Replace((Get-Location), '').Replace('\', '.').Trim('.').ToLower()
-}
-
-$solutionFile = Get-ChildItem -Path $Directory -Filter *.sln | Select-Object -ExpandProperty FullName -First 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*(?<ConfigString>(?<Configuration>.*)\|(?<Platform>.*))\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-Output "[$ProjectName] `u{23E9} Skipped. Configuration $Configuration|$Platform not supported."
- exit 0
-}
-
-$errorLogFilePath = "$LogFilesDirectory\$ProjectName.err"
-$warnLogFilePath = "$LogFilesDirectory\$ProjectName.wrn"
-Write-Output "[$ProjectName] `u{2692} Building project..."
-msbuild $solutionFile -clp:Verbosity=m -t:clean,build -property:Configuration=$Configuration -property:Platform=$Platform -p:TargetVersion=Windows10 -p:InfVerif_AdditionalOptions="/msft /sw1205 /sw1324 /sw1420 /sw1421" -p:SignToolWS=/fdws -p:DriverCFlagAddOn=/wd4996 -flp1:errorsonly`;logfile=$errorLogFilePath -flp2:WarningsOnly`;logfile=$warnLogFilePath -noLogo
-if ($LASTEXITCODE -ne 0)
-{
- Write-Warning "`u{274C} Build failed. Log available at $errorLogFilePath"
- exit 1
-}
diff --git a/Build-ProjectSet.ps1 b/Build-ProjectSet.ps1
deleted file mode 100644
index 2557b5b9..00000000
--- a/Build-ProjectSet.ps1
+++ /dev/null
@@ -1,65 +0,0 @@
-param(
- [hashtable]$ProjectSet,
- [string]$Configuration = $env:Configuration,
- [string]$Platform = $env:Platform
-)
-
-if ([string]::IsNullOrEmpty($Configuration))
-{
- $Configuration = "Debug"
-}
-
-if ([string]::IsNullOrEmpty($Platform))
-{
- $Platform = "x64"
-}
-
-$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
-}
-
-
-$exclusionsSet = @{}
-$failSet = @()
-Import-Csv 'exclusions.csv' | ForEach-Object {
- $exclusionsSet[$_.Path.Replace($root, '').Trim('\').Replace('\', '.').ToLower()] = $_.Reason
-}
-
-$ProjectSet.GetEnumerator() | ForEach-Object {
- $projectName = $_.Key
- if ($exclusionsSet.ContainsKey($projectName))
- {
- Write-Output "[$projectName] `u{23E9} Excluded and skipped. Reason: $($exclusionsSet[$projectName])"
- return;
- }
- $directory = $_.Value
- .\Build-Project -Directory $directory -ProjectName $ProjectName -Configuration $Configuration -Platform $Platform
- if ($LASTEXITCODE -ne 0)
- {
- $failSet += $ProjectName
- }
-}
-
-if ($failSet.Count -gt 0)
-{
- Write-Output "Some projects were built with errors:"
- foreach ($failedProject in $failSet)
- {
- Write-Output "$failedProject"
- }
- Write-Error "Some projects were built with errors."
-} \ No newline at end of file
diff --git a/Build-Sample.ps1 b/Build-Sample.ps1
new file mode 100644
index 00000000..57f0ce7e
--- /dev/null
+++ b/Build-Sample.ps1
@@ -0,0 +1,153 @@
+<#
+.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 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",
+ $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*(?<ConfigString>(?<Configuration>.*)\|(?<Platform>.*))\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 2
+}
+
+$errorLogFilePath = "$LogFilesDirectory\$SampleName.$Configuration.$Platform.err"
+$warnLogFilePath = "$LogFilesDirectory\$SampleName.$Configuration.$Platform.wrn"
+$OutLogFilePath = "$LogFilesDirectory\$SampleName.$Configuration.$Platform.out"
+
+Write-Verbose "Building Sample: $SampleName; Configuration: $Configuration; Platform: $Platform {"
+
+msbuild $solutionFile -clp:Verbosity=m -t:clean,build -property:Configuration=$Configuration -property:Platform=$Platform -p:TargetVersion=Windows10 -p:InfVerif_AdditionalOptions="/msft /sw1205 /sw1324 /sw1420 /sw1421" -p:SignToolWS=/fdws -p:DriverCFlagAddOn=/wd4996 -flp1:errorsonly`;logfile=$errorLogFilePath -flp2:WarningsOnly`;logfile=$warnLogFilePath -noLogo > $OutLogFilePath
+
+if ($LASTEXITCODE -ne 0)
+{
+ if ($Verbose)
+ {
+ Write-Warning "`u{274C} Build failed. Log available at $errorLogFilePath"
+ }
+ exit 1
+}
+
+Write-Verbose "Building Sample: $SampleName; Configuration: $Configuration; Platform: $Platform }"
diff --git a/Build-SampleSet.ps1 b/Build-SampleSet.ps1
new file mode 100644
index 00000000..8ab2d4e5
--- /dev/null
+++ b/Build-SampleSet.ps1
@@ -0,0 +1,190 @@
+[CmdletBinding()]
+param(
+ [hashtable]$SampleSet,
+ [string[]]$Configurations = @([string]::IsNullOrEmpty($env:Configuration) ? "Debug" : $env:Configuration),
+ [string[]]$Platforms = @([string]::IsNullOrEmpty($env:Platform) ? "x64" : $env:Platform),
+ $LogFilesDirectory = (Get-Location)
+)
+
+$Verbose = $false
+if ($PSBoundParameters.ContainsKey('Verbose')) {
+ $Verbose = $PsBoundParameters.Get_Item('Verbose')
+}
+
+New-Item -ItemType Directory -Force -Path $LogFilesDirectory | Out-Null
+$sampleBuilderFilePath = "$LogFilesDirectory\overview.htm"
+
+
+Remove-Item -Recurse -Path $LogFilesDirectory 2>&1 | Out-Null
+New-Item -ItemType Directory -Force -Path $LogFilesDirectory | Out-Null
+
+$NumberOfLogicalProcessors = (Get-CIMInstance -Class 'CIM_Processor' -Verbose:$false).NumberOfLogicalProcessors
+$SolutionsInParallel = 5 * $NumberOfLogicalProcessors
+
+Write-Verbose "Log files directory: $LogFilesDirectory"
+Write-Verbose "Results overview report: $sampleBuilderFilePath"
+Write-Verbose "Logical Processors: $NumberOfLogicalProcessors"
+Write-Verbose "Solutions in Parallel: $SolutionsInParallel"
+
+$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
+}
+
+# TODO: Make exclusion more granular; allow for configuration|platform exclusions
+$exclusionsSet = @{}
+$failSet = @()
+Import-Csv 'exclusions.csv' | ForEach-Object {
+ $exclusionsSet[$_.Path.Replace($root, '').Trim('\').Replace('\', '.').ToLower()] = $_.Reason
+}
+
+$jresult = @{
+ SolutionsBuilt = 0
+ SolutionsExcluded = 0
+ SolutionsFailed = 0
+ Results = @()
+ lock = [System.Threading.Mutex]::new($false)
+}
+
+$SolutionsTotal = $sampleSet.Count * $Configurations.Count * $Platforms.Count
+
+Write-Output "T: Total solutions: $SolutionsTotal"
+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 ""
+Write-Output "Building driver solutions..."
+
+$Results = @()
+
+$sw = [Diagnostics.Stopwatch]::StartNew()
+
+$SampleSet.GetEnumerator() | ForEach-Object -ThrottleLimit $SolutionsInParallel -Parallel {
+ $LogFilesDirectory = $using:LogFilesDirectory
+ $exclusionsSet = $using:exclusionsSet
+ $Configurations = $using:Configurations
+ $Platforms = $using:Platforms
+
+ $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
+ $thisexcluded = 0
+ $thissucceeded = 0
+ $thisresult = "Not run"
+
+ if ($exclusionsSet.ContainsKey($sampleName)) {
+ # Verbose
+ if ($thisexcluded -eq 0) {
+ Write-Verbose "[$sampleName] `u{23E9} Excluded and skipped. Reason: $($exclusionsSet[$sampleName])"
+ }
+ $thisexcluded += 1
+ $thisresult = "Excluded"
+ }
+ else {
+ .\Build-Sample -Directory $directory -SampleName $sampleName -LogFilesDirectory $LogFilesDirectory -Configuration $configuration -Platform $platform -Verbose:$Verbose
+ if ($LASTEXITCODE -eq 0) {
+ $thissucceeded += 1
+ $thisresult = "Succeeded"
+ }
+ elseif ($LASTEXITCODE -eq 1) {
+ $failSet += "$sampleName $configuration|$platform"
+ $thisfailed += 1
+ $thisresult = "Failed"
+ }
+ else {
+ # ($LASTEXITCODE -eq 2)
+ $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
+ $SolutionsTotal = $using:SolutionsTotal
+ $SolutionsInParallel = $using:SolutionsInParallel
+ $SolutionsBuilt = ($using:jresult).SolutionsBuilt
+ $SolutionsRemaining = $SolutionsTotal - $SolutionsBuilt
+ $SolutionsRunning = $SolutionsRemaining -ge $SolutionsInParallel ? ($SolutionsInParallel) : ($SolutionsRemaining)
+ $SolutionsPending = $SolutionsRemaining -ge $SolutionsInParallel ? ($SolutionsRemaining - $SolutionsInParallel) : (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)
+ Write-Progress -Activity "Building driver solutions" -Status "$SolutionsBuilt of $using:SolutionsTotal solutions 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()
+
+if ($failSet.Count -gt 0) {
+ Write-Output "Some samples were built with errors:"
+ foreach ($failedSample in $failSet) {
+ Write-Output "$failedSample"
+ }
+ Write-Error "Some samples were built with errors."
+}
+
+# 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
+$Results = $jresult.Results
+
+Write-Output ""
+Write-Output "Built solutions."
+Write-Output ""
+Write-Output "Total elapsed time: $min minutes, $seconds seconds."
+Write-Output "SolutionsTotal: $SolutionsTotal"
+Write-Output "SolutionsSucceeded: $SolutionsSucceeded"
+Write-Output "SolutionsExcluded: $SolutionsExcluded"
+Write-Output "SolutionsUnsupported: $SolutionsUnsupported"
+Write-Output "SolutionsFailed: $SolutionsFailed"
+Write-Output ""
+Write-Output "Results saved to $sampleBuilderFilePath"
+Write-Output ""
+
+$Results | Sort-Object { $_.Sample } | ConvertTo-Html -Title "Overview" | Out-File $sampleBuilderFilePath
+Invoke-Item $sampleBuilderFilePath
diff --git a/Building-Locally.md b/Building-Locally.md
new file mode 100644
index 00000000..df8c0093
--- /dev/null
+++ b/Building-Locally.md
@@ -0,0 +1,57 @@
+# How to build locally
+
+## Step 1: Install git and pwsh 7.3.0
+```
+winget install --id Microsoft.Powershell --source winget
+winget install --id Git.Git --source winget`
+```
+
+## Step 2: Create a "driver build environment"
+
+There are multiple ways to achieve this. For example, [install Visual Studio and the Windows Driver Kit](https://learn.microsoft.com/en-us/windows-hardware/drivers/download-the-wdk#download-and-install-the-windows-11-version-22h2-wdk). You can just download and mount the EWDK as well.
+
+In the following example that is what we will do:
+ * Download the Windows 11, version 22H2 EWDK ISO image from the [official site](https://learn.microsoft.com/en-us/legal/windows/hardware/enterprise-wdk-license-2022)
+ * Mount ISO image
+ * From a terminal, run `.\LaunchBuildEnv`
+
+## Step 3: Clone Windows Driver Samples and checkout main branch
+
+```
+cd path\to\your\repos
+git clone --recurse-submodules https://github.com/microsoft/Windows-driver-samples.git
+cd Windows-driver-samples
+```
+
+## Step 4: Check all samples builds with expected results for all flavors
+
+```
+pwsh
+.\Build-AllSamples.ps1 -Configurations 'Debug','Release' -Platforms 'x64','arm64' -LogFilesDirectory '_logs'
+```
+
+Expected output:
+```
+T: Total solutions: 612
+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'
+
+Building driver solutions...
+
+Built solutions.
+
+Total elapsed time: 11 minutes, 18 seconds.
+SolutionsTotal: 612
+SolutionsSucceeded: 316
+SolutionsExcluded: 56
+SolutionsUnsupported: 240
+SolutionsFailed: 0
+
+Results saved to _logs\overview.htm
+``` \ No newline at end of file