From 1a954d519b177dee7aa0a24e9b70ecce2ff8bd7d Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Thu, 6 Oct 2016 17:39:25 -0400 Subject: [PATCH] go/gcexportdata: a new simpler API for the deprecated go/gcimporter15 Fixes golang/go#15651 Change-Id: I7dc7ba731a22c677e3c01bf13e91ecfff9e46765 Reviewed-on: https://go-review.googlesource.com/30612 Reviewed-by: Robert Griesemer --- go/gcexportdata/example_test.go | 111 ++++++++++++++++++++++++++++++++ go/gcexportdata/gcexportdata.go | 88 +++++++++++++++++++++++++ go/gcexportdata/importer.go | 73 +++++++++++++++++++++ go/gcimporter15/gcimporter.go | 4 ++ 4 files changed, 276 insertions(+) create mode 100644 go/gcexportdata/example_test.go create mode 100644 go/gcexportdata/gcexportdata.go create mode 100644 go/gcexportdata/importer.go diff --git a/go/gcexportdata/example_test.go b/go/gcexportdata/example_test.go new file mode 100644 index 00000000..d16d7129 --- /dev/null +++ b/go/gcexportdata/example_test.go @@ -0,0 +1,111 @@ +// Copyright 2016 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 gcexportdata_test + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "go/types" + "log" + "os" + "path/filepath" + + "golang.org/x/tools/go/gcexportdata" +) + +// ExampleRead uses gcexportdata.Read to load type information for the +// "fmt" package from the fmt.a file produced by the gc compiler. +func ExampleRead() { + // Find the export data file. + filename, path := gcexportdata.Find("fmt", "") + if filename == "" { + log.Fatalf("can't find export data for fmt") + } + fmt.Printf("Package path: %s\n", path) + fmt.Printf("Export data: %s\n", filepath.Base(filename)) + + // Open and read the file. + f, err := os.Open(filename) + if err != nil { + log.Fatal(err) + } + defer f.Close() + r, err := gcexportdata.NewReader(f) + if err != nil { + log.Fatalf("reading export data %s: %v", filename, err) + } + + // Decode the export data. + fset := token.NewFileSet() + imports := make(map[string]*types.Package) + pkg, err := gcexportdata.Read(r, fset, imports, path) + if err != nil { + log.Fatal(err) + } + + // Print package information. + fmt.Printf("Package members: %s...\n", pkg.Scope().Names()[:5]) + println := pkg.Scope().Lookup("Println") + posn := fset.Position(println.Pos()) + posn.Line = 123 // make example deterministic + fmt.Printf("Println type: %s\n", println.Type()) + fmt.Printf("Println location: %s\n", posn) + + // Output: + // + // Package path: fmt + // Export data: fmt.a + // Package members: [Errorf Formatter Fprint Fprintf Fprintln]... + // Println type: func(a ...interface{}) (n int, err error) + // Println location: $GOROOT/src/fmt/print.go:123:1 +} + +// ExampleNewImporter demonstrates usage of NewImporter to provide type +// information for dependencies when type-checking Go source code. +func ExampleNewImporter() { + const src = `package twopi + +import "math" + +const TwoPi = 2 * math.Pi +` + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "twopi.go", src, 0) + if err != nil { + log.Fatal(err) + } + + packages := make(map[string]*types.Package) + imp := gcexportdata.NewImporter(fset, packages) + conf := types.Config{Importer: imp} + pkg, err := conf.Check("twopi", fset, []*ast.File{f}, nil) + if err != nil { + log.Fatal(err) + } + + // object from imported package + pi := packages["math"].Scope().Lookup("Pi") + fmt.Printf("const %s.%s %s = %s // %s\n", + pi.Pkg().Path(), + pi.Name(), + pi.Type(), + pi.(*types.Const).Val(), + fset.Position(pi.Pos())) + + // object in source package + twopi := pkg.Scope().Lookup("TwoPi") + fmt.Printf("const %s %s = %s // %s\n", + twopi.Name(), + twopi.Type(), + twopi.(*types.Const).Val(), + fset.Position(twopi.Pos())) + + // Output: + // + // const math.Pi untyped float = 3.14159 // $GOROOT/src/math/const.go:11:1 + // const TwoPi untyped float = 6.28319 // twopi.go:5:7 +} diff --git a/go/gcexportdata/gcexportdata.go b/go/gcexportdata/gcexportdata.go new file mode 100644 index 00000000..99d79d81 --- /dev/null +++ b/go/gcexportdata/gcexportdata.go @@ -0,0 +1,88 @@ +// Copyright 2016 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 gcexportdata provides functions for locating, reading, and +// writing export data files containing type information produced by the +// gc compiler. This package supports go1.7 export data format and all +// later versions. +// +// This package replaces the deprecated golang.org/x/tools/go/gcimporter15 +// package, which will be deleted in October 2017. +// +// Although it might seem convenient for this package to live alongside +// go/types in the standard library, this would cause version skew +// problems for developer tools that use it, since they must be able to +// consume the outputs of the gc compiler both before and after a Go +// update such as from Go 1.7 to Go 1.8. Because this package lives in +// golang.org/x/tools, sites can update their version of this repo some +// time before the Go 1.8 release and rebuild and redeploy their +// developer tools, which will then be able to consume both Go 1.7 and +// Go 1.8 export data files, so they will work before and after the +// Go update. (See discussion at https://github.com/golang/go/issues/15651.) +// +package gcexportdata // import "golang.org/x/tools/go/gcexportdata" + +import ( + "bufio" + "fmt" + "go/token" + "go/types" + "io" + "io/ioutil" + + gcimporter "golang.org/x/tools/go/gcimporter15" +) + +// Find returns the name of an object (.o) or archive (.a) file +// containing type information for the specified import path, +// using the workspace layout conventions of go/build. +// If no file was found, an empty filename is returned. +// +// A relative srcDir is interpreted relative to the current working directory. +// +// Find also returns the package's resolved (canonical) import path, +// reflecting the effects of srcDir and vendoring on importPath. +func Find(importPath string, srcDir string) (filename, path string) { + return gcimporter.FindPkg(importPath, srcDir) +} + +// NewReader returns a reader for the export data section of an object +// (.o) or archive (.a) file read from r. The new reader may provide +// additional trailing data beyond the end of the export data. +func NewReader(r io.Reader) (io.Reader, error) { + buf := bufio.NewReader(r) + _, err := gcimporter.FindExportData(buf) + // If we ever switch to a zip-like archive format with the ToC + // at the end, we can return the correct portion of export data, + // but for now we must return the entire rest of the file. + return buf, err +} + +// Read reads export data from in, decodes it, and returns type +// information for the package. +// The package name is specified by path. +// File position information is added to fset. +// +// Read may inspect and add to the imports map to ensure that references +// within the export data to other packages are consistent. The caller +// must ensure that imports[path] does not exist, or exists but is +// incomplete (see types.Package.Complete), and Read inserts the +// resulting package into this map entry. +// +// On return, the state of the reader is undefined. +func Read(in io.Reader, fset *token.FileSet, imports map[string]*types.Package, path string) (*types.Package, error) { + data, err := ioutil.ReadAll(in) + if err != nil { + return nil, fmt.Errorf("reading export data for %q: %v", path, err) + } + _, pkg, err := gcimporter.BImportData(fset, imports, data, path) + return pkg, err +} + +// Write writes encoded type information for the specified package to out. +// The FileSet provides file position information for named objects. +func Write(out io.Writer, fset *token.FileSet, pkg *types.Package) error { + _, err := out.Write(gcimporter.BExportData(fset, pkg)) + return err +} diff --git a/go/gcexportdata/importer.go b/go/gcexportdata/importer.go new file mode 100644 index 00000000..efe221e7 --- /dev/null +++ b/go/gcexportdata/importer.go @@ -0,0 +1,73 @@ +// Copyright 2016 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 gcexportdata + +import ( + "fmt" + "go/token" + "go/types" + "os" +) + +// NewImporter returns a new instance of the types.Importer interface +// that reads type information from export data files written by gc. +// The Importer also satisfies types.ImporterFrom. +// +// Export data files are located using "go build" workspace conventions +// and the build.Default context. +// +// Use this importer instead of go/importer.For("gc", ...) to avoid the +// version-skew problems described in the documentation of this package, +// or to control the FileSet or access the imports map populated during +// package loading. +// +func NewImporter(fset *token.FileSet, imports map[string]*types.Package) types.ImporterFrom { + return importer{fset, imports} +} + +type importer struct { + fset *token.FileSet + imports map[string]*types.Package +} + +func (imp importer) Import(importPath string) (*types.Package, error) { + return imp.ImportFrom(importPath, "", 0) +} + +func (imp importer) ImportFrom(importPath, srcDir string, mode types.ImportMode) (_ *types.Package, err error) { + filename, path := Find(importPath, srcDir) + if filename == "" { + if importPath == "unsafe" { + // Even for unsafe, call Find first in case + // the package was vendored. + return types.Unsafe, nil + } + return nil, fmt.Errorf("can't find import: %s", importPath) + } + + if pkg, ok := imp.imports[path]; ok && pkg.Complete() { + return pkg, nil // cache hit + } + + // open file + f, err := os.Open(filename) + if err != nil { + return nil, err + } + defer func() { + f.Close() + if err != nil { + // add file name to error + err = fmt.Errorf("reading export data: %s: %v", filename, err) + } + }() + + r, err := NewReader(f) + if err != nil { + return nil, err + } + + return Read(r, imp.fset, imp.imports, path) +} diff --git a/go/gcimporter15/gcimporter.go b/go/gcimporter15/gcimporter.go index 7725b3ef..4f445f76 100644 --- a/go/gcimporter15/gcimporter.go +++ b/go/gcimporter15/gcimporter.go @@ -15,6 +15,10 @@ // standard library's go/importer package, specifically customizable // package data lookup. This package should be deleted once that // functionality becomes available in the standard library. +// +// Deprecated: this package will be deleted in October 2017. +// New code should use golang.org/x/tools/go/gcexportdata. +// package gcimporter // import "golang.org/x/tools/go/gcimporter15" import (