| // 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 |
| } |
| |
| 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() } |