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