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	"crypto/md5"
19	"fmt"
20	"sort"
21	"strings"
22
23	"github.com/google/blueprint/pathtools"
24)
25
26type GlobPath struct {
27	Pattern  string
28	Excludes []string
29	Files    []string
30	Deps     []string
31	Name     string
32}
33
34func verifyGlob(fileName, pattern string, excludes []string, g GlobPath) {
35	if pattern != g.Pattern {
36		panic(fmt.Errorf("Mismatched patterns %q and %q for glob file %q", pattern, g.Pattern, fileName))
37	}
38	if len(excludes) != len(g.Excludes) {
39		panic(fmt.Errorf("Mismatched excludes %v and %v for glob file %q", excludes, g.Excludes, fileName))
40	}
41
42	for i := range excludes {
43		if g.Excludes[i] != excludes[i] {
44			panic(fmt.Errorf("Mismatched excludes %v and %v for glob file %q", excludes, g.Excludes, fileName))
45		}
46	}
47}
48
49func (c *Context) glob(pattern string, excludes []string) ([]string, error) {
50	fileName := globToFileName(pattern, excludes)
51
52	// Try to get existing glob from the stored results
53	c.globLock.Lock()
54	g, exists := c.globs[fileName]
55	c.globLock.Unlock()
56
57	if exists {
58		// Glob has already been done, double check it is identical
59		verifyGlob(fileName, pattern, excludes, g)
60		return g.Files, nil
61	}
62
63	// Get a globbed file list
64	files, deps, err := c.fs.Glob(pattern, excludes, pathtools.FollowSymlinks)
65	if err != nil {
66		return nil, err
67	}
68
69	// Store the results
70	c.globLock.Lock()
71	if g, exists = c.globs[fileName]; !exists {
72		c.globs[fileName] = GlobPath{pattern, excludes, files, deps, fileName}
73	}
74	c.globLock.Unlock()
75
76	// Getting the list raced with another goroutine, throw away the results and use theirs
77	if exists {
78		verifyGlob(fileName, pattern, excludes, g)
79		return g.Files, nil
80	}
81
82	return files, nil
83}
84
85func (c *Context) Globs() []GlobPath {
86	fileNames := make([]string, 0, len(c.globs))
87	for k := range c.globs {
88		fileNames = append(fileNames, k)
89	}
90	sort.Strings(fileNames)
91
92	globs := make([]GlobPath, len(fileNames))
93	for i, fileName := range fileNames {
94		globs[i] = c.globs[fileName]
95	}
96
97	return globs
98}
99
100func globToString(pattern string) string {
101	ret := ""
102	for _, c := range pattern {
103		switch {
104		case c >= 'a' && c <= 'z',
105			c >= 'A' && c <= 'Z',
106			c >= '0' && c <= '9',
107			c == '_', c == '-', c == '/':
108			ret += string(c)
109		default:
110			ret += "_"
111		}
112	}
113
114	return ret
115}
116
117func globToFileName(pattern string, excludes []string) string {
118	name := globToString(pattern)
119	excludeName := ""
120	for _, e := range excludes {
121		excludeName += "__" + globToString(e)
122	}
123
124	// Prevent file names from reaching ninja's path component limit
125	if strings.Count(name, "/")+strings.Count(excludeName, "/") > 30 {
126		excludeName = fmt.Sprintf("___%x", md5.Sum([]byte(excludeName)))
127	}
128
129	return name + excludeName + ".glob"
130}
131