Source file src/go/internal/gcimporter/gcimporter.go

     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  // Package gcimporter implements Import for gc-generated object files.
     6  package gcimporter // import "go/internal/gcimporter"
     7  
     8  import (
     9  	"bufio"
    10  	"bytes"
    11  	"errors"
    12  	"fmt"
    13  	"go/build"
    14  	"go/token"
    15  	"go/types"
    16  	"internal/pkgbits"
    17  	"internal/saferio"
    18  	"io"
    19  	"os"
    20  	"os/exec"
    21  	"path/filepath"
    22  	"strings"
    23  	"sync"
    24  )
    25  
    26  // debugging/development support
    27  const debug = false
    28  
    29  var exportMap sync.Map // package dir → func() (string, error)
    30  
    31  // lookupGorootExport returns the location of the export data
    32  // (normally found in the build cache, but located in GOROOT/pkg
    33  // in prior Go releases) for the package located in pkgDir.
    34  //
    35  // (We use the package's directory instead of its import path
    36  // mainly to simplify handling of the packages in src/vendor
    37  // and cmd/vendor.)
    38  func lookupGorootExport(pkgDir string) (string, error) {
    39  	f, ok := exportMap.Load(pkgDir)
    40  	if !ok {
    41  		var (
    42  			listOnce   sync.Once
    43  			exportPath string
    44  			err        error
    45  		)
    46  		f, _ = exportMap.LoadOrStore(pkgDir, func() (string, error) {
    47  			listOnce.Do(func() {
    48  				cmd := exec.Command(filepath.Join(build.Default.GOROOT, "bin", "go"), "list", "-export", "-f", "{{.Export}}", pkgDir)
    49  				cmd.Dir = build.Default.GOROOT
    50  				cmd.Env = append(os.Environ(), "PWD="+cmd.Dir, "GOROOT="+build.Default.GOROOT)
    51  				var output []byte
    52  				output, err = cmd.Output()
    53  				if err != nil {
    54  					if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 {
    55  						err = errors.New(string(ee.Stderr))
    56  					}
    57  					return
    58  				}
    59  
    60  				exports := strings.Split(string(bytes.TrimSpace(output)), "\n")
    61  				if len(exports) != 1 {
    62  					err = fmt.Errorf("go list reported %d exports; expected 1", len(exports))
    63  					return
    64  				}
    65  
    66  				exportPath = exports[0]
    67  			})
    68  
    69  			return exportPath, err
    70  		})
    71  	}
    72  
    73  	return f.(func() (string, error))()
    74  }
    75  
    76  var pkgExts = [...]string{".a", ".o"} // a file from the build cache will have no extension
    77  
    78  // FindPkg returns the filename and unique package id for an import
    79  // path based on package information provided by build.Import (using
    80  // the build.Default build.Context). A relative srcDir is interpreted
    81  // relative to the current working directory.
    82  func FindPkg(path, srcDir string) (filename, id string, err error) {
    83  	if path == "" {
    84  		return "", "", errors.New("path is empty")
    85  	}
    86  
    87  	var noext string
    88  	switch {
    89  	default:
    90  		// "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x"
    91  		// Don't require the source files to be present.
    92  		if abs, err := filepath.Abs(srcDir); err == nil { // see issue 14282
    93  			srcDir = abs
    94  		}
    95  		var bp *build.Package
    96  		bp, err = build.Import(path, srcDir, build.FindOnly|build.AllowBinary)
    97  		if bp.PkgObj == "" {
    98  			if bp.Goroot && bp.Dir != "" {
    99  				filename, err = lookupGorootExport(bp.Dir)
   100  				if err == nil {
   101  					_, err = os.Stat(filename)
   102  				}
   103  				if err == nil {
   104  					return filename, bp.ImportPath, nil
   105  				}
   106  			}
   107  			goto notfound
   108  		} else {
   109  			noext = strings.TrimSuffix(bp.PkgObj, ".a")
   110  		}
   111  		id = bp.ImportPath
   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  	// try extensions
   133  	for _, ext := range pkgExts {
   134  		filename = noext + ext
   135  		f, statErr := os.Stat(filename)
   136  		if statErr == nil && !f.IsDir() {
   137  			return filename, id, nil
   138  		}
   139  		if err == nil {
   140  			err = statErr
   141  		}
   142  	}
   143  
   144  notfound:
   145  	if err == nil {
   146  		return "", path, fmt.Errorf("can't find import: %q", path)
   147  	}
   148  	return "", path, fmt.Errorf("can't find import: %q: %w", path, err)
   149  }
   150  
   151  // Import imports a gc-generated package given its import path and srcDir, adds
   152  // the corresponding package object to the packages map, and returns the object.
   153  // The packages map must contain all packages already imported.
   154  func Import(fset *token.FileSet, packages map[string]*types.Package, path, srcDir string, lookup func(path string) (io.ReadCloser, error)) (pkg *types.Package, err error) {
   155  	var rc io.ReadCloser
   156  	var id string
   157  	if lookup != nil {
   158  		// With custom lookup specified, assume that caller has
   159  		// converted path to a canonical import path for use in the map.
   160  		if path == "unsafe" {
   161  			return types.Unsafe, nil
   162  		}
   163  		id = path
   164  
   165  		// No need to re-import if the package was imported completely before.
   166  		if pkg = packages[id]; pkg != nil && pkg.Complete() {
   167  			return
   168  		}
   169  		f, err := lookup(path)
   170  		if err != nil {
   171  			return nil, err
   172  		}
   173  		rc = f
   174  	} else {
   175  		var filename string
   176  		filename, id, err = FindPkg(path, srcDir)
   177  		if filename == "" {
   178  			if path == "unsafe" {
   179  				return types.Unsafe, nil
   180  			}
   181  			return nil, err
   182  		}
   183  
   184  		// no need to re-import if the package was imported completely before
   185  		if pkg = packages[id]; pkg != nil && pkg.Complete() {
   186  			return
   187  		}
   188  
   189  		// open file
   190  		f, err := os.Open(filename)
   191  		if err != nil {
   192  			return nil, err
   193  		}
   194  		defer func() {
   195  			if err != nil {
   196  				// add file name to error
   197  				err = fmt.Errorf("%s: %v", filename, err)
   198  			}
   199  		}()
   200  		rc = f
   201  	}
   202  	defer rc.Close()
   203  
   204  	buf := bufio.NewReader(rc)
   205  	hdr, size, err := FindExportData(buf)
   206  	if err != nil {
   207  		return
   208  	}
   209  
   210  	switch hdr {
   211  	case "$$\n":
   212  		err = fmt.Errorf("import %q: old textual export format no longer supported (recompile library)", path)
   213  
   214  	case "$$B\n":
   215  		var exportFormat byte
   216  		if exportFormat, err = buf.ReadByte(); err != nil {
   217  			return
   218  		}
   219  		size--
   220  
   221  		// The unified export format starts with a 'u'; the indexed export
   222  		// format starts with an 'i'; and the older binary export format
   223  		// starts with a 'c', 'd', or 'v' (from "version"). Select
   224  		// appropriate importer.
   225  		switch exportFormat {
   226  		case 'u':
   227  			var data []byte
   228  			var r io.Reader = buf
   229  			if size >= 0 {
   230  				if data, err = saferio.ReadData(r, uint64(size)); err != nil {
   231  					return
   232  				}
   233  			} else if data, err = io.ReadAll(r); err != nil {
   234  				return
   235  			}
   236  			s := string(data)
   237  			s = s[:strings.LastIndex(s, "\n$$\n")]
   238  
   239  			input := pkgbits.NewPkgDecoder(id, s)
   240  			pkg = readUnifiedPackage(fset, nil, packages, input)
   241  		case 'i':
   242  			pkg, err = iImportData(fset, packages, buf, id)
   243  		default:
   244  			err = fmt.Errorf("import %q: old binary export format no longer supported (recompile library)", path)
   245  		}
   246  
   247  	default:
   248  		err = fmt.Errorf("import %q: unknown export data header: %q", path, hdr)
   249  	}
   250  
   251  	return
   252  }
   253  
   254  type byPath []*types.Package
   255  
   256  func (a byPath) Len() int           { return len(a) }
   257  func (a byPath) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
   258  func (a byPath) Less(i, j int) bool { return a[i].Path() < a[j].Path() }
   259  

View as plain text