github.com/droot/goreleaser@v0.66.2-0.20180420030140-c2db5fb17157/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 build.Ldflags == "" { 48 build.Ldflags = "-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(ctx, build); err != nil { 59 return err 60 } 61 cmd := []string{"go", "build"} 62 if build.Flags != "" { 63 cmd = append(cmd, strings.Fields(build.Flags)...) 64 } 65 flags, err := ldflags(ctx, build) 66 if err != nil { 67 return err 68 } 69 cmd = append(cmd, "-ldflags="+flags, "-o", options.Path, build.Main) 70 target, err := newBuildTarget(options.Target) 71 if err != nil { 72 return err 73 } 74 var env = append(build.Env, target.Env()...) 75 if err := run(ctx, cmd, env); err != nil { 76 return errors.Wrapf(err, "failed to build for %s", options.Target) 77 } 78 ctx.Artifacts.Add(artifact.Artifact{ 79 Type: artifact.Binary, 80 Path: options.Path, 81 Name: options.Name, 82 Goos: target.os, 83 Goarch: target.arch, 84 Goarm: target.arm, 85 Extra: map[string]string{ 86 "Binary": build.Binary, 87 "Ext": options.Ext, 88 }, 89 }) 90 return nil 91 } 92 93 func ldflags(ctx *context.Context, build config.Build) (string, error) { 94 var data = struct { 95 Commit string 96 Tag string 97 Version string 98 Date string 99 Env map[string]string 100 }{ 101 Commit: ctx.Git.Commit, 102 Tag: ctx.Git.CurrentTag, 103 Version: ctx.Version, 104 Date: time.Now().UTC().Format(time.RFC3339), 105 Env: ctx.Env, 106 } 107 var out bytes.Buffer 108 t, err := template.New("ldflags"). 109 Funcs(template.FuncMap{ 110 "time": func(s string) string { 111 return time.Now().UTC().Format(s) 112 }, 113 }). 114 Option("missingkey=error"). 115 Parse(build.Ldflags) 116 if err != nil { 117 return "", err 118 } 119 err = t.Execute(&out, data) 120 return out.String(), err 121 } 122 123 func run(ctx *context.Context, command, env []string) error { 124 /* #nosec */ 125 var cmd = exec.CommandContext(ctx, command[0], command[1:]...) 126 var log = log.WithField("env", env).WithField("cmd", command) 127 cmd.Env = append(cmd.Env, os.Environ()...) 128 cmd.Env = append(cmd.Env, env...) 129 log.WithField("cmd", command).WithField("env", env).Debug("running") 130 if out, err := cmd.CombinedOutput(); err != nil { 131 log.WithError(err).Debug("failed") 132 return errors.New(string(out)) 133 } 134 return nil 135 } 136 137 type buildTarget struct { 138 os, arch, arm string 139 } 140 141 func newBuildTarget(s string) (buildTarget, error) { 142 var t = buildTarget{} 143 parts := strings.Split(s, "_") 144 if len(parts) < 2 { 145 return t, fmt.Errorf("%s is not a valid build target", s) 146 } 147 t.os = parts[0] 148 t.arch = parts[1] 149 if len(parts) == 3 { 150 t.arm = parts[2] 151 } 152 return t, nil 153 } 154 155 func (b buildTarget) Env() []string { 156 return []string{ 157 "GOOS=" + b.os, 158 "GOARCH=" + b.arch, 159 "GOARM=" + b.arm, 160 } 161 } 162 163 func checkMain(ctx *context.Context, build config.Build) error { 164 var main = build.Main 165 if main == "" { 166 main = "." 167 } 168 stat, ferr := os.Stat(main) 169 if ferr != nil { 170 return ferr 171 } 172 if stat.IsDir() { 173 packs, err := parser.ParseDir(token.NewFileSet(), main, nil, 0) 174 if err != nil { 175 return errors.Wrapf(err, "failed to parse dir: %s", main) 176 } 177 for _, pack := range packs { 178 for _, file := range pack.Files { 179 if hasMain(file) { 180 return nil 181 } 182 } 183 } 184 return fmt.Errorf("build for %s does not contain a main function", build.Binary) 185 } 186 file, err := parser.ParseFile(token.NewFileSet(), main, nil, 0) 187 if err != nil { 188 return errors.Wrapf(err, "failed to parse file: %s", main) 189 } 190 if hasMain(file) { 191 return nil 192 } 193 return fmt.Errorf("build for %s does not contain a main function", build.Binary) 194 } 195 196 func hasMain(file *ast.File) bool { 197 for _, decl := range file.Decls { 198 fn, isFn := decl.(*ast.FuncDecl) 199 if !isFn { 200 continue 201 } 202 if fn.Name.Name == "main" && fn.Recv == nil { 203 return true 204 } 205 } 206 return false 207 }