Skip to content

Commit 11165f1

Browse files
switchupcbldez
authored andcommitted
feat: support for Go modules to imports
1 parent ae725fb commit 11165f1

File tree

7 files changed

+52
-196
lines changed

7 files changed

+52
-196
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ It powers executable Go scripts and plugins, in embedded interpreters or interac
1515
* Complete support of [Go specification][specs]
1616
* Written in pure Go, using only the standard library
1717
* Simple interpreter API: `New()`, `Eval()`, `Use()`
18+
* Supports Go Modules
1819
* Works everywhere Go works
1920
* All Go & runtime resources accessible from script (with control)
2021
* Security: `unsafe` and `syscall` packages neither used nor exported by default
@@ -114,6 +115,10 @@ func main() {
114115

115116
[Go Playground](https://play.golang.org/p/WvwH4JqrU-p)
116117

118+
### Use Go Modules
119+
120+
TODO
121+
117122
### As a command-line interpreter
118123

119124
The Yaegi command can run an interactive Read-Eval-Print-Loop:

go.mod

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
11
module github.com/traefik/yaegi
22

33
go 1.18
4+
5+
require golang.org/x/tools v0.1.12
6+
7+
require (
8+
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
9+
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
10+
)

go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
2+
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
3+
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
4+
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
5+
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
6+
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

interp/doc.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ Importing packages
99
Packages can be imported in source or binary form, using the standard
1010
Go import statement. In source form, packages are searched first in the
1111
vendor directory, the preferred way to store source dependencies. If not
12-
found in vendor, sources modules will be searched in GOPATH. Go modules
13-
are not supported yet by yaegi.
12+
found in vendor, sources modules will be searched in GOPATH.
1413
1514
Binary form packages are compiled and linked with the interpreter
1615
executable, and exposed to scripts with the Use method. The extract

interp/interp.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,9 @@ type Options struct {
328328
// New returns a new interpreter.
329329
func New(options Options) *Interpreter {
330330
i := Interpreter{
331-
opt: opt{context: build.Default, filesystem: &realFS{}, env: map[string]string{}},
331+
opt: opt{
332+
context: build.Default, filesystem: &realFS{}, env: make(map[string]string),
333+
},
332334
frame: newFrame(nil, 0, 0),
333335
fset: token.NewFileSet(),
334336
universe: initUniverse(),

interp/src.go

Lines changed: 27 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ package interp
33
import (
44
"fmt"
55
"io/fs"
6-
"os"
76
"path/filepath"
87
"strings"
8+
9+
"golang.org/x/tools/go/packages"
910
)
1011

11-
// importSrc calls gta on the source code for the package identified by
12+
// importSrc calls global tag analysis on the source code for the package identified by
1213
// importPath. rPath is the relative path to the directory containing the source
1314
// code for the package. It can also be "main" as a special value.
1415
func (interp *Interpreter) importSrc(rPath, importPath string, skipTest bool) (string, error) {
@@ -23,24 +24,9 @@ func (interp *Interpreter) importSrc(rPath, importPath string, skipTest bool) (s
2324
return name, nil
2425
}
2526

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-
}
27+
// resolve relative and absolute import paths.
28+
if dir, err = interp.getPackageDir(importPath); err != nil {
29+
return "", err
4430
}
4531

4632
if interp.rdir[importPath] {
@@ -171,119 +157,39 @@ func (interp *Interpreter) importSrc(rPath, importPath string, skipTest bool) (s
171157
return pkgName, nil
172158
}
173159

174-
// rootFromSourceLocation returns the path to the directory containing the input
175-
// Go file given to the interpreter, relative to $GOPATH/src.
176-
// It is meant to be called in the case when the initial input is a main package.
177-
func (interp *Interpreter) rootFromSourceLocation() (string, error) {
178-
sourceFile := interp.name
179-
if sourceFile == DefaultSourceName {
180-
return "", nil
181-
}
182-
wd, err := os.Getwd()
160+
// getPackageDir uses the provided Go module environment variables to find the absolute path of an import path.
161+
func (interp *Interpreter) getPackageDir(importPath string) (string, error) {
162+
absImportPath, err := filepath.Abs(importPath)
183163
if err != nil {
184-
return "", err
185-
}
186-
pkgDir := filepath.Join(wd, filepath.Dir(sourceFile))
187-
root := strings.TrimPrefix(pkgDir, filepath.Join(interp.context.GOPATH, "src")+"/")
188-
if root == wd {
189-
return "", fmt.Errorf("package location %s not in GOPATH", pkgDir)
190-
}
191-
return root, nil
192-
}
193-
194-
// pkgDir returns the absolute path in filesystem for a package given its import path
195-
// and the root of the subtree dependencies.
196-
func (interp *Interpreter) pkgDir(goPath string, root, importPath string) (string, string, error) {
197-
rPath := filepath.Join(root, "vendor")
198-
dir := filepath.Join(goPath, "src", rPath, importPath)
199-
200-
if _, err := fs.Stat(interp.opt.filesystem, dir); err == nil {
201-
return dir, rPath, nil // found!
164+
return "", fmt.Errorf("an error occurred determining the absolute path of import path %v: %w", importPath, err)
202165
}
203166

204-
dir = filepath.Join(goPath, "src", effectivePkg(root, importPath))
205-
206-
if _, err := fs.Stat(interp.opt.filesystem, dir); err == nil {
207-
return dir, root, nil // found!
208-
}
209-
210-
if len(root) == 0 {
211-
if interp.context.GOPATH == "" {
212-
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)
213-
}
214-
return "", "", fmt.Errorf("unable to find source related to: %q", importPath)
167+
config := packages.Config{
168+
Env: []string{
169+
"GOPATH=" + interp.context.GOPATH,
170+
"GOCACHE=" + interp.opt.env["GOCACHE"],
171+
"GOROOT=" + interp.opt.env["GOROOT"],
172+
"GOPRIVATE=" + interp.opt.env["GOPRIVATE"],
173+
"GOMODCACHE=" + interp.opt.env["GOMODCACHE"],
174+
"GO111MODULE=" + interp.opt.env["GO111MODULE"],
175+
},
215176
}
216177

217-
rootPath := filepath.Join(goPath, "src", root)
218-
prevRoot, err := previousRoot(interp.opt.filesystem, rootPath, root)
178+
pkgs, err := packages.Load(&config, absImportPath)
219179
if err != nil {
220-
return "", "", err
180+
return "", fmt.Errorf("an error occurred retrieving a package: %v\n%v\nIf Access is denied, run in administrator", absImportPath, err)
221181
}
222182

223-
return interp.pkgDir(goPath, prevRoot, importPath)
224-
}
225-
226-
const vendor = "vendor"
227-
228-
// Find the previous source root (vendor > vendor > ... > GOPATH).
229-
func previousRoot(filesystem fs.FS, rootPath, root string) (string, error) {
230-
rootPath = filepath.Clean(rootPath)
231-
parent, final := filepath.Split(rootPath)
232-
parent = filepath.Clean(parent)
233-
234-
// TODO(mpl): maybe it works for the special case main, but can't be bothered for now.
235-
if root != mainID && final != vendor {
236-
root = strings.TrimSuffix(root, string(filepath.Separator))
237-
prefix := strings.TrimSuffix(strings.TrimSuffix(rootPath, root), string(filepath.Separator))
238-
239-
// look for the closest vendor in one of our direct ancestors, as it takes priority.
240-
var vendored string
241-
for {
242-
fi, err := fs.Stat(filesystem, filepath.Join(parent, vendor))
243-
if err == nil && fi.IsDir() {
244-
vendored = strings.TrimPrefix(strings.TrimPrefix(parent, prefix), string(filepath.Separator))
245-
break
246-
}
247-
if !os.IsNotExist(err) {
248-
return "", err
249-
}
250-
251-
// stop when we reach GOPATH/src/blah
252-
parent = filepath.Dir(parent)
253-
if parent == prefix {
254-
break
183+
// confirm the import path is found.
184+
for _, pkg := range pkgs {
185+
for _, goFile := range pkg.GoFiles {
186+
if strings.Contains(filepath.Dir(goFile), pkg.Name) {
187+
return filepath.Dir(goFile), nil
255188
}
256-
257-
// just an additional failsafe, stop if we reach the filesystem root, or dot (if
258-
// we are dealing with relative paths).
259-
// TODO(mpl): It should probably be a critical error actually,
260-
// as we shouldn't have gone that high up in the tree.
261-
if parent == string(filepath.Separator) || parent == "." {
262-
break
263-
}
264-
}
265-
266-
if vendored != "" {
267-
return vendored, nil
268189
}
269190
}
270191

271-
// TODO(mpl): the algorithm below might be redundant with the one above,
272-
// but keeping it for now. Investigate/simplify/remove later.
273-
splitRoot := strings.Split(root, string(filepath.Separator))
274-
var index int
275-
for i := len(splitRoot) - 1; i >= 0; i-- {
276-
if splitRoot[i] == "vendor" {
277-
index = i
278-
break
279-
}
280-
}
281-
282-
if index == 0 {
283-
return "", nil
284-
}
285-
286-
return filepath.Join(splitRoot[:index]...), nil
192+
return "", fmt.Errorf("an import source could not be found: %q. Did you set the required environment variable in the Interpreter.Options?", absImportPath)
287193
}
288194

289195
func effectivePkg(root, path string) string {
@@ -313,9 +219,3 @@ func effectivePkg(root, path string) string {
313219

314220
return filepath.Join(root, frag)
315221
}
316-
317-
// isPathRelative returns true if path starts with "./" or "../".
318-
// It is intended for use on import paths, where "/" is always the directory separator.
319-
func isPathRelative(s string) bool {
320-
return strings.HasPrefix(s, "./") || strings.HasPrefix(s, "../")
321-
}

interp/src_test.go

Lines changed: 3 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ func Test_pkgDir(t *testing.T) {
183183
}
184184
}
185185

186-
dir, rPath, err := interp.pkgDir(goPath, test.root, test.path)
186+
dir, err := interp.getPackageDir(test.path)
187187
if err != nil {
188188
t.Fatal(err)
189189
}
@@ -192,71 +192,8 @@ func Test_pkgDir(t *testing.T) {
192192
t.Errorf("[dir] got: %s, want: %s", dir, test.expected.dir)
193193
}
194194

195-
if rPath != test.expected.rpath {
196-
t.Errorf(" [rpath] got: %s, want: %s", rPath, test.expected.rpath)
197-
}
198-
})
199-
}
200-
}
201-
202-
func Test_previousRoot(t *testing.T) {
203-
testCases := []struct {
204-
desc string
205-
root string
206-
rootPathSuffix string
207-
expected string
208-
}{
209-
{
210-
desc: "GOPATH",
211-
root: "github.com/foo/pkg/",
212-
expected: "",
213-
},
214-
{
215-
desc: "vendor level 1",
216-
root: "github.com/foo/pkg/vendor/guthib.com/traefik/fromage",
217-
expected: "github.com/foo/pkg",
218-
},
219-
{
220-
desc: "vendor level 2",
221-
root: "github.com/foo/pkg/vendor/guthib.com/traefik/fromage/vendor/guthib.com/traefik/fuu",
222-
expected: "github.com/foo/pkg/vendor/guthib.com/traefik/fromage",
223-
},
224-
{
225-
desc: "vendor is sibling",
226-
root: "github.com/foo/bar",
227-
rootPathSuffix: "testdata/src/github.com/foo/bar",
228-
expected: "github.com/foo",
229-
},
230-
{
231-
desc: "vendor is uncle",
232-
root: "github.com/foo/bar/baz",
233-
rootPathSuffix: "testdata/src/github.com/foo/bar/baz",
234-
expected: "github.com/foo",
235-
},
236-
}
237-
238-
for _, test := range testCases {
239-
test := test
240-
t.Run(test.desc, func(t *testing.T) {
241-
t.Parallel()
242-
243-
var rootPath string
244-
if test.rootPathSuffix != "" {
245-
wd, err := os.Getwd()
246-
if err != nil {
247-
t.Fatal(err)
248-
}
249-
rootPath = filepath.Join(wd, test.rootPathSuffix)
250-
} else {
251-
rootPath = vendor
252-
}
253-
p, err := previousRoot(&realFS{}, rootPath, test.root)
254-
if err != nil {
255-
t.Error(err)
256-
}
257-
258-
if p != test.expected {
259-
t.Errorf("got: %s, want: %s", p, test.expected)
195+
if test.root != test.expected.rpath {
196+
t.Errorf(" [rpath] got: %s, want: %s", test.root, test.expected.rpath)
260197
}
261198
})
262199
}

0 commit comments

Comments
 (0)