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