blob: 6aa6b90e97e41b9c396a2a2b1a201ed080a7022c [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 (
"fmt"
"strings"
"time"
"github.com/golang/glog"
)
var shBuiltins = []struct {
name string
pattern expr
compact func(*funcShell, []Value) Value
}{
{
name: "android:rot13",
// in repo/android/build/core/definisions.mk
// echo $(1) | tr 'a-zA-Z' 'n-za-mN-ZA-M'
pattern: expr{
literal("echo "),
matchVarref{},
literal(" | tr 'a-zA-Z' 'n-za-mN-ZA-M'"),
},
compact: func(sh *funcShell, matches []Value) Value {
return &funcShellAndroidRot13{
funcShell: sh,
v: matches[0],
}
},
},
{
name: "android:find-subdir-assets",
// in repo/android/build/core/definitions.mk
// if [ -d $1 ] ; then cd $1 ; find ./ -not -name '.*' -and -type f -and -not -type l ; fi
pattern: expr{
literal("if [ -d "),
matchVarref{},
literal(" ] ; then cd "),
matchVarref{},
literal(" ; find ./ -not -name '.*' -and -type f -and -not -type l ; fi"),
},
compact: func(sh *funcShell, v []Value) Value {
if v[0] != v[1] {
return sh
}
androidFindCache.init(nil)
return &funcShellAndroidFindFileInDir{
funcShell: sh,
dir: v[0],
}
},
},
{
name: "android:all-java-files-under",
// in repo/android/build/core/definitions.mk
// cd ${LOCAL_PATH} ; find -L $1 -name "*.java" -and -not -name ".*"
pattern: expr{
literal("cd "),
matchVarref{},
literal(" ; find -L "),
matchVarref{},
literal(` -name "*.java" -and -not -name ".*"`),
},
compact: func(sh *funcShell, v []Value) Value {
androidFindCache.init(nil)
return &funcShellAndroidFindExtFilesUnder{
funcShell: sh,
chdir: v[0],
roots: v[1],
ext: ".java",
}
},
},
{
name: "android:all-proto-files-under",
// in repo/android/build/core/definitions.mk
// cd $(LOCAL_PATH) ; \
// find -L $(1) -name "*.proto" -and -not -name ".*"
pattern: expr{
literal("cd "),
matchVarref{},
literal(" ; find -L "),
matchVarref{},
literal(" -name \"*.proto\" -and -not -name \".*\""),
},
compact: func(sh *funcShell, v []Value) Value {
androidFindCache.init(nil)
return &funcShellAndroidFindExtFilesUnder{
funcShell: sh,
chdir: v[0],
roots: v[1],
ext: ".proto",
}
},
},
{
name: "android:java_resource_file_groups",
// in repo/android/build/core/base_rules.mk
// 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 )
pattern: expr{
literal("cd "),
matchVarref{},
matchVarref{},
mustLiteralRE("(/)"),
matchVarref{},
literal(` && 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 `),
},
compact: func(sh *funcShell, v []Value) Value {
androidFindCache.init(nil)
return &funcShellAndroidFindJavaResourceFileGroup{
funcShell: sh,
dir: expr(v),
}
},
},
{
name: "android:subdir_cleanspecs",
// in repo/android/build/core/cleanspec.mk
// build/tools/findleaves.py --prune=$(OUT_DIR) --prune=.repo --prune=.git . CleanSpec.mk)
pattern: expr{
literal("build/tools/findleaves.py --prune="),
matchVarref{},
literal(" --prune=.repo --prune=.git . CleanSpec.mk"),
},
compact: func(sh *funcShell, v []Value) Value {
if !contains(androidDefaultLeafNames, "CleanSpec.mk") {
return sh
}
androidFindCache.init(nil)
return &funcShellAndroidFindleaves{
funcShell: sh,
prunes: []Value{
v[0],
literal(".repo"),
literal(".git"),
},
dirlist: literal("."),
name: literal("CleanSpec.mk"),
mindepth: -1,
}
},
},
{
name: "android:subdir_makefiles",
// in repo/android/build/core/main.mk
// build/tools/findleaves.py --prune=$(OUT_DIR) --prune=.repo --prune=.git $(subdirs) Android.mk
pattern: expr{
literal("build/tools/findleaves.py --prune="),
matchVarref{},
literal(" --prune=.repo --prune=.git "),
matchVarref{},
literal(" Android.mk"),
},
compact: func(sh *funcShell, v []Value) Value {
if !contains(androidDefaultLeafNames, "Android.mk") {
return sh
}
androidFindCache.init(nil)
return &funcShellAndroidFindleaves{
funcShell: sh,
prunes: []Value{
v[0],
literal(".repo"),
literal(".git"),
},
dirlist: v[1],
name: literal("Android.mk"),
mindepth: -1,
}
},
},
{
name: "android:first-makefiles-under",
// in repo/android/build/core/definisions.mk
// build/tools/findleaves.py --prune=$(OUT_DIR) --prune=.repo --prune=.git \
// --mindepth=2 $(1) Android.mk
pattern: expr{
literal("build/tools/findleaves.py --prune="),
matchVarref{},
literal(" --prune=.repo --prune=.git --mindepth=2 "),
matchVarref{},
literal(" Android.mk"),
},
compact: func(sh *funcShell, v []Value) Value {
if !contains(androidDefaultLeafNames, "Android.mk") {
return sh
}
androidFindCache.init(nil)
return &funcShellAndroidFindleaves{
funcShell: sh,
prunes: []Value{
v[0],
literal(".repo"),
literal(".git"),
},
dirlist: v[1],
name: literal("Android.mk"),
mindepth: 2,
}
},
},
{
name: "shell-date",
pattern: expr{
mustLiteralRE(`date \+(\S+)`),
},
compact: compactShellDate,
},
{
name: "shell-date-quoted",
pattern: expr{
mustLiteralRE(`date "\+([^"]+)"`),
},
compact: compactShellDate,
},
}
type funcShellAndroidRot13 struct {
*funcShell
v Value
}
func rot13(buf []byte) {
for i, b := range buf {
// tr 'a-zA-Z' 'n-za-mN-ZA-M'
if b >= 'a' && b <= 'z' {
b += 'n' - 'a'
if b > 'z' {
b -= 'z' - 'a' + 1
}
} else if b >= 'A' && b <= 'Z' {
b += 'N' - 'A'
if b > 'Z' {
b -= 'Z' - 'A' + 1
}
}
buf[i] = b
}
}
func (f *funcShellAndroidRot13) Eval(w evalWriter, ev *Evaluator) error {
abuf := newEbuf()
fargs, err := ev.args(abuf, f.v)
if err != nil {
return err
}
rot13(fargs[0])
w.Write(fargs[0])
abuf.release()
return nil
}
type funcShellAndroidFindFileInDir struct {
*funcShell
dir Value
}
func (f *funcShellAndroidFindFileInDir) Eval(w evalWriter, ev *Evaluator) error {
abuf := newEbuf()
fargs, err := ev.args(abuf, f.dir)
if err != nil {
return err
}
dir := string(trimSpaceBytes(fargs[0]))
abuf.release()
glog.V(1).Infof("shellAndroidFindFileInDir %s => %s", f.dir.String(), dir)
if strings.Contains(dir, "..") {
glog.Warningf("shellAndroidFindFileInDir contains ..: call original shell")
return f.funcShell.Eval(w, ev)
}
if !androidFindCache.ready() {
glog.Warningf("shellAndroidFindFileInDir androidFindCache is not ready: call original shell")
return f.funcShell.Eval(w, ev)
}
androidFindCache.findInDir(w, dir)
return nil
}
type funcShellAndroidFindExtFilesUnder struct {
*funcShell
chdir Value
roots Value
ext string
}
func (f *funcShellAndroidFindExtFilesUnder) Eval(w evalWriter, ev *Evaluator) error {
abuf := newEbuf()
err := f.chdir.Eval(abuf, ev)
if err != nil {
return err
}
chdir := string(trimSpaceBytes(abuf.Bytes()))
abuf.release()
wb := newWbuf()
err = f.roots.Eval(wb, ev)
if err != nil {
return err
}
hasDotDot := false
var roots []string
for _, word := range wb.words {
root := string(word)
if strings.Contains(root, "..") {
hasDotDot = true
}
roots = append(roots, root)
}
wb.release()
glog.V(1).Infof("shellAndroidFindExtFilesUnder %s,%s => %s,%s", f.chdir.String(), f.roots.String(), chdir, roots)
if strings.Contains(chdir, "..") || hasDotDot {
glog.Warningf("shellAndroidFindExtFilesUnder contains ..: call original shell")
return f.funcShell.Eval(w, ev)
}
if !androidFindCache.ready() {
glog.Warningf("shellAndroidFindExtFilesUnder androidFindCache is not ready: call original shell")
return f.funcShell.Eval(w, ev)
}
buf := newEbuf()
for _, root := range roots {
if !androidFindCache.findExtFilesUnder(buf, chdir, root, f.ext) {
buf.release()
glog.Warningf("shellAndroidFindExtFilesUnder androidFindCache couldn't handle: call original shell")
return f.funcShell.Eval(w, ev)
}
}
w.Write(buf.Bytes())
buf.release()
return nil
}
type funcShellAndroidFindJavaResourceFileGroup struct {
*funcShell
dir Value
}
func (f *funcShellAndroidFindJavaResourceFileGroup) Eval(w evalWriter, ev *Evaluator) error {
abuf := newEbuf()
fargs, err := ev.args(abuf, f.dir)
if err != nil {
return err
}
dir := string(trimSpaceBytes(fargs[0]))
abuf.release()
glog.V(1).Infof("shellAndroidFindJavaResourceFileGroup %s => %s", f.dir.String(), dir)
if strings.Contains(dir, "..") {
glog.Warningf("shellAndroidFindJavaResourceFileGroup contains ..: call original shell")
return f.funcShell.Eval(w, ev)
}
if !androidFindCache.ready() {
glog.Warningf("shellAndroidFindJavaResourceFileGroup androidFindCache is not ready: call original shell")
return f.funcShell.Eval(w, ev)
}
androidFindCache.findJavaResourceFileGroup(w, dir)
return nil
}
type funcShellAndroidFindleaves struct {
*funcShell
prunes []Value
dirlist Value
name Value
mindepth int
}
func (f *funcShellAndroidFindleaves) Eval(w evalWriter, ev *Evaluator) error {
if !androidFindCache.leavesReady() {
glog.Warningf("shellAndroidFindleaves androidFindCache is not ready: call original shell")
return f.funcShell.Eval(w, ev)
}
abuf := newEbuf()
var params []Value
params = append(params, f.name)
params = append(params, f.prunes...)
fargs, err := ev.args(abuf, params...)
if err != nil {
return err
}
name := string(trimSpaceBytes(fargs[0]))
var prunes []string
for _, arg := range fargs[1:] {
prunes = append(prunes, string(trimSpaceBytes(arg)))
}
abuf.release()
wb := newWbuf()
err = f.dirlist.Eval(wb, ev)
if err != nil {
return err
}
var dirs []string
for _, word := range wb.words {
dir := string(word)
if strings.Contains(dir, "..") {
glog.Warningf("shellAndroidFindleaves contains .. in %s: call original shell", dir)
return f.funcShell.Eval(w, ev)
}
dirs = append(dirs, dir)
}
wb.release()
for _, dir := range dirs {
androidFindCache.findleaves(w, dir, name, prunes, f.mindepth)
}
return nil
}
var (
// ShellDateTimestamp is an timestamp used for $(shell date).
ShellDateTimestamp time.Time
shellDateFormatRef = map[string]string{
"%Y": "2006",
"%m": "01",
"%d": "02",
"%H": "15",
"%M": "04",
"%S": "05",
"%b": "Jan",
"%k": "15", // XXX
}
)
type funcShellDate struct {
*funcShell
format string
}
func compactShellDate(sh *funcShell, v []Value) Value {
if ShellDateTimestamp.IsZero() {
return sh
}
tf, ok := v[0].(literal)
if !ok {
return sh
}
tfstr := string(tf)
for k, v := range shellDateFormatRef {
tfstr = strings.Replace(tfstr, k, v, -1)
}
return &funcShellDate{
funcShell: sh,
format: tfstr,
}
}
func (f *funcShellDate) Eval(w evalWriter, ev *Evaluator) error {
fmt.Fprint(w, ShellDateTimestamp.Format(f.format))
return nil
}