зеркало из https://github.com/golang/tools.git
274 строки
7.0 KiB
Go
274 строки
7.0 KiB
Go
// Copyright 2011 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.
|
|
|
|
// This file is a reduced copy of $GOROOT/src/go/internal/gcimporter/gcimporter.go.
|
|
|
|
// Package gcimporter provides various functions for reading
|
|
// gc-generated object files that can be used to implement the
|
|
// Importer interface defined by the Go 1.5 standard library package.
|
|
//
|
|
// The encoding is deterministic: if the encoder is applied twice to
|
|
// the same types.Package data structure, both encodings are equal.
|
|
// This property may be important to avoid spurious changes in
|
|
// applications such as build systems.
|
|
//
|
|
// However, the encoder is not necessarily idempotent. Importing an
|
|
// exported package may yield a types.Package that, while it
|
|
// represents the same set of Go types as the original, may differ in
|
|
// the details of its internal representation. Because of these
|
|
// differences, re-encoding the imported package may yield a
|
|
// different, but equally valid, encoding of the package.
|
|
package gcimporter // import "golang.org/x/tools/internal/gcimporter"
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"go/build"
|
|
"go/token"
|
|
"go/types"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
const (
|
|
// Enable debug during development: it adds some additional checks, and
|
|
// prevents errors from being recovered.
|
|
debug = false
|
|
|
|
// If trace is set, debugging output is printed to std out.
|
|
trace = false
|
|
)
|
|
|
|
var exportMap sync.Map // package dir → func() (string, bool)
|
|
|
|
// lookupGorootExport returns the location of the export data
|
|
// (normally found in the build cache, but located in GOROOT/pkg
|
|
// in prior Go releases) for the package located in pkgDir.
|
|
//
|
|
// (We use the package's directory instead of its import path
|
|
// mainly to simplify handling of the packages in src/vendor
|
|
// and cmd/vendor.)
|
|
func lookupGorootExport(pkgDir string) (string, bool) {
|
|
f, ok := exportMap.Load(pkgDir)
|
|
if !ok {
|
|
var (
|
|
listOnce sync.Once
|
|
exportPath string
|
|
)
|
|
f, _ = exportMap.LoadOrStore(pkgDir, func() (string, bool) {
|
|
listOnce.Do(func() {
|
|
cmd := exec.Command("go", "list", "-export", "-f", "{{.Export}}", pkgDir)
|
|
cmd.Dir = build.Default.GOROOT
|
|
var output []byte
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
exports := strings.Split(string(bytes.TrimSpace(output)), "\n")
|
|
if len(exports) != 1 {
|
|
return
|
|
}
|
|
|
|
exportPath = exports[0]
|
|
})
|
|
|
|
return exportPath, exportPath != ""
|
|
})
|
|
}
|
|
|
|
return f.(func() (string, bool))()
|
|
}
|
|
|
|
var pkgExts = [...]string{".a", ".o"}
|
|
|
|
// FindPkg returns the filename and unique package id for an import
|
|
// path based on package information provided by build.Import (using
|
|
// the build.Default build.Context). A relative srcDir is interpreted
|
|
// relative to the current working directory.
|
|
// If no file was found, an empty filename is returned.
|
|
func FindPkg(path, srcDir string) (filename, id string) {
|
|
if path == "" {
|
|
return
|
|
}
|
|
|
|
var noext string
|
|
switch {
|
|
default:
|
|
// "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x"
|
|
// Don't require the source files to be present.
|
|
if abs, err := filepath.Abs(srcDir); err == nil { // see issue 14282
|
|
srcDir = abs
|
|
}
|
|
bp, _ := build.Import(path, srcDir, build.FindOnly|build.AllowBinary)
|
|
if bp.PkgObj == "" {
|
|
var ok bool
|
|
if bp.Goroot && bp.Dir != "" {
|
|
filename, ok = lookupGorootExport(bp.Dir)
|
|
}
|
|
if !ok {
|
|
id = path // make sure we have an id to print in error message
|
|
return
|
|
}
|
|
} else {
|
|
noext = strings.TrimSuffix(bp.PkgObj, ".a")
|
|
id = bp.ImportPath
|
|
}
|
|
|
|
case build.IsLocalImport(path):
|
|
// "./x" -> "/this/directory/x.ext", "/this/directory/x"
|
|
noext = filepath.Join(srcDir, path)
|
|
id = noext
|
|
|
|
case filepath.IsAbs(path):
|
|
// for completeness only - go/build.Import
|
|
// does not support absolute imports
|
|
// "/x" -> "/x.ext", "/x"
|
|
noext = path
|
|
id = path
|
|
}
|
|
|
|
if false { // for debugging
|
|
if path != id {
|
|
fmt.Printf("%s -> %s\n", path, id)
|
|
}
|
|
}
|
|
|
|
if filename != "" {
|
|
if f, err := os.Stat(filename); err == nil && !f.IsDir() {
|
|
return
|
|
}
|
|
}
|
|
|
|
// try extensions
|
|
for _, ext := range pkgExts {
|
|
filename = noext + ext
|
|
if f, err := os.Stat(filename); err == nil && !f.IsDir() {
|
|
return
|
|
}
|
|
}
|
|
|
|
filename = "" // not found
|
|
return
|
|
}
|
|
|
|
// Import imports a gc-generated package given its import path and srcDir, adds
|
|
// 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, lookup func(path string) (io.ReadCloser, error)) (pkg *types.Package, err error) {
|
|
var rc io.ReadCloser
|
|
var filename, 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
|
|
}
|
|
id = path
|
|
|
|
// 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 {
|
|
filename, id = FindPkg(path, srcDir)
|
|
if filename == "" {
|
|
if path == "unsafe" {
|
|
return types.Unsafe, nil
|
|
}
|
|
return nil, fmt.Errorf("can't find import: %q", id)
|
|
}
|
|
|
|
// 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 {
|
|
return nil, err
|
|
}
|
|
defer func() {
|
|
if err != nil {
|
|
// add file name to error
|
|
err = fmt.Errorf("%s: %v", filename, err)
|
|
}
|
|
}()
|
|
rc = f
|
|
}
|
|
defer rc.Close()
|
|
|
|
var hdr string
|
|
var size int64
|
|
buf := bufio.NewReader(rc)
|
|
if hdr, size, err = FindExportData(buf); err != nil {
|
|
return
|
|
}
|
|
|
|
switch hdr {
|
|
case "$$B\n":
|
|
var data []byte
|
|
data, err = io.ReadAll(buf)
|
|
if err != nil {
|
|
break
|
|
}
|
|
|
|
// TODO(gri): allow clients of go/importer to provide a FileSet.
|
|
// Or, define a new standard go/types/gcexportdata package.
|
|
fset := token.NewFileSet()
|
|
|
|
// Select appropriate importer.
|
|
if len(data) > 0 {
|
|
switch data[0] {
|
|
case 'v', 'c', 'd': // binary, till go1.10
|
|
return nil, fmt.Errorf("binary (%c) import format is no longer supported", data[0])
|
|
|
|
case 'i': // indexed, till go1.19
|
|
_, pkg, err := IImportData(fset, packages, data[1:], id)
|
|
return pkg, err
|
|
|
|
case 'u': // unified, from go1.20
|
|
_, pkg, err := UImportData(fset, packages, data[1:size], id)
|
|
return pkg, err
|
|
|
|
default:
|
|
l := len(data)
|
|
if l > 10 {
|
|
l = 10
|
|
}
|
|
return nil, fmt.Errorf("unexpected export data with prefix %q for path %s", string(data[:l]), id)
|
|
}
|
|
}
|
|
|
|
default:
|
|
err = fmt.Errorf("unknown export data header: %q", hdr)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func deref(typ types.Type) types.Type {
|
|
if p, _ := typ.(*types.Pointer); p != nil {
|
|
return p.Elem()
|
|
}
|
|
return typ
|
|
}
|
|
|
|
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() }
|