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
8 changes: 1 addition & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,7 @@ jobs:
strategy:
fail-fast: false
matrix:
go-version:
- stable
- 1.21.x
- 1.20.x
- 1.19.x
- 1.18.x
- 1.17.x
go-version: [ stable, 1.18.x ] # min and max versions should be sufficient
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- name: Checkout code
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
golangci:
strategy:
matrix:
go: [stable]
go: [ stable, 1.18.x ] # min and max versions should be sufficient
os: [ubuntu-latest, macos-latest, windows-latest]
name: lint
runs-on: ${{ matrix.os }}
Expand Down
3 changes: 3 additions & 0 deletions .golangci.toml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ disable = [
]
enable-all = true

[linters.settings.modernize]
disable = ["any"]

[linters.settings.nestif]
min-complexity = 9

Expand Down
2 changes: 1 addition & 1 deletion .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ release:
builds:
- binary: mage
main: .
ldflags: -s -w -X github.com/magefile/mage/mage.timestamp={{.Date}} -X github.com/magefile/mage/mage.commitHash={{.Commit}} -X github.com/magefile/mage/mage.gitTag={{.Version}}
ldflags: -s -w
goos:
- darwin
- linux
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/magefile/mage

go 1.17
go 1.18
136 changes: 136 additions & 0 deletions internal/run_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package internal

import (
"runtime"
"strings"
"testing"
)

func TestSplitEnv(t *testing.T) {
tests := []struct {
name string
env []string
wantLen int
wantErr bool
}{
{
name: "normal env vars",
env: []string{"FOO=bar", "BAZ=qux"},
wantLen: 2,
},
{
name: "empty value",
env: []string{"FOO="},
wantLen: 1,
},
{
name: "value with equals sign",
env: []string{"FOO=bar=baz"},
wantLen: 1,
},
{
name: "empty input",
env: nil,
wantLen: 0,
},
{
name: "malformed entry",
env: []string{"NO_EQUALS"},
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := SplitEnv(tt.env)
if (err != nil) != tt.wantErr {
t.Errorf("SplitEnv() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && len(got) != tt.wantLen {
t.Errorf("SplitEnv() returned %d entries, want %d", len(got), tt.wantLen)
}
})
}
}

func TestSplitEnvValues(t *testing.T) {
env := []string{"FOO=bar", "BAZ=qux=quux"}
got, err := SplitEnv(env)
if err != nil {
t.Fatal(err)
}
if got["FOO"] != "bar" {
t.Errorf("expected FOO=bar, got FOO=%s", got["FOO"])
}
if got["BAZ"] != "qux=quux" {
t.Errorf("expected BAZ=qux=quux, got BAZ=%s", got["BAZ"])
}
}

func TestEnvWithCurrentGOOS(t *testing.T) {
env, err := EnvWithCurrentGOOS()
if err != nil {
t.Fatal(err)
}

var foundGOOS, foundGOARCH bool
for _, e := range env {
parts := strings.SplitN(e, "=", 2)
if len(parts) != 2 {
continue
}
switch parts[0] {
case "GOOS":
foundGOOS = true
if parts[1] != runtime.GOOS {
t.Errorf("expected GOOS=%s, got %s", runtime.GOOS, parts[1])
}
case "GOARCH":
foundGOARCH = true
if parts[1] != runtime.GOARCH {
t.Errorf("expected GOARCH=%s, got %s", runtime.GOARCH, parts[1])
}
default:
// ignore other env vars
continue
}
}
if !foundGOOS {
t.Error("GOOS not found in env")
}
if !foundGOARCH {
t.Error("GOARCH not found in env")
}
}

func TestRunDebug(t *testing.T) {
// Test successful command
err := RunDebug("echo", "hello")
if err != nil {
t.Fatalf("RunDebug with valid command failed: %v", err)
}

// Test failed command
err = RunDebug("false")
if err == nil {
t.Fatal("RunDebug with failing command should return error")
}
}

func TestOutputDebug(t *testing.T) {
// Test successful command
out, err := OutputDebug("echo", "hello")
if err != nil {
t.Fatalf("OutputDebug with valid command failed: %v", err)
}
if out != "hello" {
t.Errorf("expected 'hello', got %q", out)
}

// Test failed command
_, err = OutputDebug("false")
if err == nil {
t.Fatal("OutputDebug with failing command should return error")
}
}
45 changes: 33 additions & 12 deletions mage/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"path/filepath"
"regexp"
"runtime"
dbg "runtime/debug"
"sort"
"strings"
"syscall"
Expand Down Expand Up @@ -69,13 +70,6 @@ const (

var debug = log.New(io.Discard, "DEBUG: ", log.Ltime|log.Lmicroseconds)

// set by ldflags when you "mage build".
var (
commitHash = "<not set>"
timestamp = "<not set>"
gitTag = "<not set>"
)

//go:generate stringer -type=Command

// Command tracks invocations of mage that run without targets or other flags.
Expand Down Expand Up @@ -149,10 +143,7 @@ func ParseAndRun(stdout, stderr io.Writer, stdin io.Reader, args []string) int {

switch cmd {
case Version:
out.Println("Mage Build Tool", gitTag)
out.Println("Build Date:", timestamp)
out.Println("Commit:", commitHash)
out.Println("built with:", runtime.Version())
doVersion(out)
return 0
case Init:
if err := generateInit(inv.Dir); err != nil {
Expand All @@ -171,8 +162,38 @@ func ParseAndRun(stdout, stderr io.Writer, stdin io.Reader, args []string) int {
case CompileStatic, None:
return Invoke(inv)
default:
panic(fmt.Errorf("unknown command type: %v", cmd))
errlog.Printf("unknown command type: %v", cmd)
return 1
}
}

func doVersion(out *log.Logger) {
var (
commitHash = "<not set>"
timestamp = "<not set>"
gitTag = ""
)

info, ok := dbg.ReadBuildInfo()
if ok {
if info.Main.Version != "" {
gitTag = info.Main.Version
}
for _, kv := range info.Settings {
switch kv.Key {
case "vcs.revision":
commitHash = kv.Value
case "vcs.time":
timestamp = kv.Value
default:
continue
}
}
}
out.Println("Mage Build Tool", gitTag)
out.Println("Build Date:", timestamp)
out.Println("Commit:", commitHash)
out.Println("built with:", runtime.Version())
}

// Parse parses the given args and returns structured data. If parse returns
Expand Down
26 changes: 2 additions & 24 deletions magefiles/targets/targets.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import (
"regexp"
"runtime"
"strings"
"time"

"github.com/magefile/mage/mg"
"github.com/magefile/mage/sh"
)

// Install runs "go install" for mage. This generates the version info the binary.
func Install() error {
fmt.Println("`mage install` is deprecated. Just use `go install` now.")
name := "mage"
if runtime.GOOS == "windows" {
name += ".exe"
Expand Down Expand Up @@ -48,7 +48,7 @@ func Install() error {
// install` turns into a no-op, and `go install -a` fails on people's
// machines that have go installed in a non-writeable directory (such as
// normal OS installs in /usr/bin)
return sh.RunV(gocmd, "build", "-o", path, "-ldflags="+flags(), "github.com/magefile/mage")
return sh.RunV(gocmd, "build", "-o", path, "github.com/magefile/mage")
}

var releaseTag = regexp.MustCompile(`^v1\.\d+\.\d+$`)
Expand Down Expand Up @@ -81,28 +81,6 @@ func Clean() error {
return sh.Rm("dist")
}

func flags() string {
timestamp := time.Now().Format(time.RFC3339)
hash := hash()
tag := tag()
if tag == "" {
tag = "dev"
}
return fmt.Sprintf(`-X "github.com/magefile/mage/mage.timestamp=%s" -X "github.com/magefile/mage/mage.commitHash=%s" -X "github.com/magefile/mage/mage.gitTag=%s"`, timestamp, hash, tag)
}

// tag returns the git tag for the current branch or "" if none.
func tag() string {
s, _ := sh.Output("git", "describe", "--tags")
return s
}

// hash returns the git hash for the current repo or "" if none.
func hash() string {
hash, _ := sh.Output("git", "rev-parse", "--short", "HEAD")
return hash
}

var goTools = []string{
"github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.2",
"github.com/goreleaser/goreleaser/v2@v2.14.3",
Expand Down
22 changes: 4 additions & 18 deletions mg/color.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Package mg provides support functions for running mage targets.
package mg

import "strings"

// Color is ANSI color type.
type Color int

Expand Down Expand Up @@ -53,26 +55,10 @@ const AnsiColorReset = "\033[0m"
// It is set to Cyan as an arbitrary color, because it has a neutral meaning.
var DefaultTargetAnsiColor = ansiColor[Cyan]

func toLowerCase(s string) string {
// this is a naive implementation
// borrowed from https://golang.org/src/strings/strings.go
// and only considers alphabetical characters [a-zA-Z]
// so that we don't depend on the "strings" package
buf := make([]byte, len(s))
for i := 0; i < len(s); i++ {
c := s[i]
if 'A' <= c && c <= 'Z' {
c += 'a' - 'A'
}
buf[i] = c
}
return string(buf)
}

func getAnsiColor(color string) (string, bool) {
colorLower := toLowerCase(color)
colorLower := strings.ToLower(color)
for k, v := range ansiColor {
colorConstLower := toLowerCase(k.String())
colorConstLower := strings.ToLower(k.String())
if colorConstLower == colorLower {
return v, true
}
Expand Down
Loading