blob: 1d05621b949a67501dc672a075895f600b7e7a55 [file]
// Copyright 2015 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 blueprint
import (
"cmp"
"errors"
"fmt"
"os"
"slices"
"sort"
"strconv"
"strings"
"time"
"github.com/google/blueprint/pathtools"
)
func verifyGlob(key globKey, pattern string, excludes []string, g pathtools.GlobResult) {
if pattern != g.Pattern {
panic(fmt.Errorf("Mismatched patterns %q and %q for glob key %q", pattern, g.Pattern, key))
}
if len(excludes) != len(g.Excludes) {
panic(fmt.Errorf("Mismatched excludes %v and %v for glob key %q", excludes, g.Excludes, key))
}
for i := range excludes {
if g.Excludes[i] != excludes[i] {
panic(fmt.Errorf("Mismatched excludes %v and %v for glob key %q", excludes, g.Excludes, key))
}
}
}
func (c *Context) glob(pattern string, excludes []string) ([]string, error) {
// Sort excludes so that two globs with the same excludes in a different order reuse the same
// key. Make a copy first to avoid modifying the caller's version.
excludes = slices.Clone(excludes)
sort.Strings(excludes)
key := globToKey(pattern, excludes)
// Try to get existing glob from the stored results, compute it otherwise
entry, exists := c.globs.LoadOrCompute(key, func() globsMapEntry {
// Check if the glob was restored from the cache
if cachedGlob, exists := c.restoredGlobsFromCache[key]; exists {
return globsMapEntry{result: cachedGlob}
}
// Get a globbed file list
g, err := c.fs.Glob(pattern, excludes, pathtools.FollowSymlinks)
if err != nil {
return globsMapEntry{err: err}
}
return globsMapEntry{result: g}
})
if entry.err != nil {
return nil, entry.err
}
g := entry.result
if exists {
// Glob has already been done, double check it is identical
verifyGlob(key, pattern, excludes, g)
}
// Return a copy so that modifications don't affect the cached value.
return slices.Clone(g.Matches), nil
}
// WriteGlobFile writes the list of globs and a timestamp to files next to finalOutFile.
func (c *Context) WriteGlobFile(finalOutFile string, startTime time.Time) error {
var globKeys []globKey
c.globs.Range(func(key globKey, value globsMapEntry) bool {
globKeys = append(globKeys, key)
return true
})
slices.SortFunc(globKeys, func(a, b globKey) int {
return cmp.Or(cmp.Compare(a.pattern, b.pattern), cmp.Compare(a.excludes, b.excludes))
})
globs := make([]pathtools.GlobResult, 0, len(globKeys))
for _, key := range globKeys {
entry, _ := c.globs.Load(key)
globs = append(globs, entry.result)
}
globsFile, err := os.Create(finalOutFile + ".globs")
if err != nil {
return err
}
defer globsFile.Close()
if err := pathtools.WriteGlobFile(globsFile, globs); err != nil {
return err
}
return os.WriteFile(
finalOutFile+".globs_time",
[]byte(fmt.Sprintf("%d\n", startTime.UnixMicro())),
0666,
)
}
// RestoreGlobsFromCache reads the list of globs that was written by WriteGlobFile during a
// previous run and returns any whose dependencies have not changed to avoid redoing all the
// glob traversals during incremental analysis.
func (c *Context) RestoreGlobsFromCache(finalOutFile string) error {
c.EventHandler.Begin("restore_globs")
defer c.EventHandler.End("restore_globs")
globsFile, err := os.Open(finalOutFile + ".globs")
if errors.Is(err, os.ErrNotExist) {
return nil
} else if err != nil {
return err
}
defer globsFile.Close()
globsTimeBytes, err := os.ReadFile(finalOutFile + ".globs_time")
if errors.Is(err, os.ErrNotExist) {
globsTimeBytes = []byte("0")
} else if err != nil {
return err
}
globsTimeMicros, err := strconv.ParseInt(strings.TrimSpace(string(globsTimeBytes)), 10, 64)
if err != nil {
return err
}
globs, globMetrics, err := pathtools.RestoreGlobsFromCache(c.fs, globsFile, globsTimeMicros)
if err != nil {
return err
}
c.restoredGlobMetrics = globMetrics
c.restoredGlobsFromCache = make(map[globKey]pathtools.GlobResult, len(globs))
for _, glob := range globs {
c.restoredGlobsFromCache[globToKey(glob.Pattern, glob.Excludes)] = glob
}
return nil
}
// globKey combines a pattern and a list of excludes into a hashable struct to be used as a key in
// a map.
type globKey struct {
pattern string
excludes string
}
type globsMapEntry struct {
result pathtools.GlobResult
err error
}
// globToKey converts a pattern and an excludes list into a globKey struct that is hashable and
// usable as a key in a map.
func globToKey(pattern string, excludes []string) globKey {
return globKey{pattern, strings.Join(excludes, "|")}
}