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  }