github.com/jonathanlloyd/goreleaser@v0.91.1/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  }