Files
PowerToys/tools/build/build-common.ps1
Gordon Lam a0ac7efd2d Improve how to build the project in PowerToys easier (#41459)
This pull request refactors and standardizes the PowerToys build scripts
to improve maintainability, reusability, and platform detection. The
main changes are the introduction of a shared helper script
(`build-common.ps1`), migration of build logic in individual scripts to
use these helpers, and enhanced platform auto-detection. This makes the
build pipeline more robust and easier to use from any directory within
the repository.

As the result of our recent changes, use the following guidance when
working in the PowerToys repo:

1. Use `build-essentials.ps1` before any development in general
- Purpose: restore NuGet packages for the full solution and build a
small set of essential native projects (runner, settings). This is a
fast way to ensure native artifacts required for local development are
available.

2. Use `build.ps1` from any folder
- Purpose: lightweight local builder. It auto-discovers the target
platform (x64/arm64/x86) and builds projects it finds in the current
directory.
- Notes: you can pass additional MSBuild arguments positionally (e.g.
`./tools/build/build.ps1 '/p:CIBuild=true'`) — the script will forward
them to MSBuild.
   - Use `-RestoreOnly` to only restore packages for local projects.

3. Use `build-installer.ps1` to create a local installer (use with
caution)
- Purpose: runs the full pipeline that restores, builds the full
solution, signs packages, and builds the installer (MSI/bootstrapper).
- Caution: this script performs cleaning (git clean) and installer
packaging steps that may remove untracked files under `installer/`.

Additional notes
- Shared helpers live in `build-common.ps1` and are used by the other
scripts (`RunMSBuild`, `RestoreThenBuild`, `BuildProjectsInDirectory`,
platform auto-detection).

**Shared build logic and helper functions:**

* Added new `tools/build/build-common.ps1` file containing reusable
PowerShell functions for MSBuild invocation, solution/project restore
and build, platform detection, and project discovery. All build scripts
now dot-source this file for shared functionality.

**Refactoring of build scripts to use shared helpers:**

* Updated `tools/build/build-essentials.ps1` to use `build-common.ps1`
helpers, including auto-detection of repository root and platform, and
simplified project build logic.
* Created new `tools/build/build.ps1` for quick local builds, using
shared helpers and supporting extra MSBuild arguments and platform
auto-detection.
* Refactored `tools/build/build-installer.ps1` to remove duplicate build
logic, use shared helpers, and support platform auto-detection and
argument forwarding.
[[1]](diffhunk://#diff-21888769485d767c43c0895fe315e6f6d7384da62f60ef917d8a61a610da10b9L55-R74)
[[2]](diffhunk://#diff-21888769485d767c43c0895fe315e6f6d7384da62f60ef917d8a61a610da10b9L83-L126)

**Improved platform detection and argument handling:**

* All scripts now auto-detect the target platform if not specified,
using the new `Get-DefaultPlatform` helper. This supports x64, arm64,
and x86 hosts.
[[1]](diffhunk://#diff-43764921d6c830dbb3a15fe875aebfbc46966ae5ff62f3179adb3ff046b47b9dR1-R166)
[[2]](diffhunk://#diff-946ed85e16779fdbcfeb7de80f631eae2da0f7bd478e27e22621121b409dde88L1-R70)
[[3]](diffhunk://#diff-7a444242b2a6d9c642341bd2ef45f51ba5698ad7827e5136e85eb483863967a7R1-R88)
[[4]](diffhunk://#diff-21888769485d767c43c0895fe315e6f6d7384da62f60ef917d8a61a610da10b9L55-R74)

**Consistent MSBuild invocation and logging:**

* MSBuild calls now consistently use shared helpers, centralized
logging, and support passing extra arguments such as `/p:CIBuild=true`
and custom solution/project paths.
[[1]](diffhunk://#diff-43764921d6c830dbb3a15fe875aebfbc46966ae5ff62f3179adb3ff046b47b9dR1-R166)
[[2]](diffhunk://#diff-21888769485d767c43c0895fe315e6f6d7384da62f60ef917d8a61a610da10b9L137-R110)
[[3]](diffhunk://#diff-21888769485d767c43c0895fe315e6f6d7384da62f60ef917d8a61a610da10b9L151-R125)
[[4]](diffhunk://#diff-21888769485d767c43c0895fe315e6f6d7384da62f60ef917d8a61a610da10b9L162-R139)

**Project and solution build improvements:**

* Build scripts now discover and build projects in preferred order
(.sln, .csproj, .vcxproj), and support restoring packages only if
requested.
[[1]](diffhunk://#diff-43764921d6c830dbb3a15fe875aebfbc46966ae5ff62f3179adb3ff046b47b9dR1-R166)
[[2]](diffhunk://#diff-7a444242b2a6d9c642341bd2ef45f51ba5698ad7827e5136e85eb483863967a7R1-R88)

Let me know if you need a walkthrough of the new helper functions or how
to use the updated build scripts!
2025-08-29 16:23:52 +08:00

167 lines
5.3 KiB
PowerShell

<#
.SYNOPSIS
Shared build helper functions for PowerToys build scripts.
.DESCRIPTION
This file provides reusable helper functions used by the build scripts:
- Get-BuildPaths: returns ScriptDir, OriginalCwd, RepoRoot (repo root detection)
- RunMSBuild: wrapper around msbuild.exe (accepts optional Platform/Configuration)
- RestoreThenBuild: performs restore and optionally builds the solution/project
- BuildProjectsInDirectory: discovers and builds local .sln/.csproj/.vcxproj files
USAGE
Dot-source this file from a script to load helpers:
. "$PSScriptRoot\build-common.ps1"
.NOTES
Do not execute this file directly; dot-source it from `build.ps1` or `build-installer.ps1` so helpers are available in your script scope.
#>
function RunMSBuild {
param (
[string]$Solution,
[string]$ExtraArgs,
[string]$Platform,
[string]$Configuration
)
# Prefer the solution's folder for logs; fall back to current directory
$logRoot = Split-Path -Path $Solution
if (-not $logRoot) { $logRoot = '.' }
$cfg = $null
if ($Configuration) { $cfg = $Configuration.ToLower() } else { $cfg = 'unknown' }
$plat = $null
if ($Platform) { $plat = $Platform.ToLower() } else { $plat = 'unknown' }
$allLog = Join-Path $logRoot ("build.{0}.{1}.all.log" -f $cfg, $plat)
$warningLog = Join-Path $logRoot ("build.{0}.{1}.warnings.log" -f $cfg, $plat)
$errorsLog = Join-Path $logRoot ("build.{0}.{1}.errors.log" -f $cfg, $plat)
$binLog = Join-Path $logRoot ("build.{0}.{1}.trace.binlog" -f $cfg, $plat)
$base = @(
$Solution
"/p:Platform=$Platform"
"/p:Configuration=$Configuration"
"/verbosity:normal"
'/clp:Summary;PerformanceSummary;ErrorsOnly;WarningsOnly'
"/fileLoggerParameters:LogFile=$allLog;Verbosity=detailed"
"/fileLoggerParameters1:LogFile=$warningLog;WarningsOnly"
"/fileLoggerParameters2:LogFile=$errorsLog;ErrorsOnly"
"/bl:$binLog"
'/nologo'
)
$cmd = $base + ($ExtraArgs -split ' ')
Write-Host (("[MSBUILD] {0}" -f ($cmd -join ' ')))
Push-Location $script:RepoRoot
try {
& msbuild.exe @cmd
if ($LASTEXITCODE -ne 0) {
Write-Error (("Build failed: {0} {1}" -f $Solution, $ExtraArgs))
exit $LASTEXITCODE
}
} finally {
Pop-Location
}
}
function RestoreThenBuild {
param (
[string]$Solution,
[string]$ExtraArgs,
[string]$Platform,
[string]$Configuration,
[bool]$RestoreOnly=$false
)
$restoreArgs = '/t:restore /p:RestorePackagesConfig=true'
if ($ExtraArgs) { $restoreArgs = "$restoreArgs $ExtraArgs" }
RunMSBuild $Solution $restoreArgs $Platform $Configuration
if (-not $RestoreOnly) {
$buildArgs = '/m'
if ($ExtraArgs) { $buildArgs = "$buildArgs $ExtraArgs" }
RunMSBuild $Solution $buildArgs $Platform $Configuration
}
}
function BuildProjectsInDirectory {
param(
[string]$DirectoryPath,
[string]$ExtraArgs,
[string]$Platform,
[string]$Configuration,
[switch]$RestoreOnly
)
if (-not (Test-Path $DirectoryPath)) {
return $false
}
$files = @()
try {
$files = Get-ChildItem -Path (Join-Path $DirectoryPath '*') -Include *.sln,*.csproj,*.vcxproj -File -ErrorAction SilentlyContinue
} catch {
$files = @()
}
if (-not $files -or $files.Count -eq 0) {
return $false
}
$names = ($files | ForEach-Object { $_.Name }) -join ', '
Write-Host ("[LOCAL BUILD] Found {0} project(s) in {1}: {2}" -f $files.Count, $DirectoryPath, $names)
$preferredOrder = @('.sln', '.csproj', '.vcxproj')
$files = $files | Sort-Object @{Expression = { [array]::IndexOf($preferredOrder, $_.Extension.ToLower()) }}
foreach ($f in $files) {
Write-Host ("[LOCAL BUILD] Building {0}" -f $f.FullName)
if ($f.Extension -eq '.sln') {
RestoreThenBuild $f.FullName $ExtraArgs $Platform $Configuration $RestoreOnly
} else {
$buildArgs = '/m'
if ($ExtraArgs) { $buildArgs = "$buildArgs $ExtraArgs" }
RunMSBuild $f.FullName $buildArgs $Platform $Configuration
}
}
return $true
}
function Get-DefaultPlatform {
<#
Returns a default target platform string based on the host machine (x64, arm64, x86).
#>
try {
$envArch = $env:PROCESSOR_ARCHITECTURE
if ($envArch) { $envArch = $envArch.ToLower() }
if ($envArch -eq 'amd64' -or $envArch -eq 'x86_64') { return 'x64' }
if ($envArch -match 'arm64') { return 'arm64' }
if ($envArch -eq 'x86') { return 'x86' }
if ($env:PROCESSOR_ARCHITEW6432) {
$envArch2 = $env:PROCESSOR_ARCHITEW6432.ToLower()
if ($envArch2 -eq 'amd64') { return 'x64' }
if ($envArch2 -match 'arm64') { return 'arm64' }
}
try {
$osArch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture
switch ($osArch.ToString().ToLower()) {
'x64' { return 'x64' }
'arm64' { return 'arm64' }
'x86' { return 'x86' }
}
} catch {
# ignore - RuntimeInformation may not be available
}
} catch {
# ignore any errors and fall back
}
return 'x64'
}