github.com/ahmet2mir/goreleaser@v0.180.3-0.20210927151101-8e5ee5a9b8c5/internal/builders/golang/build.go (about) 1 package golang 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/parser" 7 "go/token" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "strconv" 12 "strings" 13 "time" 14 15 "github.com/apex/log" 16 "github.com/goreleaser/goreleaser/internal/artifact" 17 "github.com/goreleaser/goreleaser/internal/builders/buildtarget" 18 "github.com/goreleaser/goreleaser/internal/tmpl" 19 api "github.com/goreleaser/goreleaser/pkg/build" 20 "github.com/goreleaser/goreleaser/pkg/config" 21 "github.com/goreleaser/goreleaser/pkg/context" 22 ) 23 24 // Default builder instance. 25 // nolint: gochecknoglobals 26 var Default = &Builder{} 27 28 // nolint: gochecknoinits 29 func init() { 30 api.Register("go", Default) 31 } 32 33 // Builder is golang builder. 34 type Builder struct{} 35 36 // WithDefaults sets the defaults for a golang build and returns it. 37 func (*Builder) WithDefaults(build config.Build) (config.Build, error) { 38 if build.GoBinary == "" { 39 build.GoBinary = "go" 40 } 41 if build.Dir == "" { 42 build.Dir = "." 43 } 44 if build.Main == "" { 45 build.Main = "." 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}} -X main.builtBy=goreleaser"} 49 } 50 if len(build.Targets) == 0 { 51 if len(build.Goos) == 0 { 52 build.Goos = []string{"linux", "darwin"} 53 } 54 if len(build.Goarch) == 0 { 55 build.Goarch = []string{"amd64", "arm64", "386"} 56 } 57 if len(build.Goarm) == 0 { 58 build.Goarm = []string{"6"} 59 } 60 if len(build.Gomips) == 0 { 61 build.Gomips = []string{"hardfloat"} 62 } 63 targets, err := buildtarget.List(build) 64 build.Targets = targets 65 if err != nil { 66 return build, err 67 } 68 } 69 return build, nil 70 } 71 72 // Build builds a golang build. 73 func (*Builder) Build(ctx *context.Context, build config.Build, options api.Options) error { 74 if err := checkMain(build); err != nil { 75 return err 76 } 77 78 artifact := &artifact.Artifact{ 79 Type: artifact.Binary, 80 Path: options.Path, 81 Name: options.Name, 82 Goos: options.Goos, 83 Goarch: options.Goarch, 84 Goarm: options.Goarm, 85 Gomips: options.Gomips, 86 Extra: map[string]interface{}{ 87 "Binary": strings.TrimSuffix(filepath.Base(options.Path), options.Ext), 88 "Ext": options.Ext, 89 "ID": build.ID, 90 }, 91 } 92 93 env := append(ctx.Env.Strings(), build.Env...) 94 env = append( 95 env, 96 "GOOS="+options.Goos, 97 "GOARCH="+options.Goarch, 98 "GOARM="+options.Goarm, 99 "GOMIPS="+options.Gomips, 100 "GOMIPS64="+options.Gomips, 101 ) 102 103 cmd, err := buildGoBuildLine(ctx, build, options, artifact, env) 104 if err != nil { 105 return err 106 } 107 108 if err := run(ctx, cmd, env, build.Dir); err != nil { 109 return fmt.Errorf("failed to build for %s: %w", options.Target, err) 110 } 111 112 if build.ModTimestamp != "" { 113 modTimestamp, err := tmpl.New(ctx).WithEnvS(env).WithArtifact(artifact, map[string]string{}).Apply(build.ModTimestamp) 114 if err != nil { 115 return err 116 } 117 modUnix, err := strconv.ParseInt(modTimestamp, 10, 64) 118 if err != nil { 119 return err 120 } 121 modTime := time.Unix(modUnix, 0) 122 err = os.Chtimes(options.Path, modTime, modTime) 123 if err != nil { 124 return fmt.Errorf("failed to change times for %s: %w", options.Target, err) 125 } 126 } 127 128 ctx.Artifacts.Add(artifact) 129 return nil 130 } 131 132 func buildGoBuildLine(ctx *context.Context, build config.Build, options api.Options, artifact *artifact.Artifact, env []string) ([]string, error) { 133 cmd := []string{build.GoBinary, "build"} 134 flags, err := processFlags(ctx, artifact, env, build.Flags, "") 135 if err != nil { 136 return cmd, err 137 } 138 cmd = append(cmd, flags...) 139 140 asmflags, err := processFlags(ctx, artifact, env, build.Asmflags, "-asmflags=") 141 if err != nil { 142 return cmd, err 143 } 144 cmd = append(cmd, asmflags...) 145 146 gcflags, err := processFlags(ctx, artifact, env, build.Gcflags, "-gcflags=") 147 if err != nil { 148 return cmd, err 149 } 150 cmd = append(cmd, gcflags...) 151 152 // tags is not a repeatable flag 153 if len(build.Tags) > 0 { 154 tags, err := processFlags(ctx, artifact, env, build.Tags, "") 155 if err != nil { 156 return cmd, err 157 } 158 cmd = append(cmd, "-tags="+strings.Join(tags, ",")) 159 } 160 161 // ldflags is not a repeatable flag 162 if len(build.Ldflags) > 0 { 163 // flag prefix is skipped because ldflags need to output a single string 164 ldflags, err := processFlags(ctx, artifact, env, build.Ldflags, "") 165 if err != nil { 166 return cmd, err 167 } 168 // ldflags need to be single string in order to apply correctly 169 cmd = append(cmd, "-ldflags="+strings.Join(ldflags, " ")) 170 } 171 172 cmd = append(cmd, "-o", options.Path, build.Main) 173 return cmd, nil 174 } 175 176 func processFlags(ctx *context.Context, a *artifact.Artifact, env, flags []string, flagPrefix string) ([]string, error) { 177 processed := make([]string, 0, len(flags)) 178 for _, rawFlag := range flags { 179 flag, err := processFlag(ctx, a, env, rawFlag) 180 if err != nil { 181 return nil, err 182 } 183 processed = append(processed, flagPrefix+flag) 184 } 185 return processed, nil 186 } 187 188 func processFlag(ctx *context.Context, a *artifact.Artifact, env []string, rawFlag string) (string, error) { 189 return tmpl.New(ctx).WithEnvS(env).WithArtifact(a, map[string]string{}).Apply(rawFlag) 190 } 191 192 func run(ctx *context.Context, command, env []string, dir string) error { 193 /* #nosec */ 194 cmd := exec.CommandContext(ctx, command[0], command[1:]...) 195 log := log.WithField("env", env).WithField("cmd", command) 196 cmd.Env = env 197 cmd.Dir = dir 198 log.Debug("running") 199 if out, err := cmd.CombinedOutput(); err != nil { 200 return fmt.Errorf("%w: %s", err, string(out)) 201 } 202 return nil 203 } 204 205 func checkMain(build config.Build) error { 206 main := build.Main 207 if build.UnproxiedMain != "" { 208 main = build.UnproxiedMain 209 } 210 dir := build.Dir 211 if build.UnproxiedDir != "" { 212 dir = build.UnproxiedDir 213 } 214 215 if main == "" { 216 main = "." 217 } 218 if dir != "" { 219 main = filepath.Join(dir, main) 220 } 221 stat, ferr := os.Stat(main) 222 if ferr != nil { 223 return fmt.Errorf("couldn't find main file: %w", ferr) 224 } 225 if stat.IsDir() { 226 packs, err := parser.ParseDir(token.NewFileSet(), main, nil, 0) 227 if err != nil { 228 return fmt.Errorf("failed to parse dir: %s: %w", main, err) 229 } 230 for _, pack := range packs { 231 for _, file := range pack.Files { 232 if hasMain(file) { 233 return nil 234 } 235 } 236 } 237 return fmt.Errorf("build for %s does not contain a main function", build.Binary) 238 } 239 file, err := parser.ParseFile(token.NewFileSet(), main, nil, 0) 240 if err != nil { 241 return fmt.Errorf("failed to parse file: %s: %w", main, err) 242 } 243 if hasMain(file) { 244 return nil 245 } 246 return fmt.Errorf("build for %s does not contain a main function", build.Binary) 247 } 248 249 func hasMain(file *ast.File) bool { 250 for _, decl := range file.Decls { 251 fn, isFn := decl.(*ast.FuncDecl) 252 if !isFn { 253 continue 254 } 255 if fn.Name.Name == "main" && fn.Recv == nil { 256 return true 257 } 258 } 259 return false 260 }