go/gcimporter: populate (*types.Package).Imports

All importers should populate the set of imported packages.  In Go 1.5
importer API, there will be no package map from which to compute the
transitive closure of dependencies, and SSA package creation needs
this information.

+ test.

Change-Id: I1c2823b07bf7316aa62c80e2ef2a0755cf6f5384
Reviewed-on: https://go-review.googlesource.com/8924
Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
Alan Donovan 2015-04-14 17:56:55 -04:00
parent 20186168d5
commit 77d380c9e6
3 changed files with 81 additions and 33 deletions

View File

@ -15,6 +15,7 @@ import (
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"sort"
"strconv" "strconv"
"strings" "strings"
"text/scanner" "text/scanner"
@ -79,18 +80,18 @@ func FindPkg(path, srcDir string) (filename, id string) {
} }
// ImportData imports a package by reading the gc-generated export data, // ImportData imports a package by reading the gc-generated export data,
// adds the corresponding package object to the imports map indexed by id, // adds the corresponding package object to the packages map indexed by id,
// and returns the object. // and returns the object.
// //
// The imports map must contains all packages already imported. The data // The packages map must contains all packages already imported. The data
// reader position must be the beginning of the export data section. The // reader position must be the beginning of the export data section. The
// filename is only used in error messages. // filename is only used in error messages.
// //
// If imports[id] contains the completely imported package, that package // If packages[id] contains the completely imported package, that package
// can be used directly, and there is no need to call this function (but // can be used directly, and there is no need to call this function (but
// there is also no harm but for extra time used). // there is also no harm but for extra time used).
// //
func ImportData(imports map[string]*types.Package, filename, id string, data io.Reader) (pkg *types.Package, err error) { func ImportData(packages map[string]*types.Package, filename, id string, data io.Reader) (pkg *types.Package, err error) {
// support for parser error handling // support for parser error handling
defer func() { defer func() {
switch r := recover().(type) { switch r := recover().(type) {
@ -104,18 +105,18 @@ func ImportData(imports map[string]*types.Package, filename, id string, data io.
}() }()
var p parser var p parser
p.init(filename, id, data, imports) p.init(filename, id, data, packages)
pkg = p.parseExport() pkg = p.parseExport()
return return
} }
// Import imports a gc-generated package given its import path, adds the // Import imports a gc-generated package given its import path, adds the
// corresponding package object to the imports map, and returns the object. // corresponding package object to the packages map, and returns the object.
// Local import paths are interpreted relative to the current working directory. // Local import paths are interpreted relative to the current working directory.
// The imports map must contains all packages already imported. // The packages map must contains all packages already imported.
// //
func Import(imports map[string]*types.Package, path string) (pkg *types.Package, err error) { func Import(packages map[string]*types.Package, path string) (pkg *types.Package, err error) {
if path == "unsafe" { if path == "unsafe" {
return types.Unsafe, nil return types.Unsafe, nil
} }
@ -135,7 +136,7 @@ func Import(imports map[string]*types.Package, path string) (pkg *types.Package,
} }
// 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 = imports[id]; pkg != nil && pkg.Complete() { if pkg = packages[id]; pkg != nil && pkg.Complete() {
return return
} }
@ -157,7 +158,7 @@ func Import(imports map[string]*types.Package, path string) (pkg *types.Package,
return return
} }
pkg, err = ImportData(imports, filename, id, buf) pkg, err = ImportData(packages, filename, id, buf)
return return
} }
@ -174,14 +175,15 @@ func Import(imports map[string]*types.Package, path string) (pkg *types.Package,
// parser parses the exports inside a gc compiler-produced // parser parses the exports inside a gc compiler-produced
// object/archive file and populates its scope with the results. // object/archive file and populates its scope with the results.
type parser struct { type parser struct {
scanner scanner.Scanner scanner scanner.Scanner
tok rune // current token tok rune // current token
lit string // literal string; only valid for Ident, Int, String tokens lit string // literal string; only valid for Ident, Int, String tokens
id string // package id of imported package id string // package id of imported package
imports map[string]*types.Package // package id -> package object sharedPkgs map[string]*types.Package // package id -> package object (across importer)
localPkgs map[string]*types.Package // package id -> package object (just this package)
} }
func (p *parser) init(filename, id string, src io.Reader, imports map[string]*types.Package) { func (p *parser) init(filename, id string, src io.Reader, packages map[string]*types.Package) {
p.scanner.Init(src) p.scanner.Init(src)
p.scanner.Error = func(_ *scanner.Scanner, msg string) { p.error(msg) } p.scanner.Error = func(_ *scanner.Scanner, msg string) { p.error(msg) }
p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanChars | scanner.ScanStrings | scanner.ScanComments | scanner.SkipComments p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanChars | scanner.ScanStrings | scanner.ScanComments | scanner.SkipComments
@ -189,10 +191,10 @@ func (p *parser) init(filename, id string, src io.Reader, imports map[string]*ty
p.scanner.Filename = filename // for good error messages p.scanner.Filename = filename // for good error messages
p.next() p.next()
p.id = id p.id = id
p.imports = imports p.sharedPkgs = packages
if debug { if debug {
// check consistency of imports map // check consistency of packages map
for _, pkg := range imports { for _, pkg := range packages {
if pkg.Name() == "" { if pkg.Name() == "" {
fmt.Printf("no package name for %s\n", pkg.Path()) fmt.Printf("no package name for %s\n", pkg.Path())
} }
@ -338,17 +340,32 @@ func (p *parser) parseQualifiedName() (id, name string) {
// getPkg returns the package for a given id. If the package is // getPkg returns the package for a given id. If the package is
// not found but we have a package name, create the package and // not found but we have a package name, create the package and
// add it to the p.imports map. // add it to the p.localPkgs and p.sharedPkgs maps.
//
// id identifies a package, usually by a canonical package path like
// "encoding/json" but possibly by a non-canonical import path like
// "./json".
// //
func (p *parser) getPkg(id, name string) *types.Package { func (p *parser) getPkg(id, name string) *types.Package {
// package unsafe is not in the imports map - handle explicitly // package unsafe is not in the packages maps - handle explicitly
if id == "unsafe" { if id == "unsafe" {
return types.Unsafe return types.Unsafe
} }
pkg := p.imports[id]
pkg := p.localPkgs[id]
if pkg == nil && name != "" { if pkg == nil && name != "" {
pkg = types.NewPackage(id, name) // first import of id from this package
p.imports[id] = pkg pkg = p.sharedPkgs[id]
if pkg == nil {
// first import of id by this importer
pkg = types.NewPackage(id, name)
p.sharedPkgs[id] = pkg
}
if p.localPkgs == nil {
p.localPkgs = make(map[string]*types.Package)
}
p.localPkgs[id] = pkg
} }
return pkg return pkg
} }
@ -418,12 +435,12 @@ func (p *parser) parseMapType() types.Type {
func (p *parser) parseName(materializePkg bool) (pkg *types.Package, name string) { func (p *parser) parseName(materializePkg bool) (pkg *types.Package, name string) {
switch p.tok { switch p.tok {
case scanner.Ident: case scanner.Ident:
pkg = p.imports[p.id] pkg = p.sharedPkgs[p.id]
name = p.lit name = p.lit
p.next() p.next()
case '?': case '?':
// anonymous // anonymous
pkg = p.imports[p.id] pkg = p.sharedPkgs[p.id]
p.next() p.next()
case '@': case '@':
// exported name prefixed with package path // exported name prefixed with package path
@ -954,8 +971,25 @@ func (p *parser) parseExport() *types.Package {
p.errorf("expected no scanner errors, got %d", n) p.errorf("expected no scanner errors, got %d", n)
} }
// Record all referenced packages as imports.
var imports []*types.Package
for id, pkg2 := range p.localPkgs {
if id == p.id {
continue // avoid self-edge
}
imports = append(imports, pkg2)
}
sort.Sort(byPath(imports))
pkg.SetImports(imports)
// package was imported completely and without errors // package was imported completely and without errors
pkg.MarkComplete() pkg.MarkComplete()
return pkg return pkg
} }
type byPath []*types.Package
func (a byPath) Len() int { return len(a) }
func (a byPath) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byPath) Less(i, j int) bool { return a[i].Path() < a[j].Path() }

View File

@ -5,6 +5,7 @@
package gcimporter package gcimporter
import ( import (
"fmt"
"go/build" "go/build"
"io/ioutil" "io/ioutil"
"os" "os"
@ -58,15 +59,15 @@ func compile(t *testing.T, dirname, filename string) string {
// as if all tested packages were imported into a single package. // as if all tested packages were imported into a single package.
var imports = make(map[string]*types.Package) var imports = make(map[string]*types.Package)
func testPath(t *testing.T, path string) bool { func testPath(t *testing.T, path string) *types.Package {
t0 := time.Now() t0 := time.Now()
_, err := Import(imports, path) pkg, err := Import(imports, path)
if err != nil { if err != nil {
t.Errorf("testPath(%s): %s", path, err) t.Errorf("testPath(%s): %s", path, err)
return false return nil
} }
t.Logf("testPath(%s): %v", path, time.Since(t0)) t.Logf("testPath(%s): %v", path, time.Since(t0))
return true return pkg
} }
const maxTime = 30 * time.Second const maxTime = 30 * time.Second
@ -88,7 +89,7 @@ func testDir(t *testing.T, dir string, endTime time.Time) (nimports int) {
for _, ext := range pkgExts { for _, ext := range pkgExts {
if strings.HasSuffix(f.Name(), ext) { if strings.HasSuffix(f.Name(), ext) {
name := f.Name()[0 : len(f.Name())-len(ext)] // remove extension name := f.Name()[0 : len(f.Name())-len(ext)] // remove extension
if testPath(t, filepath.Join(dir, name)) { if testPath(t, filepath.Join(dir, name)) != nil {
nimports++ nimports++
} }
} }
@ -118,8 +119,17 @@ func TestImport(t *testing.T) {
} }
nimports := 0 nimports := 0
if testPath(t, "./testdata/exports") { if pkg := testPath(t, "./testdata/exports"); pkg != nil {
nimports++ nimports++
// The package's Imports should include all the types
// referenced by the exportdata, which may be more than
// the import statements in the package's source, but
// fewer than the transitive closure of dependencies.
want := `[package ast ("go/ast") package token ("go/token") package runtime ("runtime")]`
got := fmt.Sprint(pkg.Imports())
if got != want {
t.Errorf(`Package("exports").Imports() = %s, want %s`, got, want)
}
} }
nimports += testDir(t, "", time.Now().Add(maxTime)) // installed packages nimports += testDir(t, "", time.Now().Add(maxTime)) // installed packages
t.Logf("tested %d imports", nimports) t.Logf("tested %d imports", nimports)

View File

@ -45,8 +45,12 @@ func (pkg *Package) Complete() bool { return pkg.complete }
// MarkComplete marks a package as complete. // MarkComplete marks a package as complete.
func (pkg *Package) MarkComplete() { pkg.complete = true } func (pkg *Package) MarkComplete() { pkg.complete = true }
// Imports returns the list of packages explicitly imported by // Imports returns the list of packages directly imported by
// pkg; the list is in source order. Package unsafe is excluded. // pkg; the list is in source order. Package unsafe is excluded.
//
// If pkg was loaded from export data, Imports includes packages that
// provide package-level objects referenced by pkg. This may be more or
// less than the set of packages directly imported by pkg's source code.
func (pkg *Package) Imports() []*Package { return pkg.imports } func (pkg *Package) Imports() []*Package { return pkg.imports }
// SetImports sets the list of explicitly imported packages to list. // SetImports sets the list of explicitly imported packages to list.