1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2024-12-31 01:53:50 +02:00
This commit is contained in:
Mohamed Gharsallah 2017-01-21 15:05:53 +01:00
commit 354ab0683e
20 changed files with 259 additions and 96 deletions

View File

@ -5,7 +5,7 @@ conduct](/CODE_OF_CONDUCT.md).
## Setup your machine
`releaser` is written in [Go](https://golang.org/).
`goreleaser` is written in [Go](https://golang.org/).
Prerequisites are:
@ -13,11 +13,11 @@ Prerequisites are:
* `make`
* [Go 1.7+](http://golang.org/doc/install)
Clone `releaser` from source:
Clone `goreleaser` from source into `$GOPATH`:
```sh
$ git clone https://github.com/goreleaser/goreleaser.git
$ cd releaser
$ go get github.com/goreleaser/goreleaser
$ cd $GOPATH/src/github.com/goreleaser/goreleaser
```
Install the build and lint dependencies:
@ -50,5 +50,5 @@ Which runs all the linters and tests.
## Submit a pull request
Push your branch to your `releaser` fork and open a pull request against the
Push your branch to your `goreleaser` fork and open a pull request against the
master branch.

View File

@ -8,9 +8,10 @@ import (
// Homebrew contains the brew section
type Homebrew struct {
Repo string
Folder string
Caveats string
Repo string
Folder string
Caveats string
Dependencies []string
}
// Build contains the build configuration section

View File

@ -16,18 +16,18 @@ type Repo struct {
// Context carries along some data through the pipes
type Context struct {
Config *config.Project
Token *string
Git *GitInfo
ReleaseRepo *Repo
BrewRepo *Repo
Config config.Project
Token string
Git GitInfo
ReleaseRepo Repo
BrewRepo Repo
Archives map[string]string
}
// New context
func New(config config.Project) *Context {
return &Context{
Config: &config,
Config: config,
Archives: map[string]string{},
}
}

View File

@ -81,3 +81,14 @@ brew:
# Caveats for the user of your binary.
# Default is empty.
caveats: "How to use this binary"
# Dependencies of your formula.
# For more info, check https://github.com/Homebrew/brew/blob/master/docs/Formula-Cookbook.md
dependencies:
- pkg-config
- jpeg
- boost
- readline
- gtk+
- x11

View File

@ -1,11 +1,8 @@
brew:
repo: goreleaser/homebrew-tap
folder: Formula
build:
goos:
- windows
- darwin
- linux
dependencies:
- git
archive:
files:
- README.md

37
main.go
View File

@ -15,6 +15,7 @@ import (
"github.com/goreleaser/goreleaser/pipeline/git"
"github.com/goreleaser/goreleaser/pipeline/release"
"github.com/goreleaser/goreleaser/pipeline/repos"
"github.com/goreleaser/goreleaser/pipeline/source"
"github.com/urfave/cli"
)
@ -27,6 +28,8 @@ var pipes = []pipeline.Pipe{
git.Pipe{},
repos.Pipe{},
&source.Pipe{},
// real work
build.Pipe{},
archive.Pipe{},
@ -36,7 +39,7 @@ var pipes = []pipeline.Pipe{
func main() {
var app = cli.NewApp()
app.Name = "release"
app.Name = "goreleaser"
app.Version = version
app.Usage = "Deliver Go binaries as fast and easily as possible"
app.Flags = []cli.Flag{
@ -46,27 +49,39 @@ func main() {
Value: "goreleaser.yml",
},
}
app.Action = func(c *cli.Context) (err error) {
app.Action = func(c *cli.Context) error {
var file = c.String("config")
cfg, err := config.Load(file)
// Allow failing to load the config file if file is not explicitly specified
if err != nil && c.IsSet("config") {
return cli.NewExitError(err.Error(), 1)
}
context := context.New(cfg)
ctx := context.New(cfg)
log.SetFlags(0)
for _, pipe := range pipes {
log.Println(pipe.Description())
log.SetPrefix(" -> ")
if err := pipe.Run(context); err != nil {
return cli.NewExitError(err.Error(), 1)
}
log.SetPrefix("")
if err := runPipeline(ctx); err != nil {
return cli.NewExitError(err.Error(), 1)
}
log.Println("Done!")
return
return nil
}
if err := app.Run(os.Args); err != nil {
log.Fatalln(err)
}
}
func runPipeline(ctx *context.Context) error {
for _, pipe := range pipes {
log.Println(pipe.Description())
log.SetPrefix(" -> ")
err := pipe.Run(ctx)
log.SetPrefix("")
cleaner, ok := pipe.(pipeline.Cleaner)
if ok {
defer cleaner.Clean(ctx)
}
if err != nil {
return err
}
}
return nil
}

View File

@ -17,7 +17,7 @@ type Pipe struct{}
// Description of the pipe
func (Pipe) Description() string {
return "Creating archives..."
return "Creating archives"
}
// Run the pipe
@ -41,7 +41,7 @@ type Archive interface {
func create(name string, ctx *context.Context) error {
folder := filepath.Join("dist", name)
file, err := os.Create(folder + "." + ctx.Config.Archive.Format)
log.Println("Creating", file.Name(), "...")
log.Println("Creating", file.Name())
if err != nil {
return err
}

View File

@ -2,6 +2,7 @@ package brew
import (
"bytes"
"errors"
"log"
"path/filepath"
"strings"
@ -13,13 +14,23 @@ import (
"github.com/goreleaser/goreleaser/sha256sum"
)
const formulae = `class {{ .Name }} < Formula
// ErrNoDarwin64Build when there is no build for darwin_amd64 (goos doesn't
// contain darwin and/or goarch doesn't contain amd64)
var ErrNoDarwin64Build = errors.New("brew tap requires a darwin amd64 build")
const formula = `class {{ .Name }} < Formula
desc "{{ .Desc }}"
homepage "{{ .Homepage }}"
url "https://github.com/{{ .Repo }}/releases/download/{{ .Tag }}/{{ .File }}.{{ .Format }}"
version "{{ .Tag }}"
sha256 "{{ .SHA256 }}"
{{- if .Dependencies }}
{{ range $index, $element := .Dependencies }}
depends_on "{{ . }}"
{{- end }}
{{- end }}
def install
bin.install "{{ .BinaryName }}"
end
@ -34,7 +45,17 @@ end
`
type templateData struct {
Name, Desc, Homepage, Repo, Tag, BinaryName, Caveats, File, Format, SHA256 string
Name string
Desc string
Homepage string
Repo string
Tag string
BinaryName string
Caveats string
File string
Format string
SHA256 string
Dependencies []string
}
// Pipe for brew deployment
@ -42,7 +63,7 @@ type Pipe struct{}
// Description of the pipe
func (Pipe) Description() string {
return "Creating homebrew formulae..."
return "Creating homebrew formula"
}
// Run the pipe
@ -50,13 +71,13 @@ func (Pipe) Run(ctx *context.Context) error {
if ctx.Config.Brew.Repo == "" {
return nil
}
client := clients.GitHub(*ctx.Token)
client := clients.GitHub(ctx.Token)
path := filepath.Join(
ctx.Config.Brew.Folder, ctx.Config.Build.BinaryName+".rb",
)
log.Println("Updating", path, "on", ctx.Config.Brew.Repo, "...")
out, err := buildFormulae(ctx, client)
log.Println("Pushing", path, "to", ctx.Config.Brew.Repo)
out, err := buildFormula(ctx, client)
if err != nil {
return err
}
@ -86,17 +107,17 @@ func (Pipe) Run(ctx *context.Context) error {
return err
}
func buildFormulae(ctx *context.Context, client *github.Client) (bytes.Buffer, error) {
func buildFormula(ctx *context.Context, client *github.Client) (bytes.Buffer, error) {
data, err := dataFor(ctx, client)
if err != nil {
return bytes.Buffer{}, err
}
return doBuildFormulae(data)
return doBuildFormula(data)
}
func doBuildFormulae(data templateData) (bytes.Buffer, error) {
func doBuildFormula(data templateData) (bytes.Buffer, error) {
var out bytes.Buffer
tmpl, err := template.New(data.BinaryName).Parse(formulae)
tmpl, err := template.New(data.BinaryName).Parse(formula)
if err != nil {
return out, err
}
@ -112,6 +133,9 @@ func dataFor(ctx *context.Context, client *github.Client) (result templateData,
return
}
file := ctx.Archives["darwinamd64"]
if file == "" {
return result, ErrNoDarwin64Build
}
sum, err := sha256sum.For("dist/" + file + "." + ctx.Config.Archive.Format)
if err != nil {
return
@ -127,16 +151,17 @@ func dataFor(ctx *context.Context, client *github.Client) (result templateData,
description = *rep.Description
}
return templateData{
Name: formulaNameFor(ctx.Config.Build.BinaryName),
Desc: description,
Homepage: homepage,
Repo: ctx.Config.Release.Repo,
Tag: ctx.Git.CurrentTag,
BinaryName: ctx.Config.Build.BinaryName,
Caveats: ctx.Config.Brew.Caveats,
File: file,
Format: ctx.Config.Archive.Format,
SHA256: sum,
Name: formulaNameFor(ctx.Config.Build.BinaryName),
Desc: description,
Homepage: homepage,
Repo: ctx.Config.Release.Repo,
Tag: ctx.Git.CurrentTag,
BinaryName: ctx.Config.Build.BinaryName,
Caveats: ctx.Config.Brew.Caveats,
File: file,
Format: ctx.Config.Archive.Format,
SHA256: sum,
Dependencies: ctx.Config.Brew.Dependencies,
}, err
}

View File

@ -38,26 +38,29 @@ func assertDefaultTemplateData(t *testing.T, formulae string) {
assert.Contains(formulae, "sha256 \"1633f61598ab0791e213135923624eb342196b3494909c91899bcd0560f84c68\"")
assert.Contains(formulae, "version \"v0.1.3\"")
assert.Contains(formulae, "bin.install \"test\"")
}
func TestFullFormulae(t *testing.T) {
assert := assert.New(t)
data := defaultTemplateData
data.Caveats = "Here are some caveats"
out, err := doBuildFormulae(data)
data.Dependencies = []string{"gtk", "git"}
out, err := doBuildFormula(data)
assert.NoError(err)
formulae := out.String()
assertDefaultTemplateData(t, formulae)
assert.Contains(formulae, "def caveats")
assert.Contains(formulae, "Here are some caveats")
assert.Contains(formulae, "depends_on \"gtk\"")
assert.Contains(formulae, "depends_on \"git\"")
}
func TestFormulaeNoCaveats(t *testing.T) {
assert := assert.New(t)
out, err := doBuildFormulae(defaultTemplateData)
out, err := doBuildFormula(defaultTemplateData)
assert.NoError(err)
formulae := out.String()
assertDefaultTemplateData(t, formulae)
assert.NotContains(formulae, "def caveats")
assert.NotContains(formulae, "depends_on")
}

View File

@ -16,7 +16,7 @@ type Pipe struct{}
// Description of the pipe
func (Pipe) Description() string {
return "Building..."
return "Building binaries"
}
// Run the pipe
@ -42,7 +42,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
output := "dist/" + name + "/" + ctx.Config.Build.BinaryName + extFor(goos)
log.Println("Building", output, "...")
log.Println("Building", output)
cmd := exec.Command(
"go",
"build",

View File

@ -19,7 +19,7 @@ func TestExtOthers(t *testing.T) {
func TestNameFor(t *testing.T) {
assert := assert.New(t)
var config = &config.Project{
var config = config.Project{
Archive: config.Archive{
NameTemplate: "{{.BinaryName}}_{{.Os}}_{{.Arch}}_{{.Version}}",
Replacements: map[string]string{
@ -33,7 +33,7 @@ func TestNameFor(t *testing.T) {
}
var ctx = &context.Context{
Config: config,
Git: &context.GitInfo{
Git: context.GitInfo{
CurrentTag: "v1.2.3",
},
}

10
pipeline/cleaner.go Normal file
View File

@ -0,0 +1,10 @@
package pipeline
import "github.com/goreleaser/goreleaser/context"
// Cleaner is an interface that a pipe can implement
// to cleanup after all pipes ran.
type Cleaner interface {
// Clean is called after pipeline is done - even if a pipe returned an error.
Clean(*context.Context)
}

View File

@ -15,7 +15,7 @@ type Pipe struct{}
// Description of the pipe
func (Pipe) Description() string {
return "Setting defaults..."
return "Setting defaults"
}
// Run the pipe

View File

@ -11,45 +11,43 @@ import (
func TestFillBasicData(t *testing.T) {
assert := assert.New(t)
var config = &config.Project{}
var ctx = &context.Context{
Config: config,
Config: config.Project{},
}
assert.NoError(Pipe{}.Run(ctx))
assert.Equal("goreleaser/goreleaser", config.Release.Repo)
assert.Equal("goreleaser", config.Build.BinaryName)
assert.Equal("main.go", config.Build.Main)
assert.Equal("tar.gz", config.Archive.Format)
assert.Contains(config.Build.Goos, "darwin")
assert.Contains(config.Build.Goos, "linux")
assert.Contains(config.Build.Goarch, "386")
assert.Contains(config.Build.Goarch, "amd64")
assert.Equal("goreleaser/goreleaser", ctx.Config.Release.Repo)
assert.Equal("goreleaser", ctx.Config.Build.BinaryName)
assert.Equal("main.go", ctx.Config.Build.Main)
assert.Equal("tar.gz", ctx.Config.Archive.Format)
assert.Contains(ctx.Config.Build.Goos, "darwin")
assert.Contains(ctx.Config.Build.Goos, "linux")
assert.Contains(ctx.Config.Build.Goarch, "386")
assert.Contains(ctx.Config.Build.Goarch, "amd64")
assert.NotEmpty(
config.Archive.Replacements,
config.Archive.NameTemplate,
config.Build.Ldflags,
config.Archive.Files,
ctx.Config.Archive.Replacements,
ctx.Config.Archive.NameTemplate,
ctx.Config.Build.Ldflags,
ctx.Config.Archive.Files,
)
}
func TestFilesFilled(t *testing.T) {
assert := assert.New(t)
var config = &config.Project{
Archive: config.Archive{
Files: []string{
"README.md",
var ctx = &context.Context{
Config: config.Project{
Archive: config.Archive{
Files: []string{
"README.md",
},
},
},
}
var ctx = &context.Context{
Config: config,
}
assert.NoError(Pipe{}.Run(ctx))
assert.Len(config.Archive.Files, 1)
assert.Len(ctx.Config.Archive.Files, 1)
}
func TestAcceptFiles(t *testing.T) {

7
pipeline/env/env.go vendored
View File

@ -15,15 +15,14 @@ type Pipe struct{}
// Description of the pipe
func (Pipe) Description() string {
return "Loading data from environment variables..."
return "Loading environment variables"
}
// Run the pipe
func (Pipe) Run(ctx *context.Context) (err error) {
token := os.Getenv("GITHUB_TOKEN")
if token == "" {
ctx.Token = os.Getenv("GITHUB_TOKEN")
if ctx.Token == "" {
return ErrMissingToken
}
ctx.Token = &token
return
}

View File

@ -7,7 +7,7 @@ type Pipe struct{}
// Description of the pipe
func (Pipe) Description() string {
return "Gathering Git data..."
return "Getting Git info"
}
// Run the pipe
@ -25,7 +25,7 @@ func (Pipe) Run(ctx *context.Context) (err error) {
return
}
ctx.Git = &context.GitInfo{
ctx.Git = context.GitInfo{
CurrentTag: tag,
PreviousTag: previous,
Diff: log,

View File

@ -16,12 +16,12 @@ type Pipe struct{}
// Description of the pipe
func (Pipe) Description() string {
return "Releasing to GitHub..."
return "Releasing to GitHub"
}
// Run the pipe
func (Pipe) Run(ctx *context.Context) error {
client := clients.GitHub(*ctx.Token)
client := clients.GitHub(ctx.Token)
r, err := getOrCreateRelease(client, ctx)
if err != nil {
@ -48,11 +48,11 @@ func getOrCreateRelease(client *github.Client, ctx *context.Context) (*github.Re
}
r, _, err := client.Repositories.GetReleaseByTag(owner, repo, ctx.Git.CurrentTag)
if err != nil {
log.Println("Creating release", ctx.Git.CurrentTag, "on", ctx.Config.Release.Repo, "...")
log.Println("Creating release", ctx.Git.CurrentTag, "on", ctx.Config.Release.Repo)
r, _, err = client.Repositories.CreateRelease(owner, repo, data)
return r, err
}
log.Println("Updating existing release", ctx.Git.CurrentTag, "on", ctx.Config.Release.Repo, "...")
log.Println("Updating existing release", ctx.Git.CurrentTag, "on", ctx.Config.Release.Repo)
r, _, err = client.Repositories.EditRelease(owner, repo, *r.ID, data)
return r, err
}
@ -74,7 +74,7 @@ func upload(client *github.Client, releaseID int, archive string, ctx *context.C
return err
}
defer func() { _ = file.Close() }()
log.Println("Uploading", file.Name(), "...")
log.Println("Uploading", file.Name())
_, _, err = client.Repositories.UploadReleaseAsset(
ctx.ReleaseRepo.Owner,
ctx.ReleaseRepo.Name,

View File

@ -11,18 +11,18 @@ type Pipe struct{}
// Description of the pipe
func (Pipe) Description() string {
return "Filling repositories data..."
return "Setting repositories"
}
// Run the pipe
func (Pipe) Run(ctx *context.Context) (err error) {
owner, name := split(ctx.Config.Release.Repo)
ctx.ReleaseRepo = &context.Repo{
ctx.ReleaseRepo = context.Repo{
Owner: owner,
Name: name,
}
owner, name = split(ctx.Config.Brew.Repo)
ctx.BrewRepo = &context.Repo{
ctx.BrewRepo = context.Repo{
Owner: owner,
Name: name,
}
@ -31,5 +31,8 @@ func (Pipe) Run(ctx *context.Context) (err error) {
func split(pair string) (string, string) {
parts := strings.Split(pair, "/")
if len(parts) == 1 {
return parts[0], ""
}
return parts[0], parts[1]
}

View File

@ -11,4 +11,20 @@ func TestSplit(t *testing.T) {
a, b := split("a/b")
assert.Equal("a", a)
assert.Equal("b", b)
a, b = split("")
assert.Equal("", a)
assert.Equal("", b)
a, b = split("a")
assert.Equal("a", a)
assert.Equal("", b)
a, b = split("a/")
assert.Equal("a", a)
assert.Equal("", b)
a, b = split("/b")
assert.Equal("", a)
assert.Equal("b", b)
}

85
pipeline/source/source.go Normal file
View File

@ -0,0 +1,85 @@
// Package source provides pipes to take care of using the correct source files.
// For the releasing process we need the files of the tag we are releasing.
package source
import (
"bytes"
"errors"
"fmt"
"log"
"os/exec"
"github.com/goreleaser/goreleaser/context"
)
// Pipe to use the latest Git tag as source.
type Pipe struct {
dirty bool
wrongBranch bool
}
// Description of the pipe
func (p *Pipe) Description() string {
return "Using source from latest tag"
}
// Run uses the latest tag as source.
// Uncommitted changes are stashed.
func (p *Pipe) Run(ctx *context.Context) error {
cmd := exec.Command("git", "diff-index", "--quiet", "HEAD", "--")
err := cmd.Run()
dirty := err != nil
if dirty {
log.Println("Stashing changes")
if err = run("git", "stash", "--include-untracked", "--quiet"); err != nil {
return fmt.Errorf("failed stashing changes: %v", err)
}
}
p.dirty = dirty
cmd = exec.Command("git", "describe", "--exact-match", "--match", ctx.Git.CurrentTag)
err = cmd.Run()
wrongBranch := err != nil
if wrongBranch {
log.Println("Checking out tag", ctx.Git.CurrentTag)
if err = run("git", "checkout", ctx.Git.CurrentTag); err != nil {
return fmt.Errorf("failed changing branch: %v", err)
}
}
p.wrongBranch = wrongBranch
return nil
}
// Clean switches back to the original branch and restores changes.
func (p *Pipe) Clean(ctx *context.Context) {
if p.wrongBranch {
log.Println("Checking out original branch")
if err := run("git", "checkout", "-"); err != nil {
log.Println("failed changing branch: ", err.Error())
}
}
if p.dirty {
log.Println("Popping stashed changes")
if err := run("git", "stash", "pop"); err != nil {
log.Println("failed popping stashed changes:", err.Error())
}
}
}
func run(bin string, args ...string) error {
cmd := exec.Command(bin, args...)
var out bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &out
err := cmd.Run()
if err != nil {
return errors.New(out.String())
}
return nil
}