Commit 9aa6f80e authored by Russ Cox's avatar Russ Cox

go/importer: support lookup in importer.For

The support in this CL assumes that something at a higher level than
the toolchain-specific importers is taking care of converting imports
in source code into canonical import paths before invoking the
toolchain-specific importers. That kind of "what does an import mean"
as opposed to "find me the import data for this specific path"
should be provided by higher-level layers.

That's a different layering than the default behavior but matches the
current layering in the compiler and linker and works with the metadata
planned for generation by the go command for package management.
It should also eventually allow the importer code to stop concerning
itself with source directories and vendor import translation and maybe
deprecate ImporterFrom in favor of Importer once again. But that's all
in the future. For now, just make non-nil lookups work, and test that.

Fixes #13847.
Adds #22550.

Change-Id: I048c6a384492e634988a7317942667689ae680ff
Reviewed-on: https://go-review.googlesource.com/74354
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarRobert Griesemer <gri@golang.org>
parent 3e887ff7
...@@ -29,23 +29,25 @@ type Lookup func(path string) (io.ReadCloser, error) ...@@ -29,23 +29,25 @@ type Lookup func(path string) (io.ReadCloser, error)
// checker won't have access to those). // checker won't have access to those).
// //
// If lookup is nil, the default package lookup mechanism for the // If lookup is nil, the default package lookup mechanism for the
// given compiler is used. // given compiler is used, and the resulting importer attempts
// to resolve relative and absolute import paths to canonical
// import path IDs before finding the imported file.
// //
// BUG(issue13847): For does not support non-nil lookup functions. // If lookup is non-nil, then the returned importer calls lookup
// each time it needs to resolve an import path. In this mode
// the importer can only be invoked with canonical import paths
// (not relative or absolute ones); it is assumed that the translation
// to canonical import paths is being done by the client of the
// importer.
func For(compiler string, lookup Lookup) types.Importer { func For(compiler string, lookup Lookup) types.Importer {
switch compiler { switch compiler {
case "gc": case "gc":
if lookup != nil { return &gcimports{
panic("gc importer for custom import path lookup not supported (issue #13847).") packages: make(map[string]*types.Package),
lookup: lookup,
} }
return make(gcimports)
case "gccgo": case "gccgo":
if lookup != nil {
panic("gccgo importer for custom import path lookup not supported (issue #13847).")
}
var inst gccgoimporter.GccgoInstallation var inst gccgoimporter.GccgoInstallation
if err := inst.InitFromDriver("gccgo"); err != nil { if err := inst.InitFromDriver("gccgo"); err != nil {
return nil return nil
...@@ -53,6 +55,7 @@ func For(compiler string, lookup Lookup) types.Importer { ...@@ -53,6 +55,7 @@ func For(compiler string, lookup Lookup) types.Importer {
return &gccgoimports{ return &gccgoimports{
packages: make(map[string]*types.Package), packages: make(map[string]*types.Package),
importer: inst.GetImporter(nil, nil), importer: inst.GetImporter(nil, nil),
lookup: lookup,
} }
case "source": case "source":
...@@ -75,17 +78,20 @@ func Default() types.Importer { ...@@ -75,17 +78,20 @@ func Default() types.Importer {
// gc importer // gc importer
type gcimports map[string]*types.Package type gcimports struct {
packages map[string]*types.Package
lookup Lookup
}
func (m gcimports) Import(path string) (*types.Package, error) { func (m *gcimports) Import(path string) (*types.Package, error) {
return m.ImportFrom(path, "" /* no vendoring */, 0) return m.ImportFrom(path, "" /* no vendoring */, 0)
} }
func (m gcimports) ImportFrom(path, srcDir string, mode types.ImportMode) (*types.Package, error) { func (m *gcimports) ImportFrom(path, srcDir string, mode types.ImportMode) (*types.Package, error) {
if mode != 0 { if mode != 0 {
panic("mode must be 0") panic("mode must be 0")
} }
return gcimporter.Import(m, path, srcDir) return gcimporter.Import(m.packages, path, srcDir, m.lookup)
} }
// gccgo importer // gccgo importer
...@@ -93,6 +99,7 @@ func (m gcimports) ImportFrom(path, srcDir string, mode types.ImportMode) (*type ...@@ -93,6 +99,7 @@ func (m gcimports) ImportFrom(path, srcDir string, mode types.ImportMode) (*type
type gccgoimports struct { type gccgoimports struct {
packages map[string]*types.Package packages map[string]*types.Package
importer gccgoimporter.Importer importer gccgoimporter.Importer
lookup Lookup
} }
func (m *gccgoimports) Import(path string) (*types.Package, error) { func (m *gccgoimports) Import(path string) (*types.Package, error) {
...@@ -103,6 +110,5 @@ func (m *gccgoimports) ImportFrom(path, srcDir string, mode types.ImportMode) (* ...@@ -103,6 +110,5 @@ func (m *gccgoimports) ImportFrom(path, srcDir string, mode types.ImportMode) (*
if mode != 0 { if mode != 0 {
panic("mode must be 0") panic("mode must be 0")
} }
// TODO(gri) pass srcDir return m.importer(m.packages, path, srcDir, m.lookup)
return m.importer(m.packages, path)
} }
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package importer
import (
"internal/testenv"
"io"
"os"
"os/exec"
"strings"
"testing"
)
func TestFor(t *testing.T) {
testenv.MustHaveGoBuild(t)
const thePackage = "math/big"
out, err := exec.Command("go", "list", "-f={{context.Compiler}}:{{.Target}}", thePackage).CombinedOutput()
if err != nil {
t.Fatalf("go list %s: %v\n%s", thePackage, err, out)
}
target := strings.TrimSpace(string(out))
i := strings.Index(target, ":")
compiler, target := target[:i], target[i+1:]
if !strings.HasSuffix(target, ".a") {
t.Fatalf("unexpected package %s target %q (not *.a)", thePackage, target)
}
if compiler == "gccgo" {
t.Skip("golang.org/issue/22500")
}
t.Run("LookupDefault", func(t *testing.T) {
imp := For(compiler, nil)
pkg, err := imp.Import(thePackage)
if err != nil {
t.Fatal(err)
}
if pkg.Path() != thePackage {
t.Fatalf("Path() = %q, want %q", pkg.Path(), thePackage)
}
})
t.Run("LookupCustom", func(t *testing.T) {
lookup := func(path string) (io.ReadCloser, error) {
if path != "math/bigger" {
t.Fatalf("lookup called with unexpected path %q", path)
}
f, err := os.Open(target)
if err != nil {
t.Fatal(err)
}
return f, nil
}
imp := For(compiler, lookup)
pkg, err := imp.Import("math/bigger")
if err != nil {
t.Fatal(err)
}
// Even though we open math/big.a, the import request was for math/bigger
// and that should be recorded in pkg.Path(), at least for the gc toolchain.
if pkg.Path() != "math/bigger" {
t.Fatalf("Path() = %q, want %q", pkg.Path(), "math/bigger")
}
})
}
...@@ -166,14 +166,14 @@ func TestInstallationImporter(t *testing.T) { ...@@ -166,14 +166,14 @@ func TestInstallationImporter(t *testing.T) {
// all packages into the same map and then each individually. // all packages into the same map and then each individually.
pkgMap := make(map[string]*types.Package) pkgMap := make(map[string]*types.Package)
for _, pkg := range importablePackages { for _, pkg := range importablePackages {
_, err = imp(pkgMap, pkg) _, err = imp(pkgMap, pkg, ".", nil)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
} }
for _, pkg := range importablePackages { for _, pkg := range importablePackages {
_, err = imp(make(map[string]*types.Package), pkg) _, err = imp(make(map[string]*types.Package), pkg, ".", nil)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
......
...@@ -137,25 +137,53 @@ func openExportFile(fpath string) (reader io.ReadSeeker, closer io.Closer, err e ...@@ -137,25 +137,53 @@ func openExportFile(fpath string) (reader io.ReadSeeker, closer io.Closer, err e
// the map entry. Otherwise, the importer must load the package data for the // the map entry. Otherwise, the importer must load the package data for the
// given path into a new *Package, record it in imports map, and return the // given path into a new *Package, record it in imports map, and return the
// package. // package.
type Importer func(imports map[string]*types.Package, path string) (*types.Package, error) type Importer func(imports map[string]*types.Package, path, srcDir string, lookup func(string) (io.ReadCloser, error)) (*types.Package, error)
func GetImporter(searchpaths []string, initmap map[*types.Package]InitData) Importer { func GetImporter(searchpaths []string, initmap map[*types.Package]InitData) Importer {
return func(imports map[string]*types.Package, pkgpath string) (pkg *types.Package, err error) { return func(imports map[string]*types.Package, pkgpath, srcDir string, lookup func(string) (io.ReadCloser, error)) (pkg *types.Package, err error) {
// TODO(gri): Use srcDir.
// Or not. It's possible that srcDir will fade in importance as
// the go command and other tools provide a translation table
// for relative imports (like ./foo or vendored imports).
if pkgpath == "unsafe" { if pkgpath == "unsafe" {
return types.Unsafe, nil return types.Unsafe, nil
} }
fpath, err := findExportFile(searchpaths, pkgpath) var reader io.ReadSeeker
if err != nil { var fpath string
return if lookup != nil {
} if p := imports[pkgpath]; p != nil && p.Complete() {
return p, nil
}
rc, err := lookup(pkgpath)
if err != nil {
return nil, err
}
defer rc.Close()
rs, ok := rc.(io.ReadSeeker)
if !ok {
return nil, fmt.Errorf("gccgo importer requires lookup to return an io.ReadSeeker, have %T", rc)
}
reader = rs
fpath = "<lookup " + pkgpath + ">"
// Take name from Name method (like on os.File) if present.
if n, ok := rc.(interface{ Name() string }); ok {
fpath = n.Name()
}
} else {
fpath, err = findExportFile(searchpaths, pkgpath)
if err != nil {
return nil, err
}
reader, closer, err := openExportFile(fpath) r, closer, err := openExportFile(fpath)
if err != nil { if err != nil {
return return nil, err
} }
if closer != nil { if closer != nil {
defer closer.Close() defer closer.Close()
}
reader = r
} }
var magic [4]byte var magic [4]byte
......
...@@ -21,7 +21,7 @@ type importerTest struct { ...@@ -21,7 +21,7 @@ type importerTest struct {
} }
func runImporterTest(t *testing.T, imp Importer, initmap map[*types.Package]InitData, test *importerTest) { func runImporterTest(t *testing.T, imp Importer, initmap map[*types.Package]InitData, test *importerTest) {
pkg, err := imp(make(map[string]*types.Package), test.pkgpath) pkg, err := imp(make(map[string]*types.Package), test.pkgpath, ".", nil)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return return
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"go/build" "go/build"
"go/token" "go/token"
"go/types" "go/types"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
...@@ -84,36 +85,60 @@ func FindPkg(path, srcDir string) (filename, id string) { ...@@ -84,36 +85,60 @@ func FindPkg(path, srcDir string) (filename, id string) {
// the corresponding package object to the packages map, and returns the object. // the corresponding package object to the packages map, and returns the object.
// The packages map must contain all packages already imported. // The packages map must contain all packages already imported.
// //
func Import(packages map[string]*types.Package, path, srcDir string) (pkg *types.Package, err error) { func Import(packages map[string]*types.Package, path, srcDir string, lookup func(path string) (io.ReadCloser, error)) (pkg *types.Package, err error) {
filename, id := FindPkg(path, srcDir) var rc io.ReadCloser
if filename == "" { var id string
if lookup != nil {
// With custom lookup specified, assume that caller has
// converted path to a canonical import path for use in the map.
if path == "unsafe" { if path == "unsafe" {
return types.Unsafe, nil return types.Unsafe, nil
} }
err = fmt.Errorf("can't find import: %q", id) id = path
return
}
// no need to re-import if the package was imported completely before // No need to re-import if the package was imported completely before.
if pkg = packages[id]; pkg != nil && pkg.Complete() { if pkg = packages[id]; pkg != nil && pkg.Complete() {
return return
} }
f, err := lookup(path)
if err != nil {
return nil, err
}
rc = f
} else {
var filename string
filename, id = FindPkg(path, srcDir)
if filename == "" {
if path == "unsafe" {
return types.Unsafe, nil
}
return nil, fmt.Errorf("can't find import: %q", id)
}
// open file // no need to re-import if the package was imported completely before
f, err := os.Open(filename) if pkg = packages[id]; pkg != nil && pkg.Complete() {
if err != nil { return
return }
}
defer func() { // open file
f.Close() f, err := os.Open(filename)
if err != nil { if err != nil {
// add file name to error return nil, err
err = fmt.Errorf("%s: %v", filename, err)
} }
defer func() {
if err != nil {
// add file name to error
err = fmt.Errorf("%s: %v", filename, err)
}
}()
rc = f
}
defer func() {
rc.Close()
}() }()
var hdr string var hdr string
buf := bufio.NewReader(f) buf := bufio.NewReader(rc)
if hdr, err = FindExportData(buf); err != nil { if hdr, err = FindExportData(buf); err != nil {
return return
} }
......
...@@ -48,7 +48,7 @@ func compile(t *testing.T, dirname, filename string) string { ...@@ -48,7 +48,7 @@ func compile(t *testing.T, dirname, filename string) string {
func testPath(t *testing.T, path, srcDir string) *types.Package { func testPath(t *testing.T, path, srcDir string) *types.Package {
t0 := time.Now() t0 := time.Now()
pkg, err := Import(make(map[string]*types.Package), path, srcDir) pkg, err := Import(make(map[string]*types.Package), path, srcDir, nil)
if err != nil { if err != nil {
t.Errorf("testPath(%s): %s", path, err) t.Errorf("testPath(%s): %s", path, err)
return nil return nil
...@@ -142,7 +142,7 @@ func TestVersionHandling(t *testing.T) { ...@@ -142,7 +142,7 @@ func TestVersionHandling(t *testing.T) {
pkgpath := "./" + name[:len(name)-2] pkgpath := "./" + name[:len(name)-2]
// test that export data can be imported // test that export data can be imported
_, err := Import(make(map[string]*types.Package), pkgpath, dir) _, err := Import(make(map[string]*types.Package), pkgpath, dir, nil)
if err != nil { if err != nil {
t.Errorf("import %q failed: %v", pkgpath, err) t.Errorf("import %q failed: %v", pkgpath, err)
continue continue
...@@ -171,7 +171,7 @@ func TestVersionHandling(t *testing.T) { ...@@ -171,7 +171,7 @@ func TestVersionHandling(t *testing.T) {
defer os.Remove(filename) defer os.Remove(filename)
// test that importing the corrupted file results in an error // test that importing the corrupted file results in an error
_, err = Import(make(map[string]*types.Package), pkgpath, dir) _, err = Import(make(map[string]*types.Package), pkgpath, dir, nil)
if err == nil { if err == nil {
t.Errorf("import corrupted %q succeeded", pkgpath) t.Errorf("import corrupted %q succeeded", pkgpath)
} else if msg := err.Error(); !strings.Contains(msg, "version skew") { } else if msg := err.Error(); !strings.Contains(msg, "version skew") {
...@@ -223,7 +223,7 @@ func TestImportedTypes(t *testing.T) { ...@@ -223,7 +223,7 @@ func TestImportedTypes(t *testing.T) {
importPath := s[0] importPath := s[0]
objName := s[1] objName := s[1]
pkg, err := Import(make(map[string]*types.Package), importPath, ".") pkg, err := Import(make(map[string]*types.Package), importPath, ".", nil)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue continue
...@@ -280,7 +280,7 @@ func TestCorrectMethodPackage(t *testing.T) { ...@@ -280,7 +280,7 @@ func TestCorrectMethodPackage(t *testing.T) {
} }
imports := make(map[string]*types.Package) imports := make(map[string]*types.Package)
_, err := Import(imports, "net/http", ".") _, err := Import(imports, "net/http", ".", nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -336,7 +336,7 @@ func TestIssue13898(t *testing.T) { ...@@ -336,7 +336,7 @@ func TestIssue13898(t *testing.T) {
// import go/internal/gcimporter which imports go/types partially // import go/internal/gcimporter which imports go/types partially
imports := make(map[string]*types.Package) imports := make(map[string]*types.Package)
_, err := Import(imports, "go/internal/gcimporter", ".") _, err := Import(imports, "go/internal/gcimporter", ".", nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -404,7 +404,7 @@ func TestIssue15517(t *testing.T) { ...@@ -404,7 +404,7 @@ func TestIssue15517(t *testing.T) {
// The same issue occurs with vendoring.) // The same issue occurs with vendoring.)
imports := make(map[string]*types.Package) imports := make(map[string]*types.Package)
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
if _, err := Import(imports, "./././testdata/p", "."); err != nil { if _, err := Import(imports, "./././testdata/p", ".", nil); err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }
...@@ -458,7 +458,7 @@ func TestIssue20046(t *testing.T) { ...@@ -458,7 +458,7 @@ func TestIssue20046(t *testing.T) {
} }
func importPkg(t *testing.T, path string) *types.Package { func importPkg(t *testing.T, path string) *types.Package {
pkg, err := Import(make(map[string]*types.Package), path, ".") pkg, err := Import(make(map[string]*types.Package), path, ".", nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment