diff --git a/example/pkg/_pkg12/src/guthib.com/foo/main.go b/example/pkg/_pkg12/src/guthib.com/foo/main.go new file mode 100644 index 00000000..17196c59 --- /dev/null +++ b/example/pkg/_pkg12/src/guthib.com/foo/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "fmt" + + "guthib.com/foo/pkg" +) + +func main() { + fmt.Printf("%s", pkg.NewSample()()) +} diff --git a/example/pkg/_pkg12/src/guthib.com/foo/pkg/pkg.go b/example/pkg/_pkg12/src/guthib.com/foo/pkg/pkg.go new file mode 100644 index 00000000..922bb66e --- /dev/null +++ b/example/pkg/_pkg12/src/guthib.com/foo/pkg/pkg.go @@ -0,0 +1,17 @@ +package pkg + +import ( + "fmt" + + "guthib.com/bar" +) + +func Here() string { + return "hello" +} + +func NewSample() func() string { + return func() string { + return fmt.Sprintf("%s %s", bar.Bar(), Here()) + } +} diff --git a/example/pkg/_pkg12/src/guthib.com/foo/vendor/guthib.com/bar/bar.go b/example/pkg/_pkg12/src/guthib.com/foo/vendor/guthib.com/bar/bar.go new file mode 100644 index 00000000..38f3f460 --- /dev/null +++ b/example/pkg/_pkg12/src/guthib.com/foo/vendor/guthib.com/bar/bar.go @@ -0,0 +1,6 @@ +package bar + +// Bar is bar +func Bar() string { + return "Yo" +} diff --git a/example/pkg/pkg_test.go b/example/pkg/pkg_test.go index cbbbacb8..9a5c7bbf 100644 --- a/example/pkg/pkg_test.go +++ b/example/pkg/pkg_test.go @@ -83,6 +83,18 @@ func TestPackages(t *testing.T) { expected: "Fromage", evalFile: "./_pkg11/src/foo/foo.go", }, + { + desc: "vendor dir is a sibling or an uncle", + goPath: "./_pkg12/", + expected: "Yo hello", + topImport: "guthib.com/foo/pkg", + }, + { + desc: "eval main with vendor as a sibling", + goPath: "./_pkg12/", + expected: "Yo hello", + evalFile: "./_pkg12/src/guthib.com/foo/main.go", + }, } for _, test := range testCases { @@ -116,7 +128,7 @@ func TestPackages(t *testing.T) { os.Stdout = pw if _, err := i.Eval(string(data)); err != nil { - t.Fatal(err) + fatalStderrf(t, "%v", err) } var buf bytes.Buffer @@ -127,10 +139,10 @@ func TestPackages(t *testing.T) { }() if err := pw.Close(); err != nil { - t.Fatal(err) + fatalStderrf(t, "%v", err) } if err := <-errC; err != nil { - t.Fatal(err) + fatalStderrf(t, "%v", err) } msg = buf.String() } else { @@ -153,12 +165,17 @@ func TestPackages(t *testing.T) { } if msg != test.expected { - t.Errorf("Got %q, want %q", msg, test.expected) + fatalStderrf(t, "Got %q, want %q", msg, test.expected) } }) } } +func fatalStderrf(t *testing.T, format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, format+"\n", args...) + t.FailNow() +} + func TestPackagesError(t *testing.T) { testCases := []struct { desc string diff --git a/interp/src.go b/interp/src.go index 7985ce50..a369d589 100644 --- a/interp/src.go +++ b/interp/src.go @@ -22,7 +22,7 @@ func (interp *Interpreter) importSrc(rPath, path string) (string, error) { // In all other cases, absolute import paths are resolved from the GOPATH // and the nested "vendor" directories. if isPathRelative(path) { - if rPath == "main" { + if rPath == mainID { rPath = "." } dir = filepath.Join(filepath.Dir(interp.Name), rPath, path) @@ -153,7 +153,7 @@ func (interp *Interpreter) importSrc(rPath, path string) (string, error) { func (interp *Interpreter) rootFromSourceLocation(rPath string) (string, error) { sourceFile := interp.Name - if rPath != "main" || !strings.HasSuffix(sourceFile, ".go") { + if rPath != mainID || !strings.HasSuffix(sourceFile, ".go") { return rPath, nil } wd, err := os.Getwd() @@ -188,13 +188,62 @@ func pkgDir(goPath string, root, path string) (string, string, error) { return "", "", fmt.Errorf("unable to find source related to: %q", path) } - return pkgDir(goPath, previousRoot(root), path) + rootPath := filepath.Join(goPath, "src", root) + prevRoot, err := previousRoot(rootPath, root) + if err != nil { + return "", "", err + } + + return pkgDir(goPath, prevRoot, path) } -// Find the previous source root (vendor > vendor > ... > GOPATH). -func previousRoot(root string) string { - splitRoot := strings.Split(root, string(filepath.Separator)) +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(rootPath, root) + + // 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. + // 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) { + 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" { @@ -204,10 +253,10 @@ func previousRoot(root string) string { } if index == 0 { - return "" + return "", nil } - return filepath.Join(splitRoot[:index]...) + return filepath.Join(splitRoot[:index]...), nil } func effectivePkg(root, path string) string { diff --git a/interp/src_test.go b/interp/src_test.go index d23a50a8..758f897a 100644 --- a/interp/src_test.go +++ b/interp/src_test.go @@ -196,9 +196,10 @@ func Test_pkgDir(t *testing.T) { func Test_previousRoot(t *testing.T) { testCases := []struct { - desc string - root string - expected string + desc string + root string + rootPathSuffix string + expected string }{ { desc: "GOPATH", @@ -215,6 +216,18 @@ func Test_previousRoot(t *testing.T) { root: "github.com/foo/pkg/vendor/guthib.com/containous/fromage/vendor/guthib.com/containous/fuu", expected: "github.com/foo/pkg/vendor/guthib.com/containous/fromage", }, + { + desc: "vendor is sibling", + root: "github.com/foo/bar", + rootPathSuffix: "testdata/src/github.com/foo/bar", + expected: "github.com/foo", + }, + { + desc: "vendor is uncle", + root: "github.com/foo/bar/baz", + rootPathSuffix: "testdata/src/github.com/foo/bar/baz", + expected: "github.com/foo", + }, } for _, test := range testCases { @@ -222,7 +235,20 @@ func Test_previousRoot(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - p := previousRoot(test.root) + var rootPath string + if test.rootPathSuffix != "" { + wd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + rootPath = filepath.Join(wd, test.rootPathSuffix) + } else { + rootPath = vendor + } + p, err := previousRoot(rootPath, test.root) + if err != nil { + t.Error(err) + } if p != test.expected { t.Errorf("got: %s, want: %s", p, test.expected) diff --git a/interp/testdata/src/github.com/foo/bar/baz/baz.go b/interp/testdata/src/github.com/foo/bar/baz/baz.go new file mode 100644 index 00000000..d776876d --- /dev/null +++ b/interp/testdata/src/github.com/foo/bar/baz/baz.go @@ -0,0 +1 @@ +package baz diff --git a/interp/testdata/src/github.com/foo/vendor/whatever/whatever.go b/interp/testdata/src/github.com/foo/vendor/whatever/whatever.go new file mode 100644 index 00000000..5442027d --- /dev/null +++ b/interp/testdata/src/github.com/foo/vendor/whatever/whatever.go @@ -0,0 +1 @@ +package whatever