blob: 38e613fdda814a611c39ef420eca6a74102b53ca [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
Fumitoshi Ukai9042b992015-06-23 16:10:27 +090030type wildcardCacheT struct {
31 mu sync.Mutex
32 m map[string][]string
33}
34
35var wildcardCache = &wildcardCacheT{
36 m: make(map[string][]string),
37}
Fumitoshi Ukai358c68a2015-06-08 13:12:55 +090038
Fumitoshi Ukai9d959c32015-06-19 18:06:01 +090039func wildcardGlob(pat string) []string {
40 // TODO(ukai): use find cache for glob if exists.
41 pattern := filepath.Clean(pat)
42 if pattern != pat {
43 // For some reason, go's Glob normalizes
44 // foo/../bar to bar.
45 i := strings.IndexAny(pattern, "*?[")
46 if i < 0 {
47 // no wildcard. if any files matched with pattern,
48 // return pat.
49 _, err := os.Stat(pat)
50 if err != nil {
51 return nil
52 }
53 return []string{pat}
54 }
55 if strings.Contains(pattern[i+1:], "..") {
56 // We ask shell to expand a glob to avoid this.
57 cmdline := []string{"/bin/sh", "-c", "/bin/ls -d " + pat}
58 cmd := exec.Cmd{
59 Path: cmdline[0],
60 Args: cmdline,
61 }
62 // Ignore errors.
63 out, _ := cmd.Output()
64 ws := newWordScanner(out)
65 var files []string
66 for ws.Scan() {
67 files = append(files, string(ws.Bytes()))
68 }
69 return files
70 }
71 // prefix + meta + suffix, and suffix doesn't have '..'
72 prefix := pattern[:i]
73 i = strings.IndexAny(pat, "*?[")
74 if i < 0 {
75 panic(fmt.Sprintf("wildcard metachar mismatch? pattern=%q pat=%q", pattern, pat))
76 }
77 oprefix := pat[:i]
78 matched, err := filepath.Glob(pattern)
79 if err != nil {
80 panic(err)
81 }
82 var files []string
83 for _, m := range matched {
84 file := oprefix + strings.TrimPrefix(m, prefix)
85 _, err := os.Stat(file)
86 if err != nil {
87 continue
88 }
89 files = append(files, file)
90 }
91 return files
92 }
93 files, err := filepath.Glob(pat)
94 if err != nil {
95 panic(err)
96 }
97 return files
98}
99
Fumitoshi Ukai358c68a2015-06-08 13:12:55 +0900100func wildcard(sw *ssvWriter, pat string) {
101 if useWildcardCache {
102 // TODO(ukai): make sure it didn't chdir?
Fumitoshi Ukai9042b992015-06-23 16:10:27 +0900103 wildcardCache.mu.Lock()
104 files, ok := wildcardCache.m[pat]
105 wildcardCache.mu.Unlock()
106 if ok {
Fumitoshi Ukai358c68a2015-06-08 13:12:55 +0900107 for _, file := range files {
108 sw.WriteString(file)
109 }
110 return
111 }
112 }
Fumitoshi Ukai9d959c32015-06-19 18:06:01 +0900113 files := wildcardGlob(pat)
Fumitoshi Ukai358c68a2015-06-08 13:12:55 +0900114 for _, file := range files {
115 sw.WriteString(file)
116 }
117 if useWildcardCache {
Fumitoshi Ukai9042b992015-06-23 16:10:27 +0900118 wildcardCache.mu.Lock()
119 wildcardCache.m[pat] = files
120 wildcardCache.mu.Unlock()
Fumitoshi Ukai358c68a2015-06-08 13:12:55 +0900121 }
122}
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900123
124type fileInfo struct {
125 path string
126 mode os.FileMode
127}
128
129type androidFindCacheT struct {
130 once sync.Once
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900131 filesch chan []fileInfo
132 leavesch chan []fileInfo
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900133 files []fileInfo
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900134 leaves []fileInfo
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900135 scanTime time.Duration
136}
137
138var (
139 androidFindCache androidFindCacheT
140)
141
142func (c *androidFindCacheT) ready() bool {
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900143 if c.files != nil {
144 return true
145 }
146 select {
147 case c.files = <-c.filesch:
148 }
149 return c.files != nil
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900150}
151
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900152func (c *androidFindCacheT) leavesReady() bool {
153 if c.leaves != nil {
154 return true
155 }
156 select {
157 case c.leaves = <-c.leavesch:
158 }
159 return c.leaves != nil
160}
161
162func (c *androidFindCacheT) init(prunes, leaves []string) {
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900163 c.once.Do(func() {
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900164 c.filesch = make(chan []fileInfo, 1)
165 c.leavesch = make(chan []fileInfo, 1)
166 go c.start(prunes, leaves)
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900167 })
168}
169
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900170func (c *androidFindCacheT) start(prunes, leafNames []string) {
171 Logf("find cache init: prunes=%q leafNames=%q", prunes, leafNames)
172 te := traceEvent.begin("findcache", literal("init"), traceEventFindCache)
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900173 defer func() {
Fumitoshi Ukaif543f4d2015-06-15 15:21:47 +0900174 traceEvent.end(te)
175 c.scanTime = time.Since(te.t)
Shinichiro Hamajib6ad7da2015-06-10 16:49:15 +0900176 LogStats("android find cache scan: %v", c.scanTime)
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900177 }()
178
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900179 dirs := make(chan string, 32)
180 filech := make(chan fileInfo, 1000)
181 leafch := make(chan fileInfo, 1000)
182 var wg sync.WaitGroup
183 numWorker := runtime.NumCPU() - 1
184 wg.Add(numWorker)
185 for i := 0; i < numWorker; i++ {
186 go func() {
187 defer wg.Done()
188 for dir := range dirs {
189 err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
190 if info.IsDir() {
191 for _, prune := range prunes {
192 if info.Name() == prune {
193 Logf("find cache prune: %s", path)
194 return filepath.SkipDir
195 }
196 }
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900197 }
198 filech <- fileInfo{
199 path: path,
200 mode: info.Mode(),
201 }
202 for _, leaf := range leafNames {
203 if info.Name() == leaf {
204 Logf("find cache leaf: %s", path)
205 leafch <- fileInfo{
206 path: path,
207 mode: info.Mode(),
208 }
209 break
210 }
211 }
212 return nil
213 })
214 if err != nil && err != filepath.SkipDir {
215 Logf("error in adnroid find cache: %v", err)
216 close(c.filesch)
217 close(c.leavesch)
218 return
Fumitoshi Ukai0daac1f2015-06-11 11:56:50 +0900219 }
220 }
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900221 }()
222 }
223
224 go func() {
Fumitoshi Ukai040271f2015-06-18 11:04:46 +0900225 dirs := make(map[string]bool)
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900226 leavesTe := traceEvent.begin("findcache", literal("leaves"), traceEventFindCacheLeaves)
227 var leaves []fileInfo
Fumitoshi Ukai040271f2015-06-18 11:04:46 +0900228 nfiles := 0
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900229 for leaf := range leafch {
230 leaves = append(leaves, leaf)
Fumitoshi Ukai040271f2015-06-18 11:04:46 +0900231 nfiles++
232 for dir := filepath.Dir(leaf.path); dir != "."; dir = filepath.Dir(dir) {
233 if dirs[dir] {
234 break
235 }
236 leaves = append(leaves, fileInfo{
237 path: dir,
238 mode: leaf.mode | os.ModeDir,
239 })
240 dirs[dir] = true
241 }
Fumitoshi Ukai0daac1f2015-06-11 11:56:50 +0900242 }
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900243 sort.Sort(fileInfoByLeaf(leaves))
244 c.leavesch <- leaves
245 traceEvent.end(leavesTe)
Fumitoshi Ukai040271f2015-06-18 11:04:46 +0900246 LogStats("%d leaves %d dirs in find cache", nfiles, len(dirs))
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900247 for i, leaf := range leaves {
248 Logf("android findleaves cache: %d: %s %v", i, leaf.path, leaf.mode)
249 }
250 }()
251
252 go func() {
253 filesTe := traceEvent.begin("findcache", literal("files"), traceEventFindCacheFiles)
254 var files []fileInfo
255 for file := range filech {
256 files = append(files, file)
257 }
258 sort.Sort(fileInfoByName(files))
259 c.filesch <- files
260 traceEvent.end(filesTe)
Fumitoshi Ukai040271f2015-06-18 11:04:46 +0900261 LogStats("%d files in find cache", len(files))
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900262 for i, fi := range files {
263 Logf("android find cache: %d: %s %v", i, fi.path, fi.mode)
264 }
265 }()
266
267 curdir, err := os.Open(".")
268 if err != nil {
269 Logf("open . failed: %v", err)
270 close(c.filesch)
271 close(c.leavesch)
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900272 return
273 }
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900274 names, err := curdir.Readdirnames(-1)
275 if err != nil {
276 Logf("readdir . failed: %v", err)
277 close(c.filesch)
278 close(c.leavesch)
279 return
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900280 }
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900281 curdir.Close()
282
283 for _, name := range names {
284 dirs <- name
285 }
286 close(dirs)
287 wg.Wait()
288 close(filech)
289 close(leafch)
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900290}
291
292type fileInfoByName []fileInfo
293
294func (f fileInfoByName) Len() int { return len(f) }
295func (f fileInfoByName) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
296func (f fileInfoByName) Less(i, j int) bool {
297 return f[i].path < f[j].path
298}
299
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900300type fileInfoByLeaf []fileInfo
301
302func (f fileInfoByLeaf) Len() int { return len(f) }
303func (f fileInfoByLeaf) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
304func (f fileInfoByLeaf) Less(i, j int) bool {
305 di := strings.Count(f[i].path, "/")
306 dj := strings.Count(f[j].path, "/")
307 if di != dj {
308 return di < dj
309 }
Fumitoshi Ukaic39b1882015-06-16 16:29:08 +0900310 diri := filepath.Dir(f[i].path) + "/"
311 dirj := filepath.Dir(f[j].path) + "/"
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900312 if diri != dirj {
313 return diri < dirj
314 }
315 mdi := f[i].mode & os.ModeDir
316 mdj := f[j].mode & os.ModeDir
317 if mdi != mdj {
318 return mdi < mdj
319 }
320 return f[i].path < f[j].path
321}
322
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900323var errSkipDir = errors.New("skip dir")
324
325func (c *androidFindCacheT) walk(dir string, walkFn func(int, fileInfo) error) error {
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900326 i := sort.Search(len(c.files), func(i int) bool {
327 return c.files[i].path >= dir
328 })
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900329 Logf("android find in dir cache: %s i=%d/%d", dir, i, len(c.files))
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900330 start := i
331 var skipdirs []string
332Loop:
333 for i := start; i < len(c.files); i++ {
334 if c.files[i].path == dir {
335 err := walkFn(i, c.files[i])
336 if err != nil {
337 return err
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900338 }
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900339 continue
340 }
341 if !strings.HasPrefix(c.files[i].path, dir) {
342 Logf("android find in dir cache: %s end=%d/%d", dir, i, len(c.files))
343 return nil
344 }
345 if !strings.HasPrefix(c.files[i].path, dir+"/") {
346 continue
347 }
348 for _, skip := range skipdirs {
349 if strings.HasPrefix(c.files[i].path, skip+"/") {
350 continue Loop
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900351 }
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900352 }
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900353
354 err := walkFn(i, c.files[i])
355 if err == errSkipDir {
356 Logf("android find in skip dir: %s", c.files[i].path)
357 skipdirs = append(skipdirs, c.files[i].path)
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900358 continue
359 }
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900360 if err != nil {
361 return err
362 }
363 }
364 return nil
365}
366
367// pattern in repo/android/build/core/definitions.mk
368// find-subdir-assets
369// if [ -d $1 ] ; then cd $1 ; find ./ -not -name '.*' -and -type f -and -not -type l ; fi
370func (c *androidFindCacheT) findInDir(sw *ssvWriter, dir string) {
371 dir = filepath.Clean(dir)
372 Logf("android find in dir cache: %s", dir)
373 c.walk(dir, func(_ int, fi fileInfo) error {
374 // -not -name '.*'
375 if strings.HasPrefix(filepath.Base(fi.path), ".") {
376 return nil
377 }
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900378 // -type f and -not -type l
379 // regular type and not symlink
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900380 if !fi.mode.IsRegular() {
381 return nil
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900382 }
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900383 name := strings.TrimPrefix(fi.path, dir+"/")
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900384 name = "./" + name
385 sw.WriteString(name)
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900386 Logf("android find in dir cache: %s=> %s", dir, name)
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900387 return nil
388 })
Fumitoshi Ukai106fb792015-06-09 10:37:35 +0900389}
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900390
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900391// pattern in repo/android/build/core/definitions.mk
Fumitoshi Ukaib234e272015-06-18 13:14:16 +0900392// all-java-files-under etc
393// cd ${LOCAL_PATH} ; find -L $1 -name "*<ext>" -and -not -name ".*"
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900394// returns false if symlink is found.
Fumitoshi Ukaib234e272015-06-18 13:14:16 +0900395func (c *androidFindCacheT) findExtFilesUnder(sw *ssvWriter, chdir, root, ext string) bool {
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900396 chdir = filepath.Clean(chdir)
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900397 dir := filepath.Join(chdir, root)
Fumitoshi Ukaib234e272015-06-18 13:14:16 +0900398 Logf("android find %s in dir cache: %s %s", ext, chdir, root)
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900399 // check symlinks
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900400 var matches []int
401 err := c.walk(dir, func(i int, fi fileInfo) error {
402 if fi.mode&os.ModeSymlink == os.ModeSymlink {
Fumitoshi Ukaib234e272015-06-18 13:14:16 +0900403 Logf("android find %s in dir cache: detect symlink %s %v", ext, c.files[i].path, c.files[i].mode)
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900404 return fmt.Errorf("symlink %s", fi.path)
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900405 }
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900406 matches = append(matches, i)
407 return nil
408 })
409 if err != nil {
410 return false
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900411 }
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900412 // no symlinks
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900413 for _, i := range matches {
414 fi := c.files[i]
415 base := filepath.Base(fi.path)
Fumitoshi Ukaib234e272015-06-18 13:14:16 +0900416 // -name "*<ext>"
417 if filepath.Ext(base) != ext {
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900418 continue
419 }
420 // -not -name ".*"
421 if strings.HasPrefix(base, ".") {
422 continue
423 }
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900424 name := strings.TrimPrefix(fi.path, chdir+"/")
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900425 sw.WriteString(name)
Fumitoshi Ukaib234e272015-06-18 13:14:16 +0900426 Logf("android find %s in dir cache: %s=> %s", ext, dir, name)
Fumitoshi Ukai1a77c8c2015-06-09 12:56:22 +0900427 }
428 return true
429}
Fumitoshi Ukai7adc0f52015-06-09 15:34:26 +0900430
431// pattern: in repo/android/build/core/base_rules.mk
432// java_resource_file_groups+= ...
433// cd ${TOP_DIR}${LOCAL_PATH}/${dir} && find . -type d -a -name ".svn" -prune \
434// -o -type f -a \! -name "*.java" -a \! -name "package.html" -a \! \
435// -name "overview.html" -a \! -name ".*.swp" -a \! -name ".DS_Store" \
436// -a \! -name "*~" -print )
437func (c *androidFindCacheT) findJavaResourceFileGroup(sw *ssvWriter, dir string) {
438 Logf("android find java resource in dir cache: %s", dir)
439 c.walk(filepath.Clean(dir), func(_ int, fi fileInfo) error {
440 // -type d -a -name ".svn" -prune
441 if fi.mode.IsDir() && filepath.Base(fi.path) == ".svn" {
442 return errSkipDir
443 }
444 // -type f
445 if !fi.mode.IsRegular() {
446 return nil
447 }
448 // ! -name "*.java" -a ! -name "package.html" -a
449 // ! -name "overview.html" -a ! -name ".*.swp" -a
450 // ! -name ".DS_Store" -a ! -name "*~"
451 base := filepath.Base(fi.path)
452 if filepath.Ext(base) == ".java" ||
453 base == "package.html" ||
454 base == "overview.html" ||
455 (strings.HasPrefix(base, ".") && strings.HasSuffix(base, ".swp")) ||
456 base == ".DS_Store" ||
457 strings.HasSuffix(base, "~") {
458 return nil
459 }
460 name := strings.TrimPrefix(fi.path, dir+"/")
461 name = "./" + name
462 sw.WriteString(name)
463 Logf("android find java resource in dir cache: %s=> %s", dir, name)
464 return nil
465 })
466}
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900467
468func (c *androidFindCacheT) findleaves(sw *ssvWriter, dir, name string, prunes []string, mindepth int) bool {
Fumitoshi Ukai40e35f52015-06-16 16:33:04 +0900469 var found []string
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900470 var dirs []string
471 topdepth := 1 + strings.Count(dir, "/")
472 dirs = append(dirs, dir)
473 for len(dirs) > 0 {
474 dir = filepath.Clean(dirs[0]) + "/"
475 dirs = dirs[1:]
476 if dir == "./" {
477 dir = ""
478 }
479 depth := strings.Count(dir, "/")
480 // Logf("android findleaves dir=%q depth=%d dirs=%q", dir, depth, dirs)
481 i := sort.Search(len(c.leaves), func(i int) bool {
482 di := strings.Count(c.leaves[i].path, "/")
483 if di != depth {
484 return di >= depth
485 }
Fumitoshi Ukaic39b1882015-06-16 16:29:08 +0900486 diri := filepath.Dir(c.leaves[i].path) + "/"
487 if diri != dir {
488 return diri >= dir
489 }
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900490 return c.leaves[i].path >= dir
491 })
492 Logf("android findleaves dir=%q i=%d/%d", dir, i, len(c.leaves))
493
494 Scandir:
495 for ; i < len(c.leaves); i++ {
496 if dir == "" && strings.Contains(c.leaves[i].path, "/") {
497 break
498 }
499 if !strings.HasPrefix(c.leaves[i].path, dir) {
500 break
501 }
502 if mindepth < 0 || depth >= topdepth+mindepth {
503 if !c.leaves[i].mode.IsDir() && filepath.Base(c.leaves[i].path) == name {
504 n := "./" + c.leaves[i].path
Fumitoshi Ukai40e35f52015-06-16 16:33:04 +0900505 found = append(found, n)
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900506 Logf("android findleaves name=%s=> %s (depth=%d topdepth=%d mindepth=%d)", name, n, depth, topdepth, mindepth)
507 break Scandir
508 }
509 }
510 if c.leaves[i].mode.IsDir() {
511 dirs = append(dirs, c.leaves[i].path)
512 }
513 }
514 // Logf("android findleaves next dirs=%q", dirs)
515 }
516 Logf("android findleave done")
Fumitoshi Ukai40e35f52015-06-16 16:33:04 +0900517 sort.Strings(found)
518 for _, f := range found {
519 sw.WriteString(f)
520 }
Fumitoshi Ukai83410132015-06-15 14:50:07 +0900521 return true
522}