blob: 7f3d3e37a2aa9cae00e262cebd06ec97dd8d0f02 [file] [log] [blame]
Shinichiro Hamajib69bf8a2015-06-10 14:52:06 +09001// 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
Fumitoshi Ukai358c68a2015-06-08 13:12:55 +090015package main
16
17import (
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +090018 "errors"
19 "fmt"
Fumitoshi Ukai106fb792015-06-09 10:37:35 +090020 "os"
Fumitoshi Ukai358c68a2015-06-08 13:12:55 +090021 "os/exec"
22 "path/filepath"
Fumitoshi Ukai83410132015-06-15 14:50:07 +090023 "runtime"
Fumitoshi Ukai106fb792015-06-09 10:37:35 +090024 "sort"
Fumitoshi Ukai358c68a2015-06-08 13:12:55 +090025 "strings"
Fumitoshi Ukai106fb792015-06-09 10:37:35 +090026 "sync"
27 "time"
Fumitoshi Ukai358c68a2015-06-08 13:12:55 +090028)
29
30var wildcardCache = make(map[string][]string)
31
32func wildcard(sw *ssvWriter, pat string) {
33 if useWildcardCache {
34 // TODO(ukai): make sure it didn't chdir?
35 if files, ok := wildcardCache[pat]; ok {
36 for _, file := range files {
37 sw.WriteString(file)
38 }
39 return
40 }
41 }
42 if strings.Contains(pat, "..") {
43 // For some reason, go's Glob normalizes
44 // foo/../bar to bar. We ask shell to expand
45 // a glob to avoid this.
46 cmdline := []string{"/bin/sh", "-c", "/bin/ls -d " + pat}
47 cmd := exec.Cmd{
48 Path: cmdline[0],
49 Args: cmdline,
50 }
51 // Ignore errors.
52 out, _ := cmd.Output()
53 if len(trimSpaceBytes(out)) > 0 {
54 out = formatCommandOutput(out)
55 sw.Write(out)
56 }
57 if useWildcardCache {
58 ws := newWordScanner(out)
59 var files []string
60 for ws.Scan() {
61 files = append(files, string(ws.Bytes()))
62 }
63 wildcardCache[pat] = files
64 }
65 return
66 }
67 files, err := filepath.Glob(pat)
68 if err != nil {
69 panic(err)
70 }
71 for _, file := range files {
72 sw.WriteString(file)
73 }
74 if useWildcardCache {
75 wildcardCache[pat] = files
76 }
77}
Fumitoshi Ukai106fb792015-06-09 10:37:35 +090078
79type fileInfo struct {
80 path string
81 mode os.FileMode
82}
83
84type androidFindCacheT struct {
85 once sync.Once
Fumitoshi Ukai83410132015-06-15 14:50:07 +090086 filesch chan []fileInfo
87 leavesch chan []fileInfo
Fumitoshi Ukai106fb792015-06-09 10:37:35 +090088 files []fileInfo
Fumitoshi Ukai83410132015-06-15 14:50:07 +090089 leaves []fileInfo
Fumitoshi Ukai106fb792015-06-09 10:37:35 +090090 scanTime time.Duration
91}
92
93var (
94 androidFindCache androidFindCacheT
95)
96
97func (c *androidFindCacheT) ready() bool {
Fumitoshi Ukai83410132015-06-15 14:50:07 +090098 if c.files != nil {
99 return true
100 }
101 select {
102 case c.files = <-c.filesch:
103 }
104 return c.files != nil
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900105}
106
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900107func (c *androidFindCacheT) leavesReady() bool {
108 if c.leaves != nil {
109 return true
110 }
111 select {
112 case c.leaves = <-c.leavesch:
113 }
114 return c.leaves != nil
115}
116
117func (c *androidFindCacheT) init(prunes, leaves []string) {
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900118 c.once.Do(func() {
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900119 c.filesch = make(chan []fileInfo, 1)
120 c.leavesch = make(chan []fileInfo, 1)
121 go c.start(prunes, leaves)
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900122 })
123}
124
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900125func (c *androidFindCacheT) start(prunes, leafNames []string) {
126 Logf("find cache init: prunes=%q leafNames=%q", prunes, leafNames)
127 te := traceEvent.begin("findcache", literal("init"), traceEventFindCache)
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900128 defer func() {
Fumitoshi Ukaif543f4d2015-06-15 15:21:47 +0900129 traceEvent.end(te)
130 c.scanTime = time.Since(te.t)
Shinichiro Hamajib6ad7da2015-06-10 16:49:15 +0900131 LogStats("android find cache scan: %v", c.scanTime)
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900132 }()
133
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900134 dirs := make(chan string, 32)
135 filech := make(chan fileInfo, 1000)
136 leafch := make(chan fileInfo, 1000)
137 var wg sync.WaitGroup
138 numWorker := runtime.NumCPU() - 1
139 wg.Add(numWorker)
140 for i := 0; i < numWorker; i++ {
141 go func() {
142 defer wg.Done()
143 for dir := range dirs {
144 err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
145 if info.IsDir() {
146 for _, prune := range prunes {
147 if info.Name() == prune {
148 Logf("find cache prune: %s", path)
149 return filepath.SkipDir
150 }
151 }
152 leafch <- fileInfo{
153 path: path,
154 mode: info.Mode(),
155 }
156 }
157 filech <- fileInfo{
158 path: path,
159 mode: info.Mode(),
160 }
161 for _, leaf := range leafNames {
162 if info.Name() == leaf {
163 Logf("find cache leaf: %s", path)
164 leafch <- fileInfo{
165 path: path,
166 mode: info.Mode(),
167 }
168 break
169 }
170 }
171 return nil
172 })
173 if err != nil && err != filepath.SkipDir {
174 Logf("error in adnroid find cache: %v", err)
175 close(c.filesch)
176 close(c.leavesch)
177 return
Fumitoshi Ukai0daac1f2015-06-11 11:56:50 +0900178 }
179 }
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900180 }()
181 }
182
183 go func() {
184 leavesTe := traceEvent.begin("findcache", literal("leaves"), traceEventFindCacheLeaves)
185 var leaves []fileInfo
186 for leaf := range leafch {
187 leaves = append(leaves, leaf)
Fumitoshi Ukai0daac1f2015-06-11 11:56:50 +0900188 }
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900189 // TODO(ukai): remove directory that doesn't have leaf names in subdirs.
190 sort.Sort(fileInfoByLeaf(leaves))
191 c.leavesch <- leaves
192 traceEvent.end(leavesTe)
193 for i, leaf := range leaves {
194 Logf("android findleaves cache: %d: %s %v", i, leaf.path, leaf.mode)
195 }
196 }()
197
198 go func() {
199 filesTe := traceEvent.begin("findcache", literal("files"), traceEventFindCacheFiles)
200 var files []fileInfo
201 for file := range filech {
202 files = append(files, file)
203 }
204 sort.Sort(fileInfoByName(files))
205 c.filesch <- files
206 traceEvent.end(filesTe)
207 for i, fi := range files {
208 Logf("android find cache: %d: %s %v", i, fi.path, fi.mode)
209 }
210 }()
211
212 curdir, err := os.Open(".")
213 if err != nil {
214 Logf("open . failed: %v", err)
215 close(c.filesch)
216 close(c.leavesch)
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900217 return
218 }
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900219 names, err := curdir.Readdirnames(-1)
220 if err != nil {
221 Logf("readdir . failed: %v", err)
222 close(c.filesch)
223 close(c.leavesch)
224 return
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900225 }
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900226 curdir.Close()
227
228 for _, name := range names {
229 dirs <- name
230 }
231 close(dirs)
232 wg.Wait()
233 close(filech)
234 close(leafch)
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900235}
236
237type fileInfoByName []fileInfo
238
239func (f fileInfoByName) Len() int { return len(f) }
240func (f fileInfoByName) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
241func (f fileInfoByName) Less(i, j int) bool {
242 return f[i].path < f[j].path
243}
244
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900245type fileInfoByLeaf []fileInfo
246
247func (f fileInfoByLeaf) Len() int { return len(f) }
248func (f fileInfoByLeaf) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
249func (f fileInfoByLeaf) Less(i, j int) bool {
250 di := strings.Count(f[i].path, "/")
251 dj := strings.Count(f[j].path, "/")
252 if di != dj {
253 return di < dj
254 }
Fumitoshi Ukaic39b1882015-06-16 16:29:08 +0900255 diri := filepath.Dir(f[i].path) + "/"
256 dirj := filepath.Dir(f[j].path) + "/"
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900257 if diri != dirj {
258 return diri < dirj
259 }
260 mdi := f[i].mode & os.ModeDir
261 mdj := f[j].mode & os.ModeDir
262 if mdi != mdj {
263 return mdi < mdj
264 }
265 return f[i].path < f[j].path
266}
267
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900268var errSkipDir = errors.New("skip dir")
269
270func (c *androidFindCacheT) walk(dir string, walkFn func(int, fileInfo) error) error {
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900271 i := sort.Search(len(c.files), func(i int) bool {
272 return c.files[i].path >= dir
273 })
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900274 Logf("android find in dir cache: %s i=%d/%d", dir, i, len(c.files))
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900275 start := i
276 var skipdirs []string
277Loop:
278 for i := start; i < len(c.files); i++ {
279 if c.files[i].path == dir {
280 err := walkFn(i, c.files[i])
281 if err != nil {
282 return err
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900283 }
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900284 continue
285 }
286 if !strings.HasPrefix(c.files[i].path, dir) {
287 Logf("android find in dir cache: %s end=%d/%d", dir, i, len(c.files))
288 return nil
289 }
290 if !strings.HasPrefix(c.files[i].path, dir+"/") {
291 continue
292 }
293 for _, skip := range skipdirs {
294 if strings.HasPrefix(c.files[i].path, skip+"/") {
295 continue Loop
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900296 }
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900297 }
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900298
299 err := walkFn(i, c.files[i])
300 if err == errSkipDir {
301 Logf("android find in skip dir: %s", c.files[i].path)
302 skipdirs = append(skipdirs, c.files[i].path)
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900303 continue
304 }
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900305 if err != nil {
306 return err
307 }
308 }
309 return nil
310}
311
312// pattern in repo/android/build/core/definitions.mk
313// find-subdir-assets
314// if [ -d $1 ] ; then cd $1 ; find ./ -not -name '.*' -and -type f -and -not -type l ; fi
315func (c *androidFindCacheT) findInDir(sw *ssvWriter, dir string) {
316 dir = filepath.Clean(dir)
317 Logf("android find in dir cache: %s", dir)
318 c.walk(dir, func(_ int, fi fileInfo) error {
319 // -not -name '.*'
320 if strings.HasPrefix(filepath.Base(fi.path), ".") {
321 return nil
322 }
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900323 // -type f and -not -type l
324 // regular type and not symlink
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900325 if !fi.mode.IsRegular() {
326 return nil
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900327 }
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900328 name := strings.TrimPrefix(fi.path, dir+"/")
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900329 name = "./" + name
330 sw.WriteString(name)
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900331 Logf("android find in dir cache: %s=> %s", dir, name)
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900332 return nil
333 })
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900334}
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900335
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900336// pattern in repo/android/build/core/definitions.mk
337// all-java-files-under
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900338// cd ${LOCAL_PATH} ; find -L $1 -name "*.java" -and -not -name ".*"
339// returns false if symlink is found.
340func (c *androidFindCacheT) findJavaInDir(sw *ssvWriter, chdir string, root string) bool {
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900341 chdir = filepath.Clean(chdir)
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900342 dir := filepath.Join(chdir, root)
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900343 Logf("android find java in dir cache: %s %s", chdir, root)
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900344 // check symlinks
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900345 var matches []int
346 err := c.walk(dir, func(i int, fi fileInfo) error {
347 if fi.mode&os.ModeSymlink == os.ModeSymlink {
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900348 Logf("android find java in dir cache: detect symlink %s %v", c.files[i].path, c.files[i].mode)
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900349 return fmt.Errorf("symlink %s", fi.path)
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900350 }
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900351 matches = append(matches, i)
352 return nil
353 })
354 if err != nil {
355 return false
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900356 }
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900357 // no symlinks
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900358 for _, i := range matches {
359 fi := c.files[i]
360 base := filepath.Base(fi.path)
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900361 // -name "*.java"
362 if filepath.Ext(base) != ".java" {
363 continue
364 }
365 // -not -name ".*"
366 if strings.HasPrefix(base, ".") {
367 continue
368 }
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900369 name := strings.TrimPrefix(fi.path, chdir+"/")
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900370 sw.WriteString(name)
371 Logf("android find java in dir cache: %s=> %s", dir, name)
372 }
373 return true
374}
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900375
376// pattern: in repo/android/build/core/base_rules.mk
377// java_resource_file_groups+= ...
378// cd ${TOP_DIR}${LOCAL_PATH}/${dir} && find . -type d -a -name ".svn" -prune \
379// -o -type f -a \! -name "*.java" -a \! -name "package.html" -a \! \
380// -name "overview.html" -a \! -name ".*.swp" -a \! -name ".DS_Store" \
381// -a \! -name "*~" -print )
382func (c *androidFindCacheT) findJavaResourceFileGroup(sw *ssvWriter, dir string) {
383 Logf("android find java resource in dir cache: %s", dir)
384 c.walk(filepath.Clean(dir), func(_ int, fi fileInfo) error {
385 // -type d -a -name ".svn" -prune
386 if fi.mode.IsDir() && filepath.Base(fi.path) == ".svn" {
387 return errSkipDir
388 }
389 // -type f
390 if !fi.mode.IsRegular() {
391 return nil
392 }
393 // ! -name "*.java" -a ! -name "package.html" -a
394 // ! -name "overview.html" -a ! -name ".*.swp" -a
395 // ! -name ".DS_Store" -a ! -name "*~"
396 base := filepath.Base(fi.path)
397 if filepath.Ext(base) == ".java" ||
398 base == "package.html" ||
399 base == "overview.html" ||
400 (strings.HasPrefix(base, ".") && strings.HasSuffix(base, ".swp")) ||
401 base == ".DS_Store" ||
402 strings.HasSuffix(base, "~") {
403 return nil
404 }
405 name := strings.TrimPrefix(fi.path, dir+"/")
406 name = "./" + name
407 sw.WriteString(name)
408 Logf("android find java resource in dir cache: %s=> %s", dir, name)
409 return nil
410 })
411}
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900412
413func (c *androidFindCacheT) findleaves(sw *ssvWriter, dir, name string, prunes []string, mindepth int) bool {
414 var dirs []string
415 topdepth := 1 + strings.Count(dir, "/")
416 dirs = append(dirs, dir)
417 for len(dirs) > 0 {
418 dir = filepath.Clean(dirs[0]) + "/"
419 dirs = dirs[1:]
420 if dir == "./" {
421 dir = ""
422 }
423 depth := strings.Count(dir, "/")
424 // Logf("android findleaves dir=%q depth=%d dirs=%q", dir, depth, dirs)
425 i := sort.Search(len(c.leaves), func(i int) bool {
426 di := strings.Count(c.leaves[i].path, "/")
427 if di != depth {
428 return di >= depth
429 }
Fumitoshi Ukaic39b1882015-06-16 16:29:08 +0900430 diri := filepath.Dir(c.leaves[i].path) + "/"
431 if diri != dir {
432 return diri >= dir
433 }
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900434 return c.leaves[i].path >= dir
435 })
436 Logf("android findleaves dir=%q i=%d/%d", dir, i, len(c.leaves))
437
438 Scandir:
439 for ; i < len(c.leaves); i++ {
440 if dir == "" && strings.Contains(c.leaves[i].path, "/") {
441 break
442 }
443 if !strings.HasPrefix(c.leaves[i].path, dir) {
444 break
445 }
446 if mindepth < 0 || depth >= topdepth+mindepth {
447 if !c.leaves[i].mode.IsDir() && filepath.Base(c.leaves[i].path) == name {
448 n := "./" + c.leaves[i].path
449 sw.WriteString(n)
450 Logf("android findleaves name=%s=> %s (depth=%d topdepth=%d mindepth=%d)", name, n, depth, topdepth, mindepth)
451 break Scandir
452 }
453 }
454 if c.leaves[i].mode.IsDir() {
455 dirs = append(dirs, c.leaves[i].path)
456 }
457 }
458 // Logf("android findleaves next dirs=%q", dirs)
459 }
460 Logf("android findleave done")
461 return true
462}