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

View as plain text