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 }