1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-03-17 20:47:50 +02:00

Merge pull request #109 from goreleaser/fpm

added fpm support
This commit is contained in:
Carlos Alexandro Becker 2017-01-30 14:36:38 -02:00 committed by GitHub
commit 7c75b1f4c4
12 changed files with 182 additions and 18 deletions

View File

@ -4,7 +4,6 @@ install: make setup
script:
- make ci
after_success:
- git status
- test -n "$TRAVIS_TAG" && go run main.go
- test -n "$TRAVIS_TAG" && gem install fpm && go run main.go
notifications:
email: false

View File

@ -110,9 +110,14 @@ GoReleaser uses the latest [Git tag](https://git-scm.com/book/en/v2/Git-Basics-T
Create a tag:
```console
$ git tag -a v0.1 -m "First release"
$ git tag -a v0.1.0 -m "First release"
```
**Note**: we recommend the use of [semantic versioning](http://semver.org/). We
are not enforcing it though. We do remove the `v` prefix and then enforce
that the next character is a number. So, `v0.1.0` and `0.1.0` are virtually the
same and are both accepted, while `version0.1.0` is not.
Now you can run GoReleaser at the root of your repository:
```console
@ -285,6 +290,28 @@ class Program < Formula
end
```
### FPM build customization
GoReleaser can be wired to [fpm]() to generate `.deb`, `.rpm` and other archives. Check it's
[wiki](https://github.com/jordansissel/fpm/wiki) for more info.
[fpm]: https://github.com/jordansissel/fpm
```yml
# goreleaser.yml
fpm:
# Formats to generate as output
formats:
- deb
- rpm
# Dependencies of your package
dependencies:
- git
```
Note that GoReleaser will not install `fpm` nor any of it's dependencies for you.
## Integration with CI
You may want to wire this to auto-deploy your new tags on [Travis](https://travis-ci.org), for example:

View File

@ -43,12 +43,19 @@ type Release struct {
Repo string
}
// FPM config
type FPM struct {
Formats []string
Dependencies []string
}
// Project includes all project configuration
type Project struct {
Release Release
Brew Homebrew
Build Build
Archive Archive
FPM FPM `yaml:"fpm"`
}
// Load config file

View File

@ -22,6 +22,7 @@ type Context struct {
ReleaseRepo Repo
BrewRepo Repo
Archives map[string]string
Version string
}
// New context

View File

@ -3,3 +3,8 @@ brew:
folder: Formula
dependencies:
- git
fpm:
formats:
- deb
dependencies:
- git

View File

@ -12,6 +12,7 @@ import (
"github.com/goreleaser/goreleaser/pipeline/build"
"github.com/goreleaser/goreleaser/pipeline/defaults"
"github.com/goreleaser/goreleaser/pipeline/env"
"github.com/goreleaser/goreleaser/pipeline/fpm"
"github.com/goreleaser/goreleaser/pipeline/git"
"github.com/goreleaser/goreleaser/pipeline/release"
"github.com/goreleaser/goreleaser/pipeline/repos"
@ -33,6 +34,7 @@ var pipes = []pipeline.Pipe{
// real work
build.Pipe{},
archive.Pipe{},
fpm.Pipe{},
release.Pipe{},
brew.Pipe{},
}

View File

@ -22,7 +22,7 @@ const formula = `class {{ .Name }} < Formula
desc "{{ .Desc }}"
homepage "{{ .Homepage }}"
url "https://github.com/{{ .Repo }}/releases/download/{{ .Tag }}/{{ .File }}.{{ .Format }}"
version "{{ .Tag }}"
version "{{ .Version }}"
sha256 "{{ .SHA256 }}"
{{- if .Dependencies }}
@ -50,6 +50,7 @@ type templateData struct {
Homepage string
Repo string
Tag string
Version string
BinaryName string
Caveats string
File string
@ -156,6 +157,7 @@ func dataFor(ctx *context.Context, client *github.Client) (result templateData,
Homepage: homepage,
Repo: ctx.Config.Release.Repo,
Tag: ctx.Git.CurrentTag,
Version: ctx.Version,
BinaryName: ctx.Config.Build.BinaryName,
Caveats: ctx.Config.Brew.Caveats,
File: file,

View File

@ -25,6 +25,7 @@ var defaultTemplateData = templateData{
Name: "Test",
Repo: "caarlos0/test",
Tag: "v0.1.3",
Version: "0.1.3",
File: "test_Darwin_x86_64",
SHA256: "1633f61598ab0791e213135923624eb342196b3494909c91899bcd0560f84c68",
Format: "tar.gz",
@ -36,7 +37,7 @@ func assertDefaultTemplateData(t *testing.T, formulae string) {
assert.Contains(formulae, "homepage \"https://google.com\"")
assert.Contains(formulae, "url \"https://github.com/caarlos0/test/releases/download/v0.1.3/test_Darwin_x86_64.tar.gz\"")
assert.Contains(formulae, "sha256 \"1633f61598ab0791e213135923624eb342196b3494909c91899bcd0560f84c68\"")
assert.Contains(formulae, "version \"v0.1.3\"")
assert.Contains(formulae, "version \"0.1.3\"")
assert.Contains(formulae, "bin.install \"test\"")
}

View File

@ -1,7 +1,6 @@
package build
import (
"bytes"
"errors"
"log"
"os"
@ -41,7 +40,7 @@ func (Pipe) Run(ctx *context.Context) error {
}
func build(name, goos, goarch string, ctx *context.Context) error {
ldflags := ctx.Config.Build.Ldflags + " -X main.version=" + ctx.Git.CurrentTag
ldflags := ctx.Config.Build.Ldflags + " -X main.version=" + ctx.Version
output := "dist/" + name + "/" + ctx.Config.Build.BinaryName + extFor(goos)
log.Println("Building", output)
if ctx.Config.Build.Hooks.Pre != "" {
@ -67,11 +66,8 @@ func run(goos, goarch string, command []string) error {
cmd := exec.Command(command[0], command[1:]...)
cmd.Env = append(cmd.Env, os.Environ()...)
cmd.Env = append(cmd.Env, "GOOS="+goos, "GOARCH="+goarch)
var stdout bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stdout
if err := cmd.Run(); err != nil {
return errors.New(stdout.String())
if out, err := cmd.CombinedOutput(); err != nil {
return errors.New(string(out))
}
return nil
}

88
pipeline/fpm/fpm.go Normal file
View File

@ -0,0 +1,88 @@
package fpm
import (
"errors"
"log"
"os/exec"
"path/filepath"
"github.com/goreleaser/goreleaser/context"
"golang.org/x/sync/errgroup"
)
var goarchToUnix = map[string]string{
"386": "i386",
"amd64": "x86_64",
}
// ErrNoFPM is shown when fpm cannot be found in $PATH
var ErrNoFPM = errors.New("fpm not present in $PATH")
// Pipe for fpm packaging
type Pipe struct{}
// Description of the pipe
func (Pipe) Description() string {
return "Creating Linux packages with fpm"
}
// Run the pipe
func (Pipe) Run(ctx *context.Context) error {
if len(ctx.Config.FPM.Formats) == 0 {
log.Println("No output formats configured, skipping")
return nil
}
_, err := exec.LookPath("fpm")
if err != nil {
return ErrNoFPM
}
var g errgroup.Group
for _, format := range ctx.Config.FPM.Formats {
for _, goarch := range ctx.Config.Build.Goarch {
if ctx.Archives["linux"+goarch] == "" {
continue
}
archive := ctx.Archives["linux"+goarch]
g.Go(func() error {
return create(
ctx,
format,
archive,
goarchToUnix[goarch],
)
})
}
}
return g.Wait()
}
func create(ctx *context.Context, format, archive, arch string) error {
var path = filepath.Join("dist", archive)
var file = path + ".deb"
var name = ctx.Config.Build.BinaryName
log.Println("Creating", file)
var options = []string{
"-s", "dir",
"-t", format,
"-n", name,
"-v", ctx.Version,
"-a", arch,
"-C", path,
"-p", file,
"--force",
}
for _, dep := range ctx.Config.FPM.Dependencies {
options = append(options, "-d", dep)
}
// This basically tells fpm to put the binary in the /usr/local/bin
// binary=/usr/local/bin/binary
options = append(options, name+"="+filepath.Join("/usr/local/bin", name))
cmd := exec.Command("fpm", options...)
if out, err := cmd.CombinedOutput(); err != nil {
return errors.New(string(out))
}
return nil
}

View File

@ -1,6 +1,20 @@
package git
import "github.com/goreleaser/goreleaser/context"
import (
"regexp"
"strings"
"github.com/goreleaser/goreleaser/context"
)
// ErrInvalidVersionFormat is return when the version isnt in a valid format
type ErrInvalidVersionFormat struct {
version string
}
func (e ErrInvalidVersionFormat) Error() string {
return e.version + " is not in a valid version format"
}
// Pipe for brew deployment
type Pipe struct{}
@ -30,5 +44,10 @@ func (Pipe) Run(ctx *context.Context) (err error) {
PreviousTag: previous,
Diff: log,
}
// removes usual `v` prefix
ctx.Version = strings.TrimPrefix(tag, "v")
if matches, err := regexp.MatchString("^[0-9.]+", ctx.Version); !matches || err != nil {
return ErrInvalidVersionFormat{ctx.Version}
}
return
}

View File

@ -4,6 +4,7 @@ import (
"log"
"os"
"os/exec"
"path/filepath"
"github.com/google/go-github/github"
"github.com/goreleaser/goreleaser/clients"
@ -31,9 +32,14 @@ func (Pipe) Run(ctx *context.Context) error {
for _, archive := range ctx.Archives {
archive := archive
g.Go(func() error {
return upload(client, *r.ID, archive, ctx)
return upload(ctx, client, *r.ID, archive, ctx.Config.Archive.Format)
})
for _, format := range ctx.Config.FPM.Formats {
format := format
g.Go(func() error {
return upload(ctx, client, *r.ID, archive, format)
})
}
}
return g.Wait()
}
@ -67,9 +73,20 @@ func description(diff string) string {
return result + "\nBuilt with " + string(bts)
}
func upload(client *github.Client, releaseID int, archive string, ctx *context.Context) error {
archive = archive + "." + ctx.Config.Archive.Format
file, err := os.Open("dist/" + archive)
func upload(ctx *context.Context, client *github.Client, releaseID int, archive, format string) error {
archive = archive + "." + format
var path = filepath.Join("dist", archive)
// In case the file doesn't exist, we just ignore it.
// We do this because we can get invalid combinations of archive+format here,
// like darwinamd64 + deb or something like that.
// It's assumed that the archive pipe would fail the entire thing in case it fails to
// generate some archive, as well fpm pipe is expected to fail if something wrong happens.
// So, here, we just assume IsNotExist as an expected error.
// TODO: maybe add a list of files to upload in the context so we don't have to do this.
if _, err := os.Stat(path); os.IsNotExist(err) {
return nil
}
file, err := os.Open(path)
if err != nil {
return err
}