1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-03-11 14:39:28 +02:00

feat: add hooks to universal binaries (#2684)

* add hooks for universal binaries

* task fmt
This commit is contained in:
Weslei Juan Novaes Pereira 2021-11-23 15:53:12 -03:00 committed by GitHub
parent 611313571e
commit f9b693edf0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 173 additions and 36 deletions

View File

@ -13,7 +13,6 @@ import (
type Skipper interface {
// Skip returns true if the Piper should be skipped.
Skip(ctx *context.Context) bool
fmt.Stringer
}

View File

@ -24,7 +24,6 @@ import (
// Announcer should be implemented by pipes that want to announce releases.
type Announcer interface {
fmt.Stringer
Announce(ctx *context.Context) error
}

View File

@ -3,20 +3,16 @@
package build
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/apex/log"
"github.com/caarlos0/go-shellwords"
"github.com/goreleaser/goreleaser/internal/gio"
"github.com/goreleaser/goreleaser/internal/ids"
"github.com/goreleaser/goreleaser/internal/logext"
"github.com/goreleaser/goreleaser/internal/semerrgroup"
"github.com/goreleaser/goreleaser/internal/shell"
"github.com/goreleaser/goreleaser/internal/tmpl"
builders "github.com/goreleaser/goreleaser/pkg/build"
"github.com/goreleaser/goreleaser/pkg/config"
@ -151,7 +147,7 @@ func runHook(ctx *context.Context, opts builders.Options, buildEnv []string, hoo
return err
}
if err := run(ctx, dir, cmd, env); err != nil {
if err := shell.Run(ctx, dir, cmd, env); err != nil {
return err
}
}
@ -230,26 +226,3 @@ func extFor(target string, flags config.FlagArray) string {
}
return ""
}
func run(ctx *context.Context, dir string, command, env []string) error {
fields := log.Fields{
"cmd": command,
"env": env,
}
/* #nosec */
cmd := exec.CommandContext(ctx, command[0], command[1:]...)
cmd.Env = env
var b bytes.Buffer
w := gio.Safe(&b)
cmd.Stderr = io.MultiWriter(logext.NewWriter(fields, logext.Error), w)
cmd.Stdout = io.MultiWriter(logext.NewWriter(fields, logext.Info), w)
if dir != "" {
cmd.Dir = dir
}
log.WithFields(fields).Debug("running")
if err := cmd.Run(); err != nil {
log.WithFields(fields).WithError(err).Debug("failed")
return fmt.Errorf("%q: %w", b.String(), err)
}
return nil
}

View File

@ -9,10 +9,12 @@ import (
"path/filepath"
"github.com/apex/log"
"github.com/caarlos0/go-shellwords"
"github.com/goreleaser/goreleaser/internal/artifact"
"github.com/goreleaser/goreleaser/internal/ids"
"github.com/goreleaser/goreleaser/internal/pipe"
"github.com/goreleaser/goreleaser/internal/semerrgroup"
"github.com/goreleaser/goreleaser/internal/shell"
"github.com/goreleaser/goreleaser/internal/tmpl"
"github.com/goreleaser/goreleaser/pkg/config"
"github.com/goreleaser/goreleaser/pkg/context"
@ -46,9 +48,15 @@ func (Pipe) Run(ctx *context.Context) error {
for _, unibin := range ctx.Config.UniversalBinaries {
unibin := unibin
g.Go(func() error {
if err := runHook(ctx, unibin.Hooks.Pre); err != nil {
return fmt.Errorf("pre hook failed: %w", err)
}
if err := makeUniversalBinary(ctx, unibin); err != nil {
return err
}
if err := runHook(ctx, unibin.Hooks.Post); err != nil {
return fmt.Errorf("post hook failed: %w", err)
}
if !unibin.Replace {
return nil
}
@ -58,6 +66,47 @@ func (Pipe) Run(ctx *context.Context) error {
return g.Wait()
}
func runHook(ctx *context.Context, hooks config.Hooks) error {
if len(hooks) == 0 {
return nil
}
for _, hook := range hooks {
var envs []string
envs = append(envs, ctx.Env.Strings()...)
for _, rawEnv := range hook.Env {
env, err := tmpl.New(ctx).Apply(rawEnv)
if err != nil {
return err
}
envs = append(envs, env)
}
dir, err := tmpl.New(ctx).Apply(hook.Dir)
if err != nil {
return err
}
sh, err := tmpl.New(ctx).WithEnvS(envs).Apply(hook.Cmd)
if err != nil {
return err
}
log.WithField("hook", sh).Info("running hook")
cmd, err := shellwords.Parse(sh)
if err != nil {
return err
}
if err := shell.Run(ctx, dir, cmd, envs); err != nil {
return err
}
}
return nil
}
type input struct {
data []byte
cpu uint32

View File

@ -104,7 +104,9 @@ func TestRun(t *testing.T) {
"arm64": filepath.Join(dist, "fake_darwin_arm64/fake"),
}
ctx1 := context.New(config.Project{
pre := filepath.Join(dist, "pre")
post := filepath.Join(dist, "post")
cfg := config.Project{
Dist: dist,
UniversalBinaries: []config.UniversalBinary{
{
@ -113,7 +115,8 @@ func TestRun(t *testing.T) {
Replace: true,
},
},
})
}
ctx1 := context.New(cfg)
ctx2 := context.New(config.Project{
Dist: dist,
@ -145,6 +148,24 @@ func TestRun(t *testing.T) {
},
})
ctx5 := context.New(config.Project{
Dist: dist,
UniversalBinaries: []config.UniversalBinary{
{
ID: "foo",
NameTemplate: "foo",
Hooks: config.BuildHookConfig{
Pre: []config.Hook{
{Cmd: "touch " + pre},
},
Post: []config.Hook{
{Cmd: "touch " + post},
},
},
},
},
})
for arch, path := range paths {
cmd := exec.Command("go", "build", "-o", path, src)
cmd.Env = append(os.Environ(), "GOOS=darwin", "GOARCH="+arch)
@ -168,6 +189,7 @@ func TestRun(t *testing.T) {
}
ctx1.Artifacts.Add(&art)
ctx2.Artifacts.Add(&art)
ctx5.Artifacts.Add(&art)
ctx4.Artifacts.Add(&artifact.Artifact{
Name: "fake",
Path: path + "wrong",
@ -212,6 +234,25 @@ func TestRun(t *testing.T) {
t.Run("fail to open", func(t *testing.T) {
require.ErrorIs(t, Pipe{}.Run(ctx4), os.ErrNotExist)
})
t.Run("hooks", func(t *testing.T) {
require.NoError(t, Pipe{}.Run(ctx5))
require.FileExists(t, pre)
require.FileExists(t, post)
})
t.Run("failing pre-hook", func(t *testing.T) {
ctx := ctx5
ctx.Config.UniversalBinaries[0].Hooks.Pre = []config.Hook{{Cmd: "exit 1"}}
ctx.Config.UniversalBinaries[0].Hooks.Post = []config.Hook{{Cmd: "echo post"}}
require.EqualError(t, Pipe{}.Run(ctx), `pre hook failed: "": exec: "exit": executable file not found in $PATH`)
})
t.Run("failing post-hook", func(t *testing.T) {
ctx := ctx5
ctx.Config.UniversalBinaries[0].Hooks.Pre = []config.Hook{{Cmd: "echo pre"}}
ctx.Config.UniversalBinaries[0].Hooks.Post = []config.Hook{{Cmd: "exit 1"}}
require.EqualError(t, Pipe{}.Run(ctx), `post hook failed: "": exec: "exit": executable file not found in $PATH`)
})
}
func checkUniversalBinary(tb testing.TB, unibin *artifact.Artifact) {

44
internal/shell/shell.go Normal file
View File

@ -0,0 +1,44 @@
package shell
import (
"bytes"
"fmt"
"io"
"os/exec"
"github.com/apex/log"
"github.com/goreleaser/goreleaser/internal/gio"
"github.com/goreleaser/goreleaser/internal/logext"
"github.com/goreleaser/goreleaser/pkg/context"
)
// Run a shell command with given arguments and envs
func Run(ctx *context.Context, dir string, command, env []string) error {
fields := log.Fields{
"cmd": command,
"env": env,
}
/* #nosec */
cmd := exec.CommandContext(ctx, command[0], command[1:]...)
cmd.Env = env
var b bytes.Buffer
w := gio.Safe(&b)
cmd.Stderr = io.MultiWriter(logext.NewWriter(fields, logext.Error), w)
cmd.Stdout = io.MultiWriter(logext.NewWriter(fields, logext.Info), w)
if dir != "" {
cmd.Dir = dir
}
log.WithFields(fields).Debug("running")
if err := cmd.Run(); err != nil {
log.WithFields(fields).WithError(err).Debug("failed")
return fmt.Errorf("%q: %w", b.String(), err)
}
return nil
}

View File

@ -0,0 +1,31 @@
package shell_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/goreleaser/goreleaser/internal/shell"
"github.com/goreleaser/goreleaser/pkg/config"
"github.com/goreleaser/goreleaser/pkg/context"
)
func TestRunAValidCommand(t *testing.T) {
assert := assert.New(t)
ctx := context.New(config.Project{})
err := shell.Run(ctx, "", []string{"echo", "test"}, []string{})
assert.NoError(err)
}
func TestRunAnInValidCommand(t *testing.T) {
assert := assert.New(t)
ctx := context.New(config.Project{})
err := shell.Run(ctx, "", []string{"invalid", "command"}, []string{})
assert.Error(err)
assert.Contains(err.Error(), "executable file not found")
}

View File

@ -431,9 +431,10 @@ func (f File) JSONSchemaType() *jsonschema.Type {
// UniversalBinary setups macos universal binaries.
type UniversalBinary struct {
ID string `yaml:"id,omitempty"`
NameTemplate string `yaml:"name_template,omitempty"`
Replace bool `yaml:"replace,omitempty"`
ID string `yaml:"id,omitempty"`
NameTemplate string `yaml:"name_template,omitempty"`
Replace bool `yaml:"replace,omitempty"`
Hooks BuildHookConfig `yaml:"hooks,omitempty"`
}
// Archive config used for the archive.