private/Compile-BuildPlan.ps1
|
function Compile-BuildPlan { <# .SYNOPSIS Compiles a build plan from the current psake context. .DESCRIPTION Takes the registered tasks from the current context and produces a PsakeBuildPlan with validated dependency graph and execution order. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$BuildFile, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string[]]$TaskList ) Write-Debug "Compiling build plan for '$BuildFile' with tasks: $($TaskList -join ', ')" $currentContext = $psake.Context.Peek() # Validate Version declaration if present if ($currentContext.ContainsKey('requiredVersion') -and $currentContext.requiredVersion) { $psakeMajor = [int]($psake.version.Split('.')[0]) if ($currentContext.requiredVersion -ne $psakeMajor) { throw "Build script requires psake version $($currentContext.requiredVersion) but running version $($psake.version)." } } $plan = [PsakeBuildPlan]::new() $plan.BuildFile = $BuildFile $plan.CompiledAt = [datetime]::UtcNow $buildPath = Split-Path $BuildFile -Parent $psakeDir = Join-Path $buildPath '.psake' $plan.CacheDir = Join-Path $psakeDir 'cache' $plan.ValidationErrors = @() # Build TaskMap from context foreach ($key in $currentContext.tasks.Keys) { Write-Debug "Adding task '$key' to build plan from context." $plan.TaskMap[$key] = $currentContext.tasks[$key] } # Resolve aliases foreach ($key in $currentContext.aliases.Keys) { if (-not $plan.TaskMap.ContainsKey($key)) { $plan.TaskMap[$key] = $currentContext.aliases[$key] } } $plan.Tasks = @($plan.TaskMap.Values) # Resolve the starting tasks $startTasks = @() if ($TaskList -and $TaskList.Count -gt 0) { foreach ($taskName in $TaskList) { $taskKey = $taskName.ToLower() # Check aliases if ($currentContext.aliases.ContainsKey($taskKey)) { $taskKey = $currentContext.aliases[$taskKey].Name.ToLower() } if (-not $plan.TaskMap.ContainsKey($taskKey)) { $plan.ValidationErrors += "Task '$taskName' does not exist." } else { $startTasks += $taskKey } } } elseif ($plan.TaskMap.ContainsKey('default')) { $startTasks = @('default') } else { $plan.ValidationErrors += "'default' task required." } if ($plan.ValidationErrors.Count -gt 0) { $plan.IsValid = $false return $plan } # Topological sort with cycle detection $resolveSplat = @{ TaskKey = $startTasks TaskMap = $plan.TaskMap Aliases = $currentContext.aliases } $resolved = Resolve-TaskDependencies @resolveSplat $errors = $resolved.ValidationErrors $order = $resolved.Order if ($errors) { $plan.ValidationErrors += $errors } if ($plan.ValidationErrors.Count -gt 0) { $plan.IsValid = $false return $plan } if ($order.Count -eq 0) { $plan.ExecutionOrder = @() } else { $plan.ExecutionOrder = $order } # Filter TaskMap and Tasks to only include tasks in the execution order $reachableKeys = [System.Collections.Generic.HashSet[string]]::new($plan.ExecutionOrder) foreach ($key in @($plan.TaskMap.Keys)) { if (-not $reachableKeys.Contains($key)) { $plan.TaskMap.Remove($key) } } $plan.Tasks = @($plan.TaskMap.Values) $plan.IsValid = $true Write-Debug "Build plan compiled: $($plan.ExecutionOrder.Count) tasks in execution order: $($plan.ExecutionOrder -join ' -> ')" return $plan } |