Skip to content

Commit e1fda1a

Browse files
authored
add MAGEFILE_HASHFAST that intentionally doesn't rerun go build (#258)
1 parent 05f8c9d commit e1fda1a

File tree

5 files changed

+93
-11
lines changed

5 files changed

+93
-11
lines changed

mage/main.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ type Invocation struct {
110110
Args []string // args to pass to the compiled binary
111111
GoCmd string // the go binary command to run
112112
CacheDir string // the directory where we should store compiled binaries
113+
HashFast bool // don't rely on GOCACHE, just hash the magefiles
113114
}
114115

115116
// ParseAndRun parses the command line, and then compiles and runs the mage
@@ -282,7 +283,7 @@ Options:
282283
if len(inv.Args) > 0 && cmd != None {
283284
return inv, cmd, fmt.Errorf("unexpected arguments to command: %q", inv.Args)
284285
}
285-
286+
inv.HashFast = mg.HashFast()
286287
return inv, cmd, err
287288
}
288289

@@ -321,12 +322,16 @@ func Invoke(inv Invocation) int {
321322
debug.Println("output exe is ", exePath)
322323

323324
useCache := false
324-
if s, err := internal.OutputDebug(inv.GoCmd, "env", "GOCACHE"); err == nil {
325-
// if GOCACHE exists, always rebuild, so we catch transitive
326-
// dependencies that have changed.
327-
if s != "" {
328-
debug.Println("build cache exists, will ignore any compiled binary")
329-
useCache = true
325+
if inv.HashFast {
326+
debug.Println("user has set MAGEFILE_HASHFAST, so we'll ignore GOCACHE")
327+
} else {
328+
if s, err := internal.OutputDebug(inv.GoCmd, "env", "GOCACHE"); err == nil {
329+
// if GOCACHE exists, always rebuild, so we catch transitive
330+
// dependencies that have changed.
331+
if s != "" {
332+
debug.Println("go build cache exists, will ignore any compiled binary")
333+
useCache = true
334+
}
330335
}
331336
}
332337

mage/main_test.go

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func testmain(m *testing.M) int {
6161
}
6262

6363
func TestTransitiveDepCache(t *testing.T) {
64-
cache, err := internal.OutputDebug("go", "env", "gocache")
64+
cache, err := internal.OutputDebug("go", "env", "GOCACHE")
6565
if err != nil {
6666
t.Fatal(err)
6767
}
@@ -108,6 +108,61 @@ func TestTransitiveDepCache(t *testing.T) {
108108
}
109109
}
110110

111+
func TestTransitiveHashFast(t *testing.T) {
112+
cache, err := internal.OutputDebug("go", "env", "GOCACHE")
113+
if err != nil {
114+
t.Fatal(err)
115+
}
116+
if cache == "" {
117+
t.Skip("skipping hashfast tests on go version without cache")
118+
}
119+
120+
// Test that if we change a transitive dep, that we don't recompile.
121+
// We intentionally run the first time without hashfast to ensure that
122+
// we recompile the binary with the current code.
123+
stdout := &bytes.Buffer{}
124+
stderr := &bytes.Buffer{}
125+
inv := Invocation{
126+
Stderr: stderr,
127+
Stdout: stdout,
128+
Dir: "testdata/transitiveDeps",
129+
Args: []string{"Run"},
130+
}
131+
code := Invoke(inv)
132+
if code != 0 {
133+
t.Fatalf("got code %v, err: %s", code, stderr)
134+
}
135+
expected := "woof\n"
136+
if actual := stdout.String(); actual != expected {
137+
t.Fatalf("expected %q but got %q", expected, actual)
138+
}
139+
140+
// ok, so baseline, the generated and cached binary should do "woof"
141+
// now change out the transitive dependency that does the output
142+
// so that it produces different output.
143+
if err := os.Rename("testdata/transitiveDeps/dep/dog.go", "testdata/transitiveDeps/dep/dog.notgo"); err != nil {
144+
t.Fatal(err)
145+
}
146+
defer os.Rename("testdata/transitiveDeps/dep/dog.notgo", "testdata/transitiveDeps/dep/dog.go")
147+
if err := os.Rename("testdata/transitiveDeps/dep/cat.notgo", "testdata/transitiveDeps/dep/cat.go"); err != nil {
148+
t.Fatal(err)
149+
}
150+
defer os.Rename("testdata/transitiveDeps/dep/cat.go", "testdata/transitiveDeps/dep/cat.notgo")
151+
stderr.Reset()
152+
stdout.Reset()
153+
inv.HashFast = true
154+
code = Invoke(inv)
155+
if code != 0 {
156+
t.Fatalf("got code %v, err: %s", code, stderr)
157+
}
158+
// we should still get woof, even though the dependency was changed to
159+
// return "meow", because we're only hashing the top level magefiles, not
160+
// dependencies.
161+
if actual := stdout.String(); actual != expected {
162+
t.Fatalf("expected %q but got %q", expected, actual)
163+
}
164+
}
165+
111166
func TestListMagefilesMain(t *testing.T) {
112167
buf := &bytes.Buffer{}
113168
files, err := Magefiles("testdata/mixed_main_files", "", "", "go", buf, false)

mage/testdata/transitiveDeps/magefile.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
package main
44

5-
import "./dep"
5+
import "github.com/magefile/mage/mage/testdata/transitiveDeps/dep"
66

77
func Run() {
88
dep.Speak()

mg/runtime.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ const GoCmdEnv = "MAGEFILE_GOCMD"
2727
// to ignore the default target specified in the magefile.
2828
const IgnoreDefaultEnv = "MAGEFILE_IGNOREDEFAULT"
2929

30+
// HashFastEnv is the environment variable that indicates the user requested to
31+
// use a quick hash of magefiles to determine whether or not the magefile binary
32+
// needs to be rebuilt. This results in faster runtimes, but means that mage
33+
// will fail to rebuild if a dependency has changed. To force a rebuild, run
34+
// mage with the -f flag.
35+
const HashFastEnv = "MAGEFILE_HASHFAST"
36+
3037
// Verbose reports whether a magefile was run with the verbose flag.
3138
func Verbose() bool {
3239
b, _ := strconv.ParseBool(os.Getenv(VerboseEnv))
@@ -48,6 +55,13 @@ func GoCmd() string {
4855
return "go"
4956
}
5057

58+
// HashFast reports whether the user has requested to use the fast hashing
59+
// mechanism rather than rely on go's rebuilding mechanism.
60+
func HashFast() bool {
61+
b, _ := strconv.ParseBool(os.Getenv(HashFastEnv))
62+
return b
63+
}
64+
5165
// IgnoreDefault reports whether the user has requested to ignore the default target
5266
// in the magefile.
5367
func IgnoreDefault() bool {

site/content/environment/_index.en.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,13 @@ Sets the binary that mage will use to compile with (default is "go").
2222

2323
## MAGEFILE_IGNOREDEFAULT
2424

25-
If set to 1 or true, will tell the compiled magefile to ignore the default
26-
target and print the list of targets when you run `mage`.
25+
If set to "1" or "true", tells the compiled magefile to ignore the default
26+
target and print the list of targets when you run `mage`.
27+
28+
## MAGEFILE_HASHFAST
29+
30+
If set to "1" or "true", tells mage to use a quick hash of magefiles to
31+
determine whether or not the magefile binary needs to be rebuilt. This results
32+
in faster run times (especially on Windows), but means that mage will fail to
33+
rebuild if a dependency has changed. To force a rebuild when you know or suspect
34+
a dependency has changed, run mage with the -f flag.

0 commit comments

Comments
 (0)