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() }