1// Copyright 2015 Google Inc. All rights reserved.
2//
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//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package blueprint
16
17import (
18	"fmt"
19	"slices"
20	"sort"
21	"strings"
22
23	"github.com/google/blueprint/pathtools"
24)
25
26func verifyGlob(key globKey, pattern string, excludes []string, g pathtools.GlobResult) {
27	if pattern != g.Pattern {
28		panic(fmt.Errorf("Mismatched patterns %q and %q for glob key %q", pattern, g.Pattern, key))
29	}
30	if len(excludes) != len(g.Excludes) {
31		panic(fmt.Errorf("Mismatched excludes %v and %v for glob key %q", excludes, g.Excludes, key))
32	}
33
34	for i := range excludes {
35		if g.Excludes[i] != excludes[i] {
36			panic(fmt.Errorf("Mismatched excludes %v and %v for glob key %q", excludes, g.Excludes, key))
37		}
38	}
39}
40
41func (c *Context) glob(pattern string, excludes []string) ([]string, error) {
42	// Sort excludes so that two globs with the same excludes in a different order reuse the same
43	// key.  Make a copy first to avoid modifying the caller's version.
44	excludes = slices.Clone(excludes)
45	sort.Strings(excludes)
46
47	key := globToKey(pattern, excludes)
48
49	// Try to get existing glob from the stored results
50	c.globLock.Lock()
51	g, exists := c.globs[key]
52	c.globLock.Unlock()
53
54	if exists {
55		// Glob has already been done, double check it is identical
56		verifyGlob(key, pattern, excludes, g)
57		// Return a copy so that modifications don't affect the cached value.
58		return slices.Clone(g.Matches), nil
59	}
60
61	// Get a globbed file list
62	result, err := c.fs.Glob(pattern, excludes, pathtools.FollowSymlinks)
63	if err != nil {
64		return nil, err
65	}
66
67	// Store the results
68	c.globLock.Lock()
69	if g, exists = c.globs[key]; !exists {
70		c.globs[key] = result
71	}
72	c.globLock.Unlock()
73
74	if exists {
75		// Getting the list raced with another goroutine, throw away the results and use theirs
76		verifyGlob(key, pattern, excludes, g)
77		// Return a copy so that modifications don't affect the cached value.
78		return slices.Clone(g.Matches), nil
79	}
80
81	// Return a copy so that modifications don't affect the cached value.
82	return slices.Clone(result.Matches), nil
83}
84
85func (c *Context) Globs() pathtools.MultipleGlobResults {
86	keys := make([]globKey, 0, len(c.globs))
87	for k := range c.globs {
88		keys = append(keys, k)
89	}
90
91	sort.Slice(keys, func(i, j int) bool {
92		if keys[i].pattern != keys[j].pattern {
93			return keys[i].pattern < keys[j].pattern
94		}
95		return keys[i].excludes < keys[j].excludes
96	})
97
98	globs := make(pathtools.MultipleGlobResults, len(keys))
99	for i, key := range keys {
100		globs[i] = c.globs[key]
101	}
102
103	return globs
104}
105
106// globKey combines a pattern and a list of excludes into a hashable struct to be used as a key in
107// a map.
108type globKey struct {
109	pattern  string
110	excludes string
111}
112
113// globToKey converts a pattern and an excludes list into a globKey struct that is hashable and
114// usable as a key in a map.
115func globToKey(pattern string, excludes []string) globKey {
116	return globKey{pattern, strings.Join(excludes, "|")}
117}
118