1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-03-25 21:29:14 +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

232 lines
5.4 KiB
Go

// Package build provides a pipe that can build binaries for several
// languages.
package build
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/caarlos0/go-shellwords"
"github.com/caarlos0/log"
"github.com/goreleaser/goreleaser/v2/internal/ids"
"github.com/goreleaser/goreleaser/v2/internal/semerrgroup"
"github.com/goreleaser/goreleaser/v2/internal/shell"
"github.com/goreleaser/goreleaser/v2/internal/skips"
"github.com/goreleaser/goreleaser/v2/internal/tmpl"
builders "github.com/goreleaser/goreleaser/v2/pkg/build"
"github.com/goreleaser/goreleaser/v2/pkg/config"
"github.com/goreleaser/goreleaser/v2/pkg/context"
// langs to init.
_ "github.com/goreleaser/goreleaser/v2/internal/builders/golang"
_ "github.com/goreleaser/goreleaser/v2/internal/builders/zig"
)
// Pipe for build.
type Pipe struct{}
func (Pipe) String() string {
return "building binaries"
}
// Run the pipe.
func (Pipe) Run(ctx *context.Context) error {
g := semerrgroup.New(ctx.Parallelism)
for _, build := range ctx.Config.Builds {
skip, err := tmpl.New(ctx).Bool(build.Skip)
if err != nil {
return err
}
if skip {
log.WithField("id", build.ID).Info("skip is set")
continue
}
log.WithField("build", build).Debug("building")
runPipeOnBuild(ctx, g, build)
}
return g.Wait()
}
// Default sets the pipe defaults.
func (Pipe) Default(ctx *context.Context) error {
ids := ids.New("builds")
for i, build := range ctx.Config.Builds {
build, err := buildWithDefaults(ctx, build)
if err != nil {
return err
}
ctx.Config.Builds[i] = build
ids.Inc(ctx.Config.Builds[i].ID)
}
if len(ctx.Config.Builds) == 0 {
build, err := buildWithDefaults(ctx, config.Build{})
if err != nil {
return err
}
ctx.Config.Builds = []config.Build{build}
}
return ids.Validate()
}
func buildWithDefaults(ctx *context.Context, build config.Build) (config.Build, error) {
if build.Builder == "" {
build.Builder = "go"
}
if build.Binary == "" {
build.Binary = ctx.Config.ProjectName
}
if build.ID == "" {
build.ID = ctx.Config.ProjectName
}
for k, v := range build.Env {
build.Env[k] = os.ExpandEnv(v)
}
return builders.For(build.Builder).WithDefaults(build)
}
func runPipeOnBuild(ctx *context.Context, g semerrgroup.Group, build config.Build) {
for _, target := range filter(ctx, build.Targets) {
g.Go(func() error {
opts, err := buildOptionsForTarget(ctx, build, target)
if err != nil {
return err
}
if !skips.Any(ctx, skips.PreBuildHooks) {
if err := runHook(ctx, *opts, build.Env, build.Hooks.Pre); err != nil {
return fmt.Errorf("pre hook failed: %w", err)
}
}
if err := doBuild(ctx, build, *opts); err != nil {
return err
}
if !skips.Any(ctx, skips.PostBuildHooks) {
if err := runHook(ctx, *opts, build.Env, build.Hooks.Post); err != nil {
return fmt.Errorf("post hook failed: %w", err)
}
}
return nil
})
}
}
func runHook(ctx *context.Context, opts builders.Options, buildEnv []string, hooks config.Hooks) error {
if len(hooks) == 0 {
return nil
}
for _, hook := range hooks {
var env []string
env = append(env, ctx.Env.Strings()...)
for _, rawEnv := range append(buildEnv, hook.Env...) {
e, err := tmpl.New(ctx).WithBuildOptions(opts).Apply(rawEnv)
if err != nil {
return err
}
env = append(env, e)
}
dir, err := tmpl.New(ctx).WithBuildOptions(opts).Apply(hook.Dir)
if err != nil {
return err
}
sh, err := tmpl.New(ctx).WithBuildOptions(opts).
WithEnvS(env).
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, env, hook.Output); err != nil {
return err
}
}
return nil
}
func doBuild(ctx *context.Context, build config.Build, opts builders.Options) error {
return builders.For(build.Builder).Build(ctx, build, opts)
}
func buildOptionsForTarget(ctx *context.Context, build config.Build, target string) (*builders.Options, error) {
ext := extFor(target, build.BuildDetails)
buildOpts := builders.Options{
Ext: ext,
}
t, err := builders.For(build.Builder).Parse(target)
if err != nil {
return nil, err
}
buildOpts.Target = t
bin, err := tmpl.New(ctx).WithBuildOptions(buildOpts).Apply(build.Binary)
if err != nil {
return nil, err
}
name := bin + ext
dir := fmt.Sprintf("%s_%s", build.ID, t)
noUnique, err := tmpl.New(ctx).Bool(build.NoUniqueDistDir)
if err != nil {
return nil, err
}
if noUnique {
dir = ""
}
relpath := filepath.Join(ctx.Config.Dist, dir, name)
path, err := filepath.Abs(relpath)
if err != nil {
return nil, err
}
buildOpts.Path = path
buildOpts.Name = name
log.WithField("binary", relpath).Info("building")
return &buildOpts, nil
}
func extFor(target string, build config.BuildDetails) string {
// Configure the extensions for shared and static libraries - by default .so and .a respectively -
// with overrides for Windows (.dll for shared and .lib for static) and .dylib for macOS.
switch build.Buildmode {
case "c-shared":
if strings.Contains(target, "darwin") {
return ".dylib"
}
if strings.Contains(target, "windows") {
return ".dll"
}
if strings.Contains(target, "wasm") {
return ".wasm"
}
return ".so"
case "c-archive":
if strings.Contains(target, "windows") {
return ".lib"
}
return ".a"
}
if strings.Contains(target, "wasm") {
return ".wasm"
}
if strings.Contains(target, "windows") {
return ".exe"
}
return ""
}