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