blob: 1653b3e572dfec51220874394522b1e7e234ac27 [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 kati
import (
"bytes"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"sort"
"strings"
"sync"
"time"
"github.com/golang/glog"
)
type wildcardCacheT struct {
mu sync.Mutex
m map[string][]string
}
var wildcardCache = &wildcardCacheT{
m: make(map[string][]string),
}
func wildcardUnescape(pat string) string {
var buf bytes.Buffer
for i := 0; i < len(pat); i++ {
if pat[i] == '\\' && i+1 < len(pat) {
switch pat[i+1] {
case '*', '?', '[', '\\':
writeByte(&buf, pat[i])
default:
i++
}
}
writeByte(&buf, pat[i])
}
return buf.String()
}
func wildcardGlob(pat string) ([]string, error) {
// TODO(ukai): use find cache for glob if exists.
pat = wildcardUnescape(pat)
pattern := filepath.Clean(pat)
if pattern != pat {
// For some reason, go's Glob normalizes
// foo/../bar to bar.
i := strings.IndexAny(pattern, "*?[")
if i < 0 {
// no wildcard. if any files matched with pattern,
// return pat.
_, err := os.Stat(pat)
if err != nil {
return nil, nil
}
return []string{pat}, nil
}
if strings.Contains(pattern[i+1:], "..") {
// 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()
ws := newWordScanner(out)
var files []string
for ws.Scan() {
files = append(files, string(ws.Bytes()))
}
return files, nil
}
// prefix + meta + suffix, and suffix doesn't have '..'
prefix := pattern[:i]
i = strings.IndexAny(pat, "*?[")
if i < 0 {
return nil, fmt.Errorf("wildcard metachar mismatch? pattern=%q pat=%q", pattern, pat)
}
oprefix := pat[:i]
matched, err := filepath.Glob(pattern)
if err != nil {
return nil, err
}
var files []string
for _, m := range matched {
file := oprefix + strings.TrimPrefix(m, prefix)
_, err := os.Stat(file)
if err != nil {
continue
}
files = append(files, file)
}
return files, nil
}
return filepath.Glob(pat)
}
func wildcard(w evalWriter, pat string) error {
if UseWildcardCache {
// TODO(ukai): make sure it didn't chdir?
wildcardCache.mu.Lock()
files, ok := wildcardCache.m[pat]
wildcardCache.mu.Unlock()
if ok {
for _, file := range files {
w.writeWordString(file)
}
return nil
}
}
files, err := wildcardGlob(pat)
if err != nil {
return err
}
for _, file := range files {
w.writeWordString(file)
}
if UseWildcardCache {
wildcardCache.mu.Lock()
wildcardCache.m[pat] = files
wildcardCache.mu.Unlock()
}
return nil
}
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
androidDefaultLeafNames = []string{"CleanSpec.mk", "Android.mk"}
)
// AndroidFindCacheInit initializes find cache for android build.
func AndroidFindCacheInit(prunes, leafNames []string) {
if !UseFindCache {
return
}
if leafNames != nil {
androidDefaultLeafNames = leafNames
}
androidFindCache.init(prunes)
}
func (c *androidFindCacheT) ready() bool {
if !UseFindCache {
return false
}
if c.files != nil {
return true
}
select {
case c.files = <-c.filesch:
}
return c.files != nil
}
func (c *androidFindCacheT) leavesReady() bool {
if !UseFindCache {
return false
}
if c.leaves != nil {
return true
}
select {
case c.leaves = <-c.leavesch:
}
return c.leaves != nil
}
func (c *androidFindCacheT) init(prunes []string) {
if !UseFindCache {
return
}
c.once.Do(func() {
c.filesch = make(chan []fileInfo, 1)
c.leavesch = make(chan []fileInfo, 1)
go c.start(prunes, androidDefaultLeafNames)
})
}
func (c *androidFindCacheT) start(prunes, leafNames []string) {
glog.Infof("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 {
glog.V(1).Infof("find cache prune: %s", path)
return filepath.SkipDir
}
}
}
filech <- fileInfo{
path: path,
mode: info.Mode(),
}
for _, leaf := range leafNames {
if info.Name() == leaf {
glog.V(1).Infof("find cache leaf: %s", path)
leafch <- fileInfo{
path: path,
mode: info.Mode(),
}
break
}
}
return nil
})
if err != nil && err != filepath.SkipDir {
glog.Warningf("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))
if !glog.V(1) {
return
}
for i, leaf := range leaves {
glog.Infof("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))
if !glog.V(1) {
return
}
for i, fi := range files {
glog.Infof("android find cache: %d: %s %v", i, fi.path, fi.mode)
}
}()
curdir, err := os.Open(".")
if err != nil {
glog.Warningf("open . failed: %v", err)
close(c.filesch)
close(c.leavesch)
return
}
names, err := curdir.Readdirnames(-1)
if err != nil {
glog.Warningf("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
})
glog.V(1).Infof("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) {
glog.V(1).Infof("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 {
glog.V(1).Infof("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(w evalWriter, dir string) {
dir = filepath.Clean(dir)
glog.V(1).Infof("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
w.writeWordString(name)
glog.V(1).Infof("android find in dir cache: %s=> %s", dir, name)
return nil
})
}
// pattern in repo/android/build/core/definitions.mk
// all-java-files-under etc
// cd ${LOCAL_PATH} ; find -L $1 -name "*<ext>" -and -not -name ".*"
// returns false if symlink is found.
func (c *androidFindCacheT) findExtFilesUnder(w evalWriter, chdir, root, ext string) bool {
chdir = filepath.Clean(chdir)
dir := filepath.Join(chdir, root)
glog.V(1).Infof("android find %s in dir cache: %s %s", ext, chdir, root)
// check symlinks
var matches []int
err := c.walk(dir, func(i int, fi fileInfo) error {
if fi.mode&os.ModeSymlink == os.ModeSymlink {
glog.Warningf("android find %s in dir cache: detect symlink %s %v", ext, 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 "*<ext>"
if filepath.Ext(base) != ext {
continue
}
// -not -name ".*"
if strings.HasPrefix(base, ".") {
continue
}
name := strings.TrimPrefix(fi.path, chdir+"/")
w.writeWordString(name)
glog.V(1).Infof("android find %s in dir cache: %s=> %s", ext, 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(w evalWriter, dir string) {
glog.V(1).Infof("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
w.writeWordString(name)
glog.V(1).Infof("android find java resource in dir cache: %s=> %s", dir, name)
return nil
})
}
func (c *androidFindCacheT) findleaves(w evalWriter, dir, name string, prunes []string, mindepth int) bool {
var found []string
var dirs []string
dir = filepath.Clean(dir)
topdepth := 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, "/")
// glog.V(1).Infof("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
})
glog.V(1).Infof("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)
glog.V(1).Infof("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)
}
}
// glog.V(1).Infof("android findleaves next dirs=%q", dirs)
}
glog.V(1).Infof("android findleave done")
sort.Strings(found)
for _, f := range found {
w.writeWordString(f)
}
return true
}