diff --git a/tools/build/BUILD-GUIDELINES.md b/tools/build/BUILD-GUIDELINES.md index 3ee54a6bf3..d9ce1d3f75 100644 --- a/tools/build/BUILD-GUIDELINES.md +++ b/tools/build/BUILD-GUIDELINES.md @@ -1,21 +1,48 @@ -# Build scripts - quick guideline +# Build scripts – quick guideline -As the result of our recent changes, use the following guidance when working in the PowerToys repo: +Use these scripts to build PowerToys locally. They auto-detect your platform (x64/arm64), initialize the Visual Studio developer environment, and write helpful logs on failure. -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. +## Quick start (from cmd.exe) +- Fast essentials (runner + settings) and NuGet restore first: + - `tools\build\build-essentials.cmd` +- Build projects in the current folder: + - `tools\build\build.cmd` -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. +Tip: Add `D:\PowerToys\tools\build` to your PATH to use the wrappers anywhere. -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/`. +## When to use which +1) `build-essentials.ps1` + - Restores NuGet for `PowerToys.sln` and builds essentials (runner, settings). + - Auto-detects Platform; initializes VS Dev environment automatically. + - Example (PowerShell): + - `./tools/build/build-essentials.ps1` + - `./tools/build/build-essentials.ps1 -Platform arm64 -Configuration Release` -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. +2) `build.ps1` (from any folder) + - Builds any `.sln/.csproj/.vcxproj` in the current directory. + - Auto-detects Platform; initializes VS Dev environment automatically. + - Accepts extra MSBuild args (forwarded to msbuild): + - `./tools/build/build.ps1 '/p:CIBuild=true' '/p:SomeProp=Value'` + - Restore only: + - `./tools/build/build.ps1 -RestoreOnly` -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. +3) `build-installer.ps1` (use with caution) + - Full local packaging pipeline (restore, build, sign MSIX, WiX v5 MSI/bootstrapper). + - Auto-inits VS Dev environment. Cleans some output (keeps *.exe) under `installer/`. + - Key options: `-PerUser true|false`, `-InstallerSuffix wix5|vnext`. + - Example: + - `./tools/build/build-installer.ps1 -Platform x64 -Configuration Release -PerUser true -InstallerSuffix wix5` + +## Logs and troubleshooting +- On failure, see logs next to the solution/project being built: + - `build...all.log` — full text log + - `build...errors.log` — errors only + - `build...warnings.log` — warnings only + - `build...trace.binlog` — open with MSBuild Structured Log Viewer +- VS environment init: + - Scripts try DevShell first (`Microsoft.VisualStudio.DevShell.dll` / `Enter-VsDevShell`), then fall back to `VsDevCmd.bat`. + - If VS isn’t found, run from “Developer PowerShell for VS 2022”, or ensure `vswhere.exe` exists under `Program Files (x86)\Microsoft Visual Studio\Installer`. + +## Notes +- Override platform explicitly with `-Platform x64|arm64` if needed. +- CMD wrappers: `build.cmd`, `build-essentials.cmd` forward all arguments to the PowerShell scripts. diff --git a/tools/build/build-common.ps1 b/tools/build/build-common.ps1 index ec3586f2f3..eb3c604081 100644 --- a/tools/build/build-common.ps1 +++ b/tools/build/build-common.ps1 @@ -8,11 +8,22 @@ This file provides reusable helper functions used by the build scripts: - 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 +- Ensure-VsDevEnvironment: initializes the Visual Studio developer environment when possible. + It prefers the DevShell PowerShell module (Microsoft.VisualStudio.DevShell.dll / Enter-VsDevShell), + falls back to running VsDevCmd.bat and importing its environment into the current PowerShell session, + and restores the caller's working directory after initialization. USAGE Dot-source this file from a script to load helpers: . "$PSScriptRoot\build-common.ps1" +ERROR DETAILS +When a build fails, check the logs written next to the solution/project folder: +- build...all.log — full MSBuild text log +- build...errors.log — extracted errors only +- build...warnings.log — extracted warnings only +- build...trace.binlog — binary log (open with the MSBuild Structured Log Viewer) + .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. #> @@ -59,7 +70,7 @@ function RunMSBuild { try { & msbuild.exe @cmd if ($LASTEXITCODE -ne 0) { - Write-Error (("Build failed: {0} {1}" -f $Solution, $ExtraArgs)) + Write-Error (("Build failed: {0} {1}`nSee logs:`n All: {2}`n Errors: {3}`n Binlog: {4}" -f $Solution, $ExtraArgs, $allLog, $errorsLog, $binLog)) exit $LASTEXITCODE } } finally { @@ -164,3 +175,98 @@ function Get-DefaultPlatform { return 'x64' } + +function Ensure-VsDevEnvironment { + $OriginalLocationForVsInit = Get-Location + try { + + if ($env:VSINSTALLDIR -or $env:VCINSTALLDIR -or $env:DevEnvDir -or $env:VCToolsInstallDir) { + Write-Host "[VS] VS developer environment already present" + return $true + } + + # Locate vswhere if available + $vswhereCandidates = @( + "$env:ProgramFiles (x86)\Microsoft Visual Studio\Installer\vswhere.exe", + "$env:ProgramFiles\Microsoft Visual Studio\Installer\vswhere.exe" + ) + $vswhere = $vswhereCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 + if ($vswhere) { Write-Host "[VS] vswhere found: $vswhere" } else { Write-Host "[VS] vswhere not found" } + + $instPaths = @() + if ($vswhere) { + # First try with the VC tools requirement (preferred) + try { $p = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath 2>$null; if ($p) { $instPaths += $p } } catch {} + # Fallback: try without -requires to find any VS installations + if (-not $instPaths) { + try { $p2 = & $vswhere -latest -products * -property installationPath 2>$null; if ($p2) { $instPaths += $p2 } } catch {} + } + } + + # Add explicit common year-based candidates as a last resort + if (-not $instPaths) { + $explicit = @( + "$env:ProgramFiles (x86)\Microsoft Visual Studio\2022\Community", + "$env:ProgramFiles (x86)\Microsoft Visual Studio\2022\Professional", + "$env:ProgramFiles (x86)\Microsoft Visual Studio\2022\Enterprise", + "$env:ProgramFiles\Microsoft Visual Studio\2022\Community", + "$env:ProgramFiles\Microsoft Visual Studio\2022\Professional", + "$env:ProgramFiles\Microsoft Visual Studio\2022\Enterprise" + ) + foreach ($c in $explicit) { if (Test-Path $c) { $instPaths += $c } } + } + + if (-not $instPaths -or $instPaths.Count -eq 0) { + Write-Warning "[VS] Could not locate Visual Studio installation (no candidates found)" + return $false + } + + # Try each candidate installation path until one works + foreach ($inst in $instPaths) { + if (-not $inst) { continue } + Write-Host "[VS] Checking candidate: $inst" + + $devDll = Join-Path $inst 'Common7\Tools\Microsoft.VisualStudio.DevShell.dll' + if (Test-Path $devDll) { + try { + Import-Module $devDll -DisableNameChecking -ErrorAction Stop + + # Call Enter-VsDevShell using only the install path to avoid parameter name differences + try { + Enter-VsDevShell -VsInstallPath $inst -ErrorAction Stop + Write-Host "[VS] Entered Visual Studio DevShell at $inst" + return $true + } catch { + Write-Warning ("[VS] DevShell import/Enter-VsDevShell failed: {0}" -f $_) + } + } catch { + Write-Warning ("[VS] DevShell import failed: {0}" -f $_) + } + } + + $vsDevCmd = Join-Path $inst 'Common7\Tools\VsDevCmd.bat' + if (Test-Path $vsDevCmd) { + Write-Host "[VS] Running VsDevCmd.bat and importing environment from $vsDevCmd" + try { + $cmdOut = cmd.exe /c "`"$vsDevCmd`" && set" + foreach ($line in $cmdOut) { + $parts = $line -split('=',2) + if ($parts.Length -eq 2) { + try { [Environment]::SetEnvironmentVariable($parts[0], $parts[1], 'Process') } catch {} + } + } + Write-Host "[VS] Imported environment from VsDevCmd.bat at $inst" + return $true + } catch { + Write-Warning ("[VS] Failed to run/import VsDevCmd.bat at {0}: {1}" -f $inst, $_) + } + } + } + + Write-Warning "[VS] Neither DevShell module nor VsDevCmd.bat found in any candidate paths" + return $false + + } finally { + try { Set-Location $OriginalLocationForVsInit } catch {} + } +} diff --git a/tools/build/build-essentials.ps1 b/tools/build/build-essentials.ps1 index 21b378d0e3..9b12e68334 100644 --- a/tools/build/build-essentials.ps1 +++ b/tools/build/build-essentials.ps1 @@ -48,6 +48,9 @@ Set-Variable -Name RepoRoot -Value $repoRoot -Scope Script -Force # Load shared helpers . "$PSScriptRoot\build-common.ps1" +# Initialize Visual Studio dev environment +if (-not (Ensure-VsDevEnvironment)) { exit 1 } + # If platform not provided, auto-detect from host if (-not $Platform -or $Platform -eq '') { try { diff --git a/tools/build/build-installer.ps1 b/tools/build/build-installer.ps1 index 24bcc3a218..2229be63ae 100644 --- a/tools/build/build-installer.ps1 +++ b/tools/build/build-installer.ps1 @@ -61,6 +61,9 @@ param ( # Ensure helpers are available . "$PSScriptRoot\build-common.ps1" +# Initialize Visual Studio dev environment +if (-not (Ensure-VsDevEnvironment)) { exit 1 } + # Auto-detect platform when not provided if (-not $Platform -or $Platform -eq '') { try { diff --git a/tools/build/build.ps1 b/tools/build/build.ps1 index e947541283..45cd13658c 100644 --- a/tools/build/build.ps1 +++ b/tools/build/build.ps1 @@ -48,6 +48,9 @@ param ( . "$PSScriptRoot\build-common.ps1" +# Initialize Visual Studio dev environment +if (-not (Ensure-VsDevEnvironment)) { exit 1 } + # 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 = @() @@ -79,7 +82,8 @@ $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) { +$built = BuildProjectsInDirectory -DirectoryPath $cwd -ExtraArgs $extraArgsString -Platform $Platform -Configuration $Configuration -RestoreOnly:$RestoreOnly +if ($built) { Write-Host "[BUILD] Local projects built; exiting." exit 0 } else {