github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/internal/gcimporter/gcimporter.go (about)

     1  // Copyright 2011 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // This file is a reduced copy of $GOROOT/src/go/internal/gcimporter/gcimporter.go.
     6  
     7  // Package gcimporter provides various functions for reading
     8  // gc-generated object files that can be used to implement the
     9  // Importer interface defined by the Go 1.5 standard library package.
    10  package gcimporter // import "golang.org/x/tools/internal/gcimporter"
    11  
    12  import (
    13  	"bufio"
    14  	"bytes"
    15  	"fmt"
    16  	"go/build"
    17  	"go/token"
    18  	"go/types"
    19  	"io"
    20  	"io/ioutil"
    21  	"os"
    22  	"os/exec"
    23  	"path/filepath"
    24  	"strings"
    25  	"sync"
    26  )
    27  
    28  const (
    29  	// Enable debug during development: it adds some additional checks, and
    30  	// prevents errors from being recovered.
    31  	debug = false
    32  
    33  	// If trace is set, debugging output is printed to std out.
    34  	trace = false
    35  )
    36  
    37  var exportMap sync.Map // package dir → func() (string, bool)
    38  
    39  // lookupGorootExport returns the location of the export data
    40  // (normally found in the build cache, but located in GOROOT/pkg
    41  // in prior Go releases) for the package located in pkgDir.
    42  //
    43  // (We use the package's directory instead of its import path
    44  // mainly to simplify handling of the packages in src/vendor
    45  // and cmd/vendor.)
    46  func lookupGorootExport(pkgDir string) (string, bool) {
    47  	f, ok := exportMap.Load(pkgDir)
    48  	if !ok {
    49  		var (
    50  			listOnce   sync.Once
    51  			exportPath string
    52  		)
    53  		f, _ = exportMap.LoadOrStore(pkgDir, func() (string, bool) {
    54  			listOnce.Do(func() {
    55  				cmd := exec.Command("go", "list", "-export", "-f", "{{.Export}}", pkgDir)
    56  				cmd.Dir = build.Default.GOROOT
    57  				var output []byte
    58  				output, err := cmd.Output()
    59  				if err != nil {
    60  					return
    61  				}
    62  
    63  				exports := strings.Split(string(bytes.TrimSpace(output)), "\n")
    64  				if len(exports) != 1 {
    65  					return
    66  				}
    67  
    68  				exportPath = exports[0]
    69  			})
    70  
    71  			return exportPath, exportPath != ""
    72  		})
    73  	}
    74  
    75  	return f.(func() (string, bool))()
    76  }
    77  
    78  var pkgExts = [...]string{".a", ".o"}
    79  
    80  // FindPkg returns the filename and unique package id for an import
    81  // path based on package information provided by build.Import (using
    82  // the build.Default build.Context). A relative srcDir is interpreted
    83  // relative to the current working directory.
    84  // If no file was found, an empty filename is returned.
    85  func FindPkg(path, srcDir string) (filename, id string) {
    86  	if path == "" {
    87  		return
    88  	}
    89  
    90  	var noext string
    91  	switch {
    92  	default:
    93  		// "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x"
    94  		// Don't require the source files to be present.
    95  		if abs, err := filepath.Abs(srcDir); err == nil { // see issue 14282
    96  			srcDir = abs
    97  		}
    98  		bp, _ := build.Import(path, srcDir, build.FindOnly|build.AllowBinary)
    99  		if bp.PkgObj == "" {
   100  			var ok bool
   101  			if bp.Goroot && bp.Dir != "" {
   102  				filename, ok = lookupGorootExport(bp.Dir)
   103  			}
   104  			if !ok {
   105  				id = path // make sure we have an id to print in error message
   106  				return
   107  			}
   108  		} else {
   109  			noext = strings.TrimSuffix(bp.PkgObj, ".a")
   110  			id = bp.ImportPath
   111  		}
   112  
   113  	case build.IsLocalImport(path):
   114  		// "./x" -> "/this/directory/x.ext", "/this/directory/x"
   115  		noext = filepath.Join(srcDir, path)
   116  		id = noext
   117  
   118  	case filepath.IsAbs(path):
   119  		// for completeness only - go/build.Import
   120  		// does not support absolute imports
   121  		// "/x" -> "/x.ext", "/x"
   122  		noext = path
   123  		id = path
   124  	}
   125  
   126  	if false { // for debugging
   127  		if path != id {
   128  			fmt.Printf("%s -> %s\n", path, id)
   129  		}
   130  	}
   131  
   132  	if filename != "" {
   133  		if f, err := os.Stat(filename); err == nil && !f.IsDir() {
   134  			return
   135  		}
   136  	}
   137  
   138  	// try extensions
   139  	for _, ext := range pkgExts {
   140  		filename = noext + ext
   141  		if f, err := os.Stat(filename); err == nil && !f.IsDir() {
   142  			return
   143  		}
   144  	}
   145  
   146  	filename = "" // not found
   147  	return
   148  }
   149  
   150  // Import imports a gc-generated package given its import path and srcDir, adds
   151  // the corresponding package object to the packages map, and returns the object.
   152  // The packages map must contain all packages already imported.
   153  func Import(packages map[string]*types.Package, path, srcDir string, lookup func(path string) (io.ReadCloser, error)) (pkg *types.Package, err error) {
   154  	var rc io.ReadCloser
   155  	var filename, id string
   156  	if lookup != nil {
   157  		// With custom lookup specified, assume that caller has
   158  		// converted path to a canonical import path for use in the map.
   159  		if path == "unsafe" {
   160  			return types.Unsafe, nil
   161  		}
   162  		id = path
   163  
   164  		// No need to re-import if the package was imported completely before.
   165  		if pkg = packages[id]; pkg != nil && pkg.Complete() {
   166  			return
   167  		}
   168  		f, err := lookup(path)
   169  		if err != nil {
   170  			return nil, err
   171  		}
   172  		rc = f
   173  	} else {
   174  		filename, id = FindPkg(path, srcDir)
   175  		if filename == "" {
   176  			if path == "unsafe" {
   177  				return types.Unsafe, nil
   178  			}
   179  			return nil, fmt.Errorf("can't find import: %q", id)
   180  		}
   181  
   182  		// no need to re-import if the package was imported completely before
   183  		if pkg = packages[id]; pkg != nil && pkg.Complete() {
   184  			return
   185  		}
   186  
   187  		// open file
   188  		f, err := os.Open(filename)
   189  		if err != nil {
   190  			return nil, err
   191  		}
   192  		defer func() {
   193  			if err != nil {
   194  				// add file name to error
   195  				err = fmt.Errorf("%s: %v", filename, err)
   196  			}
   197  		}()
   198  		rc = f
   199  	}
   200  	defer rc.Close()
   201  
   202  	var hdr string
   203  	var size int64
   204  	buf := bufio.NewReader(rc)
   205  	if hdr, size, err = FindExportData(buf); err != nil {
   206  		return
   207  	}
   208  
   209  	switch hdr {
   210  	case "$$B\n":
   211  		var data []byte
   212  		data, err = ioutil.ReadAll(buf)
   213  		if err != nil {
   214  			break
   215  		}
   216  
   217  		// TODO(gri): allow clients of go/importer to provide a FileSet.
   218  		// Or, define a new standard go/types/gcexportdata package.
   219  		fset := token.NewFileSet()
   220  
   221  		// The indexed export format starts with an 'i'; the older
   222  		// binary export format starts with a 'c', 'd', or 'v'
   223  		// (from "version"). Select appropriate importer.
   224  		if len(data) > 0 {
   225  			switch data[0] {
   226  			case 'i':
   227  				_, pkg, err := IImportData(fset, packages, data[1:], id)
   228  				return pkg, err
   229  
   230  			case 'v', 'c', 'd':
   231  				_, pkg, err := BImportData(fset, packages, data, id)
   232  				return pkg, err
   233  
   234  			case 'u':
   235  				_, pkg, err := UImportData(fset, packages, data[1:size], id)
   236  				return pkg, err
   237  
   238  			default:
   239  				l := len(data)
   240  				if l > 10 {
   241  					l = 10
   242  				}
   243  				return nil, fmt.Errorf("unexpected export data with prefix %q for path %s", string(data[:l]), id)
   244  			}
   245  		}
   246  
   247  	default:
   248  		err = fmt.Errorf("unknown export data header: %q", hdr)
   249  	}
   250  
   251  	return
   252  }
   253  
   254  func deref(typ types.Type) types.Type {
   255  	if p, _ := typ.(*types.Pointer); p != nil {
   256  		return p.Elem()
   257  	}
   258  	return typ
   259  }
   260  
   261  type byPath []*types.Package
   262  
   263  func (a byPath) Len() int           { return len(a) }
   264  func (a byPath) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
   265  func (a byPath) Less(i, j int) bool { return a[i].Path() < a[j].Path() }