github.com/amane3/goreleaser@v0.182.0/internal/pipe/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  	"fmt"
     8  	"io"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"strings"
    13  
    14  	"github.com/amane3/goreleaser/internal/ids"
    15  	"github.com/amane3/goreleaser/internal/logext"
    16  	"github.com/amane3/goreleaser/internal/semerrgroup"
    17  	"github.com/amane3/goreleaser/internal/tmpl"
    18  	builders "github.com/amane3/goreleaser/pkg/build"
    19  	"github.com/amane3/goreleaser/pkg/config"
    20  	"github.com/amane3/goreleaser/pkg/context"
    21  	"github.com/apex/log"
    22  	"github.com/mattn/go-shellwords"
    23  
    24  	// langs to init.
    25  	_ "github.com/amane3/goreleaser/internal/builders/golang"
    26  )
    27  
    28  // Pipe for build.
    29  type Pipe struct{}
    30  
    31  func (Pipe) String() string {
    32  	return "building binaries"
    33  }
    34  
    35  // Run the pipe.
    36  func (Pipe) Run(ctx *context.Context) error {
    37  	for _, build := range ctx.Config.Builds {
    38  		if build.Skip {
    39  			log.WithField("id", build.ID).Info("skip is set")
    40  			continue
    41  		}
    42  		log.WithField("build", build).Debug("building")
    43  		if err := runPipeOnBuild(ctx, build); err != nil {
    44  			return err
    45  		}
    46  	}
    47  	return nil
    48  }
    49  
    50  // Default sets the pipe defaults.
    51  func (Pipe) Default(ctx *context.Context) error {
    52  	var ids = ids.New("builds")
    53  	for i, build := range ctx.Config.Builds {
    54  		build, err := buildWithDefaults(ctx, build)
    55  		if err != nil {
    56  			return err
    57  		}
    58  		ctx.Config.Builds[i] = build
    59  		ids.Inc(ctx.Config.Builds[i].ID)
    60  	}
    61  	if len(ctx.Config.Builds) == 0 {
    62  		build, err := buildWithDefaults(ctx, ctx.Config.SingleBuild)
    63  		if err != nil {
    64  			return err
    65  		}
    66  		ctx.Config.Builds = []config.Build{build}
    67  	}
    68  	return ids.Validate()
    69  }
    70  
    71  func buildWithDefaults(ctx *context.Context, build config.Build) (config.Build, error) {
    72  	if build.Lang == "" {
    73  		build.Lang = "go"
    74  	}
    75  	if build.Binary == "" {
    76  		build.Binary = ctx.Config.ProjectName
    77  	}
    78  	if build.ID == "" {
    79  		build.ID = ctx.Config.ProjectName
    80  	}
    81  	for k, v := range build.Env {
    82  		build.Env[k] = os.ExpandEnv(v)
    83  	}
    84  	return builders.For(build.Lang).WithDefaults(build)
    85  }
    86  
    87  func runPipeOnBuild(ctx *context.Context, build config.Build) error {
    88  	var g = semerrgroup.New(ctx.Parallelism)
    89  	for _, target := range build.Targets {
    90  		target := target
    91  		build := build
    92  		g.Go(func() error {
    93  			opts, err := buildOptionsForTarget(ctx, build, target)
    94  			if err != nil {
    95  				return err
    96  			}
    97  
    98  			if err := runHook(ctx, *opts, build.Env, build.Hooks.Pre); err != nil {
    99  				return fmt.Errorf("pre hook failed: %w", err)
   100  			}
   101  			if err := doBuild(ctx, build, *opts); err != nil {
   102  				return err
   103  			}
   104  			if !ctx.SkipPostBuildHooks {
   105  				if err := runHook(ctx, *opts, build.Env, build.Hooks.Post); err != nil {
   106  					return fmt.Errorf("post hook failed: %w", err)
   107  				}
   108  			}
   109  			return nil
   110  		})
   111  	}
   112  
   113  	return g.Wait()
   114  }
   115  
   116  func runHook(ctx *context.Context, opts builders.Options, buildEnv []string, hooks config.BuildHooks) error {
   117  	if len(hooks) == 0 {
   118  		return nil
   119  	}
   120  
   121  	for _, hook := range hooks {
   122  		var env []string
   123  
   124  		env = append(env, ctx.Env.Strings()...)
   125  		env = append(env, buildEnv...)
   126  
   127  		for _, rawEnv := range hook.Env {
   128  			e, err := tmpl.New(ctx).WithBuildOptions(opts).Apply(rawEnv)
   129  			if err != nil {
   130  				return err
   131  			}
   132  			env = append(env, e)
   133  		}
   134  
   135  		dir, err := tmpl.New(ctx).WithBuildOptions(opts).Apply(hook.Dir)
   136  		if err != nil {
   137  			return err
   138  		}
   139  
   140  		sh, err := tmpl.New(ctx).WithBuildOptions(opts).
   141  			WithEnvS(env).
   142  			Apply(hook.Cmd)
   143  		if err != nil {
   144  			return err
   145  		}
   146  
   147  		log.WithField("hook", sh).Info("running hook")
   148  		cmd, err := shellwords.Parse(sh)
   149  		if err != nil {
   150  			return err
   151  		}
   152  
   153  		if err := run(ctx, dir, cmd, env); err != nil {
   154  			return err
   155  		}
   156  	}
   157  
   158  	return nil
   159  }
   160  
   161  func doBuild(ctx *context.Context, build config.Build, opts builders.Options) error {
   162  	return builders.For(build.Lang).Build(ctx, build, opts)
   163  }
   164  
   165  func buildOptionsForTarget(ctx *context.Context, build config.Build, target string) (*builders.Options, error) {
   166  	var ext = extFor(target, build.Flags)
   167  
   168  	binary, err := tmpl.New(ctx).Apply(build.Binary)
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  
   173  	build.Binary = binary
   174  	var name = build.Binary + ext
   175  	path, err := filepath.Abs(
   176  		filepath.Join(
   177  			ctx.Config.Dist,
   178  			fmt.Sprintf("%s_%s", build.ID, target),
   179  			name,
   180  		),
   181  	)
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  
   186  	var goos string
   187  	var goarch string
   188  
   189  	if strings.Contains(target, "_") {
   190  		goos = strings.Split(target, "_")[0]
   191  		goarch = strings.Split(target, "_")[1]
   192  	}
   193  
   194  	log.WithField("binary", path).Info("building")
   195  	return &builders.Options{
   196  		Target: target,
   197  		Name:   name,
   198  		Path:   path,
   199  		Ext:    ext,
   200  		Os:     goos,
   201  		Arch:   goarch,
   202  	}, nil
   203  }
   204  
   205  func extFor(target string, flags config.FlagArray) string {
   206  	if strings.Contains(target, "windows") {
   207  		for _, s := range flags {
   208  			if s == "-buildmode=c-shared" {
   209  				return ".dll"
   210  			}
   211  			if s == "-buildmode=c-archive" {
   212  				return ".lib"
   213  			}
   214  		}
   215  		return ".exe"
   216  	}
   217  	if target == "js_wasm" {
   218  		return ".wasm"
   219  	}
   220  	return ""
   221  }
   222  
   223  func run(ctx *context.Context, dir string, command, env []string) error {
   224  	/* #nosec */
   225  	var cmd = exec.CommandContext(ctx, command[0], command[1:]...)
   226  	var entry = log.WithField("cmd", command)
   227  	cmd.Env = env
   228  	var b bytes.Buffer
   229  	cmd.Stderr = io.MultiWriter(logext.NewErrWriter(entry), &b)
   230  	cmd.Stdout = io.MultiWriter(logext.NewWriter(entry), &b)
   231  	if dir != "" {
   232  		cmd.Dir = dir
   233  	}
   234  	entry.WithField("env", env).Debug("running")
   235  	if err := cmd.Run(); err != nil {
   236  		entry.WithError(err).Debug("failed")
   237  		return fmt.Errorf("%q: %w", b.String(), err)
   238  	}
   239  	return nil
   240  }