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  }