1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-03-19 20:57:53 +02:00
Carlos Alexandro Becker 09be848e1a
feat(build): initial support for zig (#5312)
Continuing on #5308 and #5307, this finally adds Zig support to
GoReleaser!

Things are very raw still, plenty of use cases to test (please do test
on your project if you can), but it does work at least for simple
projects!

A release done by this:
https://github.com/goreleaser/example-zig/releases/tag/v0.1.0

---------

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2024-11-28 08:47:20 -03:00

229 lines
4.7 KiB
Go

package zig
import (
"errors"
"fmt"
"os/exec"
"path/filepath"
"slices"
"strings"
"github.com/caarlos0/log"
"github.com/goreleaser/goreleaser/v2/internal/artifact"
"github.com/goreleaser/goreleaser/v2/internal/gio"
"github.com/goreleaser/goreleaser/v2/internal/tmpl"
api "github.com/goreleaser/goreleaser/v2/pkg/build"
"github.com/goreleaser/goreleaser/v2/pkg/config"
"github.com/goreleaser/goreleaser/v2/pkg/context"
)
// Default builder instance.
//
//nolint:gochecknoglobals
var Default = &Builder{}
//nolint:gochecknoinits
func init() {
api.Register("zig", Default)
}
// Builder is golang builder.
type Builder struct{}
// Parse implements build.Builder.
func (b *Builder) Parse(target string) (api.Target, error) {
parts := strings.Split(target, "-")
if len(parts) < 2 {
return nil, fmt.Errorf("%s is not a valid build target", target)
}
t := Target{
Target: target,
Os: convertToGoos(parts[1]),
Arch: convertToGoarch(parts[0]),
}
if len(parts) > 2 {
t.Abi = parts[2]
}
return t, nil
}
// WithDefaults implements build.Builder.
func (b *Builder) WithDefaults(build config.Build) (config.Build, error) {
log.Warn("you are using the experimental Zig builder")
if len(build.Targets) == 0 {
build.Targets = defaultTargets()
}
if build.GoBinary == "" {
build.GoBinary = "zig"
}
if build.Command == "" {
build.Command = "build"
}
if build.Dir == "" {
build.Dir = "."
}
if build.Main != "" {
return build, errors.New("main is not used for zig")
}
if len(build.Ldflags) > 0 {
return build, errors.New("ldflags is not used for zig")
}
if len(slices.Concat(
build.Goos,
build.Goarch,
build.Goamd64,
build.Go386,
build.Goarm,
build.Goarm64,
build.Gomips,
build.Goppc64,
build.Goriscv64,
)) > 0 {
return build, errors.New("all go* fields are not used for zig, set targets instead")
}
if len(build.Ignore) > 0 {
return build, errors.New("ignore is not used for zig, set targets instead")
}
if build.Buildmode != "" {
return build, errors.New("buildmode is not used for zig")
}
if len(build.Tags) > 0 {
return build, errors.New("tags is not used for zig")
}
if len(build.Asmflags) > 0 {
return build, errors.New("asmtags is not used for zig")
}
if len(build.BuildDetailsOverrides) > 0 {
return build, errors.New("overrides is not used for zig")
}
for _, t := range build.Targets {
switch checkTarget(t) {
case targetValid:
// lfg
case targetBroken:
log.Warnf("target might not be supported: %s", t)
case targetInvalid:
return build, fmt.Errorf("invalid target: %s", t)
}
}
return build, nil
}
// Build implements build.Builder.
func (b *Builder) Build(ctx *context.Context, build config.Build, options api.Options) error {
prefix := filepath.Dir(options.Path)
options.Path = filepath.Join(prefix, "bin", options.Name)
t := options.Target.(Target)
a := &artifact.Artifact{
Type: artifact.Binary,
Path: options.Path,
Name: options.Name,
Goos: convertToGoos(t.Os),
Goarch: convertToGoarch(t.Arch),
Target: t.Target,
Extra: map[string]interface{}{
artifact.ExtraBinary: strings.TrimSuffix(filepath.Base(options.Path), options.Ext),
artifact.ExtraExt: options.Ext,
artifact.ExtraID: build.ID,
artifact.ExtraBuilder: "zig",
},
}
env := []string{}
env = append(env, ctx.Env.Strings()...)
tpl := tmpl.New(ctx).
WithBuildOptions(options).
WithEnvS(env).
WithArtifact(a)
zigbin, err := tpl.Apply(build.GoBinary)
if err != nil {
return err
}
command := []string{
zigbin,
build.Command,
"-Dtarget=" + t.Target,
"-p", prefix,
}
for _, e := range build.Env {
ee, err := tpl.Apply(e)
if err != nil {
return err
}
log.Debugf("env %q evaluated to %q", e, ee)
if ee != "" {
env = append(env, ee)
}
}
tpl = tpl.WithEnvS(env)
flags, err := processFlags(tpl, build.Flags)
if err != nil {
return err
}
command = append(command, flags...)
/* #nosec */
cmd := exec.CommandContext(ctx, command[0], command[1:]...)
cmd.Env = env
cmd.Dir = build.Dir
log.Debug("running")
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("%w: %s", err, string(out))
}
if s := string(out); s != "" {
log.WithField("cmd", command).Info(s)
}
// TODO: move this to outside builder for both go and zig
modTimestamp, err := tpl.Apply(build.ModTimestamp)
if err != nil {
return err
}
if err := gio.Chtimes(a.Path, modTimestamp); err != nil {
return err
}
ctx.Artifacts.Add(a)
return nil
}
func processFlags(tpl *tmpl.Template, flags []string) ([]string, error) {
var processed []string
for _, rawFlag := range flags {
flag, err := tpl.Apply(rawFlag)
if err != nil {
return nil, err
}
if flag == "" {
continue
}
processed = append(processed, flag)
}
return processed, nil
}