Posh-Build.ps1
<#PSScriptInfo
.VERSION 0.1.1 .GUID 3d8fd216-d40b-4838-9368-bfd3fffc178d .AUTHOR Jeremy Skinner .COMPANYNAME .COPYRIGHT (c) Jeremy Skinner 2018 .TAGS dotnet build dotnet-build .LICENSEURI https://github.com/JeremySkinner/posh-build/blob/master/LICENSE.txt .PROJECTURI https://github.com/JeremySkinner/posh-build .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES - 0.1 Initial release #> <# .DESCRIPTION A lightweight build system for .NET Core projects #> Param() $script:Targets = [System.Collections.Generic.Dictionary[string, [BuildTarget]]]::new() function target { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$name, [scriptblock]$action = $null, [string[]]$depends = @() ) if ($script:Targets.ContainsKey($name)) { throw "Target $name already defined."; } $target = [BuildTarget]::new($action, $depends, $name); $script:Targets.Add($name, $target); } function Start-Build($params = @()) { $timer = [System.Diagnostics.Stopwatch]::new() $timer.Start() $posh_build_version = (Test-ScriptFileInfo (Join-Path $PSScriptRoot "Posh-Build.ps1")).Version Write-Host "Posh-Build v$posh_build_version by Jeremy Skinner (https://github.com/JeremySkinner/Posh-Build)" -ForegroundColor Cyan $target_names = @() for($i = 0; $i -lt $params.Count; $i++) { if ($params[$i] -eq '-t' -or $params[$i] -eq '-targets') { if ($params[$i+1]) { $target_names += $params[$i+1]; } } } if ($target_names.Count -eq 0) { $target_names += 'default' } # Verify all target names before running foreach($target_name in $target_names) { if (-not $script:Targets.ContainsKey($target_names)) { throw "Target $target_name not found" } } $exit_code = 0; foreach($target_name in $target_names) { $target_success = Invoke-Target $target_name if (!$target_success) { $exit_code = 1; } } $timer.Stop() $ts = $timer.Elapsed $elapsed = [String]::Format("{0:00}:{1:00}.{2:00}", $ts.TotalMinutes, $ts.Seconds, $ts.Milliseconds / 10); Write-Host "" Write-Host "Build " -NoNewline if ($exit_code) { write-host "Failed " -NoNewline -ForegroundColor Red } else { write-Host "Completed " -NoNewline -ForegroundColor Green } write-host "in $elapsed" exit($exit_code) } function Invoke-Target([string]$name) { $success = $true; $target = $null; if (-not $script:Targets.ContainsKey($name)) { throw "Target $name not found" } $target = $script:Targets[$name]; $targets_to_execute = [System.Collections.Generic.List[BuildTarget]]::new(); foreach ($target_in_sequence in $target.GetExecutionSequence()) { if (!$targets_to_execute.Contains($target_in_sequence)) { $targets_to_execute.Add($target_in_sequence); } } try { $status_prefix = (1..10 | Foreach-Object { [char]0xBB; }) -Join "" # »»»»»»»»»» foreach ($to_execute in $targets_to_execute) { if ($to_execute.Action) { Write-Host $status_prefix $to_execute.Name -ForegroundColor Green try { & $to_execute.Action | Out-Default; if($LASTEXITCODE) { throw; } } catch { $success = $false; write-host $status_prefix $to_execute.Name failed -ForegroundColor Red if ($_.Exception.Message -and $_.Exception.Message -ne 'ScriptHalted') { write-host $_.Exception.Message -ForegroundColor Red } break; } } } } catch { $success = $false write-host ("Target failed " + $_.Exception.Message) exit(1); break; } return $success } function Invoke-Tests($test_projects, $configuration = 'debug') { $has_failures = $false foreach($project in $test_projects) { & dotnet test $project -c $configuration -nologo $args if ($LASTEXITCODE) { $has_failures = $true } } if ($has_failures) { throw '' } } function Invoke-Xunit($test_projects, $configuration = 'debug') { $has_failures = $false foreach($project in $test_projects) { # CD into the directory with the project $dir = (Get-Item $project).Directory Push-Location $dir & dotnet xunit -c $configuration -nologo $args Pop-Location if ($LASTEXITCODE) { $has_failures = $true } } if ($has_failures) { throw '' } } function Invoke-Dotnet { write-host $args dotnet $args if ($LASTEXITCODE) { throw "Dotnet failed" } } function New-Prompt($title, $details, $prompt_options, $default_choice = 0) { $options = @() foreach($entry in $prompt_options) { $key = $entry[0] $text = $entry[1] $option = [System.Management.Automation.Host.ChoiceDescription]::new($key, $text); $options += $option } return $host.ui.PromptForChoice($title, $details, $options, $default_choice) } class BuildTarget { [scriptblock]$Action; [string[]]$Dependencies; [string]$Name; BuildTarget([scriptblock]$action, [string[]] $dependencies, [string]$name) { $this.Action = $action; $this.Dependencies = $dependencies; $this.Name = $name; } [System.Collections.Generic.List[BuildTarget]] GetExecutionSequence() { $execution_sequence = [System.Collections.Generic.List[BuildTarget]]::new() $parsed_sequence = [System.Collections.Generic.List[BuildTarget]]::new() $this.PopulateExecutionSequence($execution_sequence, $parsed_sequence); $execution_sequence.Reverse() return $execution_sequence; } PopulateExecutionSequence([System.Collections.Generic.List[BuildTarget]] $execution_sequence, [System.Collections.Generic.List[BuildTarget]] $parsed_sequence) { if ($parsed_sequence.Contains($this)) { return; } $n = $this.Name; if ($execution_sequence.Contains($this)) { throw "Target ${n} has recursive dependencies" } $execution_sequence.Add($this); $reversed = [System.Collections.Generic.List[string]]::new($this.Dependencies); $reversed.Reverse() foreach ($dependency_name in $reversed) { $dependency = $this.EnsureTargetExists($dependency_name); $dependency.PopulateExecutionSequence($execution_sequence, $parsed_sequence); } $parsed_sequence.Add($this); } [BuildTarget] EnsureTargetExists([string]$dependency_name) { if (! $script:Targets.ContainsKey($dependency_name)) { throw "Target $dependency_name could not be found"; } return $script:Targets[$dependency_name]; } } |