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	"sort"
20	"strings"
21
22	"github.com/google/blueprint/pathtools"
23)
24
25func verifyGlob(key globKey, pattern string, excludes []string, g pathtools.GlobResult) {
26	if pattern != g.Pattern {
27		panic(fmt.Errorf("Mismatched patterns %q and %q for glob key %q", pattern, g.Pattern, key))
28	}
29	if len(excludes) != len(g.Excludes) {
30		panic(fmt.Errorf("Mismatched excludes %v and %v for glob key %q", excludes, g.Excludes, key))
31	}
32
33	for i := range excludes {
34		if g.Excludes[i] != excludes[i] {
35			panic(fmt.Errorf("Mismatched excludes %v and %v for glob key %q", excludes, g.Excludes, key))
36		}
37	}
38}
39
40func (c *Context) glob(pattern string, excludes []string) ([]string, error) {
41	// Sort excludes so that two globs with the same excludes in a different order reuse the same
42	// key.  Make a copy first to avoid modifying the caller's version.
43	excludes = append([]string(nil), excludes...)
44	sort.Strings(excludes)
45
46	key := globToKey(pattern, excludes)
47
48	// Try to get existing glob from the stored results
49	c.globLock.Lock()
50	g, exists := c.globs[key]
51	c.globLock.Unlock()
52
53	if exists {
54		// Glob has already been done, double check it is identical
55		verifyGlob(key, pattern, excludes, g)
56		// Return a copy so that modifications don't affect the cached value.
57		return append([]string(nil), g.Matches...), nil
58	}
59
60	// Get a globbed file list
61	result, err := c.fs.Glob(pattern, excludes, pathtools.FollowSymlinks)
62	if err != nil {
63		return nil, err
64	}
65
66	// Store the results
67	c.globLock.Lock()
68	if g, exists = c.globs[key]; !exists {
69		c.globs[key] = result
70	}
71	c.globLock.Unlock()
72
73	if exists {
74		// Getting the list raced with another goroutine, throw away the results and use theirs
75		verifyGlob(key, pattern, excludes, g)
76		// Return a copy so that modifications don't affect the cached value.
77		return append([]string(nil), g.Matches...), nil
78	}
79
80	// Return a copy so that modifications don't affect the cached value.
81	return append([]string(nil), result.Matches...), nil
82}
83
84func (c *Context) Globs() pathtools.MultipleGlobResults {
85	keys := make([]globKey, 0, len(c.globs))
86	for k := range c.globs {
87		keys = append(keys, k)
88	}
89
90	sort.Slice(keys, func(i, j int) bool {
91		if keys[i].pattern != keys[j].pattern {
92			return keys[i].pattern < keys[j].pattern
93		}
94		return keys[i].excludes < keys[j].excludes
95	})
96
97	globs := make(pathtools.MultipleGlobResults, len(keys))
98	for i, key := range keys {
99		globs[i] = c.globs[key]
100	}
101
102	return globs
103}
104
105// globKey combines a pattern and a list of excludes into a hashable struct to be used as a key in
106// a map.
107type globKey struct {
108	pattern  string
109	excludes string
110}
111
112// globToKey converts a pattern and an excludes list into a globKey struct that is hashable and
113// usable as a key in a map.
114func globToKey(pattern string, excludes []string) globKey {
115	return globKey{pattern, strings.Join(excludes, "|")}
116}
117