Skip to content

Commit 0c428a5

Browse files
committed
go/build: support vendor directories in Import
This fix, plus a one-line change to golang.org/x/tools/go/loader, is sufficient to let that loader package process source code using vendored packages. For example, GOPATH="" ssadump net/http # uses vendored http2 used to fail, not able to find net/http's import of the vendored copy of golang.org/x/net/http2/hpack. This CL plus the fix to loader (CL 17727) suffices to get ssadump working, as well as - I expect - most other source code processing built on golang.org/x/tools/go/loader. Fixes golang#12278. Change-Id: I83715e757419171159f67d49bb453636afdd91f0 Reviewed-on: https://go-review.googlesource.com/17726 Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent ae9529a commit 0c428a5

4 files changed

Lines changed: 124 additions & 50 deletions

File tree

src/cmd/go/pkg.go

Lines changed: 20 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ func (p *Package) vendored(imports []string) []string {
118118
seen := make(map[string]bool)
119119
var all []string
120120
for _, path := range imports {
121-
path, _ = vendoredImportPath(p, path)
121+
path = vendoredImportPath(p, path)
122122
if !seen[path] {
123123
seen[path] = true
124124
all = append(all, path)
@@ -256,6 +256,7 @@ func reloadPackage(arg string, stk *importStack) *Package {
256256
// The variable is obnoxiously long so that years from now when people find it in
257257
// their profiles and wonder what it does, there is some chance that a web search
258258
// might answer the question.
259+
// There is a copy of this variable in src/go/build/build.go. Delete that one when this one goes away.
259260
var go15VendorExperiment = os.Getenv("GO15VENDOREXPERIMENT") != "0"
260261

261262
// dirToImportPath returns the pseudo-import path we use for a package
@@ -312,11 +313,14 @@ func loadImport(path, srcDir string, parent *Package, stk *importStack, importPo
312313
importPath := path
313314
origPath := path
314315
isLocal := build.IsLocalImport(path)
315-
var vendorSearch []string
316316
if isLocal {
317317
importPath = dirToImportPath(filepath.Join(srcDir, path))
318318
} else if mode&useVendor != 0 {
319-
path, vendorSearch = vendoredImportPath(parent, path)
319+
// We do our own vendor resolution, because we want to
320+
// find out the key to use in packageCache without the
321+
// overhead of repeated calls to buildContext.Import.
322+
// The code is also needed in a few other places anyway.
323+
path = vendoredImportPath(parent, path)
320324
importPath = path
321325
}
322326

@@ -343,29 +347,14 @@ func loadImport(path, srcDir string, parent *Package, stk *importStack, importPo
343347
//
344348
// TODO: After Go 1, decide when to pass build.AllowBinary here.
345349
// See issue 3268 for mistakes to avoid.
346-
bp, err := buildContext.Import(path, srcDir, build.ImportComment)
347-
348-
// If we got an error from go/build about package not found,
349-
// it contains the directories from $GOROOT and $GOPATH that
350-
// were searched. Add to that message the vendor directories
351-
// that were searched.
352-
if err != nil && len(vendorSearch) > 0 {
353-
// NOTE(rsc): The direct text manipulation here is fairly awful,
354-
// but it avoids defining new go/build API (an exported error type)
355-
// late in the Go 1.5 release cycle. If this turns out to be a more general
356-
// problem we could define a real error type when the decision can be
357-
// considered more carefully.
358-
text := err.Error()
359-
if strings.Contains(text, "cannot find package \"") && strings.Contains(text, "\" in any of:\n\t") {
360-
old := strings.SplitAfter(text, "\n")
361-
lines := []string{old[0]}
362-
for _, dir := range vendorSearch {
363-
lines = append(lines, "\t"+dir+" (vendor tree)\n")
364-
}
365-
lines = append(lines, old[1:]...)
366-
err = errors.New(strings.Join(lines, ""))
367-
}
368-
}
350+
buildMode := build.ImportComment
351+
if go15VendorExperiment && mode&useVendor != 0 && path == origPath {
352+
// We've already searched the vendor directories and didn't find anything.
353+
// Let Import search them again so that, if the package is not found anywhere,
354+
// the error includes the vendor directories in the list of places considered.
355+
buildMode |= build.AllowVendor
356+
}
357+
bp, err := buildContext.Import(path, srcDir, buildMode)
369358
bp.ImportPath = importPath
370359
if gobin != "" {
371360
bp.BinDir = gobin
@@ -411,12 +400,9 @@ func isDir(path string) bool {
411400
// If parent is x/y/z, then path might expand to x/y/z/vendor/path, x/y/vendor/path,
412401
// x/vendor/path, vendor/path, or else stay path if none of those exist.
413402
// vendoredImportPath returns the expanded path or, if no expansion is found, the original.
414-
// If no expansion is found, vendoredImportPath also returns a list of vendor directories
415-
// it searched along the way, to help prepare a useful error message should path turn
416-
// out not to exist.
417-
func vendoredImportPath(parent *Package, path string) (found string, searched []string) {
403+
func vendoredImportPath(parent *Package, path string) (found string) {
418404
if parent == nil || parent.Root == "" || !go15VendorExperiment {
419-
return path, nil
405+
return path
420406
}
421407
dir := filepath.Clean(parent.Dir)
422408
root := filepath.Join(parent.Root, "src")
@@ -451,14 +437,12 @@ func vendoredImportPath(parent *Package, path string) (found string, searched []
451437
// and found c:\gopath\src\vendor\path.
452438
// We chopped \foo\bar (length 8) but the import path is "foo/bar" (length 7).
453439
// Use "vendor/path" without any prefix.
454-
return vpath, nil
440+
return vpath
455441
}
456-
return parent.ImportPath[:len(parent.ImportPath)-chopped] + "/" + vpath, nil
442+
return parent.ImportPath[:len(parent.ImportPath)-chopped] + "/" + vpath
457443
}
458-
// Note the existence of a vendor directory in case path is not found anywhere.
459-
searched = append(searched, targ)
460444
}
461-
return path, searched
445+
return path
462446
}
463447

464448
// reusePackage reuses package p to satisfy the import at the top

src/cmd/go/vendor_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ func TestVendorImportError(t *testing.T) {
102102

103103
re := regexp.MustCompile(`cannot find package "notfound" in any of:
104104
.*[\\/]testdata[\\/]src[\\/]vend[\\/]x[\\/]vendor[\\/]notfound \(vendor tree\)
105-
.*[\\/]testdata[\\/]src[\\/]vend[\\/]vendor[\\/]notfound \(vendor tree\)
105+
.*[\\/]testdata[\\/]src[\\/]vend[\\/]vendor[\\/]notfound
106106
.*[\\/]src[\\/]notfound \(from \$GOROOT\)
107107
.*[\\/]testdata[\\/]src[\\/]notfound \(from \$GOPATH\)`)
108108

src/go/build/build.go

Lines changed: 76 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ func (ctxt *Context) splitPathList(s string) []string {
110110
return filepath.SplitList(s)
111111
}
112112

113-
// isAbsPath calls ctxt.IsAbsSPath (if not nil) or else filepath.IsAbs.
113+
// isAbsPath calls ctxt.IsAbsPath (if not nil) or else filepath.IsAbs.
114114
func (ctxt *Context) isAbsPath(path string) bool {
115115
if f := ctxt.IsAbsPath; f != nil {
116116
return f(path)
@@ -343,6 +343,19 @@ const (
343343
// or finds conflicting comments in multiple source files.
344344
// See golang.org/s/go14customimport for more information.
345345
ImportComment
346+
347+
// If AllowVendor is set, Import searches vendor directories
348+
// that apply in the given source directory before searching
349+
// the GOROOT and GOPATH roots.
350+
// If an Import finds and returns a package using a vendor
351+
// directory, the resulting ImportPath is the complete path
352+
// to the package, including the path elements leading up
353+
// to and including "vendor".
354+
// For example, if Import("y", "x/subdir", AllowVendor) finds
355+
// "x/vendor/y", the returned package's ImportPath is "x/vendor/y",
356+
// not plain "y".
357+
// See golang.org/s/go15vendor for more information.
358+
AllowVendor
346359
)
347360

348361
// A Package describes the Go package found in a directory.
@@ -474,15 +487,22 @@ func (ctxt *Context) Import(path string, srcDir string, mode ImportMode) (*Packa
474487
switch ctxt.Compiler {
475488
case "gccgo":
476489
pkgtargetroot = "pkg/gccgo_" + ctxt.GOOS + "_" + ctxt.GOARCH + suffix
477-
dir, elem := pathpkg.Split(p.ImportPath)
478-
pkga = pkgtargetroot + "/" + dir + "lib" + elem + ".a"
479490
case "gc":
480491
pkgtargetroot = "pkg/" + ctxt.GOOS + "_" + ctxt.GOARCH + suffix
481-
pkga = pkgtargetroot + "/" + p.ImportPath + ".a"
482492
default:
483493
// Save error for end of function.
484494
pkgerr = fmt.Errorf("import %q: unknown compiler %q", path, ctxt.Compiler)
485495
}
496+
setPkga := func() {
497+
switch ctxt.Compiler {
498+
case "gccgo":
499+
dir, elem := pathpkg.Split(p.ImportPath)
500+
pkga = pkgtargetroot + "/" + dir + "lib" + elem + ".a"
501+
case "gc":
502+
pkga = pkgtargetroot + "/" + p.ImportPath + ".a"
503+
}
504+
}
505+
setPkga()
486506

487507
binaryOnly := false
488508
if IsLocalImport(path) {
@@ -543,9 +563,50 @@ func (ctxt *Context) Import(path string, srcDir string, mode ImportMode) (*Packa
543563

544564
// tried records the location of unsuccessful package lookups
545565
var tried struct {
566+
vendor []string
546567
goroot string
547568
gopath []string
548569
}
570+
gopath := ctxt.gopath()
571+
572+
// Vendor directories get first chance to satisfy import.
573+
if mode&AllowVendor != 0 && srcDir != "" {
574+
searchVendor := func(root string, isGoroot bool) bool {
575+
sub, ok := ctxt.hasSubdir(root, srcDir)
576+
if !ok || !strings.HasPrefix(sub, "src/") || strings.Contains(sub, "/testdata/") {
577+
return false
578+
}
579+
for {
580+
vendor := ctxt.joinPath(root, sub, "vendor")
581+
if ctxt.isDir(vendor) {
582+
dir := ctxt.joinPath(vendor, path)
583+
if ctxt.isDir(dir) {
584+
p.Dir = dir
585+
p.ImportPath = strings.TrimPrefix(pathpkg.Join(sub, "vendor", path), "src/")
586+
p.Goroot = isGoroot
587+
p.Root = root
588+
setPkga() // p.ImportPath changed
589+
return true
590+
}
591+
tried.vendor = append(tried.vendor, dir)
592+
}
593+
i := strings.LastIndex(sub, "/")
594+
if i < 0 {
595+
break
596+
}
597+
sub = sub[:i]
598+
}
599+
return false
600+
}
601+
if searchVendor(ctxt.GOROOT, true) {
602+
goto Found
603+
}
604+
for _, root := range gopath {
605+
if searchVendor(root, false) {
606+
goto Found
607+
}
608+
}
609+
}
549610

550611
// Determine directory from import path.
551612
if ctxt.GOROOT != "" {
@@ -560,7 +621,7 @@ func (ctxt *Context) Import(path string, srcDir string, mode ImportMode) (*Packa
560621
}
561622
tried.goroot = dir
562623
}
563-
for _, root := range ctxt.gopath() {
624+
for _, root := range gopath {
564625
dir := ctxt.joinPath(root, "src", path)
565626
isDir := ctxt.isDir(dir)
566627
binaryOnly = !isDir && mode&AllowBinary != 0 && pkga != "" && ctxt.isFile(ctxt.joinPath(root, pkga))
@@ -574,20 +635,22 @@ func (ctxt *Context) Import(path string, srcDir string, mode ImportMode) (*Packa
574635

575636
// package was not found
576637
var paths []string
638+
format := "\t%s (vendor tree)"
639+
for _, dir := range tried.vendor {
640+
paths = append(paths, fmt.Sprintf(format, dir))
641+
format = "\t%s"
642+
}
577643
if tried.goroot != "" {
578644
paths = append(paths, fmt.Sprintf("\t%s (from $GOROOT)", tried.goroot))
579645
} else {
580646
paths = append(paths, "\t($GOROOT not set)")
581647
}
582-
var i int
583-
var format = "\t%s (from $GOPATH)"
584-
for ; i < len(tried.gopath); i++ {
585-
if i > 0 {
586-
format = "\t%s"
587-
}
588-
paths = append(paths, fmt.Sprintf(format, tried.gopath[i]))
648+
format = "\t%s (from $GOPATH)"
649+
for _, dir := range tried.gopath {
650+
paths = append(paths, fmt.Sprintf(format, dir))
651+
format = "\t%s"
589652
}
590-
if i == 0 {
653+
if len(tried.gopath) == 0 {
591654
paths = append(paths, "\t($GOPATH not set)")
592655
}
593656
return p, fmt.Errorf("cannot find package %q in any of:\n%s", path, strings.Join(paths, "\n"))

src/go/build/build_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,3 +297,30 @@ func TestShellSafety(t *testing.T) {
297297
}
298298
}
299299
}
300+
301+
func TestImportVendor(t *testing.T) {
302+
ctxt := Default
303+
ctxt.GOPATH = ""
304+
p, err := ctxt.Import("golang.org/x/net/http2/hpack", filepath.Join(ctxt.GOROOT, "src/net/http"), AllowVendor)
305+
if err != nil {
306+
t.Fatalf("cannot find vendored golang.org/x/net/http2/hpack from net/http directory: %v", err)
307+
}
308+
want := "vendor/golang.org/x/net/http2/hpack"
309+
if p.ImportPath != want {
310+
t.Fatalf("Import succeeded but found %q, want %q", p.ImportPath, want)
311+
}
312+
}
313+
314+
func TestImportVendorFailure(t *testing.T) {
315+
ctxt := Default
316+
ctxt.GOPATH = ""
317+
p, err := ctxt.Import("x.com/y/z", filepath.Join(ctxt.GOROOT, "src/net/http"), AllowVendor)
318+
if err == nil {
319+
t.Fatalf("found made-up package x.com/y/z in %s", p.Dir)
320+
}
321+
322+
e := err.Error()
323+
if !strings.Contains(e, " (vendor tree)") {
324+
t.Fatalf("error on failed import does not mention GOROOT/src/vendor directory:\n%s", e)
325+
}
326+
}

0 commit comments

Comments
 (0)