github.com/jasei/goreleaser@v0.62.4-0.20180312171904-62cb6a8963a6/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  		Option("missingkey=error").
   110  		Parse(build.Ldflags)
   111  	if err != nil {
   112  		return "", err
   113  	}
   114  	err = t.Execute(&out, data)
   115  	return out.String(), err
   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(ctx *context.Context, 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  }