github.com/szyn/goreleaser@v0.76.1-0.20180517112710-333da09a1297/pipeline/build/build.go (about)

     1  // Package build provides a pipe that can build binaries for several
     2  // languages.
     3  package build
     4  
     5  import (
     6  	"bytes"
     7  	"os"
     8  	"os/exec"
     9  	"path/filepath"
    10  	"strings"
    11  	"text/template"
    12  	"time"
    13  
    14  	"github.com/apex/log"
    15  	"github.com/pkg/errors"
    16  	"golang.org/x/sync/errgroup"
    17  
    18  	builders "github.com/goreleaser/goreleaser/build"
    19  	"github.com/goreleaser/goreleaser/config"
    20  	"github.com/goreleaser/goreleaser/context"
    21  
    22  	// langs to init
    23  	_ "github.com/goreleaser/goreleaser/internal/builders/golang"
    24  )
    25  
    26  // Pipe for build
    27  type Pipe struct{}
    28  
    29  func (Pipe) String() string {
    30  	return "building binaries"
    31  }
    32  
    33  // Run the pipe
    34  func (Pipe) Run(ctx *context.Context) error {
    35  	for _, build := range ctx.Config.Builds {
    36  		log.WithField("build", build).Debug("building")
    37  		if err := runPipeOnBuild(ctx, build); err != nil {
    38  			return err
    39  		}
    40  	}
    41  	return nil
    42  }
    43  
    44  // Default sets the pipe defaults
    45  func (Pipe) Default(ctx *context.Context) error {
    46  	for i, build := range ctx.Config.Builds {
    47  		ctx.Config.Builds[i] = buildWithDefaults(ctx, build)
    48  	}
    49  	if len(ctx.Config.Builds) == 0 {
    50  		ctx.Config.Builds = []config.Build{
    51  			buildWithDefaults(ctx, ctx.Config.SingleBuild),
    52  		}
    53  	}
    54  	return nil
    55  }
    56  
    57  func buildWithDefaults(ctx *context.Context, build config.Build) config.Build {
    58  	if build.Lang == "" {
    59  		build.Lang = "go"
    60  	}
    61  	if build.Binary == "" {
    62  		build.Binary = ctx.Config.Release.GitHub.Name
    63  	}
    64  	for k, v := range build.Env {
    65  		build.Env[k] = os.ExpandEnv(v)
    66  	}
    67  	return builders.For(build.Lang).WithDefaults(build)
    68  }
    69  
    70  func runPipeOnBuild(ctx *context.Context, build config.Build) error {
    71  	if err := runHook(ctx, build.Env, build.Hooks.Pre); err != nil {
    72  		return errors.Wrap(err, "pre hook failed")
    73  	}
    74  	sem := make(chan bool, ctx.Parallelism)
    75  	var g errgroup.Group
    76  	for _, target := range build.Targets {
    77  		sem <- true
    78  		target := target
    79  		build := build
    80  		g.Go(func() error {
    81  			defer func() {
    82  				<-sem
    83  			}()
    84  			return doBuild(ctx, build, target)
    85  		})
    86  	}
    87  	if err := g.Wait(); err != nil {
    88  		return err
    89  	}
    90  	return errors.Wrap(runHook(ctx, build.Env, build.Hooks.Post), "post hook failed")
    91  }
    92  
    93  func runHook(ctx *context.Context, env []string, hook string) error {
    94  	if hook == "" {
    95  		return nil
    96  	}
    97  	log.WithField("hook", hook).Info("running hook")
    98  	cmd := strings.Fields(hook)
    99  	return run(ctx, cmd, env)
   100  }
   101  
   102  func doBuild(ctx *context.Context, build config.Build, target string) error {
   103  	var ext = extFor(target)
   104  
   105  	binary, err := binary(ctx, build)
   106  	if err != nil {
   107  		return err
   108  	}
   109  
   110  	build.Binary = binary
   111  	var name = build.Binary + ext
   112  	var path = filepath.Join(ctx.Config.Dist, target, name)
   113  	log.WithField("binary", path).Info("building")
   114  	return builders.For(build.Lang).Build(ctx, build, builders.Options{
   115  		Target: target,
   116  		Name:   name,
   117  		Path:   path,
   118  		Ext:    ext,
   119  	})
   120  }
   121  
   122  func binary(ctx *context.Context, build config.Build) (string, error) {
   123  	var data = struct {
   124  		Commit  string
   125  		Tag     string
   126  		Version string
   127  		Date    string
   128  		Env     map[string]string
   129  	}{
   130  		Commit:  ctx.Git.Commit,
   131  		Tag:     ctx.Git.CurrentTag,
   132  		Version: ctx.Version,
   133  		Date:    time.Now().UTC().Format(time.RFC3339),
   134  		Env:     ctx.Env,
   135  	}
   136  	var out bytes.Buffer
   137  	t, err := template.New("binary").
   138  		Option("missingkey=error").
   139  		Parse(build.Binary)
   140  	if err != nil {
   141  		return "", err
   142  	}
   143  	err = t.Execute(&out, data)
   144  	return out.String(), err
   145  }
   146  
   147  func extFor(target string) string {
   148  	if strings.Contains(target, "windows") {
   149  		return ".exe"
   150  	}
   151  	return ""
   152  }
   153  
   154  func run(ctx *context.Context, command, env []string) error {
   155  	/* #nosec */
   156  	var cmd = exec.CommandContext(ctx, command[0], command[1:]...)
   157  	var log = log.WithField("env", env).WithField("cmd", command)
   158  	cmd.Env = append(cmd.Env, os.Environ()...)
   159  	cmd.Env = append(cmd.Env, env...)
   160  	log.WithField("cmd", command).WithField("env", env).Debug("running")
   161  	if out, err := cmd.CombinedOutput(); err != nil {
   162  		log.WithError(err).Debug("failed")
   163  		return errors.New(string(out))
   164  	}
   165  	return nil
   166  }