github.com/tahsinrahman/goreleaser@v0.79.1/internal/builders/golang/build.go (about) 1 package golang 2 3 import ( 4 "bytes" 5 "fmt" 6 "go/ast" 7 "go/parser" 8 "go/token" 9 "os" 10 "os/exec" 11 "strings" 12 "text/template" 13 "time" 14 15 "github.com/apex/log" 16 api "github.com/goreleaser/goreleaser/build" 17 "github.com/goreleaser/goreleaser/config" 18 "github.com/goreleaser/goreleaser/context" 19 "github.com/goreleaser/goreleaser/internal/artifact" 20 "github.com/pkg/errors" 21 ) 22 23 // Default builder instance 24 var Default = &Builder{} 25 26 func init() { 27 api.Register("go", Default) 28 } 29 30 // Builder is golang builder 31 type Builder struct{} 32 33 // WithDefaults sets the defaults for a golang build and returns it 34 func (*Builder) WithDefaults(build config.Build) config.Build { 35 if build.Main == "" { 36 build.Main = "." 37 } 38 if len(build.Goos) == 0 { 39 build.Goos = []string{"linux", "darwin"} 40 } 41 if len(build.Goarch) == 0 { 42 build.Goarch = []string{"amd64", "386"} 43 } 44 if len(build.Goarm) == 0 { 45 build.Goarm = []string{"6"} 46 } 47 if len(build.Ldflags) == 0 { 48 build.Ldflags = []string{"-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"} 49 } 50 if len(build.Targets) == 0 { 51 build.Targets = matrix(build) 52 } 53 return build 54 } 55 56 // Build builds a golang build 57 func (*Builder) Build(ctx *context.Context, build config.Build, options api.Options) error { 58 if err := checkMain(build); err != nil { 59 return err 60 } 61 cmd := []string{"go", "build"} 62 63 cmd = append(cmd, build.Flags...) 64 65 asmflags, err := processFlags(ctx, build.Asmflags, "asmflags", "-asmflags=") 66 if err != nil { 67 return err 68 } 69 cmd = append(cmd, asmflags...) 70 71 gcflags, err := processFlags(ctx, build.Gcflags, "gcflags", "-gcflags=") 72 if err != nil { 73 return err 74 } 75 cmd = append(cmd, gcflags...) 76 77 ldflags, err := processFlags(ctx, build.Ldflags, "ldflags", "-ldflags=") 78 if err != nil { 79 return err 80 } 81 cmd = append(cmd, ldflags...) 82 83 cmd = append(cmd, "-o", options.Path, build.Main) 84 85 target, err := newBuildTarget(options.Target) 86 if err != nil { 87 return err 88 } 89 var env = append(build.Env, target.Env()...) 90 if err := run(ctx, cmd, env); err != nil { 91 return errors.Wrapf(err, "failed to build for %s", options.Target) 92 } 93 ctx.Artifacts.Add(artifact.Artifact{ 94 Type: artifact.Binary, 95 Path: options.Path, 96 Name: options.Name, 97 Goos: target.os, 98 Goarch: target.arch, 99 Goarm: target.arm, 100 Extra: map[string]string{ 101 "Binary": build.Binary, 102 "Ext": options.Ext, 103 }, 104 }) 105 return nil 106 } 107 108 func processFlags(ctx *context.Context, flags []string, flagName string, flagPrefix string) ([]string, error) { 109 processed := make([]string, 0, len(flags)) 110 for _, rawFlag := range flags { 111 flag, err := processField(ctx, rawFlag, flagName) 112 if err != nil { 113 return nil, err 114 } 115 processed = append(processed, flagPrefix+flag) 116 } 117 return processed, nil 118 } 119 120 func processField(ctx *context.Context, field string, fieldName string) (string, error) { 121 var data = struct { 122 Commit string 123 Tag string 124 Version string 125 Date string 126 Env map[string]string 127 }{ 128 Commit: ctx.Git.Commit, 129 Tag: ctx.Git.CurrentTag, 130 Version: ctx.Version, 131 Date: time.Now().UTC().Format(time.RFC3339), 132 Env: ctx.Env, 133 } 134 var out bytes.Buffer 135 t, err := template.New(fieldName). 136 Funcs(template.FuncMap{ 137 "time": func(s string) string { 138 return time.Now().UTC().Format(s) 139 }, 140 }). 141 Option("missingkey=error"). 142 Parse(field) 143 if err != nil { 144 return "", err 145 } 146 err = t.Execute(&out, data) 147 return out.String(), err 148 } 149 150 func run(ctx *context.Context, command, env []string) error { 151 /* #nosec */ 152 var cmd = exec.CommandContext(ctx, command[0], command[1:]...) 153 var log = log.WithField("env", env).WithField("cmd", command) 154 cmd.Env = append(cmd.Env, os.Environ()...) 155 cmd.Env = append(cmd.Env, env...) 156 log.WithField("cmd", command).WithField("env", env).Debug("running") 157 if out, err := cmd.CombinedOutput(); err != nil { 158 log.WithError(err).Debug("failed") 159 return errors.New(string(out)) 160 } 161 return nil 162 } 163 164 type buildTarget struct { 165 os, arch, arm string 166 } 167 168 func newBuildTarget(s string) (buildTarget, error) { 169 var t = buildTarget{} 170 parts := strings.Split(s, "_") 171 if len(parts) < 2 { 172 return t, fmt.Errorf("%s is not a valid build target", s) 173 } 174 t.os = parts[0] 175 t.arch = parts[1] 176 if len(parts) == 3 { 177 t.arm = parts[2] 178 } 179 return t, nil 180 } 181 182 func (b buildTarget) Env() []string { 183 return []string{ 184 "GOOS=" + b.os, 185 "GOARCH=" + b.arch, 186 "GOARM=" + b.arm, 187 } 188 } 189 190 func checkMain(build config.Build) error { 191 var main = build.Main 192 if main == "" { 193 main = "." 194 } 195 stat, ferr := os.Stat(main) 196 if ferr != nil { 197 return ferr 198 } 199 if stat.IsDir() { 200 packs, err := parser.ParseDir(token.NewFileSet(), main, nil, 0) 201 if err != nil { 202 return errors.Wrapf(err, "failed to parse dir: %s", main) 203 } 204 for _, pack := range packs { 205 for _, file := range pack.Files { 206 if hasMain(file) { 207 return nil 208 } 209 } 210 } 211 return fmt.Errorf("build for %s does not contain a main function", build.Binary) 212 } 213 file, err := parser.ParseFile(token.NewFileSet(), main, nil, 0) 214 if err != nil { 215 return errors.Wrapf(err, "failed to parse file: %s", main) 216 } 217 if hasMain(file) { 218 return nil 219 } 220 return fmt.Errorf("build for %s does not contain a main function", build.Binary) 221 } 222 223 func hasMain(file *ast.File) bool { 224 for _, decl := range file.Decls { 225 fn, isFn := decl.(*ast.FuncDecl) 226 if !isFn { 227 continue 228 } 229 if fn.Name.Name == "main" && fn.Recv == nil { 230 return true 231 } 232 } 233 return false 234 }