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