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  // ----------------------------------------------------------------------------