diff --git a/tools/build/BUILD-GUIDELINES.md b/tools/build/BUILD-GUIDELINES.md new file mode 100644 index 0000000000..3ee54a6bf3 --- /dev/null +++ b/tools/build/BUILD-GUIDELINES.md @@ -0,0 +1,21 @@ +# Build scripts - quick guideline + +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 (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). +- If you want a different default platform selection, set the `-Platform` parameter explicitly when invoking the scripts. + +If you want, I can add this guidance to the repository README instead or add a short one-liner comment to the top of `build-common.ps1` so tools can discover it automatically. diff --git a/tools/build/build-common.ps1 b/tools/build/build-common.ps1 new file mode 100644 index 0000000000..ec3586f2f3 --- /dev/null +++ b/tools/build/build-common.ps1 @@ -0,0 +1,166 @@ +<# +.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' +} diff --git a/tools/build/build-essentials.cmd b/tools/build/build-essentials.cmd new file mode 100644 index 0000000000..db51faaa9a --- /dev/null +++ b/tools/build/build-essentials.cmd @@ -0,0 +1,5 @@ +@echo off +REM Wrapper to run build-essentials.ps1 from cmd.exe +set SCRIPT_DIR=%~dp0 +powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%build-essentials.ps1" %* +exit /b %ERRORLEVEL% diff --git a/tools/build/build-essentials.ps1 b/tools/build/build-essentials.ps1 index 81a3c6733b..21b378d0e3 100644 --- a/tools/build/build-essentials.ps1 +++ b/tools/build/build-essentials.ps1 @@ -1,16 +1,71 @@ -cd $PSScriptRoot -cd ..\.. -$cwd = Get-Location -$SolutionDir = $cwd,"" -join "\" -cd $SolutionDir -$BuildArgs = "/p:Configuration=Release /p:Platform=x64 /p:BuildProjectReferences=false /p:SolutionDir=$SolutionDir" +<# +.SYNOPSIS +Build essential native PowerToys projects (runner and settings), restoring NuGet packages first. -$ProjectsToBuild = - ".\src\runner\runner.vcxproj", - ".\src\modules\shortcut_guide\shortcut_guide.vcxproj", - ".\src\modules\fancyzones\lib\FancyZonesLib.vcxproj", - ".\src\modules\fancyzones\dll\FancyZonesModule.vcxproj" +.DESCRIPTION +Lightweight script to build a small set of essential C++ projects used by PowerToys' runner and native modules. This script first restores NuGet packages for the full solution (`PowerToys.sln`) and then builds the runner and settings projects. Intended for fast local builds during development. -$ProjectsToBuild | % { - Invoke-Expression "msbuild $_ $BuildArgs" +.PARAMETER Platform +Target platform for the build (for example: 'x64', 'arm64'). If omitted the script will attempt to auto-detect the host platform. + +.PARAMETER Configuration +Build configuration (for example: 'Debug' or 'Release'). Default is 'Debug'. + +.EXAMPLE +.\tools\build\build-essentials.ps1 +Restores packages for the solution and builds the default set of native projects using the auto-detected platform and Debug configuration. + +.EXAMPLE +.\tools\build\build-essentials.ps1 -Platform arm64 -Configuration Release +Restores packages and builds the essentials in Release mode for ARM64, even if your machine is running on x64. + +.NOTES +- This script dot-sources `build-common.ps1` and uses the shared helper `RunMSBuild`. +- It will call `RestoreThenBuild 'PowerToys.sln'` before building the essential projects to ensure NuGet packages are restored. +- The script attempts to locate the repository root automatically and can be run from any folder inside the repo. +#> + +param ( + [string]$Platform = '', + [string]$Configuration = 'Debug' +) + +# Find repository root starting from the script location +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition +$repoRoot = $ScriptDir +while ($repoRoot -and -not (Test-Path (Join-Path $repoRoot "PowerToys.sln"))) { + $parent = Split-Path -Parent $repoRoot + if ($parent -eq $repoRoot) { + Write-Error "Could not find PowerToys repository root." + exit 1 + } + $repoRoot = $parent +} + +# Export script-scope variables used by build-common helpers +Set-Variable -Name RepoRoot -Value $repoRoot -Scope Script -Force + +# Load shared helpers +. "$PSScriptRoot\build-common.ps1" + +# If platform not provided, auto-detect from host +if (-not $Platform -or $Platform -eq '') { + try { + $Platform = Get-DefaultPlatform + Write-Host ("[AUTO-PLATFORM] Detected platform: {0}" -f $Platform) + } catch { + Write-Warning "Failed to auto-detect platform; defaulting to 'x64'" + $Platform = 'x64' + } +} + +# Ensure solution packages are restored +RestoreThenBuild 'PowerToys.sln' '' $Platform $Configuration $true + +# Build both runner and settings +$ProjectsToBuild = @(".\src\runner\runner.vcxproj", ".\src\settings-ui\Settings.UI\PowerToys.Settings.csproj") +$ExtraArgs = "/p:SolutionDir=$repoRoot\" +foreach ($proj in $ProjectsToBuild) { + Write-Host ("[BUILD-ESSENTIALS] Building {0}" -f $proj) + RunMSBuild $proj $ExtraArgs $Platform $Configuration } \ No newline at end of file diff --git a/tools/build/build-installer.ps1 b/tools/build/build-installer.ps1 index a1a1f8d5f1..24bcc3a218 100644 --- a/tools/build/build-installer.ps1 +++ b/tools/build/build-installer.ps1 @@ -52,12 +52,26 @@ Runs the pipeline for x64 Release with 'vnext' suffix. #> param ( - [string]$Platform = 'x64', + [string]$Platform = '', [string]$Configuration = 'Release', [string]$PerUser = 'true', [string]$InstallerSuffix = 'wix5' ) +# Ensure helpers are available +. "$PSScriptRoot\build-common.ps1" + +# Auto-detect platform when not provided +if (-not $Platform -or $Platform -eq '') { + try { + $Platform = Get-DefaultPlatform + Write-Host ("[AUTO-PLATFORM] Detected platform: {0}" -f $Platform) + } catch { + Write-Warning "Failed to auto-detect platform; defaulting to x64" + $Platform = 'x64' + } +} + # Find the PowerToys repository root automatically $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition $repoRoot = $scriptDir @@ -80,50 +94,7 @@ if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot "PowerToys.sln"))) { } Write-Host "PowerToys repository root detected: $repoRoot" - -function RunMSBuild { - param ( - [string]$Solution, - [string]$ExtraArgs - ) - - $base = @( - $Solution - "/p:Platform=$Platform" - "/p:Configuration=$Configuration" - "/p:CIBuild=true" - '/verbosity:normal' - '/clp:Summary;PerformanceSummary;ErrorsOnly;WarningsOnly' - '/nologo' - ) - - $cmd = $base + ($ExtraArgs -split ' ') - Write-Host ("[MSBUILD] {0} {1}" -f $Solution, ($cmd -join ' ')) - - # Run MSBuild from the repository root directory - Push-Location $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) - - # 1) restore - RunMSBuild $Solution '/t:restore /p:RestorePackagesConfig=true' - # 2) build ------------------------------------------------- - RunMSBuild $Solution '/m' -} - # WiX v5 projects use WixToolset.Sdk via NuGet/MSBuild; a separate WiX 3 installation is not required here. - Write-Host ("[PIPELINE] Start | Platform={0} Configuration={1} PerUser={2}" -f $Platform, $Configuration, $PerUser) Write-Host '' @@ -134,7 +105,9 @@ if (Test-Path $cmdpalOutputPath) { Remove-Item $cmdpalOutputPath -Recurse -Force -ErrorAction Ignore } -RestoreThenBuild 'PowerToys.sln' +$commonArgs = '/p:CIBuild=true' +# No local projects found (or continuing) - build full solution and tools +RestoreThenBuild 'PowerToys.sln' $commonArgs $Platform $Configuration $msixSearchRoot = Join-Path $repoRoot "$Platform\$Configuration" $msixFiles = Get-ChildItem -Path $msixSearchRoot -Recurse -Filter *.msix | @@ -148,8 +121,8 @@ else { Write-Warning "[SIGN] No .msix files found in $msixSearchRoot" } -RestoreThenBuild 'tools\BugReportTool\BugReportTool.sln' -RestoreThenBuild 'tools\StylesReportTool\StylesReportTool.sln' +RestoreThenBuild 'tools\BugReportTool\BugReportTool.sln' $commonArgs $Platform $Configuration +RestoreThenBuild 'tools\StylesReportTool\StylesReportTool.sln' $commonArgs $Platform $Configuration Write-Host '[CLEAN] installer (keep *.exe)' Push-Location $repoRoot @@ -159,10 +132,10 @@ try { Pop-Location } -RunMSBuild 'installer\PowerToysSetup.sln' '/t:restore /p:RestorePackagesConfig=true' +RunMSBuild 'installer\PowerToysSetup.sln' "$commonArgs /t:restore /p:RestorePackagesConfig=true" $Platform $Configuration -RunMSBuild 'installer\PowerToysSetup.sln' "/m /t:PowerToysInstallerVNext /p:PerUser=$PerUser /p:InstallerSuffix=$InstallerSuffix" +RunMSBuild 'installer\PowerToysSetup.sln' "$commonArgs /m /t:PowerToysInstallerVNext /p:PerUser=$PerUser /p:InstallerSuffix=$InstallerSuffix" $Platform $Configuration -RunMSBuild 'installer\PowerToysSetup.sln' "/m /t:PowerToysBootstrapperVNext /p:PerUser=$PerUser /p:InstallerSuffix=$InstallerSuffix" +RunMSBuild 'installer\PowerToysSetup.sln' "$commonArgs /m /t:PowerToysBootstrapperVNext /p:PerUser=$PerUser /p:InstallerSuffix=$InstallerSuffix" $Platform $Configuration Write-Host '[PIPELINE] Completed' diff --git a/tools/build/build.cmd b/tools/build/build.cmd new file mode 100644 index 0000000000..45b59c42b2 --- /dev/null +++ b/tools/build/build.cmd @@ -0,0 +1,5 @@ +@echo off +REM Wrapper to run the PowerShell build script from cmd.exe +set SCRIPT_DIR=%~dp0 +powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%build.ps1" %* +exit /b %ERRORLEVEL% diff --git a/tools/build/build.ps1 b/tools/build/build.ps1 new file mode 100644 index 0000000000..e947541283 --- /dev/null +++ b/tools/build/build.ps1 @@ -0,0 +1,88 @@ +<# +.SYNOPSIS +Light-weight wrapper to build local projects (solutions/projects) in the current working directory using helpers in build-common.ps1. + +.DESCRIPTION +This script is intended for quick local builds. It dot-sources `build-common.ps1` and calls `BuildProjectsInDirectory` against the current directory. Use `-RestoreOnly` to only restore packages for local projects. If `-Platform` is omitted the script attempts to auto-detect the host platform. + +.PARAMETER Platform +Target platform (e.g., 'x64', 'arm64'). If omitted the script will try to detect the host platform automatically. + +.PARAMETER Configuration +Build configuration (e.g., 'Debug', 'Release'). Default: 'Debug'. + +.PARAMETER RestoreOnly +If specified, only perform package restore for local projects and skip the build steps for a solution file (i.e. .sln). + +.PARAMETER ExtraArgs +Any remaining, positional arguments passed to the script are forwarded to MSBuild as additional arguments (e.g., '/p:CIBuild=true'). + +.EXAMPLE +.\tools\build\build.ps1 +Builds any .sln/.csproj/.vcxproj in the current working directory (auto-detects Platform). + +.EXAMPLE +.\tools\build\build.ps1 -Platform x64 -Configuration Release +Builds local projects for x64 Release. + +.EXAMPLE +.\tools\build\build.ps1 '/p:CIBuild=true' '/p:SomeOther=Value' +Pass additional MSBuild arguments; these are forwarded to the underlying msbuild calls. + +.EXAMPLE +.\tools\build\build.ps1 -RestoreOnly '/p:CIBuild=true' +Only restores packages for local projects; ExtraArgs still forwarded to msbuild's restore phase. + +.NOTES +- This file expects `build-common.ps1` to be located in the same folder and dot-sources it to load helper functions. +- ExtraArgs are captured using PowerShell's ValueFromRemainingArguments and joined before being passed to the helpers. +#> + +param ( + [string]$Platform = '', + [string]$Configuration = 'Debug', + [switch]$RestoreOnly, + [Parameter(ValueFromRemainingArguments=$true)] + [string[]]$ExtraArgs +) + +. "$PSScriptRoot\build-common.ps1" + +# If user passed MSBuild-style args (e.g. './build.ps1 /p:CIBuild=true'), +# those will bind to $Platform/$Configuration; detect those and move them to ExtraArgs. +$positionalExtra = @() +if ($Platform -and $Platform -match '^[\/-]') { + $positionalExtra += $Platform + $Platform = '' +} +if ($Configuration -and $Configuration -match '^[\/-]') { + $positionalExtra += $Configuration + $Configuration = 'Debug' +} +if ($positionalExtra.Count -gt 0) { + if (-not $ExtraArgs) { $ExtraArgs = @() } + $ExtraArgs = $positionalExtra + $ExtraArgs +} + +# Auto-detect platform when not provided +if (-not $Platform -or $Platform -eq '') { + try { + $Platform = Get-DefaultPlatform + Write-Host ("[AUTO-PLATFORM] Detected platform: {0}" -f $Platform) + } catch { + Write-Warning "Failed to auto-detect platform; defaulting to x64" + $Platform = 'x64' + } +} + +$cwd = (Get-Location).ProviderPath +$extraArgsString = $null +if ($ExtraArgs -and $ExtraArgs.Count -gt 0) { $extraArgsString = ($ExtraArgs -join ' ') } + +if (BuildProjectsInDirectory -DirectoryPath $cwd -ExtraArgs $extraArgsString -Platform $Platform -Configuration $Configuration -RestoreOnly:$RestoreOnly) { + Write-Host "[BUILD] Local projects built; exiting." + exit 0 +} else { + Write-Host "[BUILD] No local projects found in $cwd" + exit 0 +}