Spracherkennung für: .go vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]
// Copyright
2025 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version
2.
0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
http://www.apache.org/licenses/LICENSE-2.
0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pathtools
import (
"bufio"
"encoding/binary"
"errors"
"fmt"
"io"
"iter"
"os"
"runtime"
"slices"
"strings"
"sync"
"syscall"
"unsafe"
)
// globFileMagic is the header used at the top of the glob cache file. Any change to the
// on-disk format of the cache should be accompanied by a change to the magic string to
// avoid exposing the decoder to an unexpected format.
const globFileMagic = "Soong glob file version
1\n"
// globFileEncoder writes a list of globs to a cache file. It's helpers are conceptually
// similar to the ones in the gobtools package, and the encoder is similar to the
// ones generated by gob_gen, but those use a *bytes.Buffer, while these write directly
// to a buffered file.
type globFileEncoder struct {
w *bufio.Writer
}
func newGlobFileEncoder(w *bufio.Writer) (*globFileEncoder, error) {
_, err := w.WriteString(globFileMagic)
if err != nil {
return nil, fmt.Errorf("failed to write glob file magic: %w", err)
}
return &globFileEncoder{w: w}, nil
}
func (e *globFileEncoder) writeVarint(v int) error {
var buf [binary.MaxVarintLen64]byte
n := binary.PutVarint(buf[:], int64(v))
_, err := e.w.Write(buf[:n])
return err
}
func (e *globFileEncoder) writeString(s string) error {
err := e.writeVarint(len(s))
if err != nil {
return err
}
_, err = e.w.WriteString(s)
return err
}
func (e *globFileEncoder) writeStringList(s []string) error {
err := e.writeVarint(len(s))
if err != nil {
return err
}
for _, str := range s {
err := e.writeString(str)
if err != nil {
return err
}
}
return nil
}
func (e *globFileEncoder) encode(globs []GlobResult) error {
err := e.writeVarint(len(globs))
if err != nil {
return err
}
for _, glob := range globs {
err := e.writeString(glob.Pattern)
if err != nil {
return err
}
err = e.writeStringList(glob.Excludes)
if err != nil {
return err
}
err = e.writeStringList(glob.Matches)
if err != nil {
return err
}
err = e.writeStringList(glob.Deps)
if err != nil {
return err
}
}
return nil
}
type globFileDecoder struct {
r *bufio.Reader
n int
}
var errBadMagic = errors.New("bad glob file magic")
func newGlobFileDecoder(r *bufio.Reader) (*globFileDecoder, error) {
buf := make([]byte, len(globFileMagic))
_, err := io.ReadFull(r, buf)
if errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, io.EOF) {
return nil, errBadMagic
} else if err != nil {
return nil, fmt.Errorf("failed to read glob file magic: %w", err)
} else if string(buf) != globFileMagic {
return nil, errBadMagic
}
d := &globFileDecoder{r: r}
n, err := d.readVarint()
if err != nil {
return nil, err
}
d.n = n
return d, nil
}
func (d *globFileDecoder) readVarint() (int, error) {
n, err := binary.ReadVarint(d.r)
return int(n), err
}
func (d *globFileDecoder) readString() (string, error) {
n, err := d.readVarint()
if err != nil {
return "", err
}
b := make([]byte, n)
_, err = io.ReadFull(d.r, b)
if err != nil {
return "", err
}
return unsafe.String(unsafe.SliceData(b), len(b)), nil
}
func (d *globFileDecoder) readStringList() ([]string, error) {
n, err := d.readVarint()
if err != nil {
return nil, err
}
if n ==
0 {
return nil, nil
}
list := make([]string,
0, n)
for _ = range n {
s, err := d.readString()
if err != nil {
return nil, err
}
list = append(list, s)
}
return list, nil
}
func (d *globFileDecoder) more() bool {
return d.n >
0
}
func (d *globFileDecoder) decode() (GlobResult, error) {
var glob GlobResult
var err error
d.n -=
1
glob.Pattern, err = d.readString()
if err != nil {
return GlobResult{}, err
}
glob.Excludes, err = d.readStringList()
if err != nil {
return GlobResult{}, err
}
glob.Matches, err = d.readStringList()
if err != nil {
return GlobResult{}, err
}
glob.Deps, err = d.readStringList()
if err != nil {
return GlobResult{}, err
}
return glob, err
}
func (d *globFileDecoder) iter() (iter.Seq[GlobResult], func() error) {
var err error
return func(yield func(GlobResult) bool) {
for d.more() {
var glob GlobResult
glob, err = d.decode()
if err != nil {
return
}
if !yield(glob) {
return
}
}
}, func() error {
return err
}
}
// WriteGlobFile writes a list of glob results to a cache file for later use by CheckForChangedG
lobs.
func WriteGlobFile(w io.Writer, globs []GlobResult) (err error) {
bw := bufio.NewWriterSize(w, 16*1024*1024)
defer func() {
flushErr := bw.Flush()
if err != nil {
err = flushErr
}
}()
encoder, err := newGlobFileEncoder(bw)
if err != nil {
return err
}
return encoder.encode(globs)
}
// checkIfGlobDepsChanged returns true if any of the Deps of the cachedGlob have a timestamp newer than
// globsTimeMicros, do not exist, or have a parent that is not a directory.
func checkIfGlobDepsChanged(fs FileSystem, cachedGlob GlobResult, globsTimeMicros int64) (bool, error) {
for _, dep := range cachedGlob.Deps {
info, err := fs.Stat(dep)
if errors.Is(err, os.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
return true, nil
} else if err != nil {
return true, err
}
if info.ModTime().UnixMicro() > globsTimeMicros {
return true, nil
}
}
return false, nil
}
// checkIfGlobChanged checks if a glob result has changed from the cached value, first by checking
// the previously recursed directories against a global timestamp to see if they have changed, then
// by rerunning the glob if necessary. Returns true and a helpful error message if the glob has
// changed or an error occurred, or false if the glob has not changed.
func checkIfGlobChanged(fs FileSystem, cachedGlob GlobResult, globsTimeMicros int64) (bool, error) {
// First, check if any of the deps are newer than the last time globs were checked.
// If not, we don't need to rerun the glob.
if hasNewDep, err := checkIfGlobDepsChanged(fs, cachedGlob, globsTimeMicros); err != nil {
return true, err
} else if !hasNewDep {
return false, nil
}
// Then rerun the glob and check if we got the same result as before.
result, err := glob(fs, cachedGlob.Pattern, cachedGlob.Excludes, FollowSymlinks)
if err != nil {
return true, err
} else {
if !slices.Equal(result.Matches, cachedGlob.Matches) {
changedGlobName := result.Pattern
if len(result.Excludes) > 2 {
changedGlobName += fmt.Sprintf(" (excluding %d other patterns)", len(result.Excludes))
} else if len(result.Excludes) > 0 {
changedGlobName += " (excluding " + strings.Join(result.Excludes, " and ") + ")"
}
return true, fmt.Errorf("One culprit glob (may be more): %s", changedGlobName)
}
}
return false, nil
}
// CheckForChangedGlobs returns true if any of the globs stored in the cache file have changed
// the list of files they return. It optimizes the check to finish in a few hundred ms even when
// there are tens of thousands of globs in the cache by first checking the modification time of
// any directories that were recursed during the original glob runs, and only rerunning globs
// whose relevant directories have been modified.
//
// All errors are treated as finding modified globs in order to gracefully fall back to always
// rerunning analysis. If a modified glob is found an error is also returned that contains
// a helpful message to print to the console.
func CheckForChangedGlobs(fs FileSystem, r io.Reader, globsTimeMicros int64) (bool, error) {
globsFileDecoder, err := newGlobFileDecoder(bufio.NewReaderSize(r, 16*1024*1024))
if err != nil {
if errors.Is(err, errBadMagic) {
return true, fmt.Errorf("glob cache version is out of date")
}
return true, fmt.Errorf("failed to create glob file decoder: %w", err)
}
globsChan := make(chan GlobResult)
wg := sync.WaitGroup{}
hasChangedGlobs := false
var errFromCheckGlobMutex sync.Mutex
var errFromCheckGlob error
for i := 0; i < runtime.NumCPU()*2; i++ {
wg.Add(1)
go func() {
for cachedGlob := range globsChan {
// If we've already determined we have changed globs, just finish consuming
// the channel without doing any more checks.
if hasChangedGlobs {
continue
}
changed, err := checkIfGlobChanged(fs, cachedGlob, globsTimeMicros)
if err != nil {
errFromCheckGlobMutex.Lock()
errFromCheckGlob = err
errFromCheckGlobMutex.Unlock()
}
if changed {
hasChangedGlobs = true
}
}
wg.Done()
}()
}
globsFileIter, errFromDecoder := globsFileDecoder.iter()
for globs := range globsFileIter {
globsChan <- globs
}
close(globsChan)
wg.Wait()
if errFromCheckGlob != nil {
return true, errFromCheckGlob
}
if errFromDecoder() != nil {
return true, fmt.Errorf("failed to decode glob file: %w", errFromCheckGlob)
}
return false, nil
}
type RestoredGlobsMetrics struct {
CachedGlobs uint64
RestoredGlobs uint64
}
// RestoreGlobsFromCache returns a list of globs loaded from the glob cache whose dependencies are unchanged.
func RestoreGlobsFromCache(fs FileSystem, r io.Reader, globsTimeMicros int64) ([]GlobResult, RestoredGlobsMetrics, error) {
metrics := RestoredGlobsMetrics{}
globsFileDecoder, err := newGlobFileDecoder(bufio.NewReaderSize(r, 16*1024*1024))
if err != nil {
if errors.Is(err, errBadMagic) {
return nil, metrics, nil
}
return nil, metrics, fmt.Errorf("failed to create glob file decoder: %w", err)
}
cachedGlobsChan := make(chan GlobResult)
restoredGlobsChan := make(chan GlobResult)
errChan := make(chan error)
var errFromCheckGlob error
wg := sync.WaitGroup{}
for i := 0; i < runtime.NumCPU()*2; i++ {
wg.Add(1)
go func() {
for cachedGlob := range cachedGlobsChan {
changed, err := checkIfGlobDepsChanged(fs, cachedGlob, globsTimeMicros)
if err != nil {
errChan <- err
} else if !changed {
restoredGlobsChan <- cachedGlob
}
}
wg.Done()
}()
}
var restoredGlobs []GlobResult
doneCh := make(chan bool)
go func() {
for {
select {
case err := <-errChan:
if errFromCheckGlob == nil {
errFromCheckGlob = err
}
case restoredGlob := <-restoredGlobsChan:
restoredGlobs = append(restoredGlobs, restoredGlob)
case <-doneCh:
return
}
}
}()
globsFileIter, errFromDecoder := globsFileDecoder.iter()
for globs := range globsFileIter {
metrics.CachedGlobs++
cachedGlobsChan <- globs
}
close(cachedGlobsChan)
wg.Wait()
close(doneCh)
if errFromCheckGlob != nil {
return nil, metrics, errFromCheckGlob
}
if errFromDecoder() != nil {
return nil, metrics, fmt.Errorf("failed to decode glob file: %w", errFromCheckGlob)
}
metrics.RestoredGlobs = uint64(len(restoredGlobs))
return restoredGlobs, metrics, nil
}