blob: e07346b7856b40bfef8055229fd82dcaf1fe0272 [file] [log] [blame]
// Copyright 2015 Google Inc. All rights reserved
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"sort"
"strings"
"sync"
"time"
)
var wildcardCache = make(map[string][]string)
func wildcard(sw *ssvWriter, pat string) {
if useWildcardCache {
// TODO(ukai): make sure it didn't chdir?
if files, ok := wildcardCache[pat]; ok {
for _, file := range files {
sw.WriteString(file)
}
return
}
}
if strings.Contains(pat, "..") {
// For some reason, go's Glob normalizes
// foo/../bar to bar. We ask shell to expand
// a glob to avoid this.
cmdline := []string{"/bin/sh", "-c", "/bin/ls -d " + pat}
cmd := exec.Cmd{
Path: cmdline[0],
Args: cmdline,
}
// Ignore errors.
out, _ := cmd.Output()
if len(trimSpaceBytes(out)) > 0 {
out = formatCommandOutput(out)
sw.Write(out)
}
if useWildcardCache {
ws := newWordScanner(out)
var files []string
for ws.Scan() {
files = append(files, string(ws.Bytes()))
}
wildcardCache[pat] = files
}
return
}
files, err := filepath.Glob(pat)
if err != nil {
panic(err)
}
for _, file := range files {
sw.WriteString(file)
}
if useWildcardCache {
wildcardCache[pat] = files
}
}
type fileInfo struct {
path string
mode os.FileMode
}
type androidFindCacheT struct {
once sync.Once
filesch chan []fileInfo
leavesch chan []fileInfo
files []fileInfo
leaves []fileInfo
scanTime time.Duration
}
var (
androidFindCache androidFindCacheT
)
func (c *androidFindCacheT) ready() bool {
if c.files != nil {
return true
}
select {
case c.files = <-c.filesch:
}
return c.files != nil
}
func (c *androidFindCacheT) leavesReady() bool {
if c.leaves != nil {
return true
}
select {
case c.leaves = <-c.leavesch:
}
return c.leaves != nil
}
func (c *androidFindCacheT) init(prunes, leaves []string) {
c.once.Do(func() {
c.filesch = make(chan []fileInfo, 1)
c.leavesch = make(chan []fileInfo, 1)
go c.start(prunes, leaves)
})
}
func (c *androidFindCacheT) start(prunes, leafNames []string) {
Logf("find cache init: prunes=%q leafNames=%q", prunes, leafNames)
te := traceEvent.begin("findcache", literal("init"), traceEventFindCache)
defer func() {
traceEvent.end(te)
c.scanTime = time.Since(te.t)
LogStats("android find cache scan: %v", c.scanTime)
}()
dirs := make(chan string, 32)
filech := make(chan fileInfo, 1000)
leafch := make(chan fileInfo, 1000)
var wg sync.WaitGroup
numWorker := runtime.NumCPU() - 1
wg.Add(numWorker)
for i := 0; i < numWorker; i++ {
go func() {
defer wg.Done()
for dir := range dirs {
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
for _, prune := range prunes {
if info.Name() == prune {
Logf("find cache prune: %s", path)
return filepath.SkipDir
}
}
}
filech <- fileInfo{
path: path,
mode: info.Mode(),
}
for _, leaf := range leafNames {
if info.Name() == leaf {
Logf("find cache leaf: %s", path)
leafch <- fileInfo{
path: path,
mode: info.Mode(),
}
break
}
}
return nil
})
if err != nil && err != filepath.SkipDir {
Logf("error in adnroid find cache: %v", err)
close(c.filesch)
close(c.leavesch)
return
}
}
}()
}
go func() {
dirs := make(map[string]bool)
leavesTe := traceEvent.begin("findcache", literal("leaves"), traceEventFindCacheLeaves)
var leaves []fileInfo
nfiles := 0
for leaf := range leafch {
leaves = append(leaves, leaf)
nfiles++
for dir := filepath.Dir(leaf.path); dir != "."; dir = filepath.Dir(dir) {
if dirs[dir] {
break
}
leaves = append(leaves, fileInfo{
path: dir,
mode: leaf.mode | os.ModeDir,
})
dirs[dir] = true
}
}
sort.Sort(fileInfoByLeaf(leaves))
c.leavesch <- leaves
traceEvent.end(leavesTe)
LogStats("%d leaves %d dirs in find cache", nfiles, len(dirs))
for i, leaf := range leaves {
Logf("android findleaves cache: %d: %s %v", i, leaf.path, leaf.mode)
}
}()
go func() {
filesTe := traceEvent.begin("findcache", literal("files"), traceEventFindCacheFiles)
var files []fileInfo
for file := range filech {
files = append(files, file)
}
sort.Sort(fileInfoByName(files))
c.filesch <- files
traceEvent.end(filesTe)
LogStats("%d files in find cache", len(files))
for i, fi := range files {
Logf("android find cache: %d: %s %v", i, fi.path, fi.mode)
}
}()
curdir, err := os.Open(".")
if err != nil {
Logf("open . failed: %v", err)
close(c.filesch)
close(c.leavesch)
return
}
names, err := curdir.Readdirnames(-1)
if err != nil {
Logf("readdir . failed: %v", err)
close(c.filesch)
close(c.leavesch)
return
}
curdir.Close()
for _, name := range names {
dirs <- name
}
close(dirs)
wg.Wait()
close(filech)
close(leafch)
}
type fileInfoByName []fileInfo
func (f fileInfoByName) Len() int { return len(f) }
func (f fileInfoByName) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
func (f fileInfoByName) Less(i, j int) bool {
return f[i].path < f[j].path
}
type fileInfoByLeaf []fileInfo
func (f fileInfoByLeaf) Len() int { return len(f) }
func (f fileInfoByLeaf) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
func (f fileInfoByLeaf) Less(i, j int) bool {
di := strings.Count(f[i].path, "/")
dj := strings.Count(f[j].path, "/")
if di != dj {
return di < dj
}
diri := filepath.Dir(f[i].path) + "/"
dirj := filepath.Dir(f[j].path) + "/"
if diri != dirj {
return diri < dirj
}
mdi := f[i].mode & os.ModeDir
mdj := f[j].mode & os.ModeDir
if mdi != mdj {
return mdi < mdj
}
return f[i].path < f[j].path
}
var errSkipDir = errors.New("skip dir")
func (c *androidFindCacheT) walk(dir string, walkFn func(int, fileInfo) error) error {
i := sort.Search(len(c.files), func(i int) bool {
return c.files[i].path >= dir
})
Logf("android find in dir cache: %s i=%d/%d", dir, i, len(c.files))
start := i
var skipdirs []string
Loop:
for i := start; i < len(c.files); i++ {
if c.files[i].path == dir {
err := walkFn(i, c.files[i])
if err != nil {
return err
}
continue
}
if !strings.HasPrefix(c.files[i].path, dir) {
Logf("android find in dir cache: %s end=%d/%d", dir, i, len(c.files))
return nil
}
if !strings.HasPrefix(c.files[i].path, dir+"/") {
continue
}
for _, skip := range skipdirs {
if strings.HasPrefix(c.files[i].path, skip+"/") {
continue Loop
}
}
err := walkFn(i, c.files[i])
if err == errSkipDir {
Logf("android find in skip dir: %s", c.files[i].path)
skipdirs = append(skipdirs, c.files[i].path)
continue
}
if err != nil {
return err
}
}
return nil
}
// pattern in repo/android/build/core/definitions.mk
// find-subdir-assets
// if [ -d $1 ] ; then cd $1 ; find ./ -not -name '.*' -and -type f -and -not -type l ; fi
func (c *androidFindCacheT) findInDir(sw *ssvWriter, dir string) {
dir = filepath.Clean(dir)
Logf("android find in dir cache: %s", dir)
c.walk(dir, func(_ int, fi fileInfo) error {
// -not -name '.*'
if strings.HasPrefix(filepath.Base(fi.path), ".") {
return nil
}
// -type f and -not -type l
// regular type and not symlink
if !fi.mode.IsRegular() {
return nil
}
name := strings.TrimPrefix(fi.path, dir+"/")
name = "./" + name
sw.WriteString(name)
Logf("android find in dir cache: %s=> %s", dir, name)
return nil
})
}
// pattern in repo/android/build/core/definitions.mk
// all-java-files-under
// cd ${LOCAL_PATH} ; find -L $1 -name "*.java" -and -not -name ".*"
// returns false if symlink is found.
func (c *androidFindCacheT) findJavaInDir(sw *ssvWriter, chdir string, root string) bool {
chdir = filepath.Clean(chdir)
dir := filepath.Join(chdir, root)
Logf("android find java in dir cache: %s %s", chdir, root)
// check symlinks
var matches []int
err := c.walk(dir, func(i int, fi fileInfo) error {
if fi.mode&os.ModeSymlink == os.ModeSymlink {
Logf("android find java in dir cache: detect symlink %s %v", c.files[i].path, c.files[i].mode)
return fmt.Errorf("symlink %s", fi.path)
}
matches = append(matches, i)
return nil
})
if err != nil {
return false
}
// no symlinks
for _, i := range matches {
fi := c.files[i]
base := filepath.Base(fi.path)
// -name "*.java"
if filepath.Ext(base) != ".java" {
continue
}
// -not -name ".*"
if strings.HasPrefix(base, ".") {
continue
}
name := strings.TrimPrefix(fi.path, chdir+"/")
sw.WriteString(name)
Logf("android find java in dir cache: %s=> %s", dir, name)
}
return true
}
// pattern: in repo/android/build/core/base_rules.mk
// java_resource_file_groups+= ...
// cd ${TOP_DIR}${LOCAL_PATH}/${dir} && find . -type d -a -name ".svn" -prune \
// -o -type f -a \! -name "*.java" -a \! -name "package.html" -a \! \
// -name "overview.html" -a \! -name ".*.swp" -a \! -name ".DS_Store" \
// -a \! -name "*~" -print )
func (c *androidFindCacheT) findJavaResourceFileGroup(sw *ssvWriter, dir string) {
Logf("android find java resource in dir cache: %s", dir)
c.walk(filepath.Clean(dir), func(_ int, fi fileInfo) error {
// -type d -a -name ".svn" -prune
if fi.mode.IsDir() && filepath.Base(fi.path) == ".svn" {
return errSkipDir
}
// -type f
if !fi.mode.IsRegular() {
return nil
}
// ! -name "*.java" -a ! -name "package.html" -a
// ! -name "overview.html" -a ! -name ".*.swp" -a
// ! -name ".DS_Store" -a ! -name "*~"
base := filepath.Base(fi.path)
if filepath.Ext(base) == ".java" ||
base == "package.html" ||
base == "overview.html" ||
(strings.HasPrefix(base, ".") && strings.HasSuffix(base, ".swp")) ||
base == ".DS_Store" ||
strings.HasSuffix(base, "~") {
return nil
}
name := strings.TrimPrefix(fi.path, dir+"/")
name = "./" + name
sw.WriteString(name)
Logf("android find java resource in dir cache: %s=> %s", dir, name)
return nil
})
}
func (c *androidFindCacheT) findleaves(sw *ssvWriter, dir, name string, prunes []string, mindepth int) bool {
var found []string
var dirs []string
topdepth := 1 + strings.Count(dir, "/")
dirs = append(dirs, dir)
for len(dirs) > 0 {
dir = filepath.Clean(dirs[0]) + "/"
dirs = dirs[1:]
if dir == "./" {
dir = ""
}
depth := strings.Count(dir, "/")
// Logf("android findleaves dir=%q depth=%d dirs=%q", dir, depth, dirs)
i := sort.Search(len(c.leaves), func(i int) bool {
di := strings.Count(c.leaves[i].path, "/")
if di != depth {
return di >= depth
}
diri := filepath.Dir(c.leaves[i].path) + "/"
if diri != dir {
return diri >= dir
}
return c.leaves[i].path >= dir
})
Logf("android findleaves dir=%q i=%d/%d", dir, i, len(c.leaves))
Scandir:
for ; i < len(c.leaves); i++ {
if dir == "" && strings.Contains(c.leaves[i].path, "/") {
break
}
if !strings.HasPrefix(c.leaves[i].path, dir) {
break
}
if mindepth < 0 || depth >= topdepth+mindepth {
if !c.leaves[i].mode.IsDir() && filepath.Base(c.leaves[i].path) == name {
n := "./" + c.leaves[i].path
found = append(found, n)
Logf("android findleaves name=%s=> %s (depth=%d topdepth=%d mindepth=%d)", name, n, depth, topdepth, mindepth)
break Scandir
}
}
if c.leaves[i].mode.IsDir() {
dirs = append(dirs, c.leaves[i].path)
}
}
// Logf("android findleaves next dirs=%q", dirs)
}
Logf("android findleave done")
sort.Strings(found)
for _, f := range found {
sw.WriteString(f)
}
return true
}