gitlab.com/ethan.reesor/vscode-notebooks/yaegi@v0.0.0-20220417214422-5c573557938e/interp/src.go (about)

     1  package interp
     2  
     3  import (
     4  	"fmt"
     5  	"io/fs"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  )
    10  
    11  // importSrc calls gta on the source code for the package identified by
    12  // importPath. rPath is the relative path to the directory containing the source
    13  // code for the package. It can also be "main" as a special value.
    14  func (interp *Interpreter) importSrc(rPath, importPath string, skipTest bool) (string, error) {
    15  	var dir string
    16  	var err error
    17  
    18  	if interp.srcPkg[importPath] != nil {
    19  		name, ok := interp.pkgNames[importPath]
    20  		if !ok {
    21  			return "", fmt.Errorf("inconsistent knowledge about %s", importPath)
    22  		}
    23  		return name, nil
    24  	}
    25  
    26  	// For relative import paths in the form "./xxx" or "../xxx", the initial
    27  	// base path is the directory of the interpreter input file, or "." if no file
    28  	// was provided.
    29  	// In all other cases, absolute import paths are resolved from the GOPATH
    30  	// and the nested "vendor" directories.
    31  	if isPathRelative(importPath) {
    32  		if rPath == mainID {
    33  			rPath = "."
    34  		}
    35  		dir = filepath.Join(filepath.Dir(interp.name), rPath, importPath)
    36  	} else if dir, rPath, err = interp.pkgDir(interp.context.GOPATH, rPath, importPath); err != nil {
    37  		// Try again, assuming a root dir at the source location.
    38  		if rPath, err = interp.rootFromSourceLocation(); err != nil {
    39  			return "", err
    40  		}
    41  		if dir, rPath, err = interp.pkgDir(interp.context.GOPATH, rPath, importPath); err != nil {
    42  			return "", err
    43  		}
    44  	}
    45  
    46  	if interp.rdir[importPath] {
    47  		return "", fmt.Errorf("import cycle not allowed\n\timports %s", importPath)
    48  	}
    49  	interp.rdir[importPath] = true
    50  
    51  	files, err := fs.ReadDir(interp.opt.filesystem, dir)
    52  	if err != nil {
    53  		return "", err
    54  	}
    55  
    56  	var initNodes []*node
    57  	var rootNodes []*node
    58  	revisit := make(map[string][]*node)
    59  
    60  	var root *node
    61  	var pkgName string
    62  
    63  	// Parse source files.
    64  	for _, file := range files {
    65  		name := file.Name()
    66  		if skipFile(&interp.context, name, skipTest) {
    67  			continue
    68  		}
    69  
    70  		name = filepath.Join(dir, name)
    71  		var buf []byte
    72  		if buf, err = fs.ReadFile(interp.opt.filesystem, name); err != nil {
    73  			return "", err
    74  		}
    75  
    76  		n, err := interp.parse(string(buf), name, false)
    77  		if err != nil {
    78  			return "", err
    79  		}
    80  		if n == nil {
    81  			continue
    82  		}
    83  
    84  		var pname string
    85  		if pname, root, err = interp.ast(n); err != nil {
    86  			return "", err
    87  		}
    88  		if root == nil {
    89  			continue
    90  		}
    91  
    92  		if interp.astDot {
    93  			dotCmd := interp.dotCmd
    94  			if dotCmd == "" {
    95  				dotCmd = defaultDotCmd(name, "yaegi-ast-")
    96  			}
    97  			root.astDot(dotWriter(dotCmd), name)
    98  		}
    99  		if pkgName == "" {
   100  			pkgName = pname
   101  		} else if pkgName != pname && skipTest {
   102  			return "", fmt.Errorf("found packages %s and %s in %s", pkgName, pname, dir)
   103  		}
   104  		rootNodes = append(rootNodes, root)
   105  
   106  		subRPath := effectivePkg(rPath, importPath)
   107  		var list []*node
   108  		list, err = interp.gta(root, subRPath, importPath, pkgName)
   109  		if err != nil {
   110  			return "", err
   111  		}
   112  		revisit[subRPath] = append(revisit[subRPath], list...)
   113  	}
   114  
   115  	// Revisit incomplete nodes where GTA could not complete.
   116  	for _, nodes := range revisit {
   117  		if err = interp.gtaRetry(nodes, importPath, pkgName); err != nil {
   118  			return "", err
   119  		}
   120  	}
   121  
   122  	// Generate control flow graphs.
   123  	for _, root := range rootNodes {
   124  		var nodes []*node
   125  		if nodes, err = interp.cfg(root, nil, importPath, pkgName); err != nil {
   126  			return "", err
   127  		}
   128  		initNodes = append(initNodes, nodes...)
   129  	}
   130  
   131  	// Register source package in the interpreter. The package contains only
   132  	// the global symbols in the package scope.
   133  	interp.mutex.Lock()
   134  	gs := interp.scopes[importPath]
   135  	interp.srcPkg[importPath] = gs.sym
   136  	interp.pkgNames[importPath] = pkgName
   137  
   138  	interp.frame.mutex.Lock()
   139  	interp.resizeFrame()
   140  	interp.frame.mutex.Unlock()
   141  	interp.mutex.Unlock()
   142  
   143  	// Once all package sources have been parsed, execute entry points then init functions.
   144  	for _, n := range rootNodes {
   145  		if err = genRun(n); err != nil {
   146  			return "", err
   147  		}
   148  		interp.run(n, nil)
   149  	}
   150  
   151  	// Wire and execute global vars in global scope gs.
   152  	n, err := genGlobalVars(rootNodes, gs)
   153  	if err != nil {
   154  		return "", err
   155  	}
   156  	interp.run(n, nil)
   157  
   158  	// Add main to list of functions to run, after all inits.
   159  	if m := gs.sym[mainID]; pkgName == mainID && m != nil && skipTest {
   160  		initNodes = append(initNodes, m.node)
   161  	}
   162  
   163  	for _, n := range initNodes {
   164  		interp.run(n, interp.frame)
   165  	}
   166  
   167  	return pkgName, nil
   168  }
   169  
   170  // rootFromSourceLocation returns the path to the directory containing the input
   171  // Go file given to the interpreter, relative to $GOPATH/src.
   172  // It is meant to be called in the case when the initial input is a main package.
   173  func (interp *Interpreter) rootFromSourceLocation() (string, error) {
   174  	sourceFile := interp.name
   175  	if sourceFile == DefaultSourceName {
   176  		return "", nil
   177  	}
   178  	wd, err := os.Getwd()
   179  	if err != nil {
   180  		return "", err
   181  	}
   182  	pkgDir := filepath.Join(wd, filepath.Dir(sourceFile))
   183  	root := strings.TrimPrefix(pkgDir, filepath.Join(interp.context.GOPATH, "src")+"/")
   184  	if root == wd {
   185  		return "", fmt.Errorf("package location %s not in GOPATH", pkgDir)
   186  	}
   187  	return root, nil
   188  }
   189  
   190  // pkgDir returns the absolute path in filesystem for a package given its import path
   191  // and the root of the subtree dependencies.
   192  func (interp *Interpreter) pkgDir(goPath string, root, importPath string) (string, string, error) {
   193  	rPath := filepath.Join(root, "vendor")
   194  	dir := filepath.Join(goPath, "src", rPath, importPath)
   195  
   196  	if _, err := fs.Stat(interp.opt.filesystem, dir); err == nil {
   197  		return dir, rPath, nil // found!
   198  	}
   199  
   200  	dir = filepath.Join(goPath, "src", effectivePkg(root, importPath))
   201  
   202  	if _, err := fs.Stat(interp.opt.filesystem, dir); err == nil {
   203  		return dir, root, nil // found!
   204  	}
   205  
   206  	if len(root) == 0 {
   207  		if interp.context.GOPATH == "" {
   208  			return "", "", fmt.Errorf("unable to find source related to: %q. Either the GOPATH environment variable, or the Interpreter.Options.GoPath needs to be set", importPath)
   209  		}
   210  		return "", "", fmt.Errorf("unable to find source related to: %q", importPath)
   211  	}
   212  
   213  	rootPath := filepath.Join(goPath, "src", root)
   214  	prevRoot, err := previousRoot(interp.opt.filesystem, rootPath, root)
   215  	if err != nil {
   216  		return "", "", err
   217  	}
   218  
   219  	return interp.pkgDir(goPath, prevRoot, importPath)
   220  }
   221  
   222  const vendor = "vendor"
   223  
   224  // Find the previous source root (vendor > vendor > ... > GOPATH).
   225  func previousRoot(filesystem fs.FS, rootPath, root string) (string, error) {
   226  	rootPath = filepath.Clean(rootPath)
   227  	parent, final := filepath.Split(rootPath)
   228  	parent = filepath.Clean(parent)
   229  
   230  	// TODO(mpl): maybe it works for the special case main, but can't be bothered for now.
   231  	if root != mainID && final != vendor {
   232  		root = strings.TrimSuffix(root, string(filepath.Separator))
   233  		prefix := strings.TrimSuffix(strings.TrimSuffix(rootPath, root), string(filepath.Separator))
   234  
   235  		// look for the closest vendor in one of our direct ancestors, as it takes priority.
   236  		var vendored string
   237  		for {
   238  			fi, err := fs.Stat(filesystem, filepath.Join(parent, vendor))
   239  			if err == nil && fi.IsDir() {
   240  				vendored = strings.TrimPrefix(strings.TrimPrefix(parent, prefix), string(filepath.Separator))
   241  				break
   242  			}
   243  			if !os.IsNotExist(err) {
   244  				return "", err
   245  			}
   246  
   247  			// stop when we reach GOPATH/src/blah
   248  			parent = filepath.Dir(parent)
   249  			if parent == prefix {
   250  				break
   251  			}
   252  
   253  			// just an additional failsafe, stop if we reach the filesystem root, or dot (if
   254  			// we are dealing with relative paths).
   255  			// TODO(mpl): It should probably be a critical error actually,
   256  			// as we shouldn't have gone that high up in the tree.
   257  			if parent == string(filepath.Separator) || parent == "." {
   258  				break
   259  			}
   260  		}
   261  
   262  		if vendored != "" {
   263  			return vendored, nil
   264  		}
   265  	}
   266  
   267  	// TODO(mpl): the algorithm below might be redundant with the one above,
   268  	// but keeping it for now. Investigate/simplify/remove later.
   269  	splitRoot := strings.Split(root, string(filepath.Separator))
   270  	var index int
   271  	for i := len(splitRoot) - 1; i >= 0; i-- {
   272  		if splitRoot[i] == "vendor" {
   273  			index = i
   274  			break
   275  		}
   276  	}
   277  
   278  	if index == 0 {
   279  		return "", nil
   280  	}
   281  
   282  	return filepath.Join(splitRoot[:index]...), nil
   283  }
   284  
   285  func effectivePkg(root, path string) string {
   286  	splitRoot := strings.Split(root, string(filepath.Separator))
   287  	splitPath := strings.Split(path, string(filepath.Separator))
   288  
   289  	var result []string
   290  
   291  	rootIndex := 0
   292  	prevRootIndex := 0
   293  	for i := 0; i < len(splitPath); i++ {
   294  		part := splitPath[len(splitPath)-1-i]
   295  
   296  		index := len(splitRoot) - 1 - rootIndex
   297  		if index > 0 && part == splitRoot[index] && i != 0 {
   298  			prevRootIndex = rootIndex
   299  			rootIndex++
   300  		} else if prevRootIndex == rootIndex {
   301  			result = append(result, part)
   302  		}
   303  	}
   304  
   305  	var frag string
   306  	for i := len(result) - 1; i >= 0; i-- {
   307  		frag = filepath.Join(frag, result[i])
   308  	}
   309  
   310  	return filepath.Join(root, frag)
   311  }
   312  
   313  // isPathRelative returns true if path starts with "./" or "../".
   314  // It is intended for use on import paths, where "/" is always the directory separator.
   315  func isPathRelative(s string) bool {
   316  	return strings.HasPrefix(s, "./") || strings.HasPrefix(s, "../")
   317  }