// checker won't have access to those).
//
// 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 {
switch compiler {
case "gc":
- if lookup != nil {
- panic("gc importer for custom import path lookup not supported (issue #13847).")
+ return &gcimports{
+ packages: make(map[string]*types.Package),
+ lookup: lookup,
}
- return make(gcimports)
-
case "gccgo":
- if lookup != nil {
- panic("gccgo importer for custom import path lookup not supported (issue #13847).")
- }
-
var inst gccgoimporter.GccgoInstallation
if err := inst.InitFromDriver("gccgo"); err != nil {
return nil
return &gccgoimports{
packages: make(map[string]*types.Package),
importer: inst.GetImporter(nil, nil),
+ lookup: lookup,
}
case "source":
// 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)
}
-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 {
panic("mode must be 0")
}
- return gcimporter.Import(m, path, srcDir)
+ return gcimporter.Import(m.packages, path, srcDir, m.lookup)
}
// gccgo importer
type gccgoimports struct {
packages map[string]*types.Package
importer gccgoimporter.Importer
+ lookup Lookup
}
func (m *gccgoimports) Import(path string) (*types.Package, error) {
if mode != 0 {
panic("mode must be 0")
}
- // TODO(gri) pass srcDir
- return m.importer(m.packages, path)
+ return m.importer(m.packages, path, srcDir, m.lookup)
}
--- /dev/null
+// 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")
+ }
+ })
+}
// all packages into the same map and then each individually.
pkgMap := make(map[string]*types.Package)
for _, pkg := range importablePackages {
- _, err = imp(pkgMap, pkg)
+ _, err = imp(pkgMap, pkg, ".", nil)
if err != nil {
t.Error(err)
}
}
for _, pkg := range importablePackages {
- _, err = imp(make(map[string]*types.Package), pkg)
+ _, err = imp(make(map[string]*types.Package), pkg, ".", nil)
if err != nil {
t.Error(err)
}
// 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
// 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 {
- 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" {
return types.Unsafe, nil
}
- fpath, err := findExportFile(searchpaths, pkgpath)
- if err != nil {
- return
- }
+ var reader io.ReadSeeker
+ var fpath string
+ 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)
- if err != nil {
- return
- }
- if closer != nil {
- defer closer.Close()
+ r, closer, err := openExportFile(fpath)
+ if err != nil {
+ return nil, err
+ }
+ if closer != nil {
+ defer closer.Close()
+ }
+ reader = r
}
var magic [4]byte
}
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 {
t.Error(err)
return
"go/build"
"go/token"
"go/types"
+ "io"
"io/ioutil"
"os"
"path/filepath"
// the corresponding package object to the packages map, and returns the object.
// The packages map must contain all packages already imported.
//
-func Import(packages map[string]*types.Package, path, srcDir string) (pkg *types.Package, err error) {
- filename, id := FindPkg(path, srcDir)
- if filename == "" {
+func Import(packages map[string]*types.Package, path, srcDir string, lookup func(path string) (io.ReadCloser, error)) (pkg *types.Package, err error) {
+ var rc io.ReadCloser
+ 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" {
return types.Unsafe, nil
}
- err = fmt.Errorf("can't find import: %q", id)
- return
- }
+ id = path
- // no need to re-import if the package was imported completely before
- if pkg = packages[id]; pkg != nil && pkg.Complete() {
- return
- }
+ // No need to re-import if the package was imported completely before.
+ if pkg = packages[id]; pkg != nil && pkg.Complete() {
+ 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
- f, err := os.Open(filename)
- if err != nil {
- return
- }
- defer func() {
- f.Close()
+ // no need to re-import if the package was imported completely before
+ if pkg = packages[id]; pkg != nil && pkg.Complete() {
+ return
+ }
+
+ // open file
+ f, err := os.Open(filename)
if err != nil {
- // add file name to error
- err = fmt.Errorf("%s: %v", filename, err)
+ return nil, 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
- buf := bufio.NewReader(f)
+ buf := bufio.NewReader(rc)
if hdr, err = FindExportData(buf); err != nil {
return
}
func testPath(t *testing.T, path, srcDir string) *types.Package {
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 {
t.Errorf("testPath(%s): %s", path, err)
return nil
pkgpath := "./" + name[:len(name)-2]
// 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 {
t.Errorf("import %q failed: %v", pkgpath, err)
continue
defer os.Remove(filename)
// 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 {
t.Errorf("import corrupted %q succeeded", pkgpath)
} else if msg := err.Error(); !strings.Contains(msg, "version skew") {
importPath := s[0]
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 {
t.Error(err)
continue
}
imports := make(map[string]*types.Package)
- _, err := Import(imports, "net/http", ".")
+ _, err := Import(imports, "net/http", ".", nil)
if err != nil {
t.Fatal(err)
}
// import go/internal/gcimporter which imports go/types partially
imports := make(map[string]*types.Package)
- _, err := Import(imports, "go/internal/gcimporter", ".")
+ _, err := Import(imports, "go/internal/gcimporter", ".", nil)
if err != nil {
t.Fatal(err)
}
// The same issue occurs with vendoring.)
imports := make(map[string]*types.Package)
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)
}
}
}
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 {
t.Fatal(err)
}