core/providers/golang/golang.go
core/providers/golang/golang.goBrowse 1970 files
2,220 tokens
8,139 bytes
Token encoding: o200k_base
Snapshot 21a254f
← Back to SKILL.md
1package golang2 3import (4 "fmt"5 "path/filepath"6 "strings"7 8 "github.com/railwayapp/railpack/core/generate"9 "github.com/railwayapp/railpack/core/plan"10)11 12const (13 DEFAULT_GO_VERSION = "1.25"14 GO_BUILD_CACHE_KEY = "go-build"15 GO_BINARY_NAME = "out"16 GO_PATH = "/go"17)18 19type GoProvider struct{}20 21func (p *GoProvider) Name() string {22 return "golang"23}24 25func (p *GoProvider) Detect(ctx *generate.GenerateContext) (bool, error) {26 return p.isGoMod(ctx) || p.isGoWorkspace(ctx) || ctx.App.HasFile("main.go"), nil27}28 29func (p *GoProvider) Initialize(ctx *generate.GenerateContext) error {30 return nil31}32 33func (p *GoProvider) Plan(ctx *generate.GenerateContext) error {34 builder := p.GetBuilder(ctx)35 p.InstallGoPackages(ctx, builder)36 37 install := ctx.NewCommandStep("install")38 install.AddInput(plan.NewStepLayer(builder.Name()))39 p.InstallGoDeps(ctx, install)40 41 build := ctx.NewCommandStep("build")42 build.AddInput(plan.NewStepLayer(install.Name()))43 p.Build(ctx, build)44 45 ctx.Deploy.StartCmd = fmt.Sprintf("./%s", GO_BINARY_NAME)46 47 if p.hasCGOEnabled(ctx) {48 ctx.Logger.LogInfo("CGO is enabled")49 ctx.Deploy.AddAptPackages([]string{"libc6"})50 }51 ctx.Deploy.AddInputs([]plan.Layer{52 plan.NewStepLayer(build.Name(), plan.Filter{53 Include: []string{"."},54 }),55 })56 57 p.addMetadata(ctx)58 59 return nil60}61 62func (p *GoProvider) Build(ctx *generate.GenerateContext, build *generate.CommandStepBuilder) {63 var buildCmd string64 65 flags := "-w -s"66 baseBuildCmd := fmt.Sprintf("go build -ldflags=\"%s\" -o %s", flags, GO_BINARY_NAME)67 68 if modulePath, _ := ctx.Env.GetConfigVariable("GO_WORKSPACE_MODULE"); modulePath != "" {69 // Use the provided env var path to build the specified module70 ctx.Logger.LogInfo("Building workspace module: %s", modulePath)71 buildCmd = fmt.Sprintf("%s ./%s", baseBuildCmd, modulePath)72 } else if binName, _ := ctx.Env.GetConfigVariable("GO_BIN"); binName != "" {73 // Use the provided env var path to build the specified command74 ctx.Logger.LogInfo("Building bin: %s", binName)75 buildCmd = fmt.Sprintf("%s ./cmd/%s", baseBuildCmd, binName)76 } else if p.isGoMod(ctx) && p.hasRootGoFiles(ctx) {77 // Use the default build command if there are root go files78 buildCmd = baseBuildCmd79 } else if dirs, err := ctx.App.FindDirectories("cmd/*"); err == nil && len(dirs) > 0 {80 // Try to find a command in the cmd directory if no other build command is specified81 cmdName := filepath.Base(dirs[0])82 ctx.Logger.LogInfo("Building command: %s", cmdName)83 buildCmd = fmt.Sprintf("%s ./cmd/%s", baseBuildCmd, cmdName)84 } else if p.isGoMod(ctx) {85 // Use the default build command if there are no root go files86 buildCmd = baseBuildCmd87 } else if p.isGoWorkspace(ctx) {88 // For workspaces without explicit module selection, try to find a module with main package89 packages := p.GoWorkspacePackages(ctx)90 for _, pkg := range packages {91 if ctx.App.HasFile(filepath.Join(pkg, "main.go")) {92 ctx.Logger.LogInfo("Building workspace module: %s", pkg)93 buildCmd = fmt.Sprintf("%s ./%s", baseBuildCmd, pkg)94 break95 }96 }97 } else if ctx.App.HasFile("main.go") {98 // Fallback to building the main package if no other build command is specified99 buildCmd = fmt.Sprintf("%s main.go", baseBuildCmd)100 }101 102 build.AddInput(plan.NewLocalLayer())103 104 if buildCmd == "" {105 return106 }107 108 build.AddCache(p.goBuildCache(ctx))109 build.AddCommands([]plan.Command{110 plan.NewExecCommand(buildCmd),111 })112}113 114func (p *GoProvider) InstallGoDeps(ctx *generate.GenerateContext, install *generate.CommandStepBuilder) {115 install.AddEnvVars(map[string]string{116 "GOPATH": GO_PATH,117 "GOBIN": fmt.Sprintf("%s/bin", GO_PATH),118 })119 install.AddCommands([]plan.Command{120 plan.NewPathCommand(fmt.Sprintf("%s/bin", GO_PATH)),121 })122 123 if !p.isGoMod(ctx) && !p.isGoWorkspace(ctx) {124 return125 }126 127 install.AddCache(p.goBuildCache(ctx))128 129 if p.isGoMod(ctx) {130 install.AddCommand(plan.NewCopyCommand("go.mod"))131 if ctx.App.HasFile("go.sum") {132 install.AddCommand(plan.NewCopyCommand("go.sum"))133 }134 }135 136 if p.isGoWorkspace(ctx) {137 install.AddCommand(plan.NewCopyCommand("go.work"))138 if ctx.App.HasFile("go.work.sum") {139 install.AddCommand(plan.NewCopyCommand("go.work.sum"))140 }141 }142 143 workspacePackages := p.GoWorkspacePackages(ctx)144 for _, pkgPath := range workspacePackages {145 install.AddCommand(plan.NewCopyCommand(filepath.Join(pkgPath, "go.mod")))146 if ctx.App.HasFile(filepath.Join(pkgPath, "go.sum")) {147 install.AddCommand(plan.NewCopyCommand(filepath.Join(pkgPath, "go.sum")))148 }149 }150 151 install.AddCommand(plan.NewExecCommand("go mod download"))152 153 ctx.Logger.LogInfo("Using go mod")154 155 if !p.hasCGOEnabled(ctx) {156 install.AddEnvVars(map[string]string{"CGO_ENABLED": "0"})157 }158}159 160func (p *GoProvider) extractGoVersionFromMod(ctx *generate.GenerateContext) string {161 if goModContents, err := ctx.App.ReadFile("go.mod"); err == nil {162 // Split content into lines and look for "go X.XX" line163 lines := strings.SplitSeq(string(goModContents), "\n")164 for line := range lines {165 if strings.HasPrefix(strings.TrimSpace(line), "go ") {166 // Extract version number167 if goVersion := strings.TrimSpace(strings.TrimPrefix(line, "go")); goVersion != "" {168 return goVersion169 }170 }171 }172 }173 return ""174}175 176func (p *GoProvider) InstallGoPackages(ctx *generate.GenerateContext, miseStep *generate.MiseStepBuilder) {177 goPkg := miseStep.Default("go", DEFAULT_GO_VERSION)178 179 if goVersion := p.extractGoVersionFromMod(ctx); goVersion != "" {180 miseStep.Version(goPkg, goVersion, "go.mod")181 }182 183 if envVersion, varName := ctx.Env.GetConfigVariable("GO_VERSION"); envVersion != "" {184 miseStep.Version(goPkg, envVersion, varName)185 }186 187 miseStep.UseMiseVersions(ctx, []string{"go"})188}189 190func (p *GoProvider) GetBuilder(ctx *generate.GenerateContext) *generate.MiseStepBuilder {191 miseStep := ctx.GetMiseStepBuilder()192 193 if p.hasCGOEnabled(ctx) {194 miseStep.SupportingAptPackages = append(miseStep.SupportingAptPackages, "gcc", "g++", "libc6-dev")195 }196 197 return miseStep198}199 200func (p *GoProvider) addMetadata(ctx *generate.GenerateContext) {201 ctx.Metadata.SetBool("goMod", p.isGoMod(ctx))202 ctx.Metadata.SetBool("goWorkspace", p.isGoWorkspace(ctx))203 ctx.Metadata.SetBool("goRootFile", p.hasRootGoFiles(ctx))204 ctx.Metadata.SetBool("goGin", p.isGin(ctx))205 ctx.Metadata.SetBool("goCGO", p.hasCGOEnabled(ctx))206}207 208func (p *GoProvider) goBuildCache(ctx *generate.GenerateContext) string {209 return ctx.Caches.AddCache(GO_BUILD_CACHE_KEY, "/root/.cache/go-build")210}211 212func (p *GoProvider) hasRootGoFiles(ctx *generate.GenerateContext) bool {213 if files, err := ctx.App.FindFiles("*.go"); err == nil {214 for _, file := range files {215 if filepath.Dir(file) == "." {216 return true217 }218 }219 }220 return false221}222 223func (p *GoProvider) isGin(ctx *generate.GenerateContext) bool {224 if goModContents, err := ctx.App.ReadFile("go.mod"); err == nil {225 return strings.Contains(string(goModContents), "github.com/gin-gonic/gin")226 }227 228 return false229}230 231func (p *GoProvider) hasCGOEnabled(ctx *generate.GenerateContext) bool {232 return ctx.Env.GetVariable("CGO_ENABLED") == "1"233}234 235func (p *GoProvider) isGoMod(ctx *generate.GenerateContext) bool {236 return ctx.App.HasFile("go.mod")237}238 239func (p *GoProvider) GoWorkspacePackages(ctx *generate.GenerateContext) []string {240 var packages []string241 242 goModFiles, err := ctx.App.FindFiles("**/go.mod")243 if err != nil {244 return packages245 }246 247 for _, modFile := range goModFiles {248 if modFile == "go.mod" {249 continue250 }251 252 dir := filepath.Dir(modFile)253 packages = append(packages, dir)254 }255 256 return packages257}258 259func (p *GoProvider) isGoWorkspace(ctx *generate.GenerateContext) bool {260 return ctx.App.HasFile("go.work")261}262 263func (p *GoProvider) CleansePlan(buildPlan *plan.BuildPlan) {}264 265func (p *GoProvider) StartCommandHelp() string {266 return "To configure your start command, Railpack will check:\n\n" +267 "1. Create a main.go file in your project root\n\n" +268 "2. Create a command in the cmd directory (e.g., cmd/server/main.go)\n\n" +269 "3. Set the GO_BIN environment variable to specify which command to build\n\n" +270 "4. For workspaces: Set GO_WORKSPACE_MODULE to build a specific module (e.g., GO_WORKSPACE_MODULE=api)"271}272