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: 3 additions & 1 deletion build-1.11/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import (
"strings"
"unicode"
"unicode/utf8"

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

// A Context specifies the supporting context for a build.
Expand Down Expand Up @@ -1013,7 +1015,7 @@ func (ctxt *Context) importGo(p *Package, path, srcDir string, mode ImportMode,
abs = d
}

cmd := exec.Command("go", "list", "-compiler="+ctxt.Compiler, "-tags="+strings.Join(ctxt.BuildTags, ","), "-installsuffix="+ctxt.InstallSuffix, "-f={{.Dir}}\n{{.ImportPath}}\n{{.Root}}\n{{.Goroot}}\n", path)
cmd := exec.Command(mg.GoCmd(), "list", "-compiler="+ctxt.Compiler, "-tags="+strings.Join(ctxt.BuildTags, ","), "-installsuffix="+ctxt.InstallSuffix, "-f={{.Dir}}\n{{.ImportPath}}\n{{.Root}}\n{{.Goroot}}\n", path)
cmd.Dir = srcDir
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
Expand Down
118 changes: 69 additions & 49 deletions mage/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (
"sort"
"strconv"
"strings"
"text/tabwriter"
"text/template"
"time"
"unicode"
Expand Down Expand Up @@ -55,9 +54,9 @@ var debug = log.New(ioutil.Discard, "DEBUG: ", 0)

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

//go:generate stringer -type=Command
Expand Down Expand Up @@ -117,15 +116,10 @@ func ParseAndRun(dir string, stdout, stderr io.Writer, stdin io.Reader, args []s

switch cmd {
case Version:
if timestamp == "" {
timestamp = "<not set>"
}
if commitHash == "" {
commitHash = "<not set>"
}
log.Println("Mage Build Tool", gitTag)
log.Println("Build Date:", timestamp)
log.Println("Commit:", commitHash)
log.Println("built with:", runtime.Version())
return 0
case Init:
if err := generateInit(dir); err != nil {
Expand Down Expand Up @@ -167,39 +161,41 @@ func Parse(stderr, stdout io.Writer, args []string) (inv Invocation, cmd Command
var showVersion bool
fs.BoolVar(&showVersion, "version", false, "show version info for the mage binary")

// Categorize commands and options.
commands := []string{"clean", "init", "l", "h", "version"}
options := []string{"f", "keep", "t", "v", "compile", "debug"}

w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)

printUsage := func(flagname string) {
f := fs.Lookup(flagname)
fmt.Fprintf(w, " -%s\t\t%s\n", f.Name, f.Usage)
}

var mageInit bool
fs.BoolVar(&mageInit, "init", false, "create a starting template if no mage files exist")
var clean bool
fs.BoolVar(&clean, "clean", false, "clean out old generated binaries from CACHE_DIR")
var compileOutPath string
fs.StringVar(&compileOutPath, "compile", "", "path to which to output a static binary")
fs.StringVar(&compileOutPath, "compile", "", "output a static binary to the given path")
var goCmd string
fs.StringVar(&goCmd, "gocmd", "", "use the given go binary to compile the output")

fs.Usage = func() {
fmt.Fprintln(w, "mage [options] [target]")
fmt.Fprintln(w, "")
fmt.Fprintln(w, "Commands:")
for _, cmd := range commands {
printUsage(cmd)
}

fmt.Fprintln(w, "")
fmt.Fprintln(w, "Options:")
fmt.Fprintln(w, " -h\t\tshow description of a target")
for _, opt := range options {
printUsage(opt)
}
w.Flush()
fmt.Fprint(stdout, `
mage [options] [target]

Mage is a make-like command runner. See https://magefile.org for full docs.

Commands:
-clean clean out old generated binaries from CACHE_DIR
-compile <string>
output a static binary to the given path
-init create a starting template if no mage files exist
-l list mage targets in this directory
-h show this help
-version show version info for the mage binary

Options:
-debug turn on debug messages (implies -keep)
-h show description of a target
-f force recreation of compiled magefile
-keep keep intermediate mage files around after running
-gocmd <string>
use the given go binary to compile the output (default: "go")
-t <string>
timeout in duration parsable format (e.g. 5m30s)
-v show verbose output when running mage targets
`[1:])
}
err = fs.Parse(args)
if err == flag.ErrHelp {
Expand Down Expand Up @@ -254,6 +250,12 @@ func Parse(stderr, stdout io.Writer, args []string) (inv Invocation, cmd Command
inv.Verbose = mg.Verbose()
}

if goCmd != "" {
if err := os.Setenv(mg.GoCmdEnv, goCmd); err != nil {
return inv, cmd, fmt.Errorf("failed to set gocmd: %v", err)
}
}

if numFlags > 1 {
debug.Printf("%d commands defined", numFlags)
return inv, cmd, errors.New("-h, -init, -clean, -compile and -version cannot be used simultaneously")
Expand Down Expand Up @@ -368,25 +370,38 @@ type data struct {
// Yeah, if we get to go2, this will need to be revisited. I think that's ok.
var goVerReg = regexp.MustCompile(`1\.[0-9]+`)

func goVersion() (string, error) {
cmd := exec.Command(mg.GoCmd(), "version")
out, stderr := &bytes.Buffer{}, &bytes.Buffer{}
cmd.Stdout = out
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
if s := stderr.String(); s != "" {
return "", fmt.Errorf("failed to run `go version`: %s", s)
}
return "", fmt.Errorf("failed to run `go version`: %v", err)
}
return out.String(), nil
}

// Magefiles returns the list of magefiles in dir.
func Magefiles(dir string) ([]string, error) {
// use the build directory for the specific go binary we're running. We
// divide the world into two epochs - 1.11 and later, where we have go
// modules, and 1.10 and prior, where there are no modules.
cmd := exec.Command("go", "version")
out, stderr := &bytes.Buffer{}, &bytes.Buffer{}
cmd.Stdout = out
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("failed to run `go version`: %s", stderr)
ver, err := goVersion()
if err != nil {
return nil, err
}
v := goVerReg.FindString(out.String())
v := goVerReg.FindString(ver)
if v == "" {
return nil, fmt.Errorf("failed to get version from go version output: %s", out)
log.Println("warning, compiling with unknown go version:", ver)
log.Println("assuming go 1.11+ rules")
v = "1.11"
}
minor, err := strconv.Atoi(v[2:])
if err != nil {
return nil, fmt.Errorf("failed to parse minor version from go version output: %s", out)
return nil, fmt.Errorf("failed to parse minor version from go version: %s", ver)
}
// yes, these two blocks are exactly the same aside from the build context,
// but we need to access struct fields so... let's just copy and paste and
Expand Down Expand Up @@ -430,11 +445,12 @@ func Magefiles(dir string) ([]string, error) {
// Compile uses the go tool to compile the files into an executable at path.
func Compile(path string, stdout, stderr io.Writer, gofiles []string, isdebug bool) error {
debug.Println("compiling to", path)
debug.Println("compiling using gocmd:", mg.GoCmd())
if isdebug {
runDebug("go", "version")
runDebug("go", "env")
runDebug(mg.GoCmd(), "version")
runDebug(mg.GoCmd(), "env")
}
c := exec.Command("go", append([]string{"build", "-o", path}, gofiles...)...)
c := exec.Command(mg.GoCmd(), append([]string{"build", "-o", path}, gofiles...)...)
c.Env = os.Environ()
c.Stderr = stderr
c.Stdout = stdout
Expand Down Expand Up @@ -502,7 +518,11 @@ func ExeName(files []string) (string, error) {
// binary.
hashes = append(hashes, fmt.Sprintf("%x", sha1.Sum([]byte(tpl))))
sort.Strings(hashes)
hash := sha1.Sum([]byte(strings.Join(hashes, "") + magicRebuildKey))
ver, err := goVersion()
if err != nil {
return "", err
}
hash := sha1.Sum([]byte(strings.Join(hashes, "") + magicRebuildKey + ver))
filename := fmt.Sprintf("%x", hash)

out := filepath.Join(mg.CacheDir(), filename)
Expand Down
36 changes: 36 additions & 0 deletions mage/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ import (
"github.com/magefile/mage/mg"
)

const testExeEnv = "MAGE_TEST_STRING"

func TestMain(m *testing.M) {
if s := os.Getenv(testExeEnv); s != "" {
fmt.Fprint(os.Stdout, s)
os.Exit(0)
}
os.Exit(testmain(m))
}

Expand Down Expand Up @@ -620,3 +626,33 @@ func TestClean(t *testing.T) {
t.Errorf("expected '-clean' to remove files from CACHE_DIR, but still have %v", files)
}
}

func TestGoCmd(t *testing.T) {
textOutput := "TestGoCmd"
if err := os.Setenv(testExeEnv, textOutput); err != nil {
t.Fatal(err)
}
if err := os.Setenv(mg.GoCmdEnv, os.Args[0]); err != nil {
t.Fatal(err)
}
defer os.Unsetenv(mg.GoCmdEnv)

// fake out the compiled file, since the code checks for it.
f, err := ioutil.TempFile("", "")
if err != nil {
t.Fatal(err)
}
name := f.Name()
defer os.Remove(name)
f.Close()

buf := &bytes.Buffer{}
stderr := &bytes.Buffer{}
if err := Compile(name, buf, stderr, []string{}, false); err != nil {
t.Log("stderr: ", stderr.String())
t.Fatal(err)
}
if buf.String() != textOutput {
t.Fatal("We didn't run the custom go cmd: ", buf.String())
}
}
16 changes: 7 additions & 9 deletions magefile.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,19 @@ import (
"strings"
"time"

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

// Runs "go install" for mage. This generates the version info the binary.
func Install() error {
ldf, err := flags()
if err != nil {
return err
}

name := "mage"
if runtime.GOOS == "windows" {
name += ".exe"
}
gopath, err := sh.Output("go", "env", "GOPATH")

gocmd := mg.GoCmd()
gopath, err := sh.Output(gocmd, "env", "GOPATH")
if err != nil {
return fmt.Errorf("can't determine GOPATH: %v", err)
}
Expand All @@ -45,7 +43,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("go", "build", "-o", path, "-ldflags="+ldf, "github.com/magefile/mage")
return sh.RunV(gocmd, "build", "-o", path, "-ldflags="+flags(), "github.com/magefile/mage")
}

// Generates a new release. Expects the TAG environment variable to be set,
Expand Down Expand Up @@ -74,14 +72,14 @@ func Clean() error {
return sh.Rm("dist")
}

func flags() (string, error) {
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), nil
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.
Expand Down
14 changes: 14 additions & 0 deletions mg/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ const VerboseEnv = "MAGEFILE_VERBOSE"
// debug mode when running mage.
const DebugEnv = "MAGEFILE_DEBUG"

// GoCmdEnv is the environment variable that indicates the user requested
// verbose mode when running a magefile.
const GoCmdEnv = "MAGEFILE_GOCMD"


// Verbose reports whether a magefile was run with the verbose flag.
func Verbose() bool {
b, _ := strconv.ParseBool(os.Getenv(VerboseEnv))
Expand All @@ -31,6 +36,15 @@ func Debug() bool {
return b
}

// GoCmd reports the command that Mage will use to build go code. By default mage runs
// the "go" binary in the PATH.
func GoCmd() string {
if cmd := os.Getenv(GoCmdEnv); cmd != "" {
return cmd
}
return "go"
}

// CacheDir returns the directory where mage caches compiled binaries. It
// defaults to $HOME/.magefile, but may be overridden by the MAGEFILE_CACHE
// environment variable.
Expand Down
3 changes: 2 additions & 1 deletion parse/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"os/exec"
"strings"

"github.com/magefile/mage/mg"
mgTypes "github.com/magefile/mage/types"
)

Expand Down Expand Up @@ -317,7 +318,7 @@ func getPackage(path string, files []string, fset *token.FileSet) (*ast.Package,
func makeInfo(dir string, fset *token.FileSet, files map[string]*ast.File) (types.Info, error) {
goroot := os.Getenv("GOROOT")
if goroot == "" {
c := exec.Command("go", "env", "GOROOT")
c := exec.Command(mg.GoCmd(), "env", "GOROOT")
b, err := c.Output()
if err != nil {
return types.Info{}, fmt.Errorf("failed to get GOROOT from 'go env': %v", err)
Expand Down
6 changes: 5 additions & 1 deletion site/content/environment/_index.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@ Set to "1" or "true" to turn on debug mode (like running with -debug)

## MAGEFILE_CACHE

Sets the directory where mage will store binaries compiled from magefiles.
Sets the directory where mage will store binaries compiled from magefiles.

## MAGEFILE_GOCMD

Sets the binary that mage will use to compile with (default is "go").
Loading