mirror of
https://github.com/goreleaser/goreleaser.git
synced 2025-03-19 20:57:53 +02:00
Maybe 3rd time is the charm! This makes the CI build run on windows too, and fix broken tests/featuers on Windows. Most of the changes are related to ignoring certain tests on windows, or making sure to use the right path separators. More work to do in the future, probably! #4293 --------- Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
203 lines
4.9 KiB
Go
203 lines
4.9 KiB
Go
// Package exec can execute commands on the OS.
|
|
package exec
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
|
|
"github.com/caarlos0/go-shellwords"
|
|
"github.com/caarlos0/log"
|
|
"github.com/goreleaser/goreleaser/v2/internal/artifact"
|
|
"github.com/goreleaser/goreleaser/v2/internal/extrafiles"
|
|
"github.com/goreleaser/goreleaser/v2/internal/gio"
|
|
"github.com/goreleaser/goreleaser/v2/internal/logext"
|
|
"github.com/goreleaser/goreleaser/v2/internal/pipe"
|
|
"github.com/goreleaser/goreleaser/v2/internal/semerrgroup"
|
|
"github.com/goreleaser/goreleaser/v2/internal/tmpl"
|
|
"github.com/goreleaser/goreleaser/v2/pkg/config"
|
|
"github.com/goreleaser/goreleaser/v2/pkg/context"
|
|
)
|
|
|
|
// Environment variables to pass through to exec
|
|
var passthroughEnvVars = []string{"HOME", "USER", "USERPROFILE", "TMPDIR", "TMP", "TEMP", "PATH", "SYSTEMROOT"}
|
|
|
|
// Execute the given publisher
|
|
func Execute(ctx *context.Context, publishers []config.Publisher) error {
|
|
skips := pipe.SkipMemento{}
|
|
for _, p := range publishers {
|
|
log.WithField("name", p.Name).Debug("executing custom publisher")
|
|
err := executePublisher(ctx, p)
|
|
if err != nil && pipe.IsSkip(err) {
|
|
skips.Remember(err)
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return skips.Evaluate()
|
|
}
|
|
|
|
func executePublisher(ctx *context.Context, publisher config.Publisher) error {
|
|
disabled, err := tmpl.New(ctx).Bool(publisher.Disable)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if disabled {
|
|
return pipe.Skip("publisher is disabled")
|
|
}
|
|
|
|
log.Debugf("filtering %d artifacts", len(ctx.Artifacts.List()))
|
|
artifacts := filterArtifacts(ctx.Artifacts, publisher)
|
|
|
|
extraFiles, err := extrafiles.Find(ctx, publisher.ExtraFiles)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for name, path := range extraFiles {
|
|
artifacts = append(artifacts, &artifact.Artifact{
|
|
Name: name,
|
|
Path: path,
|
|
Type: artifact.UploadableFile,
|
|
})
|
|
}
|
|
|
|
log.Debugf("will execute custom publisher with %d artifacts", len(artifacts))
|
|
|
|
g := semerrgroup.New(ctx.Parallelism)
|
|
for _, artifact := range artifacts {
|
|
g.Go(func() error {
|
|
c, err := resolveCommand(ctx, publisher, artifact)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return executeCommand(c, artifact)
|
|
})
|
|
}
|
|
|
|
return g.Wait()
|
|
}
|
|
|
|
func executeCommand(c *command, artifact *artifact.Artifact) error {
|
|
log.WithField("args", c.Args).
|
|
WithField("artifact", artifact.Name).
|
|
Debug("executing command")
|
|
|
|
//nolint:gosec
|
|
cmd := exec.CommandContext(c.Ctx, c.Args[0], c.Args[1:]...)
|
|
cmd.Env = []string{}
|
|
for _, key := range passthroughEnvVars {
|
|
if value := os.Getenv(key); value != "" {
|
|
cmd.Env = append(cmd.Env, key+"="+value)
|
|
}
|
|
}
|
|
cmd.Env = append(cmd.Env, c.Env...)
|
|
|
|
if c.Dir != "" {
|
|
cmd.Dir = c.Dir
|
|
}
|
|
|
|
var b bytes.Buffer
|
|
w := gio.Safe(&b)
|
|
cmd.Stderr = io.MultiWriter(logext.NewWriter(), w)
|
|
cmd.Stdout = io.MultiWriter(logext.NewWriter(), w)
|
|
|
|
log := log.WithField("cmd", c.Args[0]).
|
|
WithField("artifact", artifact.Name)
|
|
|
|
log.Info("publishing")
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("publishing: %s failed: %w: %s", c.Args[0], err, b.String())
|
|
}
|
|
|
|
log.Debug("command finished successfully")
|
|
return nil
|
|
}
|
|
|
|
func filterArtifacts(artifacts *artifact.Artifacts, publisher config.Publisher) []*artifact.Artifact {
|
|
filters := []artifact.Filter{
|
|
artifact.ByType(artifact.UploadableArchive),
|
|
artifact.ByType(artifact.UploadableFile),
|
|
artifact.ByType(artifact.LinuxPackage),
|
|
artifact.ByType(artifact.UploadableBinary),
|
|
artifact.ByType(artifact.DockerImage),
|
|
artifact.ByType(artifact.DockerManifest),
|
|
}
|
|
|
|
if publisher.Checksum {
|
|
filters = append(filters, artifact.ByType(artifact.Checksum))
|
|
}
|
|
|
|
if publisher.Meta {
|
|
filters = append(filters, artifact.ByType(artifact.Metadata))
|
|
}
|
|
|
|
if publisher.Signature {
|
|
filters = append(filters, artifact.ByType(artifact.Signature), artifact.ByType(artifact.Certificate))
|
|
}
|
|
|
|
filter := artifact.Or(filters...)
|
|
|
|
if len(publisher.IDs) > 0 {
|
|
filter = artifact.And(filter, artifact.ByIDs(publisher.IDs...))
|
|
}
|
|
|
|
return artifacts.Filter(filter).List()
|
|
}
|
|
|
|
type command struct {
|
|
Ctx *context.Context
|
|
Dir string
|
|
Env []string
|
|
Args []string
|
|
}
|
|
|
|
// resolveCommand returns the a command based on publisher template with replaced variables
|
|
// Those variables can be replaced by the given context, goos, goarch, goarm and more.
|
|
func resolveCommand(ctx *context.Context, publisher config.Publisher, artifact *artifact.Artifact) (*command, error) {
|
|
var err error
|
|
dir := publisher.Dir
|
|
|
|
tpl := tmpl.New(ctx).WithArtifact(artifact)
|
|
if dir != "" {
|
|
dir, err = tpl.Apply(dir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
cmd := publisher.Cmd
|
|
if cmd != "" {
|
|
cmd, err = tpl.Apply(cmd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
args, err := shellwords.Parse(cmd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
env := make([]string, len(publisher.Env))
|
|
for i, e := range publisher.Env {
|
|
e, err = tpl.Apply(e)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
env[i] = e
|
|
}
|
|
|
|
return &command{
|
|
Ctx: ctx,
|
|
Dir: dir,
|
|
Env: env,
|
|
Args: args,
|
|
}, nil
|
|
}
|