Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Configure git identity
run: |
git config user.name "CI"
git config user.email "ci@localhost"
- name: Set up Go ${{ matrix.go-version }}
uses: actions/setup-go@v6
with:
Expand Down
2 changes: 1 addition & 1 deletion .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ builds:
archives:
-
name_template: >-
{{.Binary}}_{{.Version}}_{{.Os}}_{{.Arch}}
{{.Binary}}_{{.Version}}_
{{- if eq .Os "darwin"}}macOS
{{- else if eq .Os "linux"}}Linux
{{- else if eq .Os "windows"}}Windows
Expand Down
103 changes: 103 additions & 0 deletions magefiles/targets/release_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//go:build go1.24

// These tests fail on older Go versions because of the change in go.mod format.
// But it doesn't matter because we only call release with modern go versions.

package targets

import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
)

// expectedReleaseFiles lists the release artifacts goreleaser should produce.
// Each entry uses %s as a placeholder for the version string.
var expectedReleaseFiles = []string{
"mage_%s_checksums.txt",
"mage_%s_DragonFlyBSD-64bit.tar.gz",
"mage_%s_FreeBSD-64bit.tar.gz",
"mage_%s_FreeBSD-ARM.tar.gz",
"mage_%s_FreeBSD-ARM64.tar.gz",
"mage_%s_Linux-64bit.tar.gz",
"mage_%s_Linux-ARM.tar.gz",
"mage_%s_Linux-ARM64.tar.gz",
"mage_%s_macOS-64bit.tar.gz",
"mage_%s_macOS-ARM64.tar.gz",
"mage_%s_NetBSD-64bit.tar.gz",
"mage_%s_NetBSD-ARM.tar.gz",
"mage_%s_NetBSD-ARM64.tar.gz",
"mage_%s_OpenBSD-64bit.tar.gz",
"mage_%s_OpenBSD-ARM64.tar.gz",
"mage_%s_Windows-64bit.zip",
"mage_%s_Windows-ARM64.zip",
}

func TestRelease(t *testing.T) {
if testing.Short() {
t.Skip("skipping release test in short mode")
}

// goreleaser must run from the repo root where .goreleaser.yml lives.
repoRoot, err := filepath.Abs(filepath.Join("..", ".."))
if err != nil {
t.Fatal(err)
}
origDir, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
if err := os.Chdir(repoRoot); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = os.RemoveAll(filepath.Join(repoRoot, "dist")) })
t.Cleanup(func() { _ = os.Chdir(origDir) })

const tag = "v1.0.99"

dryRun := true
if err := Release(tag, &dryRun); err != nil {
t.Fatal(err)
}

// goreleaser strips the leading "v" from the tag for artifact names.
version := strings.TrimPrefix(tag, "v")

entries, err := os.ReadDir("dist")
if err != nil {
t.Fatal(err)
}

// Build expected set, initially marking each as not found.
expected := make(map[string]bool, len(expectedReleaseFiles))
for _, pattern := range expectedReleaseFiles {
expected[fmt.Sprintf(pattern, version)] = false
}

// Walk dist/ and match release artifacts.
for _, e := range entries {
if e.IsDir() {
continue
}
name := e.Name()
isArtifact := strings.HasSuffix(name, ".tar.gz") ||
strings.HasSuffix(name, ".zip") ||
strings.HasSuffix(name, "_checksums.txt")
if !isArtifact {
continue
}
if _, ok := expected[name]; ok {
expected[name] = true
} else {
t.Errorf("unexpected release artifact: %s", name)
}
}

for name, found := range expected {
if !found {
t.Errorf("expected release artifact not found: %s", name)
}
}
}
45 changes: 36 additions & 9 deletions magefiles/targets/targets.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,25 @@ func Install() error {
var releaseTag = regexp.MustCompile(`^v1\.\d+\.\d+$`)

// Release generates a new release. Expects a version tag in v1.x.x format.
func Release(tag string) (err error) {
mg.Deps(Tools)
// If dryRun is true, it creates a local tag and runs goreleaser without
// publishing, then deletes the tag. This can be used to verify release artifacts.
func Release(tag string, dryRun *bool) (err error) {
if err := installTool("goreleaser"); err != nil {
return err
}

if !releaseTag.MatchString(tag) {
return errors.New("TAG environment variable must be in semver v1.x.x format, but was " + tag)
}

if dryRun != nil && *dryRun {
if err := sh.RunV("git", "tag", "-a", tag, "-m", tag); err != nil {
return err
}
defer func() { _ = sh.RunV("git", "tag", "--delete", tag) }()
return sh.RunV("goreleaser", "release", "--skip=publish", "--skip=validate", "--clean")
}

if err := sh.RunV("git", "tag", "-a", tag, "-m", tag); err != nil {
return err
}
Expand All @@ -73,31 +85,46 @@ func Release(tag string) (err error) {
_ = sh.RunV("git", "push", "--delete", "origin", tag)
}
}()
return sh.RunV("goreleaser", "release")
return sh.RunV("goreleaser", "release", "--clean")
}

// Clean removes the temporarily generated files from Release.
func Clean() error {
return sh.Rm("dist")
}

var goTools = []string{
"github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.2",
"github.com/goreleaser/goreleaser/v2@v2.14.3",
var goTools = map[string]string{
"lint": "github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.2",
"goreleaser": "github.com/goreleaser/goreleaser/v2@v2.14.3",
}

// Tools installs the dev tools used by mage, such as golangci-lint.
func Tools() error {
for _, tool := range goTools {
if err := sh.Run("go", "install", tool); err != nil {
return fmt.Errorf("failed to install %s: %w", tool, err)
if err := installTool(tool); err != nil {
return err
}
}
return nil
}

func installTool(tool string) error {
version, ok := goTools[tool]
if !ok {
return fmt.Errorf("unknown tool %q", tool)
}
if err := sh.Run("go", "install", version); err != nil {
return fmt.Errorf("failed to install %s: %w", version, err)
}
return nil
}

// Lint runs golangci-lint on the codebase.
func Lint() error {
mg.Deps(Tools)
err := installTool("lint")
if err != nil {
return err
}

return sh.RunV("golangci-lint", "run")
}