@@ -28,11 +28,8 @@ Runs the pipeline for x64 Release with machine-wide installer.
2828
2929. NOTES
3030- Generated MSIX files will be signed using cert-sign-package.ps1.
31- - This script uses git to manage workspace state:
32- * Uncommitted changes are stashed before build and popped afterwards.
33- * Version files and manifests modified during build are reverted.
34- * Untracked generated files are cleaned up.
35- - Use the -Clean parameter to clean build outputs (bin/obj) and ignored files.
31+ - If the working tree is not clean, the script will prompt before continuing (use -Force to skip the prompt).
32+ - Use the -Clean parameter to clean build outputs (bin/obj) and MSBuild outputs.
3633- The built installer will be placed under: installer/PowerToysSetupVNext/[Platform]/[Configuration]/User[Machine]Setup
3734 relative to the solution root directory.
3835- To run the full installation in other machines, call "./cert-management.ps1" to export the cert used to sign the packages.
@@ -44,18 +41,20 @@ param (
4441 [string ]$Configuration = ' Release' ,
4542 [string ]$PerUser = ' true' ,
4643 [string ]$Version ,
44+ [switch ]$Force ,
4745 [switch ]$EnableCmdPalAOT ,
4846 [switch ]$Clean ,
4947 [switch ]$SkipBuild ,
5048 [switch ]$Help
5149)
5250
5351if ($Help ) {
54- Write-Host " Usage: .\build-installer.ps1 [-Platform <x64|arm64>] [-Configuration <Release|Debug>] [-PerUser <true|false>] [-Version <0.0.1>] [-EnableCmdPalAOT] [-Clean] [-SkipBuild]"
52+ Write-Host " Usage: .\build-installer.ps1 [-Platform <x64|arm64>] [-Configuration <Release|Debug>] [-PerUser <true|false>] [-Version <0.0.1>] [-Force] [- EnableCmdPalAOT] [-Clean] [-SkipBuild]"
5553 Write-Host " -Platform Target platform (default: auto-detect or x64)"
5654 Write-Host " -Configuration Build configuration (default: Release)"
5755 Write-Host " -PerUser Build per-user installer (default: true)"
5856 Write-Host " -Version Sets the PowerToys version (default: from src\Version.props)"
57+ Write-Host " -Force Continue even if the git working tree is not clean (skips the interactive prompt)."
5958 Write-Host " -EnableCmdPalAOT Enable AOT compilation for CmdPal (slower build)"
6059 Write-Host " -Clean Clean output directories before building"
6160 Write-Host " -SkipBuild Skip building the main solution and tools (assumes they are already built)"
@@ -103,6 +102,73 @@ if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot "PowerToys.slnx")))
103102
104103Write-Host " PowerToys repository root detected: $repoRoot "
105104
105+ # Safety check: avoid mixing build outputs with existing local changes unless the user confirms.
106+ if (-not $Force ) {
107+ Push-Location $repoRoot
108+ try {
109+ $gitStatus = $null
110+ $gitRelevantStatus = @ ()
111+ try {
112+ $gitStatus = git status -- porcelain= v1 -- untracked- files= all -- ignore- submodules= all
113+ } catch {
114+ Write-Warning (" [GIT] Failed to query git status: {0}" -f $_.Exception.Message )
115+ }
116+
117+ if ($gitStatus -and $gitStatus.Length -gt 0 ) {
118+ foreach ($line in $gitStatus ) {
119+ if (-not $line ) { continue }
120+
121+ # Porcelain v1 format: XY <path>
122+ # We only care about changes that affect the working tree (Y != ' ') or untracked files (??).
123+ # Index-only changes (staged, Y == ' ') are ignored per user request.
124+ if ($line.StartsWith (' ??' )) {
125+ $gitRelevantStatus += $line
126+ continue
127+ }
128+
129+ if ($line.StartsWith (' !!' )) {
130+ continue
131+ }
132+
133+ if ($line.Length -ge 2 ) {
134+ $workTreeStatus = $line [1 ]
135+ if ($workTreeStatus -ne ' ' ) {
136+ $gitRelevantStatus += $line
137+ }
138+ }
139+ }
140+ }
141+
142+ if ($gitRelevantStatus.Count -gt 0 ) {
143+ Write-Warning " [GIT] Working tree is NOT clean."
144+ Write-Warning " [GIT] This build will generate untracked files and may modify tracked files, which can mix with your current changes."
145+ Write-Host " [GIT] Unstaged/untracked status (first 50 lines):"
146+ $gitRelevantStatus | Select-Object - First 50 | ForEach-Object { Write-Host (" {0}" -f $_ ) }
147+
148+ $shouldContinue = $false
149+ try {
150+ $choices = [System.Management.Automation.Host.ChoiceDescription []]@ (
151+ (New-Object System.Management.Automation.Host.ChoiceDescription " &Yes" , " Continue the build." ),
152+ (New-Object System.Management.Automation.Host.ChoiceDescription " &No" , " Cancel the build." )
153+ )
154+ $decision = $Host.UI.PromptForChoice (" Working tree not clean" , " Continue anyway?" , $choices , 1 )
155+ $shouldContinue = ($decision -eq 0 )
156+ } catch {
157+ Write-Warning " [GIT] Interactive prompt not available."
158+ Write-Error " Refusing to proceed with a dirty working tree. Re-run with -Force to continue anyway."
159+ exit 1
160+ }
161+
162+ if (-not $shouldContinue ) {
163+ Write-Host " [GIT] Cancelled by user."
164+ exit 1
165+ }
166+ }
167+ } finally {
168+ Pop-Location
169+ }
170+ }
171+
106172$cmdpalOutputPath = Join-Path $repoRoot " $Platform \$Configuration \WinUI3Apps\CmdPal"
107173$buildOutputPath = Join-Path $repoRoot " $Platform \$Configuration "
108174
@@ -117,53 +183,10 @@ if ($Clean) {
117183 Remove-Item $buildOutputPath - Recurse - Force - ErrorAction Ignore
118184 }
119185
120- Write-Host " [CLEAN] Cleaning all build artifacts (git clean -Xfd)..."
121- Push-Location $repoRoot
122- try {
123- git clean - Xfd | Out-Null
124- } catch {
125- Write-Warning " [CLEAN] git clean failed: $_ "
126- } finally {
127- Pop-Location
128- }
129-
130186 Write-Host " [CLEAN] Cleaning solution (msbuild /t:Clean)..."
131187 RunMSBuild ' PowerToys.slnx' ' /t:Clean' $Platform $Configuration
132188}
133189
134- # Git Stash Logic to handle workspace cleanup
135- $stashedChanges = $false
136- $scriptPathRelative = " tools/build/build-installer.ps1"
137-
138- # Calculate relative path of this script to exclude it from stash/reset
139- $currentScriptPath = $MyInvocation.MyCommand.Definition
140- if ($currentScriptPath.StartsWith ($repoRoot )) {
141- $scriptPathRelative = $currentScriptPath.Substring ($repoRoot.Length ).TrimStart(' \' , ' /' )
142- $scriptPathRelative = $scriptPathRelative -replace ' \\' , ' /'
143- }
144-
145- Push-Location $repoRoot
146- try {
147- $gitStatus = git status -- porcelain
148- if ($gitStatus.Length -gt 0 ) {
149- Write-Host " [GIT] Uncommitted changes detected. Stashing (excluding this script)..."
150- $stashCountBefore = (git stash list).Count
151-
152- # Exclude the current script from stash so we don't revert it while running
153- git stash push -- include- untracked - m " PowerToys Build Auto-Stash" -- . " :(exclude)$scriptPathRelative "
154-
155- $stashCountAfter = (git stash list).Count
156- if ($stashCountAfter -gt $stashCountBefore ) {
157- $stashedChanges = $true
158- Write-Host " [GIT] Changes stashed."
159- } else {
160- Write-Host " [GIT] No changes to stash (likely only this script is modified)."
161- }
162- }
163- } finally {
164- Pop-Location
165- }
166-
167190try {
168191 if ($Version ) {
169192 Write-Host " [VERSION] Setting PowerToys version to $Version using versionSetting.ps1..."
@@ -384,28 +407,7 @@ try {
384407 RunMSBuild ' installer\PowerToysSetup.slnx' " $commonArgs /m /t:PowerToysBootstrapperVNext /p:PerUser=$PerUser " $Platform $Configuration
385408
386409} finally {
387- # Restore workspace state using Git
388- Write-Host " [GIT] Cleaning up build artifacts..."
389- Push-Location $repoRoot
390- try {
391- # Revert all changes EXCEPT the script itself
392- # This cleans up Version.props, AppxManifests, etc.
393- git checkout HEAD -- . " :(exclude)$scriptPathRelative "
394-
395- # Remove untracked files (generated manifests, etc.)
396- # -f: force, -d: remove directories, -q: quiet
397- git clean - fd - q
398-
399- if ($stashedChanges ) {
400- Write-Host " [GIT] Restoring stashed changes..."
401- git stash pop -- index
402- if ($LASTEXITCODE -ne 0 ) {
403- Write-Warning " [GIT] 'git stash pop' reported conflicts or errors. Your changes are in the stash list."
404- }
405- }
406- } finally {
407- Pop-Location
408- }
410+ # No git cleanup; leave workspace state as-is.
409411}
410412
411413Write-Host ' [PIPELINE] Completed'
0 commit comments