github.com/goplus/gogen@v1.16.0/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 "io" 22 "os" 23 "os/exec" 24 "sync" 25 "sync/atomic" 26 27 "golang.org/x/tools/go/gcexportdata" 28 ) 29 30 // ---------------------------------------------------------------------------- 31 32 // Cache represents a cache for the importer. 33 type Cache interface { 34 Find(dir, pkgPath string) (f io.ReadCloser, err error) 35 } 36 37 // Importer represents a Go package importer. 38 type Importer struct { 39 loaded map[string]*types.Package 40 fset *token.FileSet 41 dir string 42 m sync.RWMutex 43 cache Cache 44 } 45 46 // NewImporter creates an Importer object that meets types.ImporterFrom and types.Importer interface. 47 func NewImporter(fset *token.FileSet, workDir ...string) *Importer { 48 dir := "" 49 if len(workDir) > 0 { 50 dir = workDir[0] 51 } 52 if fset == nil { 53 fset = token.NewFileSet() 54 } 55 loaded := make(map[string]*types.Package) 56 loaded["unsafe"] = types.Unsafe 57 return &Importer{loaded: loaded, fset: fset, dir: dir} 58 } 59 60 // SetCache sets an optional cache for the importer. 61 func (p *Importer) SetCache(cache Cache) { 62 p.cache = cache 63 } 64 65 // Cache returns the cache of the importer. 66 func (p *Importer) Cache() Cache { 67 return p.cache 68 } 69 70 // Import returns the imported package for the given import path. 71 func (p *Importer) Import(pkgPath string) (pkg *types.Package, err error) { 72 return p.ImportFrom(pkgPath, p.dir, 0) 73 } 74 75 // ImportFrom returns the imported package for the given import 76 // path when imported by a package file located in dir. 77 // If the import failed, besides returning an error, ImportFrom 78 // is encouraged to cache and return a package anyway, if one 79 // was created. This will reduce package inconsistencies and 80 // follow-on type checker errors due to the missing package. 81 // The mode value must be 0; it is reserved for future use. 82 // Two calls to ImportFrom with the same path and dir must 83 // return the same package. 84 func (p *Importer) ImportFrom(pkgPath, dir string, mode types.ImportMode) (*types.Package, error) { 85 p.m.RLock() 86 if ret, ok := p.loaded[pkgPath]; ok && ret.Complete() { 87 p.m.RUnlock() 88 return ret, nil 89 } 90 p.m.RUnlock() 91 f, err := p.findExport(dir, pkgPath) 92 if err != nil { 93 return nil, err 94 } 95 defer f.Close() 96 return p.loadByExport(f, pkgPath) 97 } 98 99 func (p *Importer) loadByExport(f io.ReadCloser, pkgPath string) (ret *types.Package, err error) { 100 r, err := gcexportdata.NewReader(f) 101 if err == nil { 102 p.m.Lock() // use mutex because Import should be multi-thread safe 103 defer p.m.Unlock() 104 ret, err = gcexportdata.Read(r, p.fset, p.loaded, pkgPath) 105 } 106 return 107 } 108 109 // ---------------------------------------------------------------------------- 110 111 // findExport lookups export file (.a) of a package by its pkgPath. 112 func (p *Importer) findExport(dir, pkgPath string) (f io.ReadCloser, err error) { 113 if c := p.cache; c != nil { 114 return c.Find(dir, pkgPath) 115 } 116 atomic.AddInt32(&nlist, 1) 117 data, err := golistExport(dir, pkgPath) 118 if err != nil { 119 return 120 } 121 expfile := string(bytes.TrimSuffix(data, []byte{'\n'})) 122 return os.Open(expfile) 123 } 124 125 func golistExport(dir, pkgPath string) (ret []byte, err error) { 126 var stdout, stderr bytes.Buffer 127 cmd := exec.Command("go", "list", "-f={{.Export}}", "-export", pkgPath) 128 cmd.Stdout = &stdout 129 cmd.Stderr = &stderr 130 cmd.Dir = dir 131 err = cmd.Run() 132 if err == nil { 133 ret = stdout.Bytes() 134 } else if stderr.Len() > 0 { 135 err = errors.New(stderr.String()) 136 } 137 return 138 } 139 140 var ( 141 nlist int32 142 ) 143 144 // ListTimes returns the number of times of calling `go list`. 145 func ListTimes() int { 146 return int(atomic.LoadInt32(&nlist)) 147 } 148 149 // ----------------------------------------------------------------------------