mirror of
https://github.com/goreleaser/goreleaser.git
synced 2025-02-07 13:31:37 +02:00
feat: support multiple build systems
This commit is contained in:
parent
395dd1811c
commit
a457ae6e17
55
build/build.go
Normal file
55
build/build.go
Normal file
@ -0,0 +1,55 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/goreleaser/goreleaser/build/buildtarget"
|
||||
"github.com/goreleaser/goreleaser/config"
|
||||
"github.com/goreleaser/goreleaser/context"
|
||||
)
|
||||
|
||||
var (
|
||||
builders = map[string]Builder{}
|
||||
lock sync.Mutex
|
||||
)
|
||||
|
||||
func Register(lang string, builder Builder) {
|
||||
lock.Lock()
|
||||
builders[lang] = builder
|
||||
lock.Unlock()
|
||||
}
|
||||
|
||||
func For(lang string) Builder {
|
||||
return builders[lang]
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Target buildtarget.Target
|
||||
Name, Path, Ext string
|
||||
}
|
||||
|
||||
type Builder interface {
|
||||
Build(ctx *context.Context, build config.Build, options Options) error
|
||||
}
|
||||
|
||||
func Run(ctx *context.Context, target buildtarget.Target, command, env []string) error {
|
||||
|
||||
/* #nosec */
|
||||
var cmd = exec.CommandContext(ctx, command[0], command[1:]...)
|
||||
env = append(env, target.Env()...)
|
||||
var log = log.WithField("target", target.PrettyString()).
|
||||
WithField("env", env).
|
||||
WithField("cmd", command)
|
||||
cmd.Env = append(cmd.Env, os.Environ()...)
|
||||
cmd.Env = append(cmd.Env, env...)
|
||||
log.Debug("running")
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
log.WithError(err).Debug("failed")
|
||||
return errors.New(string(out))
|
||||
}
|
||||
return nil
|
||||
}
|
30
build/build_test.go
Normal file
30
build/build_test.go
Normal file
@ -0,0 +1,30 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/goreleaser/goreleaser/build/buildtarget"
|
||||
"github.com/goreleaser/goreleaser/config"
|
||||
"github.com/goreleaser/goreleaser/context"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var emptyEnv []string
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
assert.NoError(t, Run(
|
||||
context.New(config.Project{}),
|
||||
buildtarget.Runtime,
|
||||
[]string{"go", "list", "./..."},
|
||||
emptyEnv,
|
||||
))
|
||||
}
|
||||
|
||||
func TestRunInvalidCommand(t *testing.T) {
|
||||
assert.Error(t, Run(
|
||||
context.New(config.Project{}),
|
||||
buildtarget.Runtime,
|
||||
[]string{"gggggo", "nope"},
|
||||
emptyEnv,
|
||||
))
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package ext
|
||||
|
||||
import "github.com/goreleaser/goreleaser/internal/buildtarget"
|
||||
import "github.com/goreleaser/goreleaser/build/buildtarget"
|
||||
|
||||
// For returns the binary extension for the given platform
|
||||
func For(target buildtarget.Target) string {
|
@ -3,7 +3,7 @@ package ext
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/goreleaser/goreleaser/internal/buildtarget"
|
||||
"github.com/goreleaser/goreleaser/build/buildtarget"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
@ -92,6 +92,7 @@ type Build struct {
|
||||
Binary string `yaml:",omitempty"`
|
||||
Hooks Hooks `yaml:",omitempty"`
|
||||
Env []string `yaml:",omitempty"`
|
||||
Lang string `yaml:",omitempty"`
|
||||
|
||||
// Capture all undefined fields and should be empty after loading
|
||||
XXX map[string]interface{} `yaml:",inline"`
|
||||
|
132
internal/builders/golang/build.go
Normal file
132
internal/builders/golang/build.go
Normal file
@ -0,0 +1,132 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/goreleaser/goreleaser/build"
|
||||
"github.com/goreleaser/goreleaser/config"
|
||||
"github.com/goreleaser/goreleaser/context"
|
||||
"github.com/goreleaser/goreleaser/internal/artifact"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var Default = &Builder{}
|
||||
|
||||
func init() {
|
||||
build.Register("go", Default)
|
||||
}
|
||||
|
||||
type Builder struct {
|
||||
}
|
||||
|
||||
func (*Builder) Build(ctx *context.Context, cfg config.Build, options build.Options) error {
|
||||
if err := checkMain(ctx, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := []string{"go", "build"}
|
||||
if cfg.Flags != "" {
|
||||
cmd = append(cmd, strings.Fields(cfg.Flags)...)
|
||||
}
|
||||
flags, err := ldflags(ctx, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd = append(cmd, "-ldflags="+flags, "-o", options.Path, cfg.Main)
|
||||
if err := build.Run(ctx, options.Target, cmd, cfg.Env); err != nil {
|
||||
return errors.Wrapf(err, "failed to build for %s", options.Target)
|
||||
}
|
||||
ctx.Artifacts.Add(artifact.Artifact{
|
||||
Type: artifact.Binary,
|
||||
Path: options.Path,
|
||||
Name: options.Name,
|
||||
Goos: options.Target.OS,
|
||||
Goarch: options.Target.Arch,
|
||||
Goarm: options.Target.Arm,
|
||||
Extra: map[string]string{
|
||||
"Binary": cfg.Binary,
|
||||
"Ext": options.Ext,
|
||||
},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
type ldflagsData struct {
|
||||
Date string
|
||||
Tag string
|
||||
Commit string
|
||||
Version string
|
||||
Env map[string]string
|
||||
}
|
||||
|
||||
func ldflags(ctx *context.Context, build config.Build) (string, error) {
|
||||
var data = ldflagsData{
|
||||
Commit: ctx.Git.Commit,
|
||||
Tag: ctx.Git.CurrentTag,
|
||||
Version: ctx.Version,
|
||||
Date: time.Now().UTC().Format(time.RFC3339),
|
||||
Env: ctx.Env,
|
||||
}
|
||||
var out bytes.Buffer
|
||||
t, err := template.New("ldflags").
|
||||
Option("missingkey=error").
|
||||
Parse(build.Ldflags)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = t.Execute(&out, data)
|
||||
return out.String(), err
|
||||
}
|
||||
|
||||
func checkMain(ctx *context.Context, build config.Build) error {
|
||||
var main = build.Main
|
||||
if main == "" {
|
||||
main = "."
|
||||
}
|
||||
stat, ferr := os.Stat(main)
|
||||
if os.IsNotExist(ferr) {
|
||||
return errors.Wrapf(ferr, "could not open %s", main)
|
||||
}
|
||||
if stat.IsDir() {
|
||||
packs, err := parser.ParseDir(token.NewFileSet(), main, nil, 0)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to parse dir: %s", main)
|
||||
}
|
||||
for _, pack := range packs {
|
||||
for _, file := range pack.Files {
|
||||
if hasMain(file) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("build for %s does not contain a main function", build.Binary)
|
||||
}
|
||||
file, err := parser.ParseFile(token.NewFileSet(), build.Main, nil, 0)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to parse file: %s", build.Main)
|
||||
}
|
||||
if hasMain(file) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("build for %s does not contain a main function", build.Binary)
|
||||
}
|
||||
|
||||
func hasMain(file *ast.File) bool {
|
||||
for _, decl := range file.Decls {
|
||||
fn, isFn := decl.(*ast.FuncDecl)
|
||||
if !isFn {
|
||||
continue
|
||||
}
|
||||
if fn.Name.Name == "main" && fn.Recv == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package build
|
||||
package golang
|
||||
|
||||
import (
|
||||
"testing"
|
@ -1,8 +1,7 @@
|
||||
// Package build needs to be documented
|
||||
package build
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@ -10,11 +9,14 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
builders "github.com/goreleaser/goreleaser/build"
|
||||
"github.com/goreleaser/goreleaser/build/buildtarget"
|
||||
"github.com/goreleaser/goreleaser/build/ext"
|
||||
"github.com/goreleaser/goreleaser/config"
|
||||
"github.com/goreleaser/goreleaser/context"
|
||||
"github.com/goreleaser/goreleaser/internal/artifact"
|
||||
"github.com/goreleaser/goreleaser/internal/buildtarget"
|
||||
"github.com/goreleaser/goreleaser/internal/ext"
|
||||
|
||||
// langs to init
|
||||
_ "github.com/goreleaser/goreleaser/internal/builders/golang"
|
||||
)
|
||||
|
||||
// Pipe for build
|
||||
@ -28,9 +30,6 @@ func (Pipe) String() string {
|
||||
func (Pipe) Run(ctx *context.Context) error {
|
||||
for _, build := range ctx.Config.Builds {
|
||||
log.WithField("build", build).Debug("building")
|
||||
if err := checkMain(ctx, build); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := runPipeOnBuild(ctx, build); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -52,6 +51,9 @@ func (Pipe) Default(ctx *context.Context) error {
|
||||
}
|
||||
|
||||
func buildWithDefaults(ctx *context.Context, build config.Build) config.Build {
|
||||
if build.Lang == "" {
|
||||
build.Lang = "go"
|
||||
}
|
||||
if build.Binary == "" {
|
||||
build.Binary = ctx.Config.Release.GitHub.Name
|
||||
}
|
||||
@ -102,7 +104,7 @@ func runHook(ctx *context.Context, env []string, hook string) error {
|
||||
}
|
||||
log.WithField("hook", hook).Info("running hook")
|
||||
cmd := strings.Fields(hook)
|
||||
return run(ctx, buildtarget.Runtime, cmd, env)
|
||||
return builders.Run(ctx, buildtarget.Runtime, cmd, env)
|
||||
}
|
||||
|
||||
func doBuild(ctx *context.Context, build config.Build, target buildtarget.Target) error {
|
||||
@ -110,46 +112,10 @@ func doBuild(ctx *context.Context, build config.Build, target buildtarget.Target
|
||||
var binaryName = build.Binary + ext
|
||||
var binary = filepath.Join(ctx.Config.Dist, target.String(), binaryName)
|
||||
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)
|
||||
if err := run(ctx, target, cmd, build.Env); err != nil {
|
||||
return errors.Wrapf(err, "failed to build for %s", target)
|
||||
}
|
||||
ctx.Artifacts.Add(artifact.Artifact{
|
||||
Type: artifact.Binary,
|
||||
Path: binary,
|
||||
return builders.For(build.Lang).Build(ctx, build, builders.Options{
|
||||
Target: target,
|
||||
Name: binaryName,
|
||||
Goos: target.OS,
|
||||
Goarch: target.Arch,
|
||||
Goarm: target.Arm,
|
||||
Extra: map[string]string{
|
||||
"Binary": build.Binary,
|
||||
"Ext": ext,
|
||||
},
|
||||
Path: binary,
|
||||
Ext: ext,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func run(ctx *context.Context, target buildtarget.Target, command, env []string) error {
|
||||
/* #nosec */
|
||||
var cmd = exec.CommandContext(ctx, command[0], command[1:]...)
|
||||
env = append(env, target.Env()...)
|
||||
var log = log.WithField("target", target.PrettyString()).
|
||||
WithField("env", env).
|
||||
WithField("cmd", command)
|
||||
cmd.Env = append(cmd.Env, os.Environ()...)
|
||||
cmd.Env = append(cmd.Env, env...)
|
||||
log.Debug("running")
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
log.WithError(err).Debug("failed")
|
||||
return errors.New(string(out))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -7,9 +7,9 @@ import (
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/goreleaser/goreleaser/build/buildtarget"
|
||||
"github.com/goreleaser/goreleaser/config"
|
||||
"github.com/goreleaser/goreleaser/context"
|
||||
"github.com/goreleaser/goreleaser/internal/buildtarget"
|
||||
"github.com/goreleaser/goreleaser/internal/testlib"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -20,24 +20,6 @@ func TestPipeDescription(t *testing.T) {
|
||||
assert.NotEmpty(t, Pipe{}.String())
|
||||
}
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
assert.NoError(t, run(
|
||||
context.New(config.Project{}),
|
||||
buildtarget.Runtime,
|
||||
[]string{"go", "list", "./..."},
|
||||
emptyEnv,
|
||||
))
|
||||
}
|
||||
|
||||
func TestRunInvalidCommand(t *testing.T) {
|
||||
assert.Error(t, run(
|
||||
context.New(config.Project{}),
|
||||
buildtarget.Runtime,
|
||||
[]string{"gggggo", "nope"},
|
||||
emptyEnv,
|
||||
))
|
||||
}
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
var config = config.Project{
|
||||
Builds: []config.Build{
|
||||
|
@ -1,59 +0,0 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
|
||||
"github.com/goreleaser/goreleaser/config"
|
||||
"github.com/goreleaser/goreleaser/context"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func checkMain(ctx *context.Context, build config.Build) error {
|
||||
var main = build.Main
|
||||
if main == "" {
|
||||
main = "."
|
||||
}
|
||||
stat, ferr := os.Stat(main)
|
||||
if os.IsNotExist(ferr) {
|
||||
return errors.Wrapf(ferr, "could not open %s", main)
|
||||
}
|
||||
if stat.IsDir() {
|
||||
packs, err := parser.ParseDir(token.NewFileSet(), main, nil, 0)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to parse dir: %s", main)
|
||||
}
|
||||
for _, pack := range packs {
|
||||
for _, file := range pack.Files {
|
||||
if hasMain(file) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("build for %s does not contain a main function", build.Binary)
|
||||
}
|
||||
file, err := parser.ParseFile(token.NewFileSet(), build.Main, nil, 0)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to parse file: %s", build.Main)
|
||||
}
|
||||
if hasMain(file) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("build for %s does not contain a main function", build.Binary)
|
||||
}
|
||||
|
||||
func hasMain(file *ast.File) bool {
|
||||
for _, decl := range file.Decls {
|
||||
fn, isFn := decl.(*ast.FuncDecl)
|
||||
if !isFn {
|
||||
continue
|
||||
}
|
||||
if fn.Name.Name == "main" && fn.Recv == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
// Package build implements Piper and Defaulter and can build Go projects for
|
||||
// several platforms, with pre and post hook support.
|
||||
// Build also checks wether the current project has a main function, parses
|
||||
// ldflags and other goodies.
|
||||
package build
|
@ -1,37 +0,0 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/goreleaser/goreleaser/config"
|
||||
"github.com/goreleaser/goreleaser/context"
|
||||
)
|
||||
|
||||
type ldflagsData struct {
|
||||
Date string
|
||||
Tag string
|
||||
Commit string
|
||||
Version string
|
||||
Env map[string]string
|
||||
}
|
||||
|
||||
func ldflags(ctx *context.Context, build config.Build) (string, error) {
|
||||
var data = ldflagsData{
|
||||
Commit: ctx.Git.Commit,
|
||||
Tag: ctx.Git.CurrentTag,
|
||||
Version: ctx.Version,
|
||||
Date: time.Now().UTC().Format(time.RFC3339),
|
||||
Env: ctx.Env,
|
||||
}
|
||||
var out bytes.Buffer
|
||||
t, err := template.New("ldflags").
|
||||
Option("missingkey=error").
|
||||
Parse(build.Ldflags)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = t.Execute(&out, data)
|
||||
return out.String(), err
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user