1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-01-24 04:16:27 +02:00

Merge pull request #267 from goreleaser/builds

multiple builds
This commit is contained in:
Carlos Alexandro Becker 2017-07-03 09:05:18 -03:00 committed by GitHub
commit 3696f74606
25 changed files with 590 additions and 442 deletions

167
README.md
View File

@ -61,36 +61,36 @@ By default GoReleaser will build the current directory, but you can change the b
```yml
# goreleaser.yml
# Build customization
build:
binary: drum-roll
goos:
- windows
- darwin
- linux
goarch:
- amd64
builds:
- binary: drum-roll
goos:
- windows
- darwin
- linux
goarch:
- amd64
```
PS: Invalid GOOS/GOARCH combinations will automatically be skipped.
This configuration specifies the build operating systems to Windows, Linux and MacOS using 64bit architecture, the name of the binaries is `drum-roll`.
GoReleaser will then archive the result binaries of each Os/Arch into a separate file. The default format is `{{.Binary}}_{{.Os}}_{{.Arch}}`.
GoReleaser will then archive the result binaries of each Os/Arch into a separate file. The default format is `{{.ProjectName}}_{{.Os}}_{{.Arch}}`.
You can change the archives name and format. You can also replace the OS and the Architecture with your own.
Another useful feature is to add files to archives, this is very useful for integrating assets like resource files.
```yml
# goreleaser.yml
# Build customization
build:
main: main.go
binary: drum-roll
goos:
- windows
- darwin
- linux
goarch:
- amd64
builds:
- main: main.go
binary: drum-roll
goos:
- windows
- darwin
- linux
goarch:
- amd64
# Archive customization
archive:
format: tar.gz
@ -172,76 +172,87 @@ defaults are sensible and fit for most projects.
We'll cover all customizations available bellow:
### Project name
```yml
# goreleaser.yml
# The name of the project. It is used in the name of the brew formula, archives,
# etc. Defaults to the name of the git project.
project_name: myproject
```
### Build customization
```yml
# goreleaser.yml
build:
# Path to main.go file or main package.
# Default is `.`
main: ./cmd/main.go
builds:
# You can have multiple builds, its a common yaml list
-
# Path to main.go file or main package.
# Default is `.`
main: ./cmd/main.go
# Name of the binary.
# Default is the name of the project directory.
binary: program
# Name of the binary.
# Default is the name of the project directory.
binary: program
# Custom build tags.
# Default is empty
flags: -tags dev
# Custom build tags.
# Default is empty
flags: -tags dev
# Custom ldflags template.
# This is parsed with Golang template engine and the following variables
# are available:
# - Date
# - Commit
# - Tag
# - Version (Tag with the `v` prefix stripped)
# The default is `-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}`
# Date format is `2006-01-02_15:04:05`
ldflags: -s -w -X main.build={{.Version}}
# Custom ldflags template.
# This is parsed with Golang template engine and the following variables
# are available:
# - Date
# - Commit
# - Tag
# - Version (Tag with the `v` prefix stripped)
# The default is `-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}`
# Date format is `2006-01-02_15:04:05`
ldflags: -s -w -X main.build={{.Version}}
# Custom environment variables to be set durign the builds.
# Default is empty
env:
- CGO_ENABLED=0
# Custom environment variables to be set durign the builds.
# Default is empty
env:
- CGO_ENABLED=0
# GOOS list to build in.
# For more info refer to https://golang.org/doc/install/source#environment
# Defaults are darwin and linux
goos:
- freebsd
- windows
# GOOS list to build in.
# For more info refer to https://golang.org/doc/install/source#environment
# Defaults are darwin and linux
goos:
- freebsd
- windows
# GOARCH to build in.
# For more info refer to https://golang.org/doc/install/source#environment
# Defaults are 386 and amd64
goarch:
- amd64
- arm
- arm64
# GOARCH to build in.
# For more info refer to https://golang.org/doc/install/source#environment
# Defaults are 386 and amd64
goarch:
- amd64
- arm
- arm64
# GOARM to build in when GOARCH is arm.
# For more info refer to https://golang.org/doc/install/source#environment
# Defaults are 6
goarm:
- 6
- 7
# GOARM to build in when GOARCH is arm.
# For more info refer to https://golang.org/doc/install/source#environment
# Defaults are 6
goarm:
- 6
- 7
# List of combinations of GOOS + GOARCH + GOARM to ignore.
# Default is empty.
ignore:
- goos: darwin
goarch: 386
- goos: linux
goarch: arm
goarm: 7
# List of combinations of GOOS + GOARCH + GOARM to ignore.
# Default is empty.
ignore:
- goos: darwin
goarch: 386
- goos: linux
goarch: arm
goarm: 7
# Hooks can be used to customize the final binary, for example, to run
# generator or whatever you want.
# Default is both hooks empty.
hooks:
pre: rice embed-go
post: ./script.sh
# Hooks can be used to customize the final binary, for example, to run
# generator or whatever you want.
# Default is both hooks empty.
hooks:
pre: rice embed-go
post: ./script.sh
```
### Archive customization
@ -252,14 +263,14 @@ archive:
# You can change the name of the archive.
# This is parsed with Golang template engine and the following variables
# are available:
# - Binary
# - ProjectName
# - Tag
# - Version (Tag with the `v` prefix stripped)
# - Os
# - Arch
# - Arm (ARM version)
# The default is `{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}`
name_template: "{{.Binary}}_{{.Version}}_{{.Os}}_{{.Arch}}"
# The default is `{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}`
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
# Archive format. Valid options are `tar.gz` and `zip`.
# Default is `tar.gz`

View File

@ -7,6 +7,7 @@ import (
"io/ioutil"
"os"
"github.com/apex/log"
yaml "gopkg.in/yaml.v1"
)
@ -99,12 +100,16 @@ type Snapshot struct {
// Project includes all project configuration
type Project struct {
Release Release `yaml:",omitempty"`
Brew Homebrew `yaml:",omitempty"`
Build Build `yaml:",omitempty"`
Archive Archive `yaml:",omitempty"`
FPM FPM `yaml:",omitempty"`
Snapshot Snapshot `yaml:",omitempty"`
ProjectName string `yaml:"project_name,omitempty"`
Release Release `yaml:",omitempty"`
Brew Homebrew `yaml:",omitempty"`
Builds []Build `yaml:",omitempty"`
Archive Archive `yaml:",omitempty"`
FPM FPM `yaml:",omitempty"`
Snapshot Snapshot `yaml:",omitempty"`
// this is a hack ¯\_(ツ)_/¯
SingleBuild Build `yaml:"build,omitempty"`
// test only property indicating the path to the dist folder
Dist string `yaml:"-"`
@ -126,5 +131,6 @@ func LoadReader(fd io.Reader) (config Project, err error) {
return config, err
}
err = yaml.Unmarshal(data, &config)
log.WithField("config", config).Debug("loaded config file")
return
}

View File

@ -27,7 +27,7 @@ type Context struct {
Config config.Project
Token string
Git GitInfo
Archives map[string]string
Folders map[string]string
Artifacts []string
ReleaseNotes string
Version string
@ -36,23 +36,32 @@ type Context struct {
Snapshot bool
}
var lock sync.Mutex
var artifactsLock sync.Mutex
var foldersLock sync.Mutex
// AddArtifact adds a file to upload list
func (ctx *Context) AddArtifact(file string) {
lock.Lock()
defer lock.Unlock()
artifactsLock.Lock()
defer artifactsLock.Unlock()
file = strings.TrimPrefix(file, ctx.Config.Dist)
file = strings.Replace(file, "/", "", -1)
ctx.Artifacts = append(ctx.Artifacts, file)
log.WithField("artifact", file).Info("registered")
log.WithField("artifact", file).Info("new artifact")
}
// AddFolder adds a built binary to the current context
func (ctx *Context) AddFolder(key, folder string) {
foldersLock.Lock()
defer foldersLock.Unlock()
ctx.Folders[key] = folder
log.WithField("key", key).WithField("folder", folder).Info("new folder")
}
// New context
func New(config config.Project) *Context {
return &Context{
Context: ctx.Background(),
Config: config,
Archives: map[string]string{},
Context: ctx.Background(),
Config: config,
Folders: map[string]string{},
}
}

View File

@ -31,3 +31,27 @@ func TestMultipleArtifactAdds(t *testing.T) {
assert.Len(ctx.Artifacts, len(list))
assert.Contains(ctx.Artifacts, "a", "b", "c", "d")
}
func TestMultipleFolderAdds(t *testing.T) {
var assert = assert.New(t)
var list = map[string]string{
"key-a": "folder/a",
"key-b": "folder/b",
"key-c": "folder/c",
"key-d": "folder/d",
}
var ctx = New(config.Project{
Dist: "dist",
})
var g errgroup.Group
for k, f := range list {
f := f
k := k
g.Go(func() error {
ctx.AddFolder(k, f)
return nil
})
}
assert.NoError(g.Wait())
assert.Len(ctx.Folders, len(list))
}

View File

@ -1,17 +1,18 @@
homepage: &homepage http://goreleaser.github.io
description: &description Deliver Go binaries as fast and easily as possible
build:
env:
- CGO_ENABLED=0
goos:
- linux
- darwin
- windows
goarch:
- 386
- amd64
- arm
- arm64
builds:
-
env:
- CGO_ENABLED=0
goos:
- linux
- darwin
- windows
goarch:
- 386
- amd64
- arm
- arm64
archive:
name_template: '{{ .Binary }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
replacements:

View File

@ -36,7 +36,7 @@ func (c *githubClient) CreateFile(
},
Content: content.Bytes(),
Message: github.String(
ctx.Config.Build.Binary + " version " + ctx.Git.CurrentTag,
ctx.Config.ProjectName + " version " + ctx.Git.CurrentTag,
),
}

49
internal/name/name.go Normal file
View File

@ -0,0 +1,49 @@
// Package name provides name template logic for the final archive, formulae,
// etc.
package name
import (
"bytes"
"text/template"
"github.com/goreleaser/goreleaser/context"
)
type nameData struct {
Os string
Arch string
Arm string
Version string
Tag string
Binary string // deprecated
ProjectName string
}
// For returns the name for the given context, goos, goarch and goarm.
func For(ctx *context.Context, goos, goarch, goarm string) (string, error) {
var data = nameData{
Os: replace(ctx.Config.Archive.Replacements, goos),
Arch: replace(ctx.Config.Archive.Replacements, goarch),
Arm: replace(ctx.Config.Archive.Replacements, goarm),
Version: ctx.Version,
Tag: ctx.Git.CurrentTag,
Binary: ctx.Config.ProjectName,
ProjectName: ctx.Config.ProjectName,
}
var out bytes.Buffer
t, err := template.New(data.Binary).Parse(ctx.Config.Archive.NameTemplate)
if err != nil {
return "", err
}
err = t.Execute(&out, data)
return out.String(), err
}
func replace(replacements map[string]string, original string) string {
result := replacements[original]
if result == "" {
return original
}
return result
}

View File

@ -1,4 +1,4 @@
package build
package name
import (
"testing"
@ -20,9 +20,7 @@ func TestNameFor(t *testing.T) {
"amd64": "x86_64",
},
},
Build: config.Build{
Binary: "test",
},
ProjectName: "test",
}
var ctx = &context.Context{
Config: config,
@ -32,30 +30,26 @@ func TestNameFor(t *testing.T) {
},
}
name, err := nameFor(ctx, buildTarget{"darwin", "amd64", ""})
name, err := For(ctx, "darwin", "amd64", "")
assert.NoError(err)
assert.Equal("test_Darwin_x86_64_v1.2.3_1.2.3", name)
}
func TestInvalidNameTemplate(t *testing.T) {
assert := assert.New(t)
var config = config.Project{
Archive: config.Archive{
NameTemplate: "{{.Binary}_{{.Os}}_{{.Arch}}_{{.Version}}",
},
Build: config.Build{
Binary: "test",
},
}
var assert = assert.New(t)
var ctx = &context.Context{
Config: config,
Config: config.Project{
Archive: config.Archive{
NameTemplate: "{{.Binary}_{{.Os}}_{{.Arch}}_{{.Version}}",
},
ProjectName: "test",
},
Git: context.GitInfo{
CurrentTag: "v1.2.3",
},
}
_, err := nameFor(ctx, buildTarget{"darwin", "amd64", ""})
_, err := For(ctx, "darwin", "amd64", "")
assert.Error(err)
}
@ -66,19 +60,20 @@ func TestNameDefaltTemplate(t *testing.T) {
Archive: config.Archive{
NameTemplate: defaults.NameTemplate,
},
Build: config.Build{
Binary: "test",
},
ProjectName: "test",
},
Version: "1.2.3",
}
type buildTarget struct {
goos, goarch, goarm string
}
for key, target := range map[string]buildTarget{
"test_1.2.3_darwin_amd64": {"darwin", "amd64", ""},
"test_1.2.3_linux_arm64": {"linux", "arm64", ""},
"test_1.2.3_linux_armv7": {"linux", "arm", "7"},
} {
t.Run(key, func(t *testing.T) {
name, err := nameFor(ctx, target)
name, err := For(ctx, target.goos, target.goarch, target.goarm)
assert.NoError(err)
assert.Equal(key, name)
})

View File

@ -4,6 +4,7 @@
package archive
import (
"io/ioutil"
"os"
"path/filepath"
@ -11,7 +12,6 @@ import (
"github.com/goreleaser/archive"
"github.com/goreleaser/goreleaser/context"
"github.com/goreleaser/goreleaser/internal/archiveformat"
"github.com/goreleaser/goreleaser/internal/ext"
"github.com/mattn/go-zglob"
"golang.org/x/sync/errgroup"
)
@ -27,11 +27,11 @@ func (Pipe) Description() string {
// Run the pipe
func (Pipe) Run(ctx *context.Context) error {
var g errgroup.Group
for platform, archive := range ctx.Archives {
archive := archive
for platform, folder := range ctx.Folders {
folder := folder
platform := platform
g.Go(func() error {
return create(ctx, platform, archive)
return create(ctx, platform, folder)
})
}
return g.Wait()
@ -58,10 +58,16 @@ func create(ctx *context.Context, platform, name string) error {
return err
}
}
var binary = ctx.Config.Build.Binary + ext.For(platform)
if err := archive.Add(binary, filepath.Join(folder, binary)); err != nil {
var path = filepath.Join(ctx.Config.Dist, name)
binaries, err := ioutil.ReadDir(path)
if err != nil {
return err
}
for _, binary := range binaries {
if err := archive.Add(binary.Name(), filepath.Join(path, binary.Name())); err != nil {
return err
}
}
ctx.AddArtifact(file.Name())
return nil
}

View File

@ -27,23 +27,21 @@ func TestRunPipe(t *testing.T) {
}()
var dist = filepath.Join(folder, "dist")
assert.NoError(os.Mkdir(dist, 0755))
assert.NoError(os.Mkdir(filepath.Join(dist, "mybin"), 0755))
_, err = os.Create(filepath.Join(dist, "mybin", "mybin"))
assert.NoError(os.Mkdir(filepath.Join(dist, "mybin_darwin_amd64"), 0755))
assert.NoError(os.Mkdir(filepath.Join(dist, "mybin_windows_amd64"), 0755))
_, err = os.Create(filepath.Join(dist, "mybin_darwin_amd64", "mybin"))
assert.NoError(err)
_, err = os.Create(filepath.Join(dist, "mybin", "mybin.exe"))
_, err = os.Create(filepath.Join(dist, "mybin_windows_amd64", "mybin.exe"))
assert.NoError(err)
_, err = os.Create(filepath.Join(folder, "README.md"))
assert.NoError(err)
var ctx = &context.Context{
Archives: map[string]string{
"darwinamd64": "mybin",
"windowsamd64": "mybin",
Folders: map[string]string{
"darwinamd64": "mybin_darwin_amd64",
"windowsamd64": "mybin_windows_amd64",
},
Config: config.Project{
Dist: dist,
Build: config.Build{
Binary: "mybin",
},
Archive: config.Archive{
Files: []string{
"README.*",
@ -68,9 +66,8 @@ func TestRunPipe(t *testing.T) {
func TestRunPipeDistRemoved(t *testing.T) {
var assert = assert.New(t)
var ctx = &context.Context{
Archives: map[string]string{
"darwinamd64": "mybin",
"windowsamd64": "mybin",
Folders: map[string]string{
"darwinamd64": "whatever",
},
Config: config.Project{
Dist: "/path/nope",
@ -85,8 +82,8 @@ func TestRunPipeDistRemoved(t *testing.T) {
func TestRunPipeInvalidGlob(t *testing.T) {
var assert = assert.New(t)
var ctx = &context.Context{
Archives: map[string]string{
"windowsamd64": "mybin",
Folders: map[string]string{
"windowsamd64": "whatever",
},
Config: config.Project{
Dist: "/tmp",
@ -113,7 +110,7 @@ func TestRunPipeGlobFailsToAdd(t *testing.T) {
assert.NoError(os.MkdirAll(filepath.Join(folder, "folder", "another"), 0755))
var ctx = &context.Context{
Archives: map[string]string{
Folders: map[string]string{
"windows386": "mybin",
},
Config: config.Project{
@ -127,18 +124,3 @@ func TestRunPipeGlobFailsToAdd(t *testing.T) {
}
assert.Error(Pipe{}.Run(ctx))
}
func TestRunPipeBinaryDontExist(t *testing.T) {
var assert = assert.New(t)
folder, err := ioutil.TempDir("", "archivetest")
assert.NoError(err)
var ctx = &context.Context{
Archives: map[string]string{
"windows386": "mybin",
},
Config: config.Project{
Dist: folder,
},
}
assert.Error(Pipe{}.Run(ctx))
}

View File

@ -21,6 +21,8 @@ import (
// contain darwin and/or goarch doesn't contain amd64)
var ErrNoDarwin64Build = errors.New("brew tap requires a darwin amd64 build")
const platform = "darwinamd64"
const formula = `class {{ .Name }} < Formula
desc "{{ .Desc }}"
homepage "{{ .Homepage }}"
@ -70,7 +72,6 @@ type templateData struct {
Repo config.Repo // FIXME: will not work for anything but github right now.
Tag string
Version string
Binary string
Caveats string
File string
SHA256 string
@ -106,7 +107,7 @@ func doRun(ctx *context.Context, client client.Client) error {
log.Warn("skipped because release is marked as draft")
return nil
}
path := filepath.Join(ctx.Config.Brew.Folder, ctx.Config.Build.Binary+".rb")
var path = filepath.Join(ctx.Config.Brew.Folder, ctx.Config.ProjectName+".rb")
log.WithField("formula", path).
WithField("repo", ctx.Config.Brew.GitHub.String()).
Info("pushing")
@ -127,7 +128,7 @@ func buildFormula(ctx *context.Context, client client.Client) (bytes.Buffer, err
func doBuildFormula(data templateData) (bytes.Buffer, error) {
var out bytes.Buffer
tmpl, err := template.New(data.Binary).Parse(formula)
tmpl, err := template.New(data.Name).Parse(formula)
if err != nil {
return out, err
}
@ -136,23 +137,22 @@ func doBuildFormula(data templateData) (bytes.Buffer, error) {
}
func dataFor(ctx *context.Context, client client.Client) (result templateData, err error) {
var folder = ctx.Archives["darwinamd64"]
var folder = ctx.Folders[platform]
if folder == "" {
return result, ErrNoDarwin64Build
}
var file = folder + "." + archiveformat.For(ctx, "darwinamd64")
var file = folder + "." + archiveformat.For(ctx, platform)
sum, err := checksum.SHA256(filepath.Join(ctx.Config.Dist, file))
if err != nil {
return
}
return templateData{
Name: formulaNameFor(ctx.Config.Build.Binary),
Name: formulaNameFor(ctx.Config.ProjectName),
Desc: ctx.Config.Brew.Description,
Homepage: ctx.Config.Brew.Homepage,
Repo: ctx.Config.Release.GitHub,
Tag: ctx.Git.CurrentTag,
Version: ctx.Version,
Binary: ctx.Config.Build.Binary,
Caveats: ctx.Config.Brew.Caveats,
File: file,
SHA256: sum,

View File

@ -29,7 +29,6 @@ func TestSimpleName(t *testing.T) {
}
var defaultTemplateData = templateData{
Binary: "test",
Desc: "Some desc",
Homepage: "https://google.com",
Name: "Test",
@ -104,7 +103,7 @@ func TestRunPipe(t *testing.T) {
},
},
},
Archives: map[string]string{
Folders: map[string]string{
"darwinamd64": "bin",
},
Publish: true,
@ -139,7 +138,7 @@ func TestRunPipeFormatOverride(t *testing.T) {
},
},
},
Archives: map[string]string{
Folders: map[string]string{
"darwinamd64": "bin",
},
Publish: true,
@ -167,7 +166,7 @@ func TestRunPipeArchiveDoesntExist(t *testing.T) {
},
},
},
Archives: map[string]string{
Folders: map[string]string{
"darwinamd64": "bin",
},
Publish: true,

View File

@ -10,8 +10,10 @@ import (
"strings"
"github.com/apex/log"
"github.com/goreleaser/goreleaser/config"
"github.com/goreleaser/goreleaser/context"
"github.com/goreleaser/goreleaser/internal/ext"
"github.com/goreleaser/goreleaser/internal/name"
"golang.org/x/sync/errgroup"
)
@ -25,31 +27,36 @@ func (Pipe) Description() string {
// Run the pipe
func (Pipe) Run(ctx *context.Context) error {
if err := runHook(ctx.Config.Build.Env, ctx.Config.Build.Hooks.Pre); err != nil {
for _, build := range ctx.Config.Builds {
log.WithField("build", build).Debug("building")
if err := runPipeOnBuild(ctx, build); err != nil {
return err
}
}
return nil
}
func runPipeOnBuild(ctx *context.Context, build config.Build) error {
if err := runHook(build.Env, build.Hooks.Pre); err != nil {
return err
}
sem := make(chan bool, 4)
var g errgroup.Group
for _, target := range buildTargets(ctx) {
name, err := nameFor(ctx, target)
if err != nil {
return err
}
ctx.Archives[target.String()] = name
for _, target := range buildTargets(build) {
sem <- true
target := target
build := build
g.Go(func() error {
defer func() {
<-sem
}()
return build(ctx, name, target)
return doBuild(ctx, build, target)
})
}
if err := g.Wait(); err != nil {
return err
}
return runHook(ctx.Config.Build.Env, ctx.Config.Build.Hooks.Post)
return runHook(build.Env, build.Hooks.Post)
}
func runHook(env []string, hook string) error {
@ -61,23 +68,28 @@ func runHook(env []string, hook string) error {
return run(runtimeTarget, cmd, env)
}
func build(ctx *context.Context, name string, target buildTarget) error {
output := filepath.Join(
ctx.Config.Dist,
name,
ctx.Config.Build.Binary+ext.For(target.goos),
)
log.WithField("binary", output).Info("building")
cmd := []string{"go", "build"}
if ctx.Config.Build.Flags != "" {
cmd = append(cmd, strings.Fields(ctx.Config.Build.Flags)...)
}
flags, err := ldflags(ctx)
func doBuild(ctx *context.Context, build config.Build, target buildTarget) error {
folder, err := name.For(ctx, target.goos, target.goarch, target.goarm)
if err != nil {
return err
}
cmd = append(cmd, "-ldflags="+flags, "-o", output, ctx.Config.Build.Main)
return run(target, cmd, ctx.Config.Build.Env)
ctx.AddFolder(target.String(), folder)
var binary = filepath.Join(
ctx.Config.Dist,
folder,
build.Binary+ext.For(target.goos),
)
log.WithField("binary", binary).Info("building")
cmd := []string{"go", "build"}
if build.Flags != "" {
cmd = append(cmd, strings.Fields(build.Flags)...)
}
flags, err := ldflags(ctx, build)
if err != nil {
return err
}
cmd = append(cmd, "-ldflags="+flags, "-o", binary, build.Main)
return run(target, cmd, build.Env)
}
func run(target buildTarget, command, env []string) error {

View File

@ -29,16 +29,19 @@ func TestRunInvalidCommand(t *testing.T) {
func TestBuild(t *testing.T) {
assert := assert.New(t)
var config = config.Project{
Build: config.Build{
Binary: "testing",
Flags: "-n",
Env: []string{"BLAH=1"},
Builds: []config.Build{
{
Binary: "testing",
Flags: "-n",
Env: []string{"BLAH=1"},
},
},
}
var ctx = &context.Context{
Config: config,
Config: config,
Folders: map[string]string{},
}
assert.NoError(build(ctx, "build_test", runtimeTarget))
assert.NoError(doBuild(ctx, ctx.Config.Builds[0], runtimeTarget))
}
func TestRunFullPipe(t *testing.T) {
@ -50,25 +53,27 @@ func TestRunFullPipe(t *testing.T) {
var post = filepath.Join(folder, "post")
var config = config.Project{
Dist: folder,
Build: config.Build{
Binary: "testing",
Flags: "-v",
Ldflags: "-X main.test=testing",
Hooks: config.Hooks{
Pre: "touch " + pre,
Post: "touch " + post,
},
Goos: []string{
runtime.GOOS,
},
Goarch: []string{
runtime.GOARCH,
Builds: []config.Build{
{
Binary: "testing",
Flags: "-v",
Ldflags: "-X main.test=testing",
Hooks: config.Hooks{
Pre: "touch " + pre,
Post: "touch " + post,
},
Goos: []string{
runtime.GOOS,
},
Goarch: []string{
runtime.GOARCH,
},
},
},
}
var ctx = &context.Context{
Config: config,
Archives: map[string]string{},
Config: config,
Folders: map[string]string{},
}
assert.NoError(Pipe{}.Run(ctx))
assert.True(exists(binary), binary)
@ -83,25 +88,27 @@ func TestRunPipeArmBuilds(t *testing.T) {
var binary = filepath.Join(folder, "armtesting")
var config = config.Project{
Dist: folder,
Build: config.Build{
Binary: "armtesting",
Flags: "-v",
Ldflags: "-X main.test=armtesting",
Goos: []string{
"linux",
},
Goarch: []string{
"arm",
"arm64",
},
Goarm: []string{
"6",
Builds: []config.Build{
{
Binary: "armtesting",
Flags: "-v",
Ldflags: "-X main.test=armtesting",
Goos: []string{
"linux",
},
Goarch: []string{
"arm",
"arm64",
},
Goarm: []string{
"6",
},
},
},
}
var ctx = &context.Context{
Config: config,
Archives: map[string]string{},
Config: config,
Folders: map[string]string{},
}
assert.NoError(Pipe{}.Run(ctx))
assert.True(exists(binary), binary)
@ -110,19 +117,21 @@ func TestRunPipeArmBuilds(t *testing.T) {
func TestBuildFailed(t *testing.T) {
assert := assert.New(t)
var config = config.Project{
Build: config.Build{
Flags: "-flag-that-dont-exists-to-force-failure",
Goos: []string{
runtime.GOOS,
},
Goarch: []string{
runtime.GOARCH,
Builds: []config.Build{
{
Flags: "-flag-that-dont-exists-to-force-failure",
Goos: []string{
runtime.GOOS,
},
Goarch: []string{
runtime.GOARCH,
},
},
},
}
var ctx = &context.Context{
Config: config,
Archives: map[string]string{},
Config: config,
Folders: map[string]string{},
}
assert.Error(Pipe{}.Run(ctx))
}
@ -130,19 +139,21 @@ func TestBuildFailed(t *testing.T) {
func TestRunPipeWithInvalidOS(t *testing.T) {
assert := assert.New(t)
var config = config.Project{
Build: config.Build{
Flags: "-v",
Goos: []string{
"windows",
},
Goarch: []string{
"arm",
Builds: []config.Build{
{
Flags: "-v",
Goos: []string{
"windows",
},
Goarch: []string{
"arm",
},
},
},
}
var ctx = &context.Context{
Config: config,
Archives: map[string]string{},
Config: config,
Folders: map[string]string{},
}
assert.NoError(Pipe{}.Run(ctx))
}
@ -151,14 +162,16 @@ func TestRunInvalidNametemplate(t *testing.T) {
var assert = assert.New(t)
var ctx = &context.Context{
Config: config.Project{
Build: config.Build{
Binary: "nametest",
Flags: "-v",
Goos: []string{
runtime.GOOS,
},
Goarch: []string{
runtime.GOARCH,
Builds: []config.Build{
{
Binary: "nametest",
Flags: "-v",
Goos: []string{
runtime.GOOS,
},
Goarch: []string{
runtime.GOARCH,
},
},
},
Archive: config.Archive{
@ -172,17 +185,19 @@ func TestRunInvalidNametemplate(t *testing.T) {
func TestRunInvalidLdflags(t *testing.T) {
var assert = assert.New(t)
var ctx = &context.Context{
Archives: map[string]string{},
Folders: map[string]string{},
Config: config.Project{
Build: config.Build{
Binary: "nametest",
Flags: "-v",
Ldflags: "-s -w -X main.version={{.Version}",
Goos: []string{
runtime.GOOS,
},
Goarch: []string{
runtime.GOARCH,
Builds: []config.Build{
{
Binary: "nametest",
Flags: "-v",
Ldflags: "-s -w -X main.version={{.Version}",
Goos: []string{
runtime.GOOS,
},
Goarch: []string{
runtime.GOARCH,
},
},
},
},
@ -193,26 +208,28 @@ func TestRunInvalidLdflags(t *testing.T) {
func TestRunPipeFailingHooks(t *testing.T) {
assert := assert.New(t)
var config = config.Project{
Build: config.Build{
Hooks: config.Hooks{},
Goos: []string{
runtime.GOOS,
},
Goarch: []string{
runtime.GOARCH,
Builds: []config.Build{
{
Hooks: config.Hooks{},
Goos: []string{
runtime.GOOS,
},
Goarch: []string{
runtime.GOARCH,
},
},
},
}
var ctx = &context.Context{
Config: config,
Archives: map[string]string{},
Config: config,
Folders: map[string]string{},
}
t.Run("pre-hook", func(t *testing.T) {
ctx.Config.Build.Hooks.Pre = "exit 1"
ctx.Config.Builds[0].Hooks.Pre = "exit 1"
assert.Error(Pipe{}.Run(ctx))
})
t.Run("post-hook", func(t *testing.T) {
ctx.Config.Build.Hooks.Post = "exit 1"
ctx.Config.Builds[0].Hooks.Post = "exit 1"
assert.Error(Pipe{}.Run(ctx))
})
}

View File

@ -5,6 +5,7 @@ import (
"text/template"
"time"
"github.com/goreleaser/goreleaser/config"
"github.com/goreleaser/goreleaser/context"
)
@ -15,7 +16,7 @@ type ldflagsData struct {
Version string
}
func ldflags(ctx *context.Context) (string, error) {
func ldflags(ctx *context.Context, build config.Build) (string, error) {
var data = ldflagsData{
Commit: ctx.Git.Commit,
Tag: ctx.Git.CurrentTag,
@ -23,7 +24,7 @@ func ldflags(ctx *context.Context) (string, error) {
Date: time.Now().UTC().Format(time.RFC3339),
}
var out bytes.Buffer
t, err := template.New("ldflags").Parse(ctx.Config.Build.Ldflags)
t, err := template.New("ldflags").Parse(build.Ldflags)
if err != nil {
return "", err
}

View File

@ -11,8 +11,10 @@ import (
func TestLdFlagsFullTemplate(t *testing.T) {
assert := assert.New(t)
var config = config.Project{
Build: config.Build{
Ldflags: "-s -w -X main.version={{.Version}} -X main.tag={{.Tag}} -X main.date={{.Date}} -X main.commit={{.Commit}}",
Builds: []config.Build{
{
Ldflags: "-s -w -X main.version={{.Version}} -X main.tag={{.Tag}} -X main.date={{.Date}} -X main.commit={{.Commit}}",
},
},
}
var ctx = &context.Context{
@ -23,7 +25,7 @@ func TestLdFlagsFullTemplate(t *testing.T) {
Version: "1.2.3",
Config: config,
}
flags, err := ldflags(ctx)
flags, err := ldflags(ctx, ctx.Config.Builds[0])
assert.NoError(err)
assert.Contains(flags, "-s -w")
assert.Contains(flags, "-X main.version=1.2.3")
@ -35,14 +37,14 @@ func TestLdFlagsFullTemplate(t *testing.T) {
func TestInvalidTemplate(t *testing.T) {
assert := assert.New(t)
var config = config.Project{
Build: config.Build{
Ldflags: "{invalid{.Template}}}{{}}}",
Builds: []config.Build{
{Ldflags: "{invalid{.Template}}}{{}}}"},
},
}
var ctx = &context.Context{
Config: config,
}
flags, err := ldflags(ctx)
flags, err := ldflags(ctx, ctx.Config.Builds[0])
assert.Error(err)
assert.Equal(flags, "")
}

View File

@ -1,44 +0,0 @@
package build
import (
"bytes"
"text/template"
"github.com/goreleaser/goreleaser/context"
)
type nameData struct {
Os string
Arch string
Arm string
Version string
Tag string
Binary string
}
func nameFor(ctx *context.Context, target buildTarget) (string, error) {
var data = nameData{
Os: replace(ctx.Config.Archive.Replacements, target.goos),
Arch: replace(ctx.Config.Archive.Replacements, target.goarch),
Arm: replace(ctx.Config.Archive.Replacements, target.goarm),
Version: ctx.Version,
Tag: ctx.Git.CurrentTag,
Binary: ctx.Config.Build.Binary,
}
var out bytes.Buffer
t, err := template.New(data.Binary).Parse(ctx.Config.Archive.NameTemplate)
if err != nil {
return "", err
}
err = t.Execute(&out, data)
return out.String(), err
}
func replace(replacements map[string]string, original string) string {
result := replacements[original]
if result == "" {
return original
}
return result
}

View File

@ -5,7 +5,7 @@ import (
"runtime"
"github.com/apex/log"
"github.com/goreleaser/goreleaser/context"
"github.com/goreleaser/goreleaser/config"
)
var runtimeTarget = buildTarget{runtime.GOOS, runtime.GOARCH, ""}
@ -23,14 +23,14 @@ func (t buildTarget) PrettyString() string {
return fmt.Sprintf("%v/%v%v", t.goos, t.goarch, t.goarm)
}
func buildTargets(ctx *context.Context) (targets []buildTarget) {
for _, target := range allBuildTargets(ctx) {
func buildTargets(build config.Build) (targets []buildTarget) {
for _, target := range allBuildTargets(build) {
if !valid(target) {
log.WithField("target", target.PrettyString()).
Warn("skipped invalid build")
continue
}
if ignored(ctx, target) {
if ignored(build, target) {
log.WithField("target", target.PrettyString()).
Warn("skipped ignored build")
continue
@ -40,11 +40,11 @@ func buildTargets(ctx *context.Context) (targets []buildTarget) {
return
}
func allBuildTargets(ctx *context.Context) (targets []buildTarget) {
for _, goos := range ctx.Config.Build.Goos {
for _, goarch := range ctx.Config.Build.Goarch {
func allBuildTargets(build config.Build) (targets []buildTarget) {
for _, goos := range build.Goos {
for _, goarch := range build.Goarch {
if goarch == "arm" {
for _, goarm := range ctx.Config.Build.Goarm {
for _, goarm := range build.Goarm {
targets = append(targets, buildTarget{goos, goarch, goarm})
}
continue
@ -55,8 +55,8 @@ func allBuildTargets(ctx *context.Context) (targets []buildTarget) {
return
}
func ignored(ctx *context.Context, target buildTarget) bool {
for _, ig := range ctx.Config.Build.Ignore {
func ignored(build config.Build, target buildTarget) bool {
for _, ig := range build.Ignore {
var ignored = buildTarget{ig.Goos, ig.Goarch, ig.Goarm}
if ignored == target {
return true

View File

@ -5,40 +5,35 @@ import (
"testing"
"github.com/goreleaser/goreleaser/config"
"github.com/goreleaser/goreleaser/context"
"github.com/stretchr/testify/assert"
)
func TestAllBuildTargets(t *testing.T) {
var assert = assert.New(t)
var ctx = &context.Context{
Config: config.Project{
Build: config.Build{
Goos: []string{
"linux",
"darwin",
"freebsd",
},
Goarch: []string{
"386",
"amd64",
"arm",
"arm64",
},
Goarm: []string{
"6",
"7",
},
Ignore: []config.IgnoredBuild{
{
Goos: "darwin",
Goarch: "386",
}, {
Goos: "linux",
Goarch: "arm",
Goarm: "7",
},
},
var build = config.Build{
Goos: []string{
"linux",
"darwin",
"freebsd",
},
Goarch: []string{
"386",
"amd64",
"arm",
"arm64",
},
Goarm: []string{
"6",
"7",
},
Ignore: []config.IgnoredBuild{
{
Goos: "darwin",
Goarch: "386",
}, {
Goos: "linux",
Goarch: "arm",
Goarm: "7",
},
},
}
@ -52,7 +47,7 @@ func TestAllBuildTargets(t *testing.T) {
{"freebsd", "amd64", ""},
{"freebsd", "arm", "6"},
{"freebsd", "arm", "7"},
}, buildTargets(ctx))
}, buildTargets(build))
}
func TestValidGoosGoarchCombos(t *testing.T) {

View File

@ -26,7 +26,7 @@ func (Pipe) Run(ctx *context.Context) (err error) {
file, err := os.OpenFile(
filepath.Join(
ctx.Config.Dist,
fmt.Sprintf("%v_checksums.txt", ctx.Config.Build.Binary),
fmt.Sprintf("%v_checksums.txt", ctx.Config.ProjectName),
),
os.O_APPEND|os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
0644,

View File

@ -24,10 +24,8 @@ func TestPipe(t *testing.T) {
assert.NoError(ioutil.WriteFile(file, []byte("some string"), 0644))
var ctx = &context.Context{
Config: config.Project{
Dist: folder,
Build: config.Build{
Binary: binary,
},
Dist: folder,
ProjectName: binary,
},
}
ctx.AddArtifact(file)

View File

@ -4,7 +4,10 @@ package defaults
import (
"fmt"
"strings"
"github.com/apex/log"
"github.com/goreleaser/goreleaser/config"
"github.com/goreleaser/goreleaser/context"
)
@ -31,14 +34,44 @@ func (Pipe) Run(ctx *context.Context) error {
if err := setReleaseDefaults(ctx); err != nil {
return err
}
if ctx.Config.ProjectName == "" {
ctx.Config.ProjectName = ctx.Config.Release.GitHub.Name
}
setBuildDefaults(ctx)
if ctx.Config.Brew.Install == "" {
ctx.Config.Brew.Install = fmt.Sprintf(
`bin.install "%s"`,
ctx.Config.Build.Binary,
)
var installs []string
for _, build := range ctx.Config.Builds {
if !isBrewBuild(build) {
continue
}
installs = append(
installs,
fmt.Sprintf(`bin.install "%s"`, build.Binary),
)
}
ctx.Config.Brew.Install = strings.Join(installs, "\n")
}
return setArchiveDefaults(ctx)
err := setArchiveDefaults(ctx)
log.WithField("config", ctx.Config).Debug("defaults set")
return err
}
func isBrewBuild(build config.Build) bool {
for _, ignore := range build.Ignore {
if ignore.Goos == "darwin" && ignore.Goarch == "amd64" {
return false
}
}
return contains(build.Goos, "darwin") && contains(build.Goarch, "amd64")
}
func contains(ss []string, s string) bool {
for _, zs := range ss {
if zs == s {
return true
}
}
return false
}
func setReleaseDefaults(ctx *context.Context) error {
@ -54,24 +87,36 @@ func setReleaseDefaults(ctx *context.Context) error {
}
func setBuildDefaults(ctx *context.Context) {
if ctx.Config.Build.Binary == "" {
ctx.Config.Build.Binary = ctx.Config.Release.GitHub.Name
for i, build := range ctx.Config.Builds {
ctx.Config.Builds[i] = buildWithDefaults(ctx, build)
}
if ctx.Config.Build.Main == "" {
ctx.Config.Build.Main = "."
if len(ctx.Config.Builds) == 0 {
ctx.Config.Builds = []config.Build{
buildWithDefaults(ctx, ctx.Config.SingleBuild),
}
}
if len(ctx.Config.Build.Goos) == 0 {
ctx.Config.Build.Goos = []string{"linux", "darwin"}
}
func buildWithDefaults(ctx *context.Context, build config.Build) config.Build {
if build.Binary == "" {
build.Binary = ctx.Config.Release.GitHub.Name
}
if len(ctx.Config.Build.Goarch) == 0 {
ctx.Config.Build.Goarch = []string{"amd64", "386"}
if build.Main == "" {
build.Main = "."
}
if len(ctx.Config.Build.Goarm) == 0 {
ctx.Config.Build.Goarm = []string{"6"}
if len(build.Goos) == 0 {
build.Goos = []string{"linux", "darwin"}
}
if ctx.Config.Build.Ldflags == "" {
ctx.Config.Build.Ldflags = "-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"
if len(build.Goarch) == 0 {
build.Goarch = []string{"amd64", "386"}
}
if len(build.Goarm) == 0 {
build.Goarm = []string{"6"}
}
if build.Ldflags == "" {
build.Ldflags = "-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"
}
return build
}
func setArchiveDefaults(ctx *context.Context) error {

View File

@ -15,33 +15,32 @@ func TestDescription(t *testing.T) {
}
func TestFillBasicData(t *testing.T) {
assert := assert.New(t)
var assert = assert.New(t)
var ctx = &context.Context{
Config: config.Project{},
}
assert.NoError(Pipe{}.Run(ctx))
assert.Equal("goreleaser", ctx.Config.Release.GitHub.Owner)
assert.Equal("goreleaser", ctx.Config.Release.GitHub.Name)
assert.Equal("goreleaser", ctx.Config.Build.Binary)
assert.Equal(".", ctx.Config.Build.Main)
assert.NotEmpty(ctx.Config.Builds)
assert.Equal("goreleaser", ctx.Config.Builds[0].Binary)
assert.Equal(".", ctx.Config.Builds[0].Main)
assert.Contains(ctx.Config.Builds[0].Goos, "darwin")
assert.Contains(ctx.Config.Builds[0].Goos, "linux")
assert.Contains(ctx.Config.Builds[0].Goarch, "386")
assert.Contains(ctx.Config.Builds[0].Goarch, "amd64")
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.Contains(ctx.Config.Brew.Install, "bin.install \"goreleaser\"")
assert.NotEmpty(
ctx.Config.Archive.NameTemplate,
ctx.Config.Build.Ldflags,
ctx.Config.Builds[0].Ldflags,
ctx.Config.Archive.Files,
)
}
func TestFillPartial(t *testing.T) {
assert := assert.New(t)
var assert = assert.New(t)
var ctx = &context.Context{
Config: config.Project{
@ -56,10 +55,36 @@ func TestFillPartial(t *testing.T) {
"glob/*",
},
},
Builds: []config.Build{
{Binary: "testreleaser"},
{Goos: []string{"linux"}},
{
Binary: "another",
Ignore: []config.IgnoredBuild{
{Goos: "darwin", Goarch: "amd64"},
},
},
},
},
}
assert.NoError(Pipe{}.Run(ctx))
assert.Len(ctx.Config.Archive.Files, 1)
assert.Equal(`bin.install "testreleaser"`, ctx.Config.Brew.Install)
}
func TestFillSingleBuild(t *testing.T) {
var assert = assert.New(t)
var ctx = &context.Context{
Config: config.Project{
SingleBuild: config.Build{
Main: "testreleaser",
},
},
}
assert.NoError(Pipe{}.Run(ctx))
assert.Len(ctx.Config.Builds, 1)
assert.Equal(ctx.Config.Builds[0].Binary, "goreleaser")
}
func TestNotAGitRepo(t *testing.T) {

View File

@ -3,19 +3,17 @@ package fpm
import (
"errors"
"fmt"
"io/ioutil"
"os/exec"
"path/filepath"
"strings"
"github.com/apex/log"
"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")
@ -40,31 +38,38 @@ func (Pipe) Run(ctx *context.Context) error {
var g errgroup.Group
for _, format := range ctx.Config.FPM.Formats {
format := format
for _, goarch := range ctx.Config.Build.Goarch {
if ctx.Archives["linux"+goarch] == "" {
for key, folder := range ctx.Folders {
if !strings.Contains(key, "linux") {
log.WithField("key", key).Debug("skipped non-linux builds for fpm")
continue
}
archive := ctx.Archives["linux"+goarch]
arch := goarchToUnix[goarch]
folder := folder
format := format
arch := archFor(key)
g.Go(func() error {
return create(ctx, format, archive, arch)
return create(ctx, format, folder, arch)
})
}
}
return g.Wait()
}
func create(ctx *context.Context, format, archive, arch string) error {
var path = filepath.Join(ctx.Config.Dist, archive)
func archFor(key string) string {
if strings.Contains(key, "386") {
return "i386"
}
return "x86_64"
}
func create(ctx *context.Context, format, folder, arch string) error {
var path = filepath.Join(ctx.Config.Dist, folder)
var file = path + "." + format
var name = ctx.Config.Build.Binary
log.WithField("file", file).Info("Creating")
log.WithField("file", file).Info("creating fpm archive")
var options = []string{
"--input-type", "dir",
"--output-type", format,
"--name", name,
"--name", ctx.Config.ProjectName,
"--version", ctx.Version,
"--architecture", arch,
"--chdir", path,
@ -94,9 +99,21 @@ func create(ctx *context.Context, format, archive, arch string) error {
options = append(options, "--conflicts", conflict)
}
// 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))
files, err := ioutil.ReadDir(path)
if err != nil {
return err
}
for _, file := range files {
// XXX: skip non executable files here?
// This basically tells fpm to put the binary in the /usr/local/bin
// binary=/usr/local/bin/binary
log.WithField("file", file.Name()).Debug("passed binary to fpm")
options = append(options, fmt.Sprintf(
"%s=%s",
file.Name(),
filepath.Join("/usr/local/bin", file.Name()),
))
}
if out, err := exec.Command("fpm", options...).CombinedOutput(); err != nil {
return errors.New(string(out))

View File

@ -33,18 +33,14 @@ func TestRunPipe(t *testing.T) {
_, err = os.Create(filepath.Join(dist, "mybin", "mybin"))
assert.NoError(err)
var ctx = &context.Context{
Archives: map[string]string{
"linuxamd64": "mybin",
Folders: map[string]string{
"linuxamd64": "mybin",
"linux386": "mybin",
"darwinamd64": "anotherbin",
},
Config: config.Project{
Dist: dist,
Build: config.Build{
Goarch: []string{
"amd64",
"i386",
},
Binary: "mybin",
},
ProjectName: "mybin",
Dist: dist,
FPM: config.FPM{
Formats: []string{"deb"},
Dependencies: []string{"make"},
@ -85,18 +81,11 @@ func TestCreateFileDoesntExist(t *testing.T) {
assert.NoError(os.Mkdir(dist, 0755))
assert.NoError(os.Mkdir(filepath.Join(dist, "mybin"), 0755))
var ctx = &context.Context{
Archives: map[string]string{
Folders: map[string]string{
"linuxamd64": "mybin",
},
Config: config.Project{
Dist: dist,
Build: config.Build{
Goarch: []string{
"amd64",
"i386",
},
Binary: "mybin",
},
FPM: config.FPM{
Formats: []string{"deb"},
},
@ -104,3 +93,12 @@ func TestCreateFileDoesntExist(t *testing.T) {
}
assert.Error(Pipe{}.Run(ctx))
}
func TestCreatePathDoesntExist(t *testing.T) {
var assert = assert.New(t)
var ctx = &context.Context{}
assert.Contains(
create(ctx, "tar.gz", "/nope/noooo", "windowsarm64").Error(),
"no such file",
)
}