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