From 3b94daa1b31a5eeb58bb1b4507fe4662c07b585b Mon Sep 17 00:00:00 2001 From: Morgan Hein Date: Tue, 15 Oct 2024 20:53:54 -0700 Subject: [PATCH 1/2] feat: walk up to the root directory or to a .git repository looking for a ./magefiles folder --- mage/main.go | 87 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 69 insertions(+), 18 deletions(-) diff --git a/mage/main.go b/mage/main.go index 0062bd35..6f4a7d3a 100644 --- a/mage/main.go +++ b/mage/main.go @@ -310,6 +310,68 @@ Options: const dotDirectory = "." +// walkUpToMageFiles walks up the directory tree from the given directory until it finds a magefiles directory. +// It stops: +// 1. if a ./magefiles directory is found +// 2. if a ./.git directory is found, but no magefiles directory exists +// 3. if the root directory is reached and no magefiles directory exists +func walkUpToMageFiles(dir string, log *log.Logger) (string, bool, error) { + if dir == "." { + // set dir to the current working directory + cwd, err := os.Getwd() + if err != nil { + return "", false, err + } + dir = cwd + } + magefilesDir, hasMagefiles := checkHasMagefiles(dir) + if !hasMagefiles && dir == "/" { + return "", false, nil + } + // check if the folder contains a .git folder, if so then also stop walking up the tree + _, err := os.Stat(filepath.Join(dir, ".git")) + if !hasMagefiles && err == nil { + return "", false, nil + } + if !hasMagefiles { + // walk up the tree and try again + newDir := filepath.Dir(dir) + return walkUpToMageFiles(newDir, log) + } + return magefilesDir, true, nil +} + +// checkHasMagefiles checks if the given directory contains a magefiles directory. +func checkHasMagefiles(dir string) (string, bool) { + magefilesDir := filepath.Join(dir, MagefilesDirName) + fmt.Printf("checking if %s exists\n", magefilesDir) + mfSt, err := os.Stat(magefilesDir) + if err != nil { + return "", false + } + return magefilesDir, mfSt.IsDir() +} + +// checkCollidingMageFiles is used if we've found a magefiles folder, and then +// determines if our current working directory contains magefiles as well. It defaults to the CWD +// if both are found, and provides a warning. +func checkCollidingMagefiles(inv Invocation, dir string) Invocation { + originalDir := inv.Dir + stderrBuf := &bytes.Buffer{} + inv.Dir = dir // preemptive assignment + // TODO: Remove this fallback and the above Magefiles invocation when the bw compatibility is removed. + files, err := Magefiles(originalDir, inv.GOOS, inv.GOARCH, inv.GoCmd, stderrBuf, false, inv.Debug) + if err == nil { + if len(files) != 0 { + log.Println("[WARNING] You have both a magefiles directory and mage files in the " + + "current directory, in future versions the files will be ignored in favor of the directory") + inv.Dir = originalDir + return inv + } + } + return inv +} + // Invoke runs Mage with the given arguments. func Invoke(inv Invocation) int { errlog := log.New(inv.Stderr, "", 0) @@ -322,24 +384,13 @@ func Invoke(inv Invocation) int { if inv.WorkDir == "" { inv.WorkDir = inv.Dir } - magefilesDir := filepath.Join(inv.Dir, MagefilesDirName) - // . will be default unless we find a mage folder. - mfSt, err := os.Stat(magefilesDir) - if err == nil { - if mfSt.IsDir() { - stderrBuf := &bytes.Buffer{} - originalDir := inv.Dir - inv.Dir = magefilesDir // preemptive assignment - // TODO: Remove this fallback and the above Magefiles invocation when the bw compatibility is removed. - files, err := Magefiles(originalDir, inv.GOOS, inv.GOARCH, inv.GoCmd, stderrBuf, false, inv.Debug) - if err == nil { - if len(files) != 0 { - errlog.Println("[WARNING] You have both a magefiles directory and mage files in the " + - "current directory, in future versions the files will be ignored in favor of the directory") - inv.Dir = originalDir - } - } - } + mageDir, found, err := walkUpToMageFiles(inv.Dir, errlog) + if err != nil { + errlog.Println("Error walking up to magefiles directory:", err) + return 1 + } + if found { + inv = checkCollidingMagefiles(inv, mageDir) } if inv.CacheDir == "" { From d415595c481900ade7948284369568610cf8d4e8 Mon Sep 17 00:00:00 2001 From: Morgan Hein Date: Tue, 15 Oct 2024 21:20:47 -0700 Subject: [PATCH 2/2] feat: added the ability to opt out of traversal using env variable and program argument --- mage/main.go | 68 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/mage/main.go b/mage/main.go index 6f4a7d3a..64b7d967 100644 --- a/mage/main.go +++ b/mage/main.go @@ -99,26 +99,27 @@ func Main() int { // Invocation contains the args for invoking a run of Mage. type Invocation struct { - Debug bool // turn on debug messages - Dir string // directory to read magefiles from - WorkDir string // directory where magefiles will run - Force bool // forces recreation of the compiled binary - Verbose bool // tells the magefile to print out log statements - List bool // tells the magefile to print out a list of targets - Help bool // tells the magefile to print out help for a specific target - Keep bool // tells mage to keep the generated main file after compiling - Timeout time.Duration // tells mage to set a timeout to running the targets - CompileOut string // tells mage to compile a static binary to this path, but not execute - GOOS string // sets the GOOS when producing a binary with -compileout - GOARCH string // sets the GOARCH when producing a binary with -compileout - Ldflags string // sets the ldflags when producing a binary with -compileout - Stdout io.Writer // writer to write stdout messages to - Stderr io.Writer // writer to write stderr messages to - Stdin io.Reader // reader to read stdin from - Args []string // args to pass to the compiled binary - GoCmd string // the go binary command to run - CacheDir string // the directory where we should store compiled binaries - HashFast bool // don't rely on GOCACHE, just hash the magefiles + Debug bool // turn on debug messages + Dir string // directory to read magefiles from + WorkDir string // directory where magefiles will run + DisableTraversal bool // disable walking up the tree from the current directory to find a magefiles directory + Force bool // forces recreation of the compiled binary + Verbose bool // tells the magefile to print out log statements + List bool // tells the magefile to print out a list of targets + Help bool // tells the magefile to print out help for a specific target + Keep bool // tells mage to keep the generated main file after compiling + Timeout time.Duration // tells mage to set a timeout to running the targets + CompileOut string // tells mage to compile a static binary to this path, but not execute + GOOS string // sets the GOOS when producing a binary with -compileout + GOARCH string // sets the GOARCH when producing a binary with -compileout + Ldflags string // sets the ldflags when producing a binary with -compileout + Stdout io.Writer // writer to write stdout messages to + Stderr io.Writer // writer to write stderr messages to + Stdin io.Reader // reader to read stdin from + Args []string // args to pass to the compiled binary + GoCmd string // the go binary command to run + CacheDir string // the directory where we should store compiled binaries + HashFast bool // don't rely on GOCACHE, just hash the magefiles } // MagefilesDirName is the name of the default folder to look for if no directory was specified, @@ -190,6 +191,7 @@ func Parse(stderr, stdout io.Writer, args []string) (inv Invocation, cmd Command fs.BoolVar(&inv.Debug, "debug", mg.Debug(), "turn on debug messages") fs.BoolVar(&inv.Verbose, "v", mg.Verbose(), "show verbose output when running mage targets") fs.BoolVar(&inv.Help, "h", false, "show this help") + fs.BoolVar(&inv.DisableTraversal, "no-traverse", false, "disable traversing up the directory tree to find magefiles") fs.DurationVar(&inv.Timeout, "t", 0, "timeout in duration parsable format (e.g. 5m30s)") fs.BoolVar(&inv.Keep, "keep", false, "keep intermediate mage files around after running") fs.StringVar(&inv.Dir, "d", "", "directory to read magefiles from") @@ -240,6 +242,8 @@ Options: -keep keep intermediate mage files around after running -t timeout in duration parsable format (e.g. 5m30s) + -no-traverse + disable traversing up the directory tree to find magefiles -v show verbose output when running mage targets -w working directory where magefiles will run (default -d value) @@ -344,7 +348,6 @@ func walkUpToMageFiles(dir string, log *log.Logger) (string, bool, error) { // checkHasMagefiles checks if the given directory contains a magefiles directory. func checkHasMagefiles(dir string) (string, bool) { magefilesDir := filepath.Join(dir, MagefilesDirName) - fmt.Printf("checking if %s exists\n", magefilesDir) mfSt, err := os.Stat(magefilesDir) if err != nil { return "", false @@ -384,13 +387,16 @@ func Invoke(inv Invocation) int { if inv.WorkDir == "" { inv.WorkDir = inv.Dir } - mageDir, found, err := walkUpToMageFiles(inv.Dir, errlog) - if err != nil { - errlog.Println("Error walking up to magefiles directory:", err) - return 1 - } - if found { - inv = checkCollidingMagefiles(inv, mageDir) + enableTraverse := !inv.DisableTraversal && os.Getenv("MAGEFILE_NO_TRAVERSE") != "1" + if enableTraverse { + mageDir, found, err := walkUpToMageFiles(inv.Dir, errlog) + if err != nil { + errlog.Println("Error walking up to magefiles directory:", err) + return 1 + } + if found { + inv = checkCollidingMagefiles(inv, mageDir) + } } if inv.CacheDir == "" { @@ -404,7 +410,11 @@ func Invoke(inv Invocation) int { } if len(files) == 0 { - errlog.Println("No .go files marked with the mage build tag in this directory.") + if enableTraverse { + errlog.Println("No magefiles found in this directory or any parent. Run 'mage -init' to create one.") + } else { + errlog.Println("No magefiles found in this directory. Run 'mage -init' to create one.") + } return 1 } debug.Printf("found magefiles: %s", strings.Join(files, ", "))