Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Android/build/build/blueprint/pathtools/   (Android Betriebssystem Version 17©)  Datei vom 26.5.2026 mit Größe 11 kB image not shown  

Quelle  glob_cache.go   Sprache: unbekannt

 
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 CheckForChangedGlobs.
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
}

[Dauer der Verarbeitung: 0.2 Sekunden, vorverarbeitet 2026-06-28]