github.com/goplus/gox@v1.14.13-0.20240308130321-6ff7f61cfae8/packages/imp.go (about) 1 /* 2 Copyright 2022 The GoPlus Authors (goplus.org) 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 http://www.apache.org/licenses/LICENSE-2.0 7 Unless required by applicable law or agreed to in writing, software 8 distributed under the License is distributed on an "AS IS" BASIS, 9 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 See the License for the specific language governing permissions and 11 limitations under the License. 12 */ 13 14 package packages 15 16 import ( 17 "bytes" 18 "errors" 19 "go/token" 20 "go/types" 21 "os" 22 "os/exec" 23 "sync" 24 25 "golang.org/x/tools/go/gcexportdata" 26 ) 27 28 // ---------------------------------------------------------------------------- 29 30 type Importer struct { 31 loaded map[string]*types.Package 32 fset *token.FileSet 33 dir string 34 m sync.RWMutex 35 } 36 37 // NewImporter creates an Importer object that meets types.Importer interface. 38 func NewImporter(fset *token.FileSet, workDir ...string) *Importer { 39 dir := "" 40 if len(workDir) > 0 { 41 dir = workDir[0] 42 } 43 if fset == nil { 44 fset = token.NewFileSet() 45 } 46 loaded := make(map[string]*types.Package) 47 loaded["unsafe"] = types.Unsafe 48 return &Importer{loaded: loaded, fset: fset, dir: dir} 49 } 50 51 func (p *Importer) Import(pkgPath string) (pkg *types.Package, err error) { 52 return p.ImportFrom(pkgPath, p.dir, 0) 53 } 54 55 // ImportFrom returns the imported package for the given import 56 // path when imported by a package file located in dir. 57 // If the import failed, besides returning an error, ImportFrom 58 // is encouraged to cache and return a package anyway, if one 59 // was created. This will reduce package inconsistencies and 60 // follow-on type checker errors due to the missing package. 61 // The mode value must be 0; it is reserved for future use. 62 // Two calls to ImportFrom with the same path and dir must 63 // return the same package. 64 func (p *Importer) ImportFrom(pkgPath, dir string, mode types.ImportMode) (*types.Package, error) { 65 p.m.RLock() 66 if ret, ok := p.loaded[pkgPath]; ok && ret.Complete() { 67 p.m.RUnlock() 68 return ret, nil 69 } 70 p.m.RUnlock() 71 expfile, err := FindExport(dir, pkgPath) 72 if err != nil { 73 return nil, err 74 } 75 return p.loadByExport(expfile, pkgPath) 76 } 77 78 func (p *Importer) loadByExport(expfile string, pkgPath string) (ret *types.Package, err error) { 79 f, err := os.Open(expfile) 80 if err != nil { 81 return nil, err 82 } 83 defer f.Close() 84 85 r, err := gcexportdata.NewReader(f) 86 if err == nil { 87 p.m.Lock() // use mutex because Import should be multi-thread safe 88 defer p.m.Unlock() 89 ret, err = gcexportdata.Read(r, p.fset, p.loaded, pkgPath) 90 } 91 return 92 } 93 94 // ---------------------------------------------------------------------------- 95 96 // FindExport lookups export file (.a) of a package by its pkgPath. 97 // It returns empty if pkgPath not found. 98 func FindExport(dir, pkgPath string) (expfile string, err error) { 99 data, err := golistExport(dir, pkgPath) 100 if err != nil { 101 return 102 } 103 expfile = string(bytes.TrimSuffix(data, []byte{'\n'})) 104 return 105 } 106 107 func golistExport(dir, pkgPath string) (ret []byte, err error) { 108 var stdout, stderr bytes.Buffer 109 cmd := exec.Command("go", "list", "-f={{.Export}}", "-export", pkgPath) 110 cmd.Stdout = &stdout 111 cmd.Stderr = &stderr 112 cmd.Dir = dir 113 err = cmd.Run() 114 if err == nil { 115 ret = stdout.Bytes() 116 } else if stderr.Len() > 0 { 117 err = errors.New(stderr.String()) 118 } 119 return 120 } 121 122 // ----------------------------------------------------------------------------