mirror of
https://github.com/goreleaser/goreleaser.git
synced 2025-01-08 03:31:59 +02:00
e8c8a2832f
* feat: improve universal binaries on krew/brew/gofish Signed-off-by: Carlos A Becker <caarlos0@gmail.com> * test: OnlyReplacingUnibins Signed-off-by: Carlos A Becker <caarlos0@gmail.com> * fix: archive Signed-off-by: Carlos A Becker <caarlos0@gmail.com> * fix: do not replace single-arch Signed-off-by: Carlos A Becker <caarlos0@gmail.com>
234 lines
5.7 KiB
Go
234 lines
5.7 KiB
Go
// Package universalbinary can join multiple darwin binaries into a single universal binary.
|
|
package universalbinary
|
|
|
|
import (
|
|
"debug/macho"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"os"
|
|
"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"
|
|
)
|
|
|
|
// Pipe for macos universal binaries.
|
|
type Pipe struct{}
|
|
|
|
func (Pipe) String() string { return "universal binaries" }
|
|
func (Pipe) Skip(ctx *context.Context) bool { return len(ctx.Config.UniversalBinaries) == 0 }
|
|
|
|
// Default sets the pipe defaults.
|
|
func (Pipe) Default(ctx *context.Context) error {
|
|
ids := ids.New("universal_binaries")
|
|
for i := range ctx.Config.UniversalBinaries {
|
|
unibin := &ctx.Config.UniversalBinaries[i]
|
|
if unibin.ID == "" {
|
|
unibin.ID = ctx.Config.ProjectName
|
|
}
|
|
if unibin.NameTemplate == "" {
|
|
unibin.NameTemplate = "{{ .ProjectName }}"
|
|
}
|
|
ids.Inc(unibin.ID)
|
|
}
|
|
return ids.Validate()
|
|
}
|
|
|
|
// Run the pipe.
|
|
func (Pipe) Run(ctx *context.Context) error {
|
|
g := semerrgroup.NewSkipAware(semerrgroup.New(ctx.Parallelism))
|
|
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
|
|
}
|
|
return ctx.Artifacts.Remove(filterFor(unibin))
|
|
})
|
|
}
|
|
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
|
|
subcpu uint32
|
|
offset int64
|
|
}
|
|
|
|
const (
|
|
// Alignment wanted for each sub-file.
|
|
// amd64 needs 12 bits, arm64 needs 14. We choose the max of all requirements here.
|
|
alignBits = 14
|
|
align = 1 << alignBits
|
|
)
|
|
|
|
// heavily based on https://github.com/randall77/makefat
|
|
func makeUniversalBinary(ctx *context.Context, unibin config.UniversalBinary) error {
|
|
name, err := tmpl.New(ctx).Apply(unibin.NameTemplate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
path := filepath.Join(ctx.Config.Dist, name+"_darwin_all", name)
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
return err
|
|
}
|
|
|
|
binaries := ctx.Artifacts.Filter(filterFor(unibin)).List()
|
|
if len(binaries) == 0 {
|
|
return pipe.Skip(fmt.Sprintf("no darwin binaries found with id %q", unibin.ID))
|
|
}
|
|
|
|
log.WithField("binary", path).Infof("creating from %d binaries", len(binaries))
|
|
|
|
var inputs []input
|
|
offset := int64(align)
|
|
for _, f := range binaries {
|
|
data, err := os.ReadFile(f.Path)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read binary: %w", err)
|
|
}
|
|
inputs = append(inputs, input{
|
|
data: data,
|
|
cpu: binary.LittleEndian.Uint32(data[4:8]),
|
|
subcpu: binary.LittleEndian.Uint32(data[8:12]),
|
|
offset: offset,
|
|
})
|
|
offset += int64(len(data))
|
|
offset = (offset + align - 1) / align * align
|
|
}
|
|
|
|
// Make output file.
|
|
out, err := os.Create(path)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create file: %w", err)
|
|
}
|
|
defer out.Close()
|
|
|
|
if err := out.Chmod(0o755); err != nil {
|
|
return fmt.Errorf("failed to create file: %w", err)
|
|
}
|
|
|
|
// Build a fat_header.
|
|
hdr := []uint32{macho.MagicFat, uint32(len(inputs))}
|
|
|
|
// Build a fat_arch for each input file.
|
|
for _, i := range inputs {
|
|
hdr = append(hdr, i.cpu)
|
|
hdr = append(hdr, i.subcpu)
|
|
hdr = append(hdr, uint32(i.offset))
|
|
hdr = append(hdr, uint32(len(i.data)))
|
|
hdr = append(hdr, alignBits)
|
|
}
|
|
|
|
// Write header.
|
|
// Note that the fat binary header is big-endian, regardless of the
|
|
// endianness of the contained files.
|
|
if err := binary.Write(out, binary.BigEndian, hdr); err != nil {
|
|
return fmt.Errorf("failed to write to file: %w", err)
|
|
}
|
|
offset = int64(4 * len(hdr))
|
|
|
|
// Write each contained file.
|
|
for _, i := range inputs {
|
|
if offset < i.offset {
|
|
if _, err := out.Write(make([]byte, i.offset-offset)); err != nil {
|
|
return fmt.Errorf("failed to write to file: %w", err)
|
|
}
|
|
offset = i.offset
|
|
}
|
|
if _, err := out.Write(i.data); err != nil {
|
|
return fmt.Errorf("failed to write to file: %w", err)
|
|
}
|
|
offset += int64(len(i.data))
|
|
}
|
|
|
|
if err := out.Close(); err != nil {
|
|
return fmt.Errorf("failed to close file: %w", err)
|
|
}
|
|
|
|
extra := map[string]interface{}{}
|
|
for k, v := range binaries[0].Extra {
|
|
extra[k] = v
|
|
}
|
|
extra[artifact.ExtraReplaces] = unibin.Replace
|
|
|
|
ctx.Artifacts.Add(&artifact.Artifact{
|
|
Type: artifact.UniversalBinary,
|
|
Name: name,
|
|
Path: path,
|
|
Goos: "darwin",
|
|
Goarch: "all",
|
|
Extra: extra,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func filterFor(unibin config.UniversalBinary) artifact.Filter {
|
|
return artifact.And(
|
|
artifact.ByType(artifact.Binary),
|
|
artifact.ByGoos("darwin"),
|
|
artifact.ByIDs(unibin.ID),
|
|
)
|
|
}
|