...

Source file src/golang.org/x/tools/internal/gcimporter/gcimporter.go

Documentation: golang.org/x/tools/internal/gcimporter

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

View as plain text