core/plan/plan.go
core/plan/plan.goBrowse 1970 files
1,027 tokens
3,856 bytes
Token encoding: o200k_base
Snapshot 21a254f
← Back to SKILL.md
1package plan2 3import (4 "fmt"5 6 "github.com/railwayapp/railpack/core/mise"7)8 9var (10 RailpackRuntimeImage = fmt.Sprintf("ghcr.io/railwayapp/railpack-runtime:mise-%s", mise.Version)11)12 13// serialized to railpack.json14type BuildPlan struct {15 Steps []Step `json:"steps,omitempty"`16 Caches map[string]*Cache `json:"caches,omitempty"`17 Secrets []string `json:"secrets,omitempty"`18 Deploy Deploy `json:"deploy"`19 Exclude []string `json:"exclude,omitempty"`20}21 22type Deploy struct {23 // The base layer for the deploy step24 Base Layer `json:"base"`25 26 // The layers for the deploy step27 Inputs []Layer `json:"inputs,omitempty"`28 29 // The command to run in the container30 StartCmd string `json:"startCommand,omitempty"`31 32 // The variables available to this step. The key is the name of the variable that is referenced in a variable command33 Variables map[string]string `json:"variables,omitempty"`34 35 // The paths to prepend to the $PATH environment variable36 Paths []string `json:"paths,omitempty"`37}38 39func NewBuildPlan() *BuildPlan {40 return &BuildPlan{41 Steps: []Step{},42 Deploy: Deploy{},43 Caches: make(map[string]*Cache),44 Secrets: []string{},45 Exclude: []string{},46 }47}48 49func (p *BuildPlan) AddStep(step Step) {50 p.Steps = append(p.Steps, step)51}52 53func (p *BuildPlan) Normalize() {54 // Remove empty inputs from steps55 for i := range p.Steps {56 if p.Steps[i].Inputs == nil {57 continue58 }59 normalizedInputs := []Layer{}60 for _, input := range p.Steps[i].Inputs {61 if !input.IsEmpty() {62 normalizedInputs = append(normalizedInputs, input)63 }64 }65 p.Steps[i].Inputs = normalizedInputs66 }67 68 // Remove empty inputs from deploy69 if p.Deploy.Inputs != nil {70 normalizedDeployInputs := []Layer{}71 for _, input := range p.Deploy.Inputs {72 if !input.IsEmpty() {73 normalizedDeployInputs = append(normalizedDeployInputs, input)74 }75 }76 if len(normalizedDeployInputs) == 0 {77 p.Deploy.Inputs = nil78 } else {79 p.Deploy.Inputs = normalizedDeployInputs80 }81 }82 83 // Track which steps are referenced by deploy or transitively referenced steps84 referencedSteps := make(map[string]bool)85 86 // Start with steps referenced directly by deploy87 if p.Deploy.Base.Step != "" {88 referencedSteps[p.Deploy.Base.Step] = true89 }90 91 if p.Deploy.Inputs != nil {92 for _, input := range p.Deploy.Inputs {93 if input.Step != "" {94 referencedSteps[input.Step] = true95 }96 }97 }98 99 // Keep finding new referenced steps until no more are found100 // Use a map to track which steps we've already checked to avoid infinite loops101 checkedSteps := make(map[string]bool)102 // We use maxIterations to prevent infinite loops from circular dependencies.103 // If we exceed this maximum possible number of unique edges, we can safely104 // break as we've already collected all reachable steps.105 maxIterations := len(p.Steps) * len(p.Steps) // Maximum possible unique edges in a directed graph106 107 for range maxIterations {108 newReferences := false109 for _, step := range p.Steps {110 // Skip if this step isn't referenced111 if !referencedSteps[step.Name] {112 continue113 }114 115 // Skip if we've already checked this step's inputs116 if checkedSteps[step.Name] {117 continue118 }119 120 // Mark this step as checked121 checkedSteps[step.Name] = true122 123 // Check this step's inputs for references124 if step.Inputs != nil {125 for _, input := range step.Inputs {126 if input.Step != "" && !referencedSteps[input.Step] {127 referencedSteps[input.Step] = true128 newReferences = true129 }130 }131 }132 }133 if !newReferences {134 break135 }136 }137 138 // Keep only steps that are referenced139 if len(referencedSteps) > 0 {140 normalizedSteps := make([]Step, 0, len(p.Steps))141 for _, step := range p.Steps {142 if referencedSteps[step.Name] {143 normalizedSteps = append(normalizedSteps, step)144 }145 }146 p.Steps = normalizedSteps147 }148}149