Add soong_build primary builder
Initial build logic for building android with soong. It can build
a variety of C and C++ files for arm/arm64 and host.
Change-Id: I10eb37c2c2a50be6af1bb5fd568c0962b9476bf0
diff --git a/androidmk/parser/make_strings.go b/androidmk/parser/make_strings.go
new file mode 100644
index 0000000..558853f
--- /dev/null
+++ b/androidmk/parser/make_strings.go
@@ -0,0 +1,170 @@
+package parser
+
+import (
+ "strings"
+ "text/scanner"
+ "unicode"
+)
+
+// A MakeString is a string that may contain variable substitutions in it.
+// It can be considered as an alternating list of raw Strings and variable
+// substitutions, where the first and last entries in the list must be raw
+// Strings (possibly empty). A MakeString that starts with a variable
+// will have an empty first raw string, and a MakeString that ends with a
+// variable will have an empty last raw string. Two sequential Variables
+// will have an empty raw string between them.
+//
+// The MakeString is stored as two lists, a list of raw Strings and a list
+// of Variables. The raw string list is always one longer than the variable
+// list.
+type MakeString struct {
+ Pos scanner.Position
+ Strings []string
+ Variables []Variable
+}
+
+func SimpleMakeString(s string, pos scanner.Position) *MakeString {
+ return &MakeString{
+ Pos: pos,
+ Strings: []string{s},
+ }
+}
+
+func (ms *MakeString) appendString(s string) {
+ if len(ms.Strings) == 0 {
+ ms.Strings = []string{s}
+ return
+ } else {
+ ms.Strings[len(ms.Strings)-1] += s
+ }
+}
+
+func (ms *MakeString) appendVariable(v Variable) {
+ if len(ms.Strings) == 0 {
+ ms.Strings = []string{"", ""}
+ ms.Variables = []Variable{v}
+ } else {
+ ms.Strings = append(ms.Strings, "")
+ ms.Variables = append(ms.Variables, v)
+ }
+}
+
+func (ms *MakeString) appendMakeString(other *MakeString) {
+ last := len(ms.Strings) - 1
+ ms.Strings[last] += other.Strings[0]
+ ms.Strings = append(ms.Strings, other.Strings[1:]...)
+ ms.Variables = append(ms.Variables, other.Variables...)
+}
+
+func (ms *MakeString) Value(scope Scope) string {
+ if len(ms.Strings) == 0 {
+ return ""
+ } else {
+ ret := ms.Strings[0]
+ for i := range ms.Strings[1:] {
+ ret += ms.Variables[i].Value(scope)
+ ret += ms.Strings[i+1]
+ }
+ return ret
+ }
+}
+
+func (ms *MakeString) Dump() string {
+ if len(ms.Strings) == 0 {
+ return ""
+ } else {
+ ret := ms.Strings[0]
+ for i := range ms.Strings[1:] {
+ ret += ms.Variables[i].Dump()
+ ret += ms.Strings[i+1]
+ }
+ return ret
+ }
+}
+
+func (ms *MakeString) Const() bool {
+ return len(ms.Strings) <= 1
+}
+
+func (ms *MakeString) Empty() bool {
+ return len(ms.Strings) == 0 || (len(ms.Strings) == 1 && ms.Strings[0] == "")
+}
+
+func (ms *MakeString) Split(sep string) []*MakeString {
+ return ms.SplitN(sep, -1)
+}
+
+func (ms *MakeString) SplitN(sep string, n int) []*MakeString {
+ ret := []*MakeString{}
+
+ curMs := SimpleMakeString("", ms.Pos)
+
+ var i int
+ var s string
+ for i, s = range ms.Strings {
+ if n != 0 {
+ split := splitAnyN(s, sep, n)
+ if n != -1 {
+ if len(split) > n {
+ panic("oops!")
+ } else {
+ n -= len(split)
+ }
+ }
+ curMs.appendString(split[0])
+
+ for _, r := range split[1:] {
+ ret = append(ret, curMs)
+ curMs = SimpleMakeString(r, ms.Pos)
+ }
+ } else {
+ curMs.appendString(s)
+ }
+
+ if i < len(ms.Strings)-1 {
+ curMs.appendVariable(ms.Variables[i])
+ }
+ }
+
+ ret = append(ret, curMs)
+ return ret
+}
+
+func (ms *MakeString) TrimLeftSpaces() {
+ ms.Strings[0] = strings.TrimLeftFunc(ms.Strings[0], unicode.IsSpace)
+}
+
+func (ms *MakeString) TrimRightSpaces() {
+ last := len(ms.Strings) - 1
+ ms.Strings[last] = strings.TrimRightFunc(ms.Strings[last], unicode.IsSpace)
+}
+
+func (ms *MakeString) TrimRightOne() {
+ last := len(ms.Strings) - 1
+ if len(ms.Strings[last]) > 1 {
+ ms.Strings[last] = ms.Strings[last][0 : len(ms.Strings[last])-1]
+ }
+}
+
+func (ms *MakeString) EndsWith(ch rune) bool {
+ s := ms.Strings[len(ms.Strings)-1]
+ return s[len(s)-1] == uint8(ch)
+}
+
+func splitAnyN(s, sep string, n int) []string {
+ ret := []string{}
+ for n == -1 || n > 1 {
+ index := strings.IndexAny(s, sep)
+ if index >= 0 {
+ ret = append(ret, s[0:index])
+ s = s[index+1:]
+ if n > 0 {
+ n--
+ }
+ } else {
+ break
+ }
+ }
+ ret = append(ret, s)
+ return ret
+}
diff --git a/androidmk/parser/make_strings_test.go b/androidmk/parser/make_strings_test.go
new file mode 100644
index 0000000..db5840c
--- /dev/null
+++ b/androidmk/parser/make_strings_test.go
@@ -0,0 +1,96 @@
+package parser
+
+import (
+ "strings"
+ "testing"
+)
+
+var splitNTestCases = []struct {
+ in *MakeString
+ expected []*MakeString
+ sep string
+ n int
+}{
+ {
+ in: &MakeString{
+ strings: []string{
+ "a b c",
+ "d e f",
+ " h i j",
+ },
+ variables: []Variable{
+ variable{name: SimpleMakeString("var1")},
+ variable{name: SimpleMakeString("var2")},
+ },
+ },
+ sep: " ",
+ n: -1,
+ expected: []*MakeString{
+ SimpleMakeString("a"),
+ SimpleMakeString("b"),
+ &MakeString{
+ strings: []string{"c", "d"},
+ variables: []Variable{
+ variable{name: SimpleMakeString("var1")},
+ },
+ },
+ SimpleMakeString("e"),
+ &MakeString{
+ strings: []string{"f", ""},
+ variables: []Variable{
+ variable{name: SimpleMakeString("var2")},
+ },
+ },
+ SimpleMakeString("h"),
+ SimpleMakeString("i"),
+ SimpleMakeString("j"),
+ },
+ },
+ {
+ in: &MakeString{
+ strings: []string{
+ "a b c",
+ "d e f",
+ " h i j",
+ },
+ variables: []Variable{
+ variable{name: SimpleMakeString("var1")},
+ variable{name: SimpleMakeString("var2")},
+ },
+ },
+ sep: " ",
+ n: 3,
+ expected: []*MakeString{
+ SimpleMakeString("a"),
+ SimpleMakeString("b"),
+ &MakeString{
+ strings: []string{"c", "d e f", " h i j"},
+ variables: []Variable{
+ variable{name: SimpleMakeString("var1")},
+ variable{name: SimpleMakeString("var2")},
+ },
+ },
+ },
+ },
+}
+
+func TestMakeStringSplitN(t *testing.T) {
+ for _, test := range splitNTestCases {
+ got := test.in.SplitN(test.sep, test.n)
+ gotString := dumpArray(got)
+ expectedString := dumpArray(test.expected)
+ if gotString != expectedString {
+ t.Errorf("expected:\n%s\ngot:\n%s", expectedString, gotString)
+ }
+ }
+}
+
+func dumpArray(a []*MakeString) string {
+ ret := make([]string, len(a))
+
+ for i, s := range a {
+ ret[i] = s.Dump()
+ }
+
+ return strings.Join(ret, "|||")
+}
diff --git a/androidmk/parser/makething.go b/androidmk/parser/makething.go
new file mode 100644
index 0000000..7d60a77
--- /dev/null
+++ b/androidmk/parser/makething.go
@@ -0,0 +1,142 @@
+package parser
+
+import (
+ "text/scanner"
+)
+
+type MakeThing interface {
+ AsAssignment() (Assignment, bool)
+ AsComment() (Comment, bool)
+ AsDirective() (Directive, bool)
+ AsRule() (Rule, bool)
+ AsVariable() (Variable, bool)
+ Dump() string
+ Pos() scanner.Position
+ EndPos() scanner.Position
+}
+
+type Assignment struct {
+ makeThing
+ Name *MakeString
+ Value *MakeString
+ Target *MakeString
+ Type string
+}
+
+type Comment struct {
+ makeThing
+ Comment string
+}
+
+type Directive struct {
+ makeThing
+ Name string
+ Args *MakeString
+}
+
+type Rule struct {
+ makeThing
+ Target *MakeString
+ Prerequisites *MakeString
+ Recipe string
+}
+
+type Variable struct {
+ makeThing
+ Name *MakeString
+}
+
+type makeThing struct {
+ pos scanner.Position
+ endPos scanner.Position
+}
+
+func (m makeThing) Pos() scanner.Position {
+ return m.pos
+}
+
+func (m makeThing) EndPos() scanner.Position {
+ return m.endPos
+}
+
+func (makeThing) AsAssignment() (a Assignment, ok bool) {
+ return
+}
+
+func (a Assignment) AsAssignment() (Assignment, bool) {
+ return a, true
+}
+
+func (a Assignment) Dump() string {
+ target := ""
+ if a.Target != nil {
+ target = a.Target.Dump() + ": "
+ }
+ return target + a.Name.Dump() + a.Type + a.Value.Dump()
+}
+
+func (makeThing) AsComment() (c Comment, ok bool) {
+ return
+}
+
+func (c Comment) AsComment() (Comment, bool) {
+ return c, true
+}
+
+func (c Comment) Dump() string {
+ return "#" + c.Comment
+}
+
+func (makeThing) AsDirective() (d Directive, ok bool) {
+ return
+}
+
+func (d Directive) AsDirective() (Directive, bool) {
+ return d, true
+}
+
+func (d Directive) Dump() string {
+ return d.Name + " " + d.Args.Dump()
+}
+
+func (makeThing) AsRule() (r Rule, ok bool) {
+ return
+}
+
+func (r Rule) AsRule() (Rule, bool) {
+ return r, true
+}
+
+func (r Rule) Dump() string {
+ recipe := ""
+ if r.Recipe != "" {
+ recipe = "\n" + r.Recipe
+ }
+ return "rule: " + r.Target.Dump() + ": " + r.Prerequisites.Dump() + recipe
+}
+
+func (makeThing) AsVariable() (v Variable, ok bool) {
+ return
+}
+
+func (v Variable) AsVariable() (Variable, bool) {
+ return v, true
+}
+
+func (v Variable) Dump() string {
+ return "$(" + v.Name.Dump() + ")"
+}
+
+type byPosition []MakeThing
+
+func (s byPosition) Len() int {
+ return len(s)
+}
+
+func (s byPosition) Swap(i, j int) {
+ s[i], s[j] = s[j], s[i]
+}
+
+func (s byPosition) Less(i, j int) bool {
+ return s[i].Pos().Offset < s[j].Pos().Offset
+}
diff --git a/androidmk/parser/parser.go b/androidmk/parser/parser.go
new file mode 100644
index 0000000..58e612e
--- /dev/null
+++ b/androidmk/parser/parser.go
@@ -0,0 +1,633 @@
+package parser
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "sort"
+ "text/scanner"
+)
+
+var errTooManyErrors = errors.New("too many errors")
+
+const maxErrors = 100
+
+type ParseError struct {
+ Err error
+ Pos scanner.Position
+}
+
+func (e *ParseError) Error() string {
+ return fmt.Sprintf("%s: %s", e.Pos, e.Err)
+}
+
+func (p *parser) Parse() ([]MakeThing, []error) {
+ defer func() {
+ if r := recover(); r != nil {
+ if r == errTooManyErrors {
+ return
+ }
+ panic(r)
+ }
+ }()
+
+ p.parseLines()
+ p.accept(scanner.EOF)
+ p.things = append(p.things, p.comments...)
+ sort.Sort(byPosition(p.things))
+
+ return p.things, p.errors
+}
+
+type parser struct {
+ scanner scanner.Scanner
+ tok rune
+ errors []error
+ comments []MakeThing
+ things []MakeThing
+}
+
+func NewParser(filename string, r io.Reader) *parser {
+ p := &parser{}
+ p.scanner.Init(r)
+ p.scanner.Error = func(sc *scanner.Scanner, msg string) {
+ p.errorf(msg)
+ }
+ p.scanner.Whitespace = 0
+ p.scanner.IsIdentRune = func(ch rune, i int) bool {
+ return ch > 0 && ch != ':' && ch != '#' && ch != '=' && ch != '+' && ch != '$' &&
+ ch != '\\' && ch != '(' && ch != ')' && ch != '{' && ch != '}' && ch != ';' &&
+ ch != '|' && ch != '?' && ch != '\r' && !isWhitespace(ch)
+ }
+ p.scanner.Mode = scanner.ScanIdents
+ p.scanner.Filename = filename
+ p.next()
+ return p
+}
+
+func (p *parser) errorf(format string, args ...interface{}) {
+ pos := p.scanner.Position
+ if !pos.IsValid() {
+ pos = p.scanner.Pos()
+ }
+ err := &ParseError{
+ Err: fmt.Errorf(format, args...),
+ Pos: pos,
+ }
+ p.errors = append(p.errors, err)
+ if len(p.errors) >= maxErrors {
+ panic(errTooManyErrors)
+ }
+}
+
+func (p *parser) accept(toks ...rune) bool {
+ for _, tok := range toks {
+ if p.tok != tok {
+ p.errorf("expected %s, found %s", scanner.TokenString(tok),
+ scanner.TokenString(p.tok))
+ return false
+ }
+ p.next()
+ }
+ return true
+}
+
+func (p *parser) next() {
+ if p.tok != scanner.EOF {
+ p.tok = p.scanner.Scan()
+ for p.tok == '\r' {
+ p.tok = p.scanner.Scan()
+ }
+ }
+ return
+}
+
+func (p *parser) parseLines() {
+ for {
+ p.ignoreWhitespace()
+
+ if p.parseDirective() {
+ continue
+ }
+
+ ident, _ := p.parseExpression('=', '?', ':', '#', '\n')
+
+ p.ignoreSpaces()
+
+ switch p.tok {
+ case '?':
+ p.accept('?')
+ if p.tok == '=' {
+ p.parseAssignment("?=", nil, ident)
+ } else {
+ p.errorf("expected = after ?")
+ }
+ case '+':
+ p.accept('+')
+ if p.tok == '=' {
+ p.parseAssignment("+=", nil, ident)
+ } else {
+ p.errorf("expected = after +")
+ }
+ case ':':
+ p.accept(':')
+ switch p.tok {
+ case '=':
+ p.parseAssignment(":=", nil, ident)
+ default:
+ p.parseRule(ident)
+ }
+ case '=':
+ p.parseAssignment("=", nil, ident)
+ case '#', '\n', scanner.EOF:
+ ident.TrimRightSpaces()
+ if v, ok := toVariable(ident); ok {
+ p.things = append(p.things, v)
+ } else if !ident.Empty() {
+ p.errorf("expected directive, rule, or assignment after ident " + ident.Dump())
+ }
+ switch p.tok {
+ case scanner.EOF:
+ return
+ case '\n':
+ p.accept('\n')
+ case '#':
+ p.parseComment()
+ }
+ default:
+ p.errorf("expected assignment or rule definition, found %s\n",
+ p.scanner.TokenText())
+ return
+ }
+ }
+}
+
+func (p *parser) parseDirective() bool {
+ if p.tok != scanner.Ident || !isDirective(p.scanner.TokenText()) {
+ return false
+ }
+
+ d := p.scanner.TokenText()
+ pos := p.scanner.Position
+ endPos := pos
+ p.accept(scanner.Ident)
+
+ expression := SimpleMakeString("", pos)
+
+ switch d {
+ case "endif", "endef", "else":
+ // Nothing
+ case "define":
+ expression = p.parseDefine()
+ default:
+ p.ignoreSpaces()
+ expression, endPos = p.parseExpression()
+ }
+
+ p.things = append(p.things, Directive{
+ makeThing: makeThing{
+ pos: pos,
+ endPos: endPos,
+ },
+ Name: d,
+ Args: expression,
+ })
+ return true
+}
+
+func (p *parser) parseDefine() *MakeString {
+ value := SimpleMakeString("", p.scanner.Position)
+
+loop:
+ for {
+ switch p.tok {
+ case scanner.Ident:
+ if p.scanner.TokenText() == "endef" {
+ p.accept(scanner.Ident)
+ break loop
+ }
+ value.appendString(p.scanner.TokenText())
+ p.accept(scanner.Ident)
+ case '\\':
+ p.parseEscape()
+ switch p.tok {
+ case '\n':
+ value.appendString(" ")
+ case scanner.EOF:
+ p.errorf("expected escaped character, found %s",
+ scanner.TokenString(p.tok))
+ break loop
+ default:
+ value.appendString(`\` + string(p.tok))
+ }
+ p.accept(p.tok)
+ //TODO: handle variables inside defines? result depends if
+ //define is used in make or rule context
+ //case '$':
+ // variable := p.parseVariable()
+ // value.appendVariable(variable)
+ case scanner.EOF:
+ p.errorf("unexpected EOF while looking for endef")
+ break loop
+ default:
+ value.appendString(p.scanner.TokenText())
+ p.accept(p.tok)
+ }
+ }
+
+ return value
+}
+
+func (p *parser) parseEscape() {
+ p.scanner.Mode = 0
+ p.accept('\\')
+ p.scanner.Mode = scanner.ScanIdents
+}
+
+func (p *parser) parseExpression(end ...rune) (*MakeString, scanner.Position) {
+ value := SimpleMakeString("", p.scanner.Position)
+
+ endParen := false
+ for _, r := range end {
+ if r == ')' {
+ endParen = true
+ }
+ }
+ parens := 0
+
+ endPos := p.scanner.Position
+
+loop:
+ for {
+ if endParen && parens > 0 && p.tok == ')' {
+ parens--
+ value.appendString(")")
+ endPos = p.scanner.Position
+ p.accept(')')
+ continue
+ }
+
+ for _, r := range end {
+ if p.tok == r {
+ break loop
+ }
+ }
+
+ switch p.tok {
+ case '\n':
+ break loop
+ case scanner.Ident:
+ value.appendString(p.scanner.TokenText())
+ endPos = p.scanner.Position
+ p.accept(scanner.Ident)
+ case '\\':
+ p.parseEscape()
+ switch p.tok {
+ case '\n':
+ value.appendString(" ")
+ case scanner.EOF:
+ p.errorf("expected escaped character, found %s",
+ scanner.TokenString(p.tok))
+ return value, endPos
+ default:
+ value.appendString(`\` + string(p.tok))
+ }
+ endPos = p.scanner.Position
+ p.accept(p.tok)
+ case '#':
+ p.parseComment()
+ break loop
+ case '$':
+ var variable Variable
+ variable, endPos = p.parseVariable()
+ value.appendVariable(variable)
+ case scanner.EOF:
+ break loop
+ case '(':
+ if endParen {
+ parens++
+ }
+ value.appendString("(")
+ endPos = p.scanner.Position
+ p.accept('(')
+ default:
+ value.appendString(p.scanner.TokenText())
+ endPos = p.scanner.Position
+ p.accept(p.tok)
+ }
+ }
+
+ if parens > 0 {
+ p.errorf("expected closing paren %s", value.Dump())
+ }
+ return value, endPos
+}
+
+func (p *parser) parseVariable() (Variable, scanner.Position) {
+ pos := p.scanner.Position
+ endPos := pos
+ p.accept('$')
+ var name *MakeString
+ switch p.tok {
+ case '(':
+ return p.parseBracketedVariable('(', ')', pos)
+ case '{':
+ return p.parseBracketedVariable('{', '}', pos)
+ case '$':
+ name = SimpleMakeString("__builtin_dollar", scanner.Position{})
+ case scanner.EOF:
+ p.errorf("expected variable name, found %s",
+ scanner.TokenString(p.tok))
+ default:
+ name, endPos = p.parseExpression(variableNameEndRunes...)
+ }
+
+ return p.nameToVariable(name, pos, endPos), endPos
+}
+
+func (p *parser) parseBracketedVariable(start, end rune, pos scanner.Position) (Variable, scanner.Position) {
+ p.accept(start)
+ name, endPos := p.parseExpression(end)
+ p.accept(end)
+ return p.nameToVariable(name, pos, endPos), endPos
+}
+
+func (p *parser) nameToVariable(name *MakeString, pos, endPos scanner.Position) Variable {
+ return Variable{
+ makeThing: makeThing{
+ pos: pos,
+ endPos: endPos,
+ },
+ Name: name,
+ }
+}
+
+func (p *parser) parseRule(target *MakeString) {
+ prerequisites, newLine := p.parseRulePrerequisites(target)
+
+ recipe := ""
+ endPos := p.scanner.Position
+loop:
+ for {
+ if newLine {
+ if p.tok == '\t' {
+ endPos = p.scanner.Position
+ p.accept('\t')
+ newLine = false
+ continue loop
+ } else if p.parseDirective() {
+ newLine = false
+ continue
+ } else {
+ break loop
+ }
+ }
+
+ newLine = false
+ switch p.tok {
+ case '\\':
+ p.parseEscape()
+ recipe += string(p.tok)
+ endPos = p.scanner.Position
+ p.accept(p.tok)
+ case '\n':
+ newLine = true
+ recipe += "\n"
+ endPos = p.scanner.Position
+ p.accept('\n')
+ case scanner.EOF:
+ break loop
+ default:
+ recipe += p.scanner.TokenText()
+ endPos = p.scanner.Position
+ p.accept(p.tok)
+ }
+ }
+
+ if prerequisites != nil {
+ p.things = append(p.things, Rule{
+ makeThing: makeThing{
+ pos: target.Pos,
+ endPos: endPos,
+ },
+ Target: target,
+ Prerequisites: prerequisites,
+ Recipe: recipe,
+ })
+ }
+}
+
+func (p *parser) parseRulePrerequisites(target *MakeString) (*MakeString, bool) {
+ newLine := false
+
+ p.ignoreSpaces()
+
+ prerequisites, _ := p.parseExpression('#', '\n', ';', ':', '=')
+
+ switch p.tok {
+ case '\n':
+ p.accept('\n')
+ newLine = true
+ case '#':
+ p.parseComment()
+ newLine = true
+ case ';':
+ p.accept(';')
+ case ':':
+ p.accept(':')
+ if p.tok == '=' {
+ p.parseAssignment(":=", target, prerequisites)
+ return nil, true
+ } else {
+ more, _ := p.parseExpression('#', '\n', ';')
+ prerequisites.appendMakeString(more)
+ }
+ case '=':
+ p.parseAssignment("=", target, prerequisites)
+ return nil, true
+ default:
+ p.errorf("unexpected token %s after rule prerequisites", scanner.TokenString(p.tok))
+ }
+
+ return prerequisites, newLine
+}
+
+func (p *parser) parseComment() {
+ pos := p.scanner.Position
+ p.accept('#')
+ comment := ""
+ endPos := pos
+loop:
+ for {
+ switch p.tok {
+ case '\\':
+ p.parseEscape()
+ if p.tok == '\n' {
+ comment += "\n"
+ } else {
+ comment += "\\" + p.scanner.TokenText()
+ }
+ endPos = p.scanner.Position
+ p.accept(p.tok)
+ case '\n':
+ endPos = p.scanner.Position
+ p.accept('\n')
+ break loop
+ case scanner.EOF:
+ break loop
+ default:
+ comment += p.scanner.TokenText()
+ endPos = p.scanner.Position
+ p.accept(p.tok)
+ }
+ }
+
+ p.comments = append(p.comments, Comment{
+ makeThing: makeThing{
+ pos: pos,
+ endPos: endPos,
+ },
+ Comment: comment,
+ })
+}
+
+func (p *parser) parseAssignment(t string, target *MakeString, ident *MakeString) {
+ // The value of an assignment is everything including and after the first
+ // non-whitespace character after the = until the end of the logical line,
+ // which may included escaped newlines
+ p.accept('=')
+ value, endPos := p.parseExpression()
+ value.TrimLeftSpaces()
+ if ident.EndsWith('+') && t == "=" {
+ ident.TrimRightOne()
+ t = "+="
+ }
+
+ ident.TrimRightSpaces()
+
+ p.things = append(p.things, Assignment{
+ makeThing: makeThing{
+ pos: ident.Pos,
+ endPos: endPos,
+ },
+ Name: ident,
+ Value: value,
+ Target: target,
+ Type: t,
+ })
+}
+
+type androidMkModule struct {
+ assignments map[string]string
+}
+
+type androidMkFile struct {
+ assignments map[string]string
+ modules []androidMkModule
+ includes []string
+}
+
+var directives = [...]string{
+ "define",
+ "else",
+ "endef",
+ "endif",
+ "ifdef",
+ "ifeq",
+ "ifndef",
+ "ifneq",
+ "include",
+ "-include",
+}
+
+var functions = [...]string{
+ "abspath",
+ "addprefix",
+ "addsuffix",
+ "basename",
+ "dir",
+ "notdir",
+ "subst",
+ "suffix",
+ "filter",
+ "filter-out",
+ "findstring",
+ "firstword",
+ "flavor",
+ "join",
+ "lastword",
+ "patsubst",
+ "realpath",
+ "shell",
+ "sort",
+ "strip",
+ "wildcard",
+ "word",
+ "wordlist",
+ "words",
+ "origin",
+ "foreach",
+ "call",
+ "info",
+ "error",
+ "warning",
+ "if",
+ "or",
+ "and",
+ "value",
+ "eval",
+ "file",
+}
+
+func init() {
+ sort.Strings(directives[:])
+ sort.Strings(functions[:])
+}
+
+func isDirective(s string) bool {
+ for _, d := range directives {
+ if s == d {
+ return true
+ } else if s < d {
+ return false
+ }
+ }
+ return false
+}
+
+func isFunctionName(s string) bool {
+ for _, f := range functions {
+ if s == f {
+ return true
+ } else if s < f {
+ return false
+ }
+ }
+ return false
+}
+
+func isWhitespace(ch rune) bool {
+ return ch == ' ' || ch == '\t' || ch == '\n'
+}
+
+func isValidVariableRune(ch rune) bool {
+ return ch != scanner.Ident && ch != ':' && ch != '=' && ch != '#'
+}
+
+var whitespaceRunes = []rune{' ', '\t', '\n'}
+var variableNameEndRunes = append([]rune{':', '=', '#', ')', '}'}, whitespaceRunes...)
+
+func (p *parser) ignoreSpaces() int {
+ skipped := 0
+ for p.tok == ' ' || p.tok == '\t' {
+ p.accept(p.tok)
+ skipped++
+ }
+ return skipped
+}
+
+func (p *parser) ignoreWhitespace() {
+ for isWhitespace(p.tok) {
+ p.accept(p.tok)
+ }
+}
diff --git a/androidmk/parser/scope.go b/androidmk/parser/scope.go
new file mode 100644
index 0000000..742ad38
--- /dev/null
+++ b/androidmk/parser/scope.go
@@ -0,0 +1,88 @@
+package parser
+
+import "strings"
+
+type Scope interface {
+ Get(name string) string
+ Set(name, value string)
+ Call(name string, args []string) string
+ SetFunc(name string, f func([]string) string)
+}
+
+type scope struct {
+ variables map[string]string
+ functions map[string]func([]string) string
+ parent Scope
+}
+
+func (s *scope) Get(name string) string {
+ if val, ok := s.variables[name]; ok {
+ return val
+ } else if s.parent != nil {
+ return s.parent.Get(name)
+ } else if val, ok := builtinScope[name]; ok {
+ return val
+ } else {
+ return "<'" + name + "' unset>"
+ }
+}
+
+func (s *scope) Set(name, value string) {
+ s.variables[name] = value
+}
+
+func (s *scope) Call(name string, args []string) string {
+ if f, ok := s.functions[name]; ok {
+ return f(args)
+ }
+
+ return "<func:'" + name + "' unset>"
+}
+
+func (s *scope) SetFunc(name string, f func([]string) string) {
+ s.functions[name] = f
+}
+
+func NewScope(parent Scope) Scope {
+ return &scope{
+ variables: make(map[string]string),
+ functions: make(map[string]func([]string) string),
+ parent: parent,
+ }
+}
+
+var builtinScope map[string]string
+
+func init() {
+ builtinScope := make(map[string]string)
+ builtinScope["__builtin_dollar"] = "$"
+}
+
+func (v Variable) Value(scope Scope) string {
+ f := v.Name.SplitN(" \t", 2)
+ if len(f) > 1 && f[0].Const() {
+ fname := f[0].Value(nil)
+ if isFunctionName(fname) {
+ args := f[1].Split(",")
+ argVals := make([]string, len(args))
+ for i, a := range args {
+ argVals[i] = a.Value(scope)
+ }
+
+ if fname == "call" {
+ return scope.Call(argVals[0], argVals[1:])
+ } else {
+ return "__builtin_func:" + fname + " " + strings.Join(argVals, " ")
+ }
+ }
+ }
+
+ return scope.Get(v.Name.Value(scope))
+}
+
+func toVariable(ms *MakeString) (Variable, bool) {
+ if len(ms.Variables) == 1 && ms.Strings[0] == "" && ms.Strings[1] == "" {
+ return ms.Variables[0], true
+ }
+ return Variable{}, false
+}