github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/gnovm/pkg/gnoenv/gnoroot.go (about) 1 package gnoenv 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "os/exec" 8 "path/filepath" 9 "runtime" 10 "strings" 11 "sync" 12 ) 13 14 var ErrUnableToGuessGnoRoot = errors.New("gno was unable to determine GNOROOT. Please set the GNOROOT environment variable") 15 16 // Can be set manually at build time using: 17 // -ldflags="-X github.com/gnolang/gno/gnovm/pkg/gnoenv._GNOROOT" 18 var _GNOROOT string 19 20 // RootDir guesses the Gno root directory and panics if it fails. 21 func RootDir() string { 22 root, err := GuessRootDir() 23 if err != nil { 24 panic(err) 25 } 26 27 return root 28 } 29 30 var muGnoRoot sync.Mutex 31 32 // GuessRootDir attempts to determine the Gno root directory using various strategies: 33 // 1. First, It tries to obtain it from the `GNOROOT` environment variable. 34 // 2. If the env variable isn't set, It checks if `_GNOROOT` has been previously determined or set with -ldflags. 35 // 3. If not, it uses the `go list` command to infer from go.mod. 36 // 4. As a last resort, it determines `GNOROOT` based on the caller stack's file path. 37 func GuessRootDir() (string, error) { 38 muGnoRoot.Lock() 39 defer muGnoRoot.Unlock() 40 41 // First try to get the root directory from the `GNOROOT` environment variable. 42 if rootdir := os.Getenv("GNOROOT"); rootdir != "" { 43 return strings.TrimSpace(rootdir), nil 44 } 45 46 var err error 47 if _GNOROOT == "" { 48 // Try to guess `GNOROOT` using various strategies 49 _GNOROOT, err = guessRootDir() 50 } 51 52 return _GNOROOT, err 53 } 54 55 func guessRootDir() (string, error) { 56 // Attempt to guess `GNOROOT` from go.mod by using the `go list` command. 57 if rootdir, err := inferRootFromGoMod(); err == nil { 58 return filepath.Clean(rootdir), nil 59 } 60 61 // If the above method fails, ultimately try to determine `GNOROOT` based 62 // on the caller stack's file path. 63 // Path need to be absolute here, that mostly mean that if `-trimpath` 64 // as been passed this method will not works. 65 if _, filename, _, ok := runtime.Caller(1); ok && filepath.IsAbs(filename) { 66 if currentDir := filepath.Dir(filename); currentDir != "" { 67 // Deduce Gno root directory relative from the current file's path. 68 // gno/ .. /gnovm/ .. /pkg/ .. /gnoenv/gnoenv.go 69 rootdir, err := filepath.Abs(filepath.Join(currentDir, "..", "..", "..")) 70 if err == nil { 71 return rootdir, nil 72 } 73 } 74 } 75 76 return "", ErrUnableToGuessGnoRoot 77 } 78 79 func inferRootFromGoMod() (string, error) { 80 gobin, err := exec.LookPath("go") 81 if err != nil { 82 return "", fmt.Errorf("unable to find `go` binary: %w", err) 83 } 84 85 cmd := exec.Command(gobin, "list", "-m", "-mod=mod", "-f", "{{.Dir}}", "github.com/gnolang/gno") 86 out, err := cmd.CombinedOutput() 87 if err != nil { 88 return "", fmt.Errorf("unable to infer GnoRoot from go.mod: %w", err) 89 } 90 91 return strings.TrimSpace(string(out)), nil 92 }