github.com/traefik/yaegi@v0.15.1/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 if gs == nil { 136 interp.mutex.Unlock() 137 // A nil scope means that no even an empty package is created from source. 138 return "", fmt.Errorf("no Go files in %s", dir) 139 } 140 interp.srcPkg[importPath] = gs.sym 141 interp.pkgNames[importPath] = pkgName 142 143 interp.frame.mutex.Lock() 144 interp.resizeFrame() 145 interp.frame.mutex.Unlock() 146 interp.mutex.Unlock() 147 148 // Once all package sources have been parsed, execute entry points then init functions. 149 for _, n := range rootNodes { 150 if err = genRun(n); err != nil { 151 return "", err 152 } 153 interp.run(n, nil) 154 } 155 156 // Wire and execute global vars in global scope gs. 157 n, err := genGlobalVars(rootNodes, gs) 158 if err != nil { 159 return "", err 160 } 161 interp.run(n, nil) 162 163 // Add main to list of functions to run, after all inits. 164 if m := gs.sym[mainID]; pkgName == mainID && m != nil && skipTest { 165 initNodes = append(initNodes, m.node) 166 } 167 168 for _, n := range initNodes { 169 interp.run(n, interp.frame) 170 } 171 172 return pkgName, nil 173 } 174 175 // rootFromSourceLocation returns the path to the directory containing the input 176 // Go file given to the interpreter, relative to $GOPATH/src. 177 // It is meant to be called in the case when the initial input is a main package. 178 func (interp *Interpreter) rootFromSourceLocation() (string, error) { 179 sourceFile := interp.name 180 if sourceFile == DefaultSourceName { 181 return "", nil 182 } 183 wd, err := os.Getwd() 184 if err != nil { 185 return "", err 186 } 187 pkgDir := filepath.Join(wd, filepath.Dir(sourceFile)) 188 root := strings.TrimPrefix(pkgDir, filepath.Join(interp.context.GOPATH, "src")+"/") 189 if root == wd { 190 return "", fmt.Errorf("package location %s not in GOPATH", pkgDir) 191 } 192 return root, nil 193 } 194 195 // pkgDir returns the absolute path in filesystem for a package given its import path 196 // and the root of the subtree dependencies. 197 func (interp *Interpreter) pkgDir(goPath string, root, importPath string) (string, string, error) { 198 rPath := filepath.Join(root, "vendor") 199 dir := filepath.Join(goPath, "src", rPath, importPath) 200 201 if _, err := fs.Stat(interp.opt.filesystem, dir); err == nil { 202 return dir, rPath, nil // found! 203 } 204 205 dir = filepath.Join(goPath, "src", effectivePkg(root, importPath)) 206 207 if _, err := fs.Stat(interp.opt.filesystem, dir); err == nil { 208 return dir, root, nil // found! 209 } 210 211 if len(root) == 0 { 212 if interp.context.GOPATH == "" { 213 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) 214 } 215 return "", "", fmt.Errorf("unable to find source related to: %q", importPath) 216 } 217 218 rootPath := filepath.Join(goPath, "src", root) 219 prevRoot, err := previousRoot(interp.opt.filesystem, rootPath, root) 220 if err != nil { 221 return "", "", err 222 } 223 224 return interp.pkgDir(goPath, prevRoot, importPath) 225 } 226 227 const vendor = "vendor" 228 229 // Find the previous source root (vendor > vendor > ... > GOPATH). 230 func previousRoot(filesystem fs.FS, rootPath, root string) (string, error) { 231 rootPath = filepath.Clean(rootPath) 232 parent, final := filepath.Split(rootPath) 233 parent = filepath.Clean(parent) 234 235 // TODO(mpl): maybe it works for the special case main, but can't be bothered for now. 236 if root != mainID && final != vendor { 237 root = strings.TrimSuffix(root, string(filepath.Separator)) 238 prefix := strings.TrimSuffix(strings.TrimSuffix(rootPath, root), string(filepath.Separator)) 239 240 // look for the closest vendor in one of our direct ancestors, as it takes priority. 241 var vendored string 242 for { 243 fi, err := fs.Stat(filesystem, filepath.Join(parent, vendor)) 244 if err == nil && fi.IsDir() { 245 vendored = strings.TrimPrefix(strings.TrimPrefix(parent, prefix), string(filepath.Separator)) 246 break 247 } 248 if !os.IsNotExist(err) { 249 return "", err 250 } 251 // stop when we reach GOPATH/src 252 if parent == prefix { 253 break 254 } 255 256 // stop when we reach GOPATH/src/blah 257 parent = filepath.Dir(parent) 258 if parent == prefix { 259 break 260 } 261 262 // just an additional failsafe, stop if we reach the filesystem root, or dot (if 263 // we are dealing with relative paths). 264 // TODO(mpl): It should probably be a critical error actually, 265 // as we shouldn't have gone that high up in the tree. 266 // TODO(dennwc): This partially fails on Windows, since it cannot recognize drive letters as "root". 267 if parent == string(filepath.Separator) || parent == "." || parent == "" { 268 break 269 } 270 } 271 272 if vendored != "" { 273 return vendored, nil 274 } 275 } 276 277 // TODO(mpl): the algorithm below might be redundant with the one above, 278 // but keeping it for now. Investigate/simplify/remove later. 279 splitRoot := strings.Split(root, string(filepath.Separator)) 280 var index int 281 for i := len(splitRoot) - 1; i >= 0; i-- { 282 if splitRoot[i] == "vendor" { 283 index = i 284 break 285 } 286 } 287 288 if index == 0 { 289 return "", nil 290 } 291 292 return filepath.Join(splitRoot[:index]...), nil 293 } 294 295 func effectivePkg(root, path string) string { 296 splitRoot := strings.Split(root, string(filepath.Separator)) 297 splitPath := strings.Split(path, string(filepath.Separator)) 298 299 var result []string 300 301 rootIndex := 0 302 prevRootIndex := 0 303 for i := 0; i < len(splitPath); i++ { 304 part := splitPath[len(splitPath)-1-i] 305 306 index := len(splitRoot) - 1 - rootIndex 307 if index > 0 && part == splitRoot[index] && i != 0 { 308 prevRootIndex = rootIndex 309 rootIndex++ 310 } else if prevRootIndex == rootIndex { 311 result = append(result, part) 312 } 313 } 314 315 var frag string 316 for i := len(result) - 1; i >= 0; i-- { 317 frag = filepath.Join(frag, result[i]) 318 } 319 320 return filepath.Join(root, frag) 321 } 322 323 // isPathRelative returns true if path starts with "./" or "../". 324 // It is intended for use on import paths, where "/" is always the directory separator. 325 func isPathRelative(s string) bool { 326 return strings.HasPrefix(s, "./") || strings.HasPrefix(s, "../") 327 }