github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+incompatible/hack/make.ps1 (about) 1 <# 2 .NOTES 3 Author: @jhowardmsft 4 5 Summary: Windows native build script. This is similar to functionality provided 6 by hack\make.sh, but uses native Windows PowerShell semantics. It does 7 not support the full set of options provided by the Linux counterpart. 8 For example: 9 10 - You can't cross-build Linux docker binaries on Windows 11 - Hashes aren't generated on binaries 12 - 'Releasing' isn't supported. 13 - Integration tests. This is because they currently cannot run inside a container, 14 and require significant external setup. 15 16 It does however provided the minimum necessary to support parts of local Windows 17 development and Windows to Windows CI. 18 19 Usage Examples (run from repo root): 20 "hack\make.ps1 -Client" to build docker.exe client 64-bit binary (remote repo) 21 "hack\make.ps1 -TestUnit" to run unit tests 22 "hack\make.ps1 -Daemon -TestUnit" to build the daemon and run unit tests 23 "hack\make.ps1 -All" to run everything this script knows about that can run in a container 24 "hack\make.ps1" to build the daemon binary (same as -Daemon) 25 "hack\make.ps1 -Binary" shortcut to -Client and -Daemon 26 27 .PARAMETER Client 28 Builds the client binaries. 29 30 .PARAMETER Daemon 31 Builds the daemon binary. 32 33 .PARAMETER Binary 34 Builds the client and daemon binaries. A convenient shortcut to `make.ps1 -Client -Daemon`. 35 36 .PARAMETER Race 37 Use -race in go build and go test. 38 39 .PARAMETER Noisy 40 Use -v in go build. 41 42 .PARAMETER ForceBuildAll 43 Use -a in go build. 44 45 .PARAMETER NoOpt 46 Use -gcflags -N -l in go build to disable optimisation (can aide debugging). 47 48 .PARAMETER CommitSuffix 49 Adds a custom string to be appended to the commit ID (spaces are stripped). 50 51 .PARAMETER DCO 52 Runs the DCO (Developer Certificate Of Origin) test (must be run outside a container). 53 54 .PARAMETER PkgImports 55 Runs the pkg\ directory imports test (must be run outside a container). 56 57 .PARAMETER GoFormat 58 Runs the Go formatting test (must be run outside a container). 59 60 .PARAMETER TestUnit 61 Runs unit tests. 62 63 .PARAMETER TestIntegration 64 Runs integration tests. 65 66 .PARAMETER All 67 Runs everything this script knows about that can run in a container. 68 69 70 TODO 71 - Unify the head commit 72 - Add golint and other checks (swagger maybe?) 73 74 #> 75 76 77 param( 78 [Parameter(Mandatory=$False)][switch]$Client, 79 [Parameter(Mandatory=$False)][switch]$Daemon, 80 [Parameter(Mandatory=$False)][switch]$Binary, 81 [Parameter(Mandatory=$False)][switch]$Race, 82 [Parameter(Mandatory=$False)][switch]$Noisy, 83 [Parameter(Mandatory=$False)][switch]$ForceBuildAll, 84 [Parameter(Mandatory=$False)][switch]$NoOpt, 85 [Parameter(Mandatory=$False)][string]$CommitSuffix="", 86 [Parameter(Mandatory=$False)][switch]$DCO, 87 [Parameter(Mandatory=$False)][switch]$PkgImports, 88 [Parameter(Mandatory=$False)][switch]$GoFormat, 89 [Parameter(Mandatory=$False)][switch]$TestUnit, 90 [Parameter(Mandatory=$False)][switch]$TestIntegration, 91 [Parameter(Mandatory=$False)][switch]$All 92 ) 93 94 $ErrorActionPreference = "Stop" 95 $ProgressPreference = "SilentlyContinue" 96 $pushed=$False # To restore the directory if we have temporarily pushed to one. 97 Set-Variable GOTESTSUM_LOCATION -option Constant -value "$env:GOPATH/bin/" 98 99 # Utility function to get the commit ID of the repository 100 Function Get-GitCommit() { 101 if (-not (Test-Path ".\.git")) { 102 # If we don't have a .git directory, but we do have the environment 103 # variable DOCKER_GITCOMMIT set, that can override it. 104 if ($env:DOCKER_GITCOMMIT.Length -eq 0) { 105 Throw ".git directory missing and DOCKER_GITCOMMIT environment variable not specified." 106 } 107 Write-Host "INFO: Git commit ($env:DOCKER_GITCOMMIT) assumed from DOCKER_GITCOMMIT environment variable" 108 return $env:DOCKER_GITCOMMIT 109 } 110 $gitCommit=$(git rev-parse --short HEAD) 111 if ($(git status --porcelain --untracked-files=no).Length -ne 0) { 112 $gitCommit="$gitCommit-unsupported" 113 Write-Host "" 114 Write-Warning "This version is unsupported because there are uncommitted file(s)." 115 Write-Warning "Either commit these changes, or add them to .gitignore." 116 git status --porcelain --untracked-files=no | Write-Warning 117 Write-Host "" 118 } 119 return $gitCommit 120 } 121 122 # Utility function to determine if we are running in a container or not. 123 # In Windows, we get this through an environment variable set in `Dockerfile.Windows` 124 Function Check-InContainer() { 125 if ($env:FROM_DOCKERFILE.Length -eq 0) { 126 Write-Host "" 127 Write-Warning "Not running in a container. The result might be an incorrect build." 128 Write-Host "" 129 return $False 130 } 131 return $True 132 } 133 134 # Utility function to warn if the version of go is correct. Used for local builds 135 # outside of a container where it may be out of date with master. 136 Function Verify-GoVersion() { 137 Try { 138 $goVersionDockerfile=(Select-String -Path ".\Dockerfile" -Pattern "^ARG[\s]+GO_VERSION=(.*)$").Matches.groups[1].Value -replace '\.0$','' 139 $goVersionInstalled=(go version).ToString().Split(" ")[2].SubString(2) 140 } 141 Catch [Exception] { 142 Throw "Failed to validate go version correctness: $_" 143 } 144 if (-not($goVersionInstalled -eq $goVersionDockerfile)) { 145 Write-Host "" 146 Write-Warning "Building with golang version $goVersionInstalled. You should update to $goVersionDockerfile" 147 Write-Host "" 148 } 149 } 150 151 # Utility function to get the commit for HEAD 152 Function Get-HeadCommit() { 153 $head = Invoke-Expression "git rev-parse --verify HEAD" 154 if ($LASTEXITCODE -ne 0) { Throw "Failed getting HEAD commit" } 155 156 return $head 157 } 158 159 # Utility function to get the commit for upstream 160 Function Get-UpstreamCommit() { 161 Invoke-Expression "git fetch -q https://github.com/docker/docker.git refs/heads/master" 162 if ($LASTEXITCODE -ne 0) { Throw "Failed fetching" } 163 164 $upstream = Invoke-Expression "git rev-parse --verify FETCH_HEAD" 165 if ($LASTEXITCODE -ne 0) { Throw "Failed getting upstream commit" } 166 167 return $upstream 168 } 169 170 # Build a binary (client or daemon) 171 Function Execute-Build($type, $additionalBuildTags, $directory) { 172 # Generate the build flags 173 $buildTags = "autogen" 174 if ($Noisy) { $verboseParm=" -v" } 175 if ($Race) { Write-Warning "Using race detector"; $raceParm=" -race"} 176 if ($ForceBuildAll) { $allParm=" -a" } 177 if ($NoOpt) { $optParm=" -gcflags "+""""+"-N -l"+"""" } 178 if ($additionalBuildTags -ne "") { $buildTags += $(" " + $additionalBuildTags) } 179 180 # Do the go build in the appropriate directory 181 # Note -linkmode=internal is required to be able to debug on Windows. 182 # https://github.com/golang/go/issues/14319#issuecomment-189576638 183 Write-Host "INFO: Building $type..." 184 Push-Location $root\cmd\$directory; $global:pushed=$True 185 $buildCommand = "go build" + ` 186 $raceParm + ` 187 $verboseParm + ` 188 $allParm + ` 189 $optParm + ` 190 " -tags """ + $buildTags + """" + ` 191 " -ldflags """ + "-linkmode=internal" + """" + ` 192 " -o $root\bundles\"+$directory+".exe" 193 Invoke-Expression $buildCommand 194 if ($LASTEXITCODE -ne 0) { Throw "Failed to compile $type" } 195 Pop-Location; $global:pushed=$False 196 } 197 198 199 # Validates the DCO marker is present on each commit 200 Function Validate-DCO($headCommit, $upstreamCommit) { 201 Write-Host "INFO: Validating Developer Certificate of Origin..." 202 # Username may only contain alphanumeric characters or dashes and cannot begin with a dash 203 $usernameRegex='[a-zA-Z0-9][a-zA-Z0-9-]+' 204 205 $dcoPrefix="Signed-off-by:" 206 $dcoRegex="^(Docker-DCO-1.1-)?$dcoPrefix ([^<]+) <([^<>@]+@[^<>]+)>( \(github: ($usernameRegex)\))?$" 207 208 $counts = Invoke-Expression "git diff --numstat $upstreamCommit...$headCommit" 209 if ($LASTEXITCODE -ne 0) { Throw "Failed git diff --numstat" } 210 211 # Counts of adds and deletes after removing multiple white spaces. AWK anyone? :( 212 $adds=0; $dels=0; $($counts -replace '\s+', ' ') | %{ 213 $a=$_.Split(" "); 214 if ($a[0] -ne "-") { $adds+=[int]$a[0] } 215 if ($a[1] -ne "-") { $dels+=[int]$a[1] } 216 } 217 if (($adds -eq 0) -and ($dels -eq 0)) { 218 Write-Warning "DCO validation - nothing to validate!" 219 return 220 } 221 222 $commits = Invoke-Expression "git log $upstreamCommit..$headCommit --format=format:%H%n" 223 if ($LASTEXITCODE -ne 0) { Throw "Failed git log --format" } 224 $commits = $($commits -split '\s+' -match '\S') 225 $badCommits=@() 226 $commits | ForEach-Object{ 227 # Skip commits with no content such as merge commits etc 228 if ($(git log -1 --format=format: --name-status $_).Length -gt 0) { 229 # Ignore exit code on next call - always process regardless 230 $commitMessage = Invoke-Expression "git log -1 --format=format:%B --name-status $_" 231 if (($commitMessage -match $dcoRegex).Length -eq 0) { $badCommits+=$_ } 232 } 233 } 234 if ($badCommits.Length -eq 0) { 235 Write-Host "Congratulations! All commits are properly signed with the DCO!" 236 } else { 237 $e = "`nThese commits do not have a proper '$dcoPrefix' marker:`n" 238 $badCommits | %{ $e+=" - $_`n"} 239 $e += "`nPlease amend each commit to include a properly formatted DCO marker.`n`n" 240 $e += "Visit the following URL for information about the Docker DCO:`n" 241 $e += "https://github.com/docker/docker/blob/master/CONTRIBUTING.md#sign-your-work`n" 242 Throw $e 243 } 244 } 245 246 # Validates that .\pkg\... is safely isolated from internal code 247 Function Validate-PkgImports($headCommit, $upstreamCommit) { 248 Write-Host "INFO: Validating pkg import isolation..." 249 250 # Get a list of go source-code files which have changed under pkg\. Ignore exit code on next call - always process regardless 251 $files=@(); $files = Invoke-Expression "git diff $upstreamCommit...$headCommit --diff-filter=ACMR --name-only -- `'pkg\*.go`'" 252 $badFiles=@(); $files | ForEach-Object{ 253 $file=$_ 254 # For the current changed file, get its list of dependencies, sorted and uniqued. 255 $imports = Invoke-Expression "go list -e -f `'{{ .Deps }}`' $file" 256 if ($LASTEXITCODE -ne 0) { Throw "Failed go list for dependencies on $file" } 257 $imports = $imports -Replace "\[" -Replace "\]", "" -Split(" ") | Sort-Object | Get-Unique 258 # Filter out what we are looking for 259 $imports = @() + $imports -NotMatch "^github.com/docker/docker/pkg/" ` 260 -NotMatch "^github.com/docker/docker/vendor" ` 261 -Match "^github.com/docker/docker" ` 262 -Replace "`n", "" 263 $imports | ForEach-Object{ $badFiles+="$file imports $_`n" } 264 } 265 if ($badFiles.Length -eq 0) { 266 Write-Host 'Congratulations! ".\pkg\*.go" is safely isolated from internal code.' 267 } else { 268 $e = "`nThese files import internal code: (either directly or indirectly)`n" 269 $badFiles | ForEach-Object{ $e+=" - $_"} 270 Throw $e 271 } 272 } 273 274 # Validates that changed files are correctly go-formatted 275 Function Validate-GoFormat($headCommit, $upstreamCommit) { 276 Write-Host "INFO: Validating go formatting on changed files..." 277 278 # Verify gofmt is installed 279 if ($(Get-Command gofmt -ErrorAction SilentlyContinue) -eq $nil) { Throw "gofmt does not appear to be installed" } 280 281 # Get a list of all go source-code files which have changed. Ignore exit code on next call - always process regardless 282 $files=@(); $files = Invoke-Expression "git diff $upstreamCommit...$headCommit --diff-filter=ACMR --name-only -- `'*.go`'" 283 $files = $files | Select-String -NotMatch "^vendor/" 284 $badFiles=@(); $files | %{ 285 # Deliberately ignore error on next line - treat as failed 286 $content=Invoke-Expression "git show $headCommit`:$_" 287 288 # Next set of hoops are to ensure we have LF not CRLF semantics as otherwise gofmt on Windows will not succeed. 289 # Also note that gofmt on Windows does not appear to support stdin piping correctly. Hence go through a temporary file. 290 $content=$content -join "`n" 291 $content+="`n" 292 $outputFile=[System.IO.Path]::GetTempFileName() 293 if (Test-Path $outputFile) { Remove-Item $outputFile } 294 [System.IO.File]::WriteAllText($outputFile, $content, (New-Object System.Text.UTF8Encoding($False))) 295 $currentFile = $_ -Replace("/","\") 296 Write-Host Checking $currentFile 297 Invoke-Expression "gofmt -s -l $outputFile" 298 if ($LASTEXITCODE -ne 0) { $badFiles+=$currentFile } 299 if (Test-Path $outputFile) { Remove-Item $outputFile } 300 } 301 if ($badFiles.Length -eq 0) { 302 Write-Host 'Congratulations! All Go source files are properly formatted.' 303 } else { 304 $e = "`nThese files are not properly gofmt`'d:`n" 305 $badFiles | ForEach-Object{ $e+=" - $_`n"} 306 $e+= "`nPlease reformat the above files using `"gofmt -s -w`" and commit the result." 307 Throw $e 308 } 309 } 310 311 # Run the unit tests 312 Function Run-UnitTests() { 313 Write-Host "INFO: Running unit tests..." 314 $testPath="./..." 315 $goListCommand = "go list -e -f '{{if ne .Name """ + '\"github.com/docker/docker\"' + """}}{{.ImportPath}}{{end}}' $testPath" 316 $pkgList = $(Invoke-Expression $goListCommand) 317 if ($LASTEXITCODE -ne 0) { Throw "go list for unit tests failed" } 318 $pkgList = $pkgList | Select-String -Pattern "github.com/docker/docker" 319 $pkgList = $pkgList | Select-String -NotMatch "github.com/docker/docker/vendor" 320 $pkgList = $pkgList | Select-String -NotMatch "github.com/docker/docker/man" 321 $pkgList = $pkgList | Select-String -NotMatch "github.com/docker/docker/integration" 322 $pkgList = $pkgList -replace "`r`n", " " 323 $goTestCommand = "$GOTESTSUM_LOCATION\gotestsum.exe --format=standard-quiet --jsonfile=bundles\go-test-report-unit-tests.json --junitfile=bundles\junit-report-unit-tests.xml -- " + $raceParm + " -cover -ldflags -w -a """ + "-test.timeout=10m" + """ $pkgList" 324 Write-Host "INFO: Invoking unit tests run with $goTestCommand" 325 Invoke-Expression $goTestCommand 326 if ($LASTEXITCODE -ne 0) { Throw "Unit tests failed" } 327 } 328 329 # Run the integration tests 330 Function Run-IntegrationTests() { 331 $escRoot = [Regex]::Escape($root) 332 $env:DOCKER_INTEGRATION_DAEMON_DEST = $bundlesDir + "\tmp" 333 $dirs = go list -test -f '{{- if ne .ForTest `"`" -}}{{- .Dir -}}{{- end -}}' .\integration\... 334 ForEach($dir in $dirs) { 335 # Normalize directory name for using in the test results files. 336 $normDir = $dir.Trim() 337 $normDir = $normDir -replace $escRoot, "" 338 $normDir = $normDir -replace "\\", "-" 339 $normDir = $normDir -replace "\/", "-" 340 $normDir = $normDir -replace "\.", "-" 341 if ($normDir.StartsWith("-")) 342 { 343 $normDir = $normDir.TrimStart("-") 344 } 345 if ($normDir.EndsWith("-")) 346 { 347 $normDir = $normDir.TrimEnd("-") 348 } 349 $jsonFilePath = $bundlesDir + "\go-test-report-int-tests-$normDir" + ".json" 350 $xmlFilePath = $bundlesDir + "\junit-report-int-tests-$normDir" + ".xml" 351 Set-Location $dir 352 Write-Host "Running $($PWD.Path)" 353 $pinfo = New-Object System.Diagnostics.ProcessStartInfo 354 $pinfo.FileName = "gotestsum.exe" 355 $pinfo.WorkingDirectory = "$($PWD.Path)" 356 $pinfo.RedirectStandardError = $true 357 $pinfo.UseShellExecute = $false 358 $pinfo.Arguments = "--format=standard-quiet --jsonfile=$jsonFilePath --junitfile=$xmlFilePath -- $env:INTEGRATION_TESTFLAGS" 359 $p = New-Object System.Diagnostics.Process 360 $p.StartInfo = $pinfo 361 $p.Start() | Out-Null 362 $p.WaitForExit() 363 $err = $p.StandardError.ReadToEnd() 364 if (($LASTEXITCODE -ne 0) -and ($err -notlike "*warning: no tests to run*")) { 365 Throw "Integration tests failed: $err" 366 } else { 367 Write-Host "$err" 368 } 369 } 370 } 371 372 # Start of main code. 373 Try { 374 Write-Host -ForegroundColor Cyan "INFO: make.ps1 starting at $(Get-Date)" 375 376 # Get to the root of the repo 377 $root = $(Split-Path $MyInvocation.MyCommand.Definition -Parent | Split-Path -Parent) 378 Push-Location $root 379 380 # Ensure the bundles directory exists 381 $bundlesDir = $root + "\bundles" 382 Set-Variable bundlesDir -option ReadOnly 383 New-Item -Force $bundlesDir -ItemType Directory | Out-Null 384 385 # Handle the "-All" shortcut to turn on all things we can handle. 386 # Note we expressly only include the items which can run in a container - the validations tests cannot 387 # as they require the .git directory which is excluded from the image by .dockerignore 388 if ($All) { $Client=$True; $Daemon=$True; $TestUnit=$True; } 389 390 # Handle the "-Binary" shortcut to build both client and daemon. 391 if ($Binary) { $Client = $True; $Daemon = $True } 392 393 # Default to building the daemon if not asked for anything explicitly. 394 if (-not($Client) -and -not($Daemon) -and -not($DCO) -and -not($PkgImports) -and -not($GoFormat) -and -not($TestUnit) -and -not($TestIntegration)) { $Daemon=$True } 395 396 # Verify git is installed 397 if ($(Get-Command git -ErrorAction SilentlyContinue) -eq $nil) { Throw "Git does not appear to be installed" } 398 399 # Verify go is installed 400 if ($(Get-Command go -ErrorAction SilentlyContinue) -eq $nil) { Throw "GoLang does not appear to be installed" } 401 402 # Get the git commit. This will also verify if we are in a repo or not. Then add a custom string if supplied. 403 $gitCommit=Get-GitCommit 404 if ($CommitSuffix -ne "") { $gitCommit += "-"+$CommitSuffix -Replace ' ', '' } 405 406 # Get the version of docker (eg 17.04.0-dev) 407 $dockerVersion="0.0.0-dev" 408 # Overwrite dockerVersion if VERSION Environment variable is available 409 if (Test-Path Env:\VERSION) { $dockerVersion=$env:VERSION } 410 411 # Give a warning if we are not running in a container and are building binaries or running unit tests. 412 # Not relevant for validation tests as these are fine to run outside of a container. 413 if ($Client -or $Daemon -or $TestUnit) { $inContainer=Check-InContainer } 414 415 # If we are not in a container, validate the version of GO that is installed. 416 if (-not $inContainer) { Verify-GoVersion } 417 418 # Verify GOPATH is set 419 if ($env:GOPATH.Length -eq 0) { Throw "Missing GOPATH environment variable. See https://golang.org/doc/code.html#GOPATH" } 420 421 # Run autogen if building binaries or running unit tests. 422 if ($Client -or $Daemon -or $TestUnit) { 423 Write-Host "INFO: Invoking autogen..." 424 Try { .\hack\make\.go-autogen.ps1 -CommitString $gitCommit -DockerVersion $dockerVersion -Platform "$env:PLATFORM" -Product "$env:PRODUCT" } 425 Catch [Exception] { Throw $_ } 426 } 427 428 # DCO, Package import and Go formatting tests. 429 if ($DCO -or $PkgImports -or $GoFormat) { 430 # We need the head and upstream commits for these 431 $headCommit=Get-HeadCommit 432 $upstreamCommit=Get-UpstreamCommit 433 434 # Run DCO validation 435 if ($DCO) { Validate-DCO $headCommit $upstreamCommit } 436 437 # Run `gofmt` validation 438 if ($GoFormat) { Validate-GoFormat $headCommit $upstreamCommit } 439 440 # Run pkg isolation validation 441 if ($PkgImports) { Validate-PkgImports $headCommit $upstreamCommit } 442 } 443 444 # Build the binaries 445 if ($Client -or $Daemon) { 446 447 # Perform the actual build 448 if ($Daemon) { Execute-Build "daemon" "daemon" "dockerd" } 449 if ($Client) { 450 # Get the Docker channel and version from the environment, or use the defaults. 451 if (-not ($channel = $env:DOCKERCLI_CHANNEL)) { $channel = "stable" } 452 if (-not ($version = $env:DOCKERCLI_VERSION)) { $version = "17.06.2-ce" } 453 454 # Download the zip file and extract the client executable. 455 Write-Host "INFO: Downloading docker/cli version $version from $channel..." 456 $url = "https://download.docker.com/win/static/$channel/x86_64/docker-$version.zip" 457 Invoke-WebRequest $url -OutFile "docker.zip" 458 Try { 459 Add-Type -AssemblyName System.IO.Compression.FileSystem 460 $zip = [System.IO.Compression.ZipFile]::OpenRead("$PWD\docker.zip") 461 Try { 462 if (-not ($entry = $zip.Entries | Where-Object { $_.Name -eq "docker.exe" })) { 463 Throw "Cannot find docker.exe in $url" 464 } 465 [System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, "$PWD\bundles\docker.exe", $true) 466 } 467 Finally { 468 $zip.Dispose() 469 } 470 } 471 Finally { 472 Remove-Item -Force "docker.zip" 473 } 474 } 475 } 476 477 # Run unit tests 478 if ($TestUnit) { Run-UnitTests } 479 480 # Run integration tests 481 if ($TestIntegration) { Run-IntegrationTests } 482 483 # Gratuitous ASCII art. 484 if ($Daemon -or $Client) { 485 Write-Host 486 Write-Host -ForegroundColor Green " ________ ____ __." 487 Write-Host -ForegroundColor Green " \_____ \ `| `|/ _`|" 488 Write-Host -ForegroundColor Green " / `| \`| `<" 489 Write-Host -ForegroundColor Green " / `| \ `| \" 490 Write-Host -ForegroundColor Green " \_______ /____`|__ \" 491 Write-Host -ForegroundColor Green " \/ \/" 492 Write-Host 493 } 494 } 495 Catch [Exception] { 496 Write-Host -ForegroundColor Red ("`nERROR: make.ps1 failed:`n$_") 497 Write-Host -ForegroundColor Red ($_.InvocationInfo.PositionMessage) 498 499 # More gratuitous ASCII art. 500 Write-Host 501 Write-Host -ForegroundColor Red "___________ .__.__ .___" 502 Write-Host -ForegroundColor Red "\_ _____/____ `|__`| `| ____ __`| _/" 503 Write-Host -ForegroundColor Red " `| __) \__ \ `| `| `| _/ __ \ / __ `| " 504 Write-Host -ForegroundColor Red " `| \ / __ \`| `| `|_\ ___// /_/ `| " 505 Write-Host -ForegroundColor Red " \___ / (____ /__`|____/\___ `>____ `| " 506 Write-Host -ForegroundColor Red " \/ \/ \/ \/ " 507 Write-Host 508 509 Throw $_ 510 } 511 Finally { 512 Pop-Location # As we pushed to the root of the repo as the very first thing 513 if ($global:pushed) { Pop-Location } 514 Write-Host -ForegroundColor Cyan "INFO: make.ps1 ended at $(Get-Date)" 515 }