gitee.com/mirrors_opencollective/goreleaser@v0.45.0/pipeline/fpm/fpm.go (about)

     1  // Package fpm implements the Pipe interface providing FPM bindings.
     2  package fpm
     3  
     4  import (
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"os/exec"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	"github.com/apex/log"
    13  	"github.com/pkg/errors"
    14  	"golang.org/x/sync/errgroup"
    15  
    16  	"github.com/goreleaser/goreleaser/context"
    17  	"github.com/goreleaser/goreleaser/internal/artifact"
    18  	"github.com/goreleaser/goreleaser/internal/filenametemplate"
    19  	"github.com/goreleaser/goreleaser/internal/linux"
    20  	"github.com/goreleaser/goreleaser/pipeline"
    21  )
    22  
    23  // ErrNoFPM is shown when fpm cannot be found in $PATH
    24  var ErrNoFPM = errors.New("fpm not present in $PATH")
    25  
    26  const (
    27  	defaultNameTemplate = "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
    28  	// path to gnu-tar on macOS when installed with homebrew
    29  	gnuTarPath = "/usr/local/opt/gnu-tar/libexec/gnubin"
    30  )
    31  
    32  // Pipe for fpm packaging
    33  type Pipe struct{}
    34  
    35  func (Pipe) String() string {
    36  	return "creating Linux packages with fpm"
    37  }
    38  
    39  // Default sets the pipe defaults
    40  func (Pipe) Default(ctx *context.Context) error {
    41  	var fpm = &ctx.Config.FPM
    42  	if fpm.Bindir == "" {
    43  		fpm.Bindir = "/usr/local/bin"
    44  	}
    45  	if fpm.NameTemplate == "" {
    46  		fpm.NameTemplate = defaultNameTemplate
    47  	}
    48  	return nil
    49  }
    50  
    51  // Run the pipe
    52  func (Pipe) Run(ctx *context.Context) error {
    53  	if len(ctx.Config.FPM.Formats) == 0 {
    54  		return pipeline.Skip("no output formats configured")
    55  	}
    56  	_, err := exec.LookPath("fpm")
    57  	if err != nil {
    58  		return ErrNoFPM
    59  	}
    60  	return doRun(ctx)
    61  }
    62  
    63  func doRun(ctx *context.Context) error {
    64  	var g errgroup.Group
    65  	sem := make(chan bool, ctx.Parallelism)
    66  	for _, format := range ctx.Config.FPM.Formats {
    67  		for platform, artifacts := range ctx.Artifacts.Filter(
    68  			artifact.And(
    69  				artifact.ByType(artifact.Binary),
    70  				artifact.ByGoos("linux"),
    71  			),
    72  		).GroupByPlatform() {
    73  			sem <- true
    74  			format := format
    75  			arch := linux.Arch(platform)
    76  			artifacts := artifacts
    77  			g.Go(func() error {
    78  				defer func() {
    79  					<-sem
    80  				}()
    81  				return create(ctx, format, arch, artifacts)
    82  			})
    83  		}
    84  	}
    85  	return g.Wait()
    86  }
    87  
    88  func create(ctx *context.Context, format, arch string, binaries []artifact.Artifact) error {
    89  	name, err := filenametemplate.Apply(
    90  		ctx.Config.FPM.NameTemplate,
    91  		filenametemplate.NewFields(ctx, ctx.Config.FPM.Replacements, binaries...),
    92  	)
    93  	if err != nil {
    94  		return err
    95  	}
    96  	var path = filepath.Join(ctx.Config.Dist, name)
    97  	var file = path + "." + format
    98  	var log = log.WithField("format", format).WithField("arch", arch)
    99  	dir, err := ioutil.TempDir("", "fpm")
   100  	if err != nil {
   101  		return err
   102  	}
   103  	log.WithField("file", file).WithField("workdir", dir).Info("creating fpm archive")
   104  	var options = basicOptions(ctx, dir, format, arch, file)
   105  
   106  	for _, binary := range binaries {
   107  		// This basically tells fpm to put the binary in the bindir, e.g. /usr/local/bin
   108  		// binary=/usr/local/bin/binary
   109  		log.WithField("path", binary.Path).
   110  			WithField("name", binary.Name).
   111  			Debug("added binary to fpm package")
   112  		options = append(options, fmt.Sprintf(
   113  			"%s=%s",
   114  			binary.Path,
   115  			filepath.Join(ctx.Config.FPM.Bindir, binary.Name),
   116  		))
   117  	}
   118  
   119  	for src, dest := range ctx.Config.FPM.Files {
   120  		log.WithField("src", src).
   121  			WithField("dest", dest).
   122  			Debug("added an extra file to the fpm package")
   123  		options = append(options, fmt.Sprintf(
   124  			"%s=%s",
   125  			src,
   126  			dest,
   127  		))
   128  	}
   129  
   130  	log.WithField("args", options).Debug("creating fpm package")
   131  	if out, err := cmd(ctx, options).CombinedOutput(); err != nil {
   132  		return errors.Wrap(err, string(out))
   133  	}
   134  	ctx.Artifacts.Add(artifact.Artifact{
   135  		Type:   artifact.LinuxPackage,
   136  		Name:   name + "." + format,
   137  		Path:   file,
   138  		Goos:   binaries[0].Goos,
   139  		Goarch: binaries[0].Goarch,
   140  		Goarm:  binaries[0].Goarm,
   141  	})
   142  	return nil
   143  }
   144  
   145  func cmd(ctx *context.Context, options []string) *exec.Cmd {
   146  	/* #nosec */
   147  	var cmd = exec.CommandContext(ctx, "fpm", options...)
   148  	cmd.Env = []string{fmt.Sprintf("PATH=%s:%s", gnuTarPath, os.Getenv("PATH"))}
   149  	for _, env := range os.Environ() {
   150  		if strings.HasPrefix(env, "PATH=") {
   151  			continue
   152  		}
   153  		cmd.Env = append(cmd.Env, env)
   154  	}
   155  	return cmd
   156  }
   157  
   158  func basicOptions(ctx *context.Context, workdir, format, arch, file string) []string {
   159  	var options = []string{
   160  		"--input-type", "dir",
   161  		"--output-type", format,
   162  		"--name", ctx.Config.ProjectName,
   163  		"--version", ctx.Version,
   164  		"--architecture", arch,
   165  		"--package", file,
   166  		"--force",
   167  		"--workdir", workdir,
   168  	}
   169  
   170  	if ctx.Debug {
   171  		options = append(options, "--debug")
   172  	}
   173  
   174  	if ctx.Config.FPM.Vendor != "" {
   175  		options = append(options, "--vendor", ctx.Config.FPM.Vendor)
   176  	}
   177  	if ctx.Config.FPM.Homepage != "" {
   178  		options = append(options, "--url", ctx.Config.FPM.Homepage)
   179  	}
   180  	if ctx.Config.FPM.Maintainer != "" {
   181  		options = append(options, "--maintainer", ctx.Config.FPM.Maintainer)
   182  	}
   183  	if ctx.Config.FPM.Description != "" {
   184  		options = append(options, "--description", ctx.Config.FPM.Description)
   185  	}
   186  	if ctx.Config.FPM.License != "" {
   187  		options = append(options, "--license", ctx.Config.FPM.License)
   188  	}
   189  	for _, dep := range ctx.Config.FPM.Dependencies {
   190  		options = append(options, "--depends", dep)
   191  	}
   192  	for _, conflict := range ctx.Config.FPM.Conflicts {
   193  		options = append(options, "--conflicts", conflict)
   194  	}
   195  
   196  	// FPM requires --rpm-os=linux if your rpm target is linux
   197  	if format == "rpm" {
   198  		options = append(options, "--rpm-os", "linux")
   199  	}
   200  	return options
   201  }