github.com/goplus/gogen@v1.16.0/packages/cache/cache.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 cache 15 16 import ( 17 "bytes" 18 "errors" 19 "io" 20 "os" 21 "os/exec" 22 "strconv" 23 "strings" 24 "sync" 25 "sync/atomic" 26 ) 27 28 const ( 29 HashInvalid = "?" 30 HashSkip = "" 31 ) 32 33 type depPkg struct { 34 path string 35 hash string // empty means dirty cache 36 } 37 38 type pkgCache struct { 39 expfile string 40 hash string 41 deps []depPkg 42 } 43 44 // PkgHash represents a package hash function. 45 type PkgHash func(pkgPath string, self bool) (hash string) 46 47 // Impl represents a cache. 48 type Impl struct { 49 cache sync.Map // map[string]*pkgCache 50 h PkgHash // package hash func 51 nlist int32 // list count 52 } 53 54 // New creates a new cache. 55 func New(h PkgHash) *Impl { 56 return &Impl{h: h} 57 } 58 59 // ---------------------------------------------------------------------------- 60 61 // ListTimes returns the number of times of calling `go list`. 62 func (p *Impl) ListTimes() int { 63 return int(atomic.LoadInt32(&p.nlist)) 64 } 65 66 // Prepare prepares the cache for a list of pkgPath. 67 func (p *Impl) Prepare(dir string, pkgPath ...string) (err error) { 68 atomic.AddInt32(&p.nlist, 1) 69 ret, err := golistExport(dir, pkgPath) 70 if err != nil { 71 return 72 } 73 h := p.h 74 for _, v := range ret { 75 deps := make([]depPkg, 0, len(v.deps)) 76 pkg := &pkgCache{expfile: v.expfile, hash: h(v.path, true), deps: deps} 77 for _, dep := range v.deps { 78 if hash := h(dep, false); hash != HashSkip { 79 pkg.deps = append(pkg.deps, depPkg{dep, hash}) 80 } 81 } 82 p.cache.Store(v.path, pkg) 83 } 84 return 85 } 86 87 // Find finds the cache for a pkgPath. 88 func (p *Impl) Find(dir, pkgPath string) (f io.ReadCloser, err error) { 89 val, ok := p.cache.Load(pkgPath) 90 if !ok || isDirty(&f, pkgPath, val, p.h) { 91 err = p.Prepare(dir, pkgPath) 92 if val, ok = p.cache.Load(pkgPath); ok { 93 return os.Open(val.(*pkgCache).expfile) 94 } 95 if err == nil { 96 err = os.ErrNotExist 97 } 98 } 99 return 100 } 101 102 func isDirty(pf *io.ReadCloser, pkgPath string, val any, h PkgHash) bool { 103 pkg := val.(*pkgCache) 104 if pkg.hash == HashInvalid || h(pkgPath, true) != pkg.hash { 105 return true 106 } 107 for _, dep := range pkg.deps { 108 if h(dep.path, false) != dep.hash { 109 return true 110 } 111 } 112 f, err := os.Open(pkg.expfile) 113 *pf = f 114 return err != nil 115 } 116 117 type exportPkg struct { 118 path string 119 expfile string 120 deps []string 121 } 122 123 func golistExport(dir string, pkgPath []string) (ret []exportPkg, err error) { 124 var stdout, stderr bytes.Buffer 125 var args = make([]string, 0, 3+len(pkgPath)) 126 args = append(args, "list", "-f={{.ImportPath}}\t{{.Export}}\t{{.Deps}}", "-export") 127 args = append(args, pkgPath...) 128 cmd := exec.Command("go", args...) 129 cmd.Stdout = &stdout 130 cmd.Stderr = &stderr 131 cmd.Dir = dir 132 err = cmd.Run() 133 if err == nil { 134 return parseExports(stdout.String()) 135 } else if stderr.Len() > 0 { 136 err = errors.New(stderr.String()) 137 } 138 return 139 } 140 141 func parseExports(s string) (ret []exportPkg, err error) { 142 lines := strings.Split(strings.TrimRight(s, "\n"), "\n") 143 ret = make([]exportPkg, 0, len(lines)) 144 for _, line := range lines { 145 v, e := parseExport(line) 146 if e != nil { 147 return nil, e 148 } 149 ret = append(ret, v) 150 } 151 return 152 } 153 154 var ( 155 errInvalidFormat = errors.New("invalid format") 156 ) 157 158 // {{.ImportPath}}\t{{.Export}}\t{{.Deps}} 159 // 160 // <path> <expfile> [<depPkg1> <depPkg2> ...] 161 func parseExport(line string) (ret exportPkg, err error) { 162 parts := strings.SplitN(line, "\t", 3) 163 if len(parts) != 3 { 164 err = errInvalidFormat 165 return 166 } 167 deps := parts[2] 168 if len(deps) > 2 { 169 ret.deps = strings.Split(deps[1:len(deps)-1], " ") 170 } 171 ret.path, ret.expfile = parts[0], parts[1] 172 return 173 } 174 175 // ---------------------------------------------------------------------------- 176 177 var ( 178 readFile = os.ReadFile 179 writeFile = os.WriteFile 180 ) 181 182 /* 183 DiskCache cacheFile format: 184 <pkgPath> <exportFile> <pkgHash> <depPkgNum> 185 <depPkgPath1> <depPkgHash1> 186 <depPkgPath2> <depPkgHash2> 187 ... 188 <pkgPath> <exportFile> <depPkgNum> 189 ... 190 */ 191 192 // Load loads the cache from a file. 193 func (p *Impl) Load(cacheFile string) (err error) { 194 b, err := readFile(cacheFile) 195 if err != nil { 196 if os.IsNotExist(err) { 197 err = nil 198 } 199 return 200 } 201 lines := strings.Split(string(bytes.TrimRight(b, "\n")), "\n") 202 return p.loadCachePkgs(lines) 203 } 204 205 func (p *Impl) loadCachePkgs(lines []string) error { 206 for len(lines) > 0 { 207 line := lines[0] 208 parts := strings.SplitN(line, "\t", 4) 209 if len(parts) != 4 || parts[0] == "" { 210 return errInvalidFormat 211 } 212 n, e := strconv.Atoi(parts[3]) 213 if e != nil || len(lines) < n+1 { 214 return errInvalidFormat 215 } 216 deps := make([]depPkg, 0, n) 217 for i := 1; i <= n; i++ { 218 line = lines[i] 219 if !strings.HasPrefix(line, "\t") { 220 return errInvalidFormat 221 } 222 line = line[1:] 223 pos := strings.IndexByte(line, '\t') 224 if pos <= 0 { 225 return errInvalidFormat 226 } 227 deps = append(deps, depPkg{line[:pos], line[pos+1:]}) 228 } 229 pkg := &pkgCache{expfile: parts[1], hash: parts[2], deps: deps} 230 p.cache.Store(parts[0], pkg) 231 lines = lines[n+1:] 232 } 233 return nil 234 } 235 236 // Save saves the cache to a file. 237 func (p *Impl) Save(cacheFile string) (err error) { 238 if atomic.LoadInt32(&p.nlist) == 0 { // not dirty 239 return 240 } 241 var buf bytes.Buffer 242 p.cache.Range(func(key, val any) bool { 243 pkg := val.(*pkgCache) 244 buf.WriteString(key.(string)) 245 buf.WriteByte('\t') 246 buf.WriteString(pkg.expfile) 247 buf.WriteByte('\t') 248 buf.WriteString(pkg.hash) 249 buf.WriteByte('\t') 250 buf.WriteString(strconv.Itoa(len(pkg.deps))) 251 buf.WriteByte('\n') 252 for _, dep := range pkg.deps { 253 buf.WriteByte('\t') 254 buf.WriteString(dep.path) 255 buf.WriteByte('\t') 256 buf.WriteString(dep.hash) 257 buf.WriteByte('\n') 258 } 259 return true 260 }) 261 return writeFile(cacheFile, buf.Bytes(), 0666) 262 } 263 264 // ----------------------------------------------------------------------------