package interp import ( "fmt" "io/ioutil" "os" "path/filepath" "strings" ) // importSrc calls gta on the source code for the package identified by // importPath. rPath is the relative path to the directory containing the source // code for the package. It can also be "main" as a special value. func (interp *Interpreter) importSrc(rPath, importPath string, skipTest bool) (string, error) { var dir string var err error if interp.srcPkg[importPath] != nil { name, ok := interp.pkgNames[importPath] if !ok { return "", fmt.Errorf("inconsistent knowledge about %s", importPath) } return name, nil } // For relative import paths in the form "./xxx" or "../xxx", the initial // base path is the directory of the interpreter input file, or "." if no file // was provided. // In all other cases, absolute import paths are resolved from the GOPATH // and the nested "vendor" directories. if isPathRelative(importPath) { if rPath == mainID { rPath = "." } dir = filepath.Join(filepath.Dir(interp.name), rPath, importPath) } else if dir, rPath, err = interp.pkgDir(interp.context.GOPATH, rPath, importPath); err != nil { // Try again, assuming a root dir at the source location. if rPath, err = interp.rootFromSourceLocation(); err != nil { return "", err } if dir, rPath, err = interp.pkgDir(interp.context.GOPATH, rPath, importPath); err != nil { return "", err } } if interp.rdir[importPath] { return "", fmt.Errorf("import cycle not allowed\n\timports %s", importPath) } interp.rdir[importPath] = true files, err := ioutil.ReadDir(dir) if err != nil { return "", err } var initNodes []*node var rootNodes []*node revisit := make(map[string][]*node) var root *node var pkgName string // Parse source files. for _, file := range files { name := file.Name() if skipFile(&interp.context, name, skipTest) { continue } name = filepath.Join(dir, name) var buf []byte if buf, err = ioutil.ReadFile(name); err != nil { return "", err } var pname string if pname, root, err = interp.ast(string(buf), name, false); err != nil { return "", err } if root == nil { continue } if interp.astDot { dotCmd := interp.dotCmd if dotCmd == "" { dotCmd = defaultDotCmd(name, "yaegi-ast-") } root.astDot(dotWriter(dotCmd), name) } if pkgName == "" { pkgName = pname } else if pkgName != pname && skipTest { return "", fmt.Errorf("found packages %s and %s in %s", pkgName, pname, dir) } rootNodes = append(rootNodes, root) subRPath := effectivePkg(rPath, importPath) var list []*node list, err = interp.gta(root, subRPath, importPath) if err != nil { return "", err } revisit[subRPath] = append(revisit[subRPath], list...) } // Revisit incomplete nodes where GTA could not complete. for _, nodes := range revisit { if err = interp.gtaRetry(nodes, importPath); err != nil { return "", err } } // Generate control flow graphs. for _, root := range rootNodes { var nodes []*node if nodes, err = interp.cfg(root, importPath); err != nil { return "", err } initNodes = append(initNodes, nodes...) } // Register source package in the interpreter. The package contains only // the global symbols in the package scope. interp.mutex.Lock() gs := interp.scopes[importPath] interp.srcPkg[importPath] = gs.sym interp.pkgNames[importPath] = pkgName interp.frame.mutex.Lock() interp.resizeFrame() interp.frame.mutex.Unlock() interp.mutex.Unlock() // Once all package sources have been parsed, execute entry points then init functions. for _, n := range rootNodes { if err = genRun(n); err != nil { return "", err } interp.run(n, nil) } // Wire and execute global vars in global scope gs. n, err := genGlobalVars(rootNodes, gs) if err != nil { return "", err } interp.run(n, nil) // Add main to list of functions to run, after all inits. if m := gs.sym[mainID]; pkgName == mainID && m != nil && skipTest { initNodes = append(initNodes, m.node) } for _, n := range initNodes { interp.run(n, interp.frame) } return pkgName, nil } // rootFromSourceLocation returns the path to the directory containing the input // Go file given to the interpreter, relative to $GOPATH/src. // It is meant to be called in the case when the initial input is a main package. func (interp *Interpreter) rootFromSourceLocation() (string, error) { sourceFile := interp.name if sourceFile == DefaultSourceName { return "", nil } wd, err := os.Getwd() if err != nil { return "", err } pkgDir := filepath.Join(wd, filepath.Dir(sourceFile)) root := strings.TrimPrefix(pkgDir, filepath.Join(interp.context.GOPATH, "src")+"/") if root == wd { return "", fmt.Errorf("package location %s not in GOPATH", pkgDir) } return root, nil } // pkgDir returns the absolute path in filesystem for a package given its import path // and the root of the subtree dependencies. func (interp *Interpreter) pkgDir(goPath string, root, importPath string) (string, string, error) { rPath := filepath.Join(root, "vendor") dir := filepath.Join(goPath, "src", rPath, importPath) if _, err := os.Stat(dir); err == nil { return dir, rPath, nil // found! } dir = filepath.Join(goPath, "src", effectivePkg(root, importPath)) if _, err := os.Stat(dir); err == nil { return dir, root, nil // found! } if len(root) == 0 { if interp.context.GOPATH == "" { 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) } return "", "", fmt.Errorf("unable to find source related to: %q", importPath) } rootPath := filepath.Join(goPath, "src", root) prevRoot, err := previousRoot(rootPath, root) if err != nil { return "", "", err } return interp.pkgDir(goPath, prevRoot, importPath) } const vendor = "vendor" // Find the previous source root (vendor > vendor > ... > GOPATH). func previousRoot(rootPath, root string) (string, error) { rootPath = filepath.Clean(rootPath) parent, final := filepath.Split(rootPath) parent = filepath.Clean(parent) // TODO(mpl): maybe it works for the special case main, but can't be bothered for now. if root != mainID && final != vendor { root = strings.TrimSuffix(root, string(filepath.Separator)) prefix := strings.TrimSuffix(strings.TrimSuffix(rootPath, root), string(filepath.Separator)) // look for the closest vendor in one of our direct ancestors, as it takes priority. var vendored string for { fi, err := os.Lstat(filepath.Join(parent, vendor)) if err == nil && fi.IsDir() { vendored = strings.TrimPrefix(strings.TrimPrefix(parent, prefix), string(filepath.Separator)) break } if !os.IsNotExist(err) { return "", err } // stop when we reach GOPATH/src/blah parent = filepath.Dir(parent) if parent == prefix { break } // just an additional failsafe, stop if we reach the filesystem root, or dot (if // we are dealing with relative paths). // TODO(mpl): It should probably be a critical error actually, // as we shouldn't have gone that high up in the tree. if parent == string(filepath.Separator) || parent == "." { break } } if vendored != "" { return vendored, nil } } // TODO(mpl): the algorithm below might be redundant with the one above, // but keeping it for now. Investigate/simplify/remove later. splitRoot := strings.Split(root, string(filepath.Separator)) var index int for i := len(splitRoot) - 1; i >= 0; i-- { if splitRoot[i] == "vendor" { index = i break } } if index == 0 { return "", nil } return filepath.Join(splitRoot[:index]...), nil } func effectivePkg(root, path string) string { splitRoot := strings.Split(root, string(filepath.Separator)) splitPath := strings.Split(path, string(filepath.Separator)) var result []string rootIndex := 0 prevRootIndex := 0 for i := 0; i < len(splitPath); i++ { part := splitPath[len(splitPath)-1-i] index := len(splitRoot) - 1 - rootIndex if index > 0 && part == splitRoot[index] && i != 0 { prevRootIndex = rootIndex rootIndex++ } else if prevRootIndex == rootIndex { result = append(result, part) } } var frag string for i := len(result) - 1; i >= 0; i-- { frag = filepath.Join(frag, result[i]) } return filepath.Join(root, frag) } // isPathRelative returns true if path starts with "./" or "../". func isPathRelative(s string) bool { p := "." + string(filepath.Separator) return strings.HasPrefix(s, p) || strings.HasPrefix(s, "."+p) }