1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-03-17 20:47:50 +02:00

refactor(build): preparing to support multiple languages (#5307)

This starts laying the foundation for supporting more languages, the
first of which will probably be Zig, and then Rust.

I already have a zig prototype working in another branch, just raw
dogged it to see if it would work, and since it does, now I'll do it
piece by piece but with hopefully slightly better code.
This commit is contained in:
Carlos Alexandro Becker 2024-11-25 23:00:28 -03:00 committed by GitHub
parent f061ae92ad
commit d43f84aa3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 497 additions and 311 deletions

View File

@ -38,6 +38,46 @@ func init() {
// Builder is golang builder.
type Builder struct{}
// Parse implements build.Builder.
func (b *Builder) Parse(target string) (api.Target, error) {
target = fixTarget(target)
parts := strings.Split(target, "_")
if len(parts) < 2 {
return nil, fmt.Errorf("%s is not a valid build target", target)
}
goos := parts[0]
goarch := parts[1]
t := Target{
Target: target,
Goos: goos,
Goarch: goarch,
}
if len(parts) > 2 {
extra := parts[2]
switch goarch {
case "amd64":
t.Goamd64 = extra
case "arm64":
t.Goarm64 = extra
case "386":
t.Go386 = extra
case "arm":
t.Goarm = extra
case "mips", "mipsle", "mips64", "mips64le":
t.Gomips = extra
case "ppc64":
t.Goppc64 = extra
case "riscv":
t.Goriscv64 = extra
}
}
return t, nil
}
// WithDefaults sets the defaults for a golang build and returns it.
func (*Builder) WithDefaults(build config.Build) (config.Build, error) {
if build.GoBinary == "" {
@ -187,19 +227,21 @@ func (*Builder) Build(ctx *context.Context, build config.Build, options api.Opti
return err
}
t := options.Target.(Target)
a := &artifact.Artifact{
Type: artifact.Binary,
Path: options.Path,
Name: options.Name,
Goos: options.Goos,
Goarch: options.Goarch,
Goamd64: options.Goamd64,
Go386: options.Go386,
Goarm: options.Goarm,
Goarm64: options.Goarm64,
Gomips: options.Gomips,
Goppc64: options.Goppc64,
Goriscv64: options.Goriscv64,
Goos: t.Goos,
Goarch: t.Goarch,
Goamd64: t.Goamd64,
Go386: t.Go386,
Goarm: t.Goarm,
Goarm64: t.Goarm64,
Gomips: t.Gomips,
Goppc64: t.Goppc64,
Goriscv64: t.Goriscv64,
Extra: map[string]interface{}{
artifact.ExtraBinary: strings.TrimSuffix(filepath.Base(options.Path), options.Ext),
artifact.ExtraExt: options.Ext,
@ -211,12 +253,12 @@ func (*Builder) Build(ctx *context.Context, build config.Build, options api.Opti
a.Type = artifact.CArchive
ctx.Artifacts.Add(getHeaderArtifactForLibrary(build, options))
}
if build.Buildmode == "c-shared" && !strings.Contains(options.Target, "wasm") {
if build.Buildmode == "c-shared" && !strings.Contains(t.Target, "wasm") {
a.Type = artifact.CShared
ctx.Artifacts.Add(getHeaderArtifactForLibrary(build, options))
}
details, err := withOverrides(ctx, build, options)
details, err := withOverrides(ctx, build, t)
if err != nil {
return err
}
@ -238,20 +280,8 @@ func (*Builder) Build(ctx *context.Context, build config.Build, options api.Opti
}
}
}
env = append(
env,
"GOOS="+options.Goos,
"GOARCH="+options.Goarch,
"GOAMD64="+options.Goamd64,
"GO386="+options.Go386,
"GOARM="+options.Goarm,
"GOARM64="+options.Goarm64,
"GOMIPS="+options.Gomips,
"GOMIPS64="+options.Gomips,
"GOPPC64="+options.Goppc64,
"GORISCV64="+options.Goriscv64,
)
env = append(env, t.env()...)
if v := os.Getenv("GOCACHEPROG"); v != "" {
env = append(env, "GOCACHEPROG="+v)
}
@ -281,18 +311,10 @@ func (*Builder) Build(ctx *context.Context, build config.Build, options api.Opti
return nil
}
func withOverrides(ctx *context.Context, build config.Build, options api.Options) (config.BuildDetails, error) {
optsTarget := options.Goos + "_" + options.Goarch
if extra := options.Goamd64 + options.Go386 + options.Goarm + options.Goarm64 + options.Gomips + options.Goppc64 + options.Goriscv64; extra != "" {
optsTarget += "_" + extra
}
optsTarget = fixTarget(optsTarget)
func withOverrides(ctx *context.Context, build config.Build, target Target) (config.BuildDetails, error) {
optsTarget := target.Target
for _, o := range build.BuildDetailsOverrides {
s := o.Goos + "_" + o.Goarch
if extra := o.Goamd64 + o.Go386 + o.Goarm + o.Goarm64 + o.Gomips + o.Goppc64 + o.Goriscv64; extra != "" {
s += "_" + extra
}
overrideTarget, err := tmpl.New(ctx).Apply(s)
overrideTarget, err := tmpl.New(ctx).Apply(formatTarget(o))
if err != nil {
return build.BuildDetails, err
}
@ -521,20 +543,21 @@ func getHeaderArtifactForLibrary(build config.Build, options api.Options) *artif
basePath := filepath.Base(fullPathWithoutExt)
fullPath := fullPathWithoutExt + ".h"
headerName := basePath + ".h"
t := options.Target.(Target)
return &artifact.Artifact{
Type: artifact.Header,
Path: fullPath,
Name: headerName,
Goos: options.Goos,
Goarch: options.Goarch,
Goamd64: options.Goamd64,
Go386: options.Go386,
Goarm: options.Goarm,
Goarm64: options.Goarm64,
Gomips: options.Gomips,
Goppc64: options.Goppc64,
Goriscv64: options.Goriscv64,
Goos: t.Goos,
Goarch: t.Goarch,
Goamd64: t.Goamd64,
Go386: t.Go386,
Goarm: t.Goarm,
Goarm64: t.Goarm64,
Gomips: t.Gomips,
Goppc64: t.Goppc64,
Goriscv64: t.Goriscv64,
Extra: map[string]interface{}{
artifact.ExtraBinary: headerName,
artifact.ExtraExt: ".h",

View File

@ -11,6 +11,7 @@ import (
"time"
"github.com/goreleaser/goreleaser/v2/internal/artifact"
"github.com/goreleaser/goreleaser/v2/internal/experimental"
"github.com/goreleaser/goreleaser/v2/internal/testctx"
"github.com/goreleaser/goreleaser/v2/internal/testlib"
"github.com/goreleaser/goreleaser/v2/internal/tmpl"
@ -33,6 +34,78 @@ var go118FirstClassAdjustedTargets = []string{
"windows_amd64_v1",
}
func TestParse(t *testing.T) {
for target, dst := range map[string]Target{
"linux_amd64": {
Target: "linux_amd64_v1",
Goos: "linux",
Goarch: "amd64",
Goamd64: "v1",
},
"linux_amd64_v2": {
Target: "linux_amd64_v2",
Goos: "linux",
Goarch: "amd64",
Goamd64: "v2",
},
"linux_arm": {
Target: "linux_arm_" + experimental.DefaultGOARM(),
Goos: "linux",
Goarch: "arm",
Goarm: experimental.DefaultGOARM(),
},
"linux_arm_7": {
Target: "linux_arm_7",
Goos: "linux",
Goarch: "arm",
Goarm: "7",
},
"linux_mips": {
Target: "linux_mips_hardfloat",
Goos: "linux",
Goarch: "mips",
Gomips: "hardfloat",
},
"linux_mips_softfloat": {
Target: "linux_mips_softfloat",
Goos: "linux",
Goarch: "mips",
Gomips: "softfloat",
},
"linux_386": {
Target: "linux_386_sse2",
Goos: "linux",
Goarch: "386",
Go386: "sse2",
},
"linux_386_hardfloat": {
Target: "linux_386_hardfloat",
Goos: "linux",
Goarch: "386",
Go386: "hardfloat",
},
"linux_arm64": {
Target: "linux_arm64_v8.0",
Goos: "linux",
Goarch: "arm64",
Goarm64: "v8.0",
},
"linux_arm64_v9.0": {
Target: "linux_arm64_v9.0",
Goos: "linux",
Goarch: "arm64",
Goarm64: "v9.0",
},
} {
t.Run(target, func(t *testing.T) {
got, err := Default.Parse(target)
require.NoError(t, err)
require.IsType(t, Target{}, got)
require.Equal(t, dst, got.(Target))
})
}
}
func TestWithDefaults(t *testing.T) {
for name, testcase := range map[string]struct {
build config.Build
@ -429,30 +502,14 @@ func TestBuild(t *testing.T) {
// injecting some delay here to force inconsistent mod times on bins
time.Sleep(2 * time.Second)
parts := strings.Split(target, "_")
goos := parts[0]
goarch := parts[1]
goarm := ""
gomips := ""
if len(parts) > 2 {
if strings.Contains(goarch, "arm") {
goarm = parts[2]
}
if strings.Contains(goarch, "mips") {
gomips = parts[2]
}
}
err := Default.Build(ctx, build, api.Options{
Target: target,
gtarget, err := Default.Parse(target)
require.NoError(t, err)
require.NoError(t, Default.Build(ctx, build, api.Options{
Target: gtarget,
Name: bin + ext,
Path: filepath.Join(folder, "dist", target, bin+ext),
Goos: goos,
Goarch: goarch,
Goarm: goarm,
Gomips: gomips,
Ext: ext,
})
require.NoError(t, err)
}))
}
list := ctx.Artifacts
require.NoError(t, list.Visit(func(a *artifact.Artifact) error {
@ -462,13 +519,14 @@ func TestBuild(t *testing.T) {
}
return nil
}))
require.ElementsMatch(t, list.List(), []*artifact.Artifact{
expected := []*artifact.Artifact{
{
Name: "bin/foo-v5.6.7",
Path: filepath.ToSlash(filepath.Join("dist", "linux_amd64", "bin", "foo-v5.6.7")),
Goos: "linux",
Goarch: "amd64",
Type: artifact.Binary,
Name: "bin/foo-v5.6.7",
Path: filepath.ToSlash(filepath.Join("dist", "linux_amd64", "bin", "foo-v5.6.7")),
Goos: "linux",
Goarch: "amd64",
Goamd64: "v1",
Type: artifact.Binary,
Extra: map[string]interface{}{
artifact.ExtraExt: "",
artifact.ExtraBinary: "foo-v5.6.7",
@ -505,11 +563,12 @@ func TestBuild(t *testing.T) {
},
},
{
Name: "bin/foo-v5.6.7",
Path: filepath.ToSlash(filepath.Join("dist", "darwin_amd64", "bin", "foo-v5.6.7")),
Goos: "darwin",
Goarch: "amd64",
Type: artifact.Binary,
Name: "bin/foo-v5.6.7",
Path: filepath.ToSlash(filepath.Join("dist", "darwin_amd64", "bin", "foo-v5.6.7")),
Goos: "darwin",
Goarch: "amd64",
Goamd64: "v1",
Type: artifact.Binary,
Extra: map[string]interface{}{
artifact.ExtraExt: "",
artifact.ExtraBinary: "foo-v5.6.7",
@ -532,11 +591,12 @@ func TestBuild(t *testing.T) {
},
},
{
Name: "bin/foo-v5.6.7.exe",
Path: filepath.ToSlash(filepath.Join("dist", "windows_amd64", "bin", "foo-v5.6.7.exe")),
Goos: "windows",
Goarch: "amd64",
Type: artifact.Binary,
Name: "bin/foo-v5.6.7.exe",
Path: filepath.ToSlash(filepath.Join("dist", "windows_amd64", "bin", "foo-v5.6.7.exe")),
Goos: "windows",
Goarch: "amd64",
Goamd64: "v1",
Type: artifact.Binary,
Extra: map[string]interface{}{
artifact.ExtraExt: ".exe",
artifact.ExtraBinary: "foo-v5.6.7",
@ -557,7 +617,10 @@ func TestBuild(t *testing.T) {
"testEnvs": []string{"TEST_T="},
},
},
})
}
got := list.List()
testlib.RequireEqualArtifacts(t, expected, got)
modTimes := map[int64]bool{}
for _, bin := range ctx.Artifacts.List() {
@ -599,7 +662,7 @@ func TestBuildInvalidEnv(t *testing.T) {
}, testctx.WithCurrentTag("5.6.7"))
build := ctx.Config.Builds[0]
err := Default.Build(ctx, build, api.Options{
Target: runtimeTarget,
Target: mustParse(t, runtimeTarget),
Name: build.Binary,
Path: filepath.Join("dist", runtimeTarget, build.Binary),
Ext: "",
@ -632,7 +695,7 @@ func TestBuildCodeInSubdir(t *testing.T) {
}, testctx.WithCurrentTag("5.6.7"))
build := ctx.Config.Builds[0]
err = Default.Build(ctx, build, api.Options{
Target: runtimeTarget,
Target: mustParse(t, runtimeTarget),
Name: build.Binary,
Path: filepath.Join("dist", runtimeTarget, build.Binary),
Ext: "",
@ -660,7 +723,7 @@ func TestBuildWithDotGoDir(t *testing.T) {
}, testctx.WithCurrentTag("5.6.7"))
build := ctx.Config.Builds[0]
require.NoError(t, Default.Build(ctx, build, api.Options{
Target: runtimeTarget,
Target: mustParse(t, runtimeTarget),
Name: build.Binary,
Path: filepath.Join("dist", runtimeTarget, build.Binary),
Ext: "",
@ -686,7 +749,7 @@ func TestBuildFailed(t *testing.T) {
},
}, testctx.WithCurrentTag("5.6.7"))
err := Default.Build(ctx, ctx.Config.Builds[0], api.Options{
Target: "darwin_amd64",
Target: mustParse(t, "darwin_amd64"),
})
require.ErrorContains(t, err, `flag provided but not defined: -flag-that-dont-exists-to-force-failure`)
require.Empty(t, ctx.Artifacts.List())
@ -709,7 +772,7 @@ func TestRunInvalidAsmflags(t *testing.T) {
},
}, testctx.WithCurrentTag("5.6.7"))
err := Default.Build(ctx, ctx.Config.Builds[0], api.Options{
Target: runtimeTarget,
Target: mustParse(t, runtimeTarget),
})
testlib.RequireTemplateError(t, err)
}
@ -731,7 +794,7 @@ func TestRunInvalidGcflags(t *testing.T) {
},
}, testctx.WithCurrentTag("5.6.7"))
err := Default.Build(ctx, ctx.Config.Builds[0], api.Options{
Target: runtimeTarget,
Target: mustParse(t, runtimeTarget),
})
testlib.RequireTemplateError(t, err)
}
@ -754,7 +817,7 @@ func TestRunInvalidLdflags(t *testing.T) {
},
}, testctx.WithCurrentTag("5.6.7"))
err := Default.Build(ctx, ctx.Config.Builds[0], api.Options{
Target: runtimeTarget,
Target: mustParse(t, runtimeTarget),
})
testlib.RequireTemplateError(t, err)
}
@ -776,7 +839,7 @@ func TestRunInvalidFlags(t *testing.T) {
},
})
err := Default.Build(ctx, ctx.Config.Builds[0], api.Options{
Target: runtimeTarget,
Target: mustParse(t, runtimeTarget),
})
testlib.RequireTemplateError(t, err)
}
@ -795,28 +858,28 @@ func TestRunPipeWithoutMainFunc(t *testing.T) {
ctx := newCtx(t)
ctx.Config.Builds[0].Main = ""
require.EqualError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
Target: runtimeTarget,
Target: mustParse(t, runtimeTarget),
}), errNoMain{"no-main"}.Error())
})
t.Run("not main.go", func(t *testing.T) {
ctx := newCtx(t)
ctx.Config.Builds[0].Main = "foo.go"
require.ErrorIs(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
Target: runtimeTarget,
Target: mustParse(t, runtimeTarget),
}), os.ErrNotExist)
})
t.Run("glob", func(t *testing.T) {
ctx := newCtx(t)
ctx.Config.Builds[0].Main = "."
require.EqualError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
Target: runtimeTarget,
Target: mustParse(t, runtimeTarget),
}), errNoMain{"no-main"}.Error())
})
t.Run("fixed main.go", func(t *testing.T) {
ctx := newCtx(t)
ctx.Config.Builds[0].Main = "main.go"
require.EqualError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
Target: runtimeTarget,
Target: mustParse(t, runtimeTarget),
}), errNoMain{"no-main"}.Error())
})
t.Run("using gomod.proxy", func(t *testing.T) {
@ -827,7 +890,7 @@ func TestRunPipeWithoutMainFunc(t *testing.T) {
ctx.Config.Builds[0].UnproxiedDir = "."
ctx.Config.Builds[0].UnproxiedMain = "."
require.EqualError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
Target: runtimeTarget,
Target: mustParse(t, runtimeTarget),
}), errNoMain{"no-main"}.Error())
})
}
@ -848,7 +911,7 @@ func TestBuildTests(t *testing.T) {
build, err := Default.WithDefaults(ctx.Config.Builds[0])
require.NoError(t, err)
require.NoError(t, Default.Build(ctx, build, api.Options{
Target: runtimeTarget,
Target: mustParse(t, runtimeTarget),
}))
}
@ -899,7 +962,7 @@ import _ "github.com/goreleaser/goreleaser"
})
require.NoError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
Target: runtimeTarget,
Target: mustParse(t, runtimeTarget),
}))
}
@ -929,19 +992,19 @@ func TestRunPipeWithMainFuncNotInMainGoFile(t *testing.T) {
t.Run("empty", func(t *testing.T) {
ctx.Config.Builds[0].Main = ""
require.NoError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
Target: runtimeTarget,
Target: mustParse(t, runtimeTarget),
}))
})
t.Run("foo.go", func(t *testing.T) {
ctx.Config.Builds[0].Main = "foo.go"
require.NoError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
Target: runtimeTarget,
Target: mustParse(t, runtimeTarget),
}))
})
t.Run("glob", func(t *testing.T) {
ctx.Config.Builds[0].Main = "."
require.NoError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
Target: runtimeTarget,
Target: mustParse(t, runtimeTarget),
}))
})
}
@ -1099,7 +1162,7 @@ func TestBuildModTimestamp(t *testing.T) {
time.Sleep(2 * time.Second)
err := Default.Build(ctx, build, api.Options{
Target: target,
Target: mustParse(t, runtimeTarget),
Name: bin + ext,
Path: filepath.Join(folder, "dist", target, bin+ext),
Ext: ext,
@ -1131,11 +1194,10 @@ func TestBuildGoBuildLine(t *testing.T) {
)
options := api.Options{
Path: ctx.Config.Builds[0].Binary,
Goos: "linux",
Goarch: "amd64",
Target: mustParse(t, "linux_amd64"),
}
dets, err := withOverrides(ctx, build, options)
dets, err := withOverrides(ctx, build, options.Target.(Target))
require.NoError(t, err)
line, err := buildGoBuildLine(
@ -1298,10 +1360,7 @@ func TestOverrides(t *testing.T) {
},
},
},
}, api.Options{
Goos: "linux",
Goarch: arch,
},
}, mustParse(t, "linux_"+arch),
)
require.NoError(t, err)
require.ElementsMatch(t, dets.Ldflags, []string{"overridden"})
@ -1326,10 +1385,7 @@ func TestOverrides(t *testing.T) {
},
},
},
}, api.Options{
Goos: "linux",
Goarch: "amd64",
},
}, mustParse(t, "linux_amd64"),
)
require.NoError(t, err)
require.Equal(t, config.BuildDetails{
@ -1358,10 +1414,7 @@ func TestOverrides(t *testing.T) {
},
},
},
}, api.Options{
Goos: runtime.GOOS,
Goarch: runtime.GOARCH,
},
}, mustParse(t, runtimeTarget),
)
require.NoError(t, err)
require.Equal(t, config.BuildDetails{
@ -1380,10 +1433,7 @@ func TestOverrides(t *testing.T) {
Goos: "{{ .Runtime.Goos }",
},
},
}, api.Options{
Goos: runtime.GOOS,
Goarch: runtime.GOARCH,
},
}, mustParse(t, runtimeTarget),
)
testlib.RequireTemplateError(t, err)
})
@ -1405,11 +1455,7 @@ func TestOverrides(t *testing.T) {
},
},
},
}, api.Options{
Goos: "linux",
Goarch: "arm64",
Goarm64: "v8.0",
},
}, mustParse(t, "linux_arm64_v8.0"),
)
require.NoError(t, err)
require.Equal(t, config.BuildDetails{
@ -1434,11 +1480,7 @@ func TestOverrides(t *testing.T) {
},
},
},
}, api.Options{
Goos: "linux",
Goarch: "arm64",
Goarm64: "v8.0",
},
}, mustParse(t, "linux_arm64_v8.0"),
)
require.NoError(t, err)
require.Equal(t, config.BuildDetails{
@ -1464,11 +1506,7 @@ func TestOverrides(t *testing.T) {
},
},
},
}, api.Options{
Goos: "linux",
Goarch: "arm",
Goarm: "6",
},
}, mustParse(t, "linux_arm_6"),
)
require.NoError(t, err)
require.Equal(t, config.BuildDetails{
@ -1493,11 +1531,7 @@ func TestOverrides(t *testing.T) {
},
},
},
}, api.Options{
Goos: "linux",
Goarch: "arm",
Goarm: "6",
},
}, mustParse(t, "linux_arm_6"),
)
require.NoError(t, err)
require.Equal(t, config.BuildDetails{
@ -1523,11 +1557,7 @@ func TestOverrides(t *testing.T) {
},
},
},
}, api.Options{
Goos: "linux",
Goarch: "mips",
Gomips: "softfloat",
},
}, mustParse(t, "linux_mips_softfloat"),
)
require.NoError(t, err)
require.Equal(t, config.BuildDetails{
@ -1552,11 +1582,7 @@ func TestOverrides(t *testing.T) {
},
},
},
}, api.Options{
Goos: "linux",
Goarch: "mips",
Gomips: "hardfloat",
},
}, mustParse(t, "linux_mips_hardfloat"),
)
require.NoError(t, err)
require.Equal(t, config.BuildDetails{
@ -1582,11 +1608,7 @@ func TestOverrides(t *testing.T) {
},
},
},
}, api.Options{
Goos: "linux",
Goarch: "riscv64",
Goriscv64: "rva22u64",
},
}, mustParse(t, "linux_riscv64_rva22u64"),
)
require.NoError(t, err)
require.Equal(t, config.BuildDetails{
@ -1612,11 +1634,7 @@ func TestOverrides(t *testing.T) {
},
},
},
}, api.Options{
Goos: "linux",
Goarch: "riscv64",
Goriscv64: "rva22u64",
},
}, mustParse(t, "linux_riscv64_rva22u64"),
)
require.NoError(t, err)
require.Equal(t, config.BuildDetails{
@ -1642,11 +1660,7 @@ func TestOverrides(t *testing.T) {
},
},
},
}, api.Options{
Goos: "linux",
Goarch: "386",
Go386: "sse2",
},
}, mustParse(t, "linux_386_sse2"),
)
require.NoError(t, err)
require.Equal(t, config.BuildDetails{
@ -1672,11 +1686,7 @@ func TestOverrides(t *testing.T) {
},
},
},
}, api.Options{
Goos: "linux",
Goarch: "386",
Go386: "sse2",
},
}, mustParse(t, "linux_386_sse2"),
)
require.NoError(t, err)
require.Equal(t, config.BuildDetails{
@ -1733,7 +1743,7 @@ func TestInvalidGoBinaryTpl(t *testing.T) {
})
build := ctx.Config.Builds[0]
testlib.RequireTemplateError(t, Default.Build(ctx, build, api.Options{
Target: runtimeTarget,
Target: mustParse(t, runtimeTarget),
Name: build.Binary,
Path: filepath.Join("dist", runtimeTarget, build.Binary),
Ext: "",
@ -1794,3 +1804,10 @@ func writeTest(t *testing.T, folder string) {
0o666,
))
}
func mustParse(tb testing.TB, target string) Target {
tb.Helper()
got, err := Default.Parse(target)
require.NoError(tb, err)
return got.(Target)
}

View File

@ -0,0 +1,63 @@
package golang
import (
"github.com/goreleaser/goreleaser/v2/internal/tmpl"
"github.com/goreleaser/goreleaser/v2/pkg/config"
)
func formatTarget(o config.BuildDetailsOverride) string {
target := o.Goos + "_" + o.Goarch
if extra := o.Goamd64 + o.Go386 + o.Goarm + o.Goarm64 + o.Gomips + o.Goppc64 + o.Goriscv64; extra != "" {
target += "_" + extra
}
return target
}
// Target is a Go build target.
type Target struct {
Target string
Goos string
Goarch string
Goamd64 string
Go386 string
Goarm string
Goarm64 string
Gomips string
Goppc64 string
Goriscv64 string
}
// Fields implements build.Target.
func (t Target) Fields() map[string]string {
return map[string]string{
tmpl.KeyOS: t.Goos,
tmpl.KeyArch: t.Goarch,
tmpl.KeyAmd64: t.Goamd64,
tmpl.Key386: t.Go386,
tmpl.KeyArm: t.Goarm,
tmpl.KeyArm64: t.Goarm64,
tmpl.KeyMips: t.Gomips,
tmpl.KeyPpc64: t.Goppc64,
tmpl.KeyRiscv64: t.Goriscv64,
}
}
// String implements fmt.Stringer.
func (t Target) String() string {
return t.Target
}
func (t Target) env() []string {
return []string{
"GOOS=" + t.Goos,
"GOARCH=" + t.Goarch,
"GOAMD64=" + t.Goamd64,
"GO386=" + t.Go386,
"GOARM=" + t.Goarm,
"GOARM64=" + t.Goarm64,
"GOMIPS=" + t.Gomips,
"GOMIPS64=" + t.Gomips,
"GOPPC64=" + t.Goppc64,
"GORISCV64=" + t.Goriscv64,
}
}

View File

@ -165,34 +165,15 @@ func buildOptionsForTarget(ctx *context.Context, build config.Build, target stri
return nil, fmt.Errorf("%s is not a valid build target", target)
}
goos := parts[0]
goarch := parts[1]
buildOpts := builders.Options{
Target: target,
Ext: ext,
Goos: goos,
Goarch: goarch,
Ext: ext,
}
if len(parts) > 2 {
//nolint:gocritic
if strings.HasPrefix(goarch, "amd64") {
buildOpts.Goamd64 = parts[2]
} else if goarch == "386" {
buildOpts.Go386 = parts[2]
} else if strings.HasPrefix(goarch, "arm64") {
buildOpts.Goarm64 = parts[2]
} else if strings.HasPrefix(goarch, "arm") {
buildOpts.Goarm = parts[2]
} else if strings.HasPrefix(goarch, "mips") {
buildOpts.Gomips = parts[2]
} else if strings.HasPrefix(goarch, "ppc64") {
buildOpts.Goppc64 = parts[2]
} else if goarch == "riscv64" {
buildOpts.Goriscv64 = parts[2]
}
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 {
@ -200,7 +181,7 @@ func buildOptionsForTarget(ctx *context.Context, build config.Build, target stri
}
name := bin + ext
dir := fmt.Sprintf("%s_%s", build.ID, target)
dir := fmt.Sprintf("%s_%s", build.ID, t)
noUnique, err := tmpl.New(ctx).Bool(build.NoUniqueDistDir)
if err != nil {
return nil, err

View File

@ -5,6 +5,7 @@ import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"github.com/goreleaser/goreleaser/v2/internal/artifact"
@ -24,11 +25,34 @@ var (
errFailedDefault = errors.New("fake builder defaults failed")
)
type fakeTarget struct {
target string
}
// String implements build.Target.
func (f fakeTarget) String() string {
return f.target
}
// Fields implements build.Target.
func (f fakeTarget) Fields() map[string]string {
os, arch, _ := strings.Cut(f.target, "_")
return map[string]string{
tmpl.KeyOS: os,
tmpl.KeyArch: arch,
}
}
type fakeBuilder struct {
fail bool
failDefault bool
}
// Parse implements build.Builder.
func (f *fakeBuilder) Parse(target string) (api.Target, error) {
return fakeTarget{target}, nil
}
func (f *fakeBuilder) WithDefaults(build config.Build) (config.Build, error) {
if f.failDefault {
return build, errFailedDefault
@ -585,7 +609,7 @@ func TestBuildOptionsForTarget(t *testing.T) {
testCases := []struct {
name string
build config.Build
expectedOpts *api.Options
expectedOpts api.Options
expectedErr string
}{
{
@ -597,13 +621,9 @@ func TestBuildOptionsForTarget(t *testing.T) {
"linux_amd64",
},
},
expectedOpts: &api.Options{
Name: "testbinary",
Path: filepath.Join(tmpDir, "testid_linux_amd64_v1", "testbinary"),
Target: "linux_amd64_v1",
Goos: "linux",
Goarch: "amd64",
Goamd64: "v1",
expectedOpts: api.Options{
Name: "testbinary",
Path: filepath.Join(tmpDir, "testid_linux_amd64_v1", "testbinary"),
},
},
{
@ -615,13 +635,9 @@ func TestBuildOptionsForTarget(t *testing.T) {
"linux_amd64",
},
},
expectedOpts: &api.Options{
Name: "testbinary_linux_amd64",
Path: filepath.Join(tmpDir, "testid_linux_amd64_v1", "testbinary_linux_amd64"),
Target: "linux_amd64_v1",
Goos: "linux",
Goarch: "amd64",
Goamd64: "v1",
expectedOpts: api.Options{
Name: "testbinary_linux_amd64",
Path: filepath.Join(tmpDir, "testid_linux_amd64_v1", "testbinary_linux_amd64"),
},
},
{
@ -634,13 +650,9 @@ func TestBuildOptionsForTarget(t *testing.T) {
},
NoUniqueDistDir: `{{ printf "true"}}`,
},
expectedOpts: &api.Options{
Name: "distpath/linux/amd64/testbinary",
Path: filepath.Join(tmpDir, "distpath", "linux", "amd64", "testbinary"),
Target: "linux_amd64_v1",
Goos: "linux",
Goarch: "amd64",
Goamd64: "v1",
expectedOpts: api.Options{
Name: "distpath/linux/amd64/testbinary",
Path: filepath.Join(tmpDir, "distpath", "linux", "amd64", "testbinary"),
},
},
{
@ -653,13 +665,9 @@ func TestBuildOptionsForTarget(t *testing.T) {
},
NoUniqueDistDir: `{{ printf "false"}}`,
},
expectedOpts: &api.Options{
Name: "testbinary",
Path: filepath.Join(tmpDir, "testid_linux_amd64_v1", "testbinary"),
Target: "linux_amd64_v1",
Goos: "linux",
Goarch: "amd64",
Goamd64: "v1",
expectedOpts: api.Options{
Name: "testbinary",
Path: filepath.Join(tmpDir, "testid_linux_amd64_v1", "testbinary"),
},
},
{
@ -671,13 +679,9 @@ func TestBuildOptionsForTarget(t *testing.T) {
"linux_arm_6",
},
},
expectedOpts: &api.Options{
Name: "testbinary",
Path: filepath.Join(tmpDir, "testid_linux_arm_6", "testbinary"),
Target: "linux_arm_6",
Goos: "linux",
Goarch: "arm",
Goarm: "6",
expectedOpts: api.Options{
Name: "testbinary",
Path: filepath.Join(tmpDir, "testid_linux_arm_6", "testbinary"),
},
},
{
@ -689,13 +693,9 @@ func TestBuildOptionsForTarget(t *testing.T) {
"linux_mips_softfloat",
},
},
expectedOpts: &api.Options{
Name: "testbinary",
Path: filepath.Join(tmpDir, "testid_linux_mips_softfloat", "testbinary"),
Target: "linux_mips_softfloat",
Goos: "linux",
Goarch: "mips",
Gomips: "softfloat",
expectedOpts: api.Options{
Name: "testbinary",
Path: filepath.Join(tmpDir, "testid_linux_mips_softfloat", "testbinary"),
},
},
{
@ -707,13 +707,9 @@ func TestBuildOptionsForTarget(t *testing.T) {
"linux_amd64_v3",
},
},
expectedOpts: &api.Options{
Name: "testbinary",
Path: filepath.Join(tmpDir, "testid_linux_amd64_v3", "testbinary"),
Target: "linux_amd64_v3",
Goos: "linux",
Goarch: "amd64",
Goamd64: "v3",
expectedOpts: api.Options{
Name: "testbinary",
Path: filepath.Join(tmpDir, "testid_linux_amd64_v3", "testbinary"),
},
},
}
@ -728,7 +724,8 @@ func TestBuildOptionsForTarget(t *testing.T) {
opts, err := buildOptionsForTarget(ctx, ctx.Config.Builds[0], ctx.Config.Builds[0].Targets[0])
if tc.expectedErr == "" {
require.NoError(t, err)
require.Equal(t, tc.expectedOpts, opts)
opts.Target = nil
require.Equal(t, tc.expectedOpts, *opts)
} else {
require.EqualError(t, err, tc.expectedErr)
}

View File

@ -0,0 +1,14 @@
package universalbinary
import "github.com/goreleaser/goreleaser/v2/internal/tmpl"
type unitarget struct{}
func (unitarget) String() string { return "darwin_all" }
func (unitarget) Fields() map[string]string {
return map[string]string{
tmpl.KeyOS: "darwin",
tmpl.KeyArch: "all",
}
}

View File

@ -55,9 +55,7 @@ func (Pipe) Run(ctx *context.Context) error {
for _, unibin := range ctx.Config.UniversalBinaries {
g.Go(func() error {
opts := build.Options{
Target: "darwin_all",
Goos: "darwin",
Goarch: "all",
Target: unitarget{},
}
if !skips.Any(ctx, skips.PreBuildHooks) {
if err := runHook(ctx, &opts, unibin.Hooks.Pre); err != nil {

View File

@ -179,7 +179,7 @@ func TestRun(t *testing.T) {
},
Post: []config.Hook{
{Cmd: testlib.Touch(post)},
{Cmd: testlib.ShC(`echo "{{ .Name }} {{ .Os }} {{ .Arch }} {{ .Arm }} {{ .Target }} {{ .Ext }}" > {{ .Path }}.post`), Output: true},
{Cmd: testlib.ShC(`echo "{{ .Name }} {{ .Os }} {{ .Arch }} {{ .Target }} {{ .Ext }}" > {{ .Path }}.post`), Output: true},
},
},
},
@ -213,7 +213,7 @@ func TestRun(t *testing.T) {
Post: []config.Hook{
{Cmd: testlib.Touch(post)},
{
Cmd: testlib.ShC(`echo "{{ .Name }} {{ .Os }} {{ .Arch }} {{ .Arm }} {{ .Target }} {{ .Ext }}" > {{ .Path }}.post`),
Cmd: testlib.ShC(`echo "{{ .Name }} {{ .Os }} {{ .Arch }} {{ .Target }} {{ .Ext }}" > {{ .Path }}.post`),
Output: true,
},
},
@ -312,7 +312,7 @@ func TestRun(t *testing.T) {
require.FileExists(t, post)
bts, err := os.ReadFile(post)
require.NoError(t, err)
require.Contains(t, string(bts), "foo darwin all darwin_all")
require.Contains(t, string(bts), "foo darwin all darwin_all")
})
t.Run("failing pre-hook", func(t *testing.T) {

View File

@ -0,0 +1,30 @@
package testlib
import (
"slices"
"strings"
"testing"
"github.com/goreleaser/goreleaser/v2/internal/artifact"
"github.com/stretchr/testify/require"
)
func RequireEqualArtifacts(tb testing.TB, expected, got []*artifact.Artifact) {
tb.Helper()
slices.SortFunc(expected, artifactSort)
slices.SortFunc(got, artifactSort)
require.Equal(tb, filenames(expected), filenames(got))
require.Equal(tb, expected, got)
}
func artifactSort(a, b *artifact.Artifact) int {
return strings.Compare(a.Path, b.Path)
}
func filenames(ts []*artifact.Artifact) []string {
result := make([]string, len(ts))
for i, t := range ts {
result[i] = t.Path
}
return result
}

View File

@ -28,8 +28,21 @@ type Template struct {
// Fields that will be available to the template engine.
type Fields map[string]interface{}
// Template fields names used in build targets and more.
const (
KeyOS = "Os"
KeyArch = "Arch"
KeyAmd64 = "Amd64"
Key386 = "I386"
KeyArm = "Arm"
KeyArm64 = "Arm64"
KeyMips = "Mips"
KeyPpc64 = "Ppc64"
KeyRiscv64 = "Riscv64"
)
// general keys.
const (
// general keys.
projectName = "ProjectName"
version = "Version"
rawVersion = "RawVersion"
@ -65,23 +78,18 @@ const (
modulePath = "ModulePath"
releaseNotes = "ReleaseNotes"
runtimeK = "Runtime"
)
// artifact-only keys.
osKey = "Os"
arch = "Arch"
amd64 = "Amd64"
go386 = "I386"
arm = "Arm"
arm64 = "Arm64"
mips = "Mips"
ppc64 = "Ppc64"
riscv64 = "Riscv64"
// artifact-only keys.
const (
binary = "Binary"
artifactName = "ArtifactName"
artifactExt = "ArtifactExt"
artifactPath = "ArtifactPath"
)
// build keys.
// build keys.
const (
name = "Name"
ext = "Ext"
path = "Path"
@ -177,15 +185,15 @@ func (t *Template) WithEnv(e map[string]string) *Template {
// WithArtifact populates Fields from the artifact.
func (t *Template) WithArtifact(a *artifact.Artifact) *Template {
return t.WithExtraFields(Fields{
osKey: a.Goos,
arch: a.Goarch,
amd64: a.Goamd64,
go386: a.Go386,
arm: a.Goarm,
arm64: a.Goarm64,
mips: a.Gomips,
ppc64: a.Goppc64,
riscv64: a.Goriscv64,
KeyOS: a.Goos,
KeyArch: a.Goarch,
KeyAmd64: a.Goamd64,
Key386: a.Go386,
KeyArm: a.Goarm,
KeyArm64: a.Goarm64,
KeyMips: a.Gomips,
KeyPpc64: a.Goppc64,
KeyRiscv64: a.Goriscv64,
binary: artifact.ExtraOr(*a, binary, t.fields[projectName].(string)),
artifactName: a.Name,
artifactExt: artifact.ExtraOr(*a, artifact.ExtraExt, ""),
@ -198,21 +206,29 @@ func (t *Template) WithBuildOptions(opts build.Options) *Template {
}
func buildOptsToFields(opts build.Options) Fields {
return Fields{
target: opts.Target,
ext: opts.Ext,
name: opts.Name,
path: opts.Path,
osKey: opts.Goos,
arch: opts.Goarch,
amd64: opts.Goamd64,
go386: opts.Go386,
arm: opts.Goarm,
arm64: opts.Goarm64,
mips: opts.Gomips,
ppc64: opts.Goppc64,
riscv64: opts.Goriscv64,
f := Fields{
target: opts.Target.String(),
ext: opts.Ext,
name: opts.Name,
path: opts.Path,
// set them all to empty, which should prevent breaking templates.
// the .Fields() call will override whichever values are actually
// available.
KeyOS: "",
KeyArch: "",
KeyAmd64: "",
Key386: "",
KeyArm: "",
KeyArm64: "",
KeyMips: "",
KeyPpc64: "",
KeyRiscv64: "",
}
for k, v := range opts.Target.Fields() {
f[k] = v
}
return f
}
// Bool Apply the given string, and converts it to a bool.

View File

@ -460,17 +460,21 @@ func TestInvalidMap(t *testing.T) {
}
func TestWithBuildOptions(t *testing.T) {
// testtarget doesn ot set riscv64, it still should not fail to compile the template
ts := "{{.Name}}_{{.Path}}_{{.Ext}}_{{.Target}}_{{.Os}}_{{.Arch}}_{{.Amd64}}_{{.Arm}}_{{.Mips}}{{with .Riscv64}}{{.}}{{end}}"
out, err := New(testctx.New()).WithBuildOptions(build.Options{
Name: "name",
Path: "./path",
Ext: ".ext",
Target: "target",
Goos: "os",
Goarch: "arch",
Goamd64: "amd64",
Goarm: "arm",
Gomips: "mips",
}).Apply("{{.Name}}_{{.Path}}_{{.Ext}}_{{.Target}}_{{.Os}}_{{.Arch}}_{{.Amd64}}_{{.Arm}}_{{.Mips}}")
Name: "name",
Path: "./path",
Ext: ".ext",
Target: testTarget{
Target: "target",
Goos: "os",
Goarch: "arch",
Goamd64: "amd64",
Goarm: "arm",
Gomips: "mips",
},
}).Apply(ts)
require.NoError(t, err)
require.Equal(t, "name_./path_.ext_target_os_arch_amd64_arm_mips", out)
}
@ -491,3 +495,25 @@ func TestReuseTpl(t *testing.T) {
require.NoError(t, err)
require.Equal(t, "bar", s3)
}
type testTarget struct {
Target string
Goos string
Goarch string
Goamd64 string
Goarm string
Gomips string
}
func (t testTarget) String() string { return t.Target }
func (t testTarget) Fields() map[string]string {
return map[string]string{
target: t.Target,
KeyOS: t.Goos,
KeyArch: t.Goarch,
KeyAmd64: t.Goamd64,
KeyArm: t.Goarm,
KeyMips: t.Gomips,
}
}

View File

@ -28,23 +28,27 @@ func For(name string) Builder {
// Options to be passed down to a builder.
type Options struct {
Name string
Path string
Ext string // with the leading `.`.
Target string
Goos string
Goarch string
Goamd64 string
Go386 string
Goarm string
Goarm64 string
Gomips string
Goppc64 string
Goriscv64 string
Name string
Path string
Ext string // with the leading `.`.
Target Target
}
// Target represents a build target.
//
// Each Builder implementation can implement its own.
type Target interface {
// String returns the original target.
String() string
// Fields returns the template fields that will be available for this
// target (e.g. Os, Arch, etc).
Fields() map[string]string
}
// Builder defines a builder.
type Builder interface {
WithDefaults(build config.Build) (config.Build, error)
Build(ctx *context.Context, build config.Build, options Options) error
Parse(target string) (Target, error)
}

View File

@ -8,8 +8,25 @@ import (
"github.com/stretchr/testify/require"
)
type dummyTarget struct{}
// String implements Target.
func (d dummyTarget) String() string {
return "dummy"
}
// Fields implements Target.
func (d dummyTarget) Fields() map[string]string {
return nil
}
type dummy struct{}
// Parse implements Builder.
func (d *dummy) Parse(string) (Target, error) {
return dummyTarget{}, nil
}
func (*dummy) WithDefaults(build config.Build) (config.Build, error) {
return build, nil
}