Initial androidbp translator.

Translates Android.bp files back to Android.mk

Change-Id: Ib4bd1e0abc58ab514a7dd4a01008af645d6a3d13
diff --git a/androidbp/cmd/androidbp.go b/androidbp/cmd/androidbp.go
new file mode 100644
index 0000000..dbca35a
--- /dev/null
+++ b/androidbp/cmd/androidbp.go
@@ -0,0 +1,195 @@
+package main
+
+import (
+	"bufio"
+	"fmt"
+	"os"
+	"path"
+	"strings"
+
+	bpparser "github.com/google/blueprint/parser"
+)
+
+type androidMkWriter struct {
+	*bufio.Writer
+
+	file *bpparser.File
+	path string
+}
+
+func (w *androidMkWriter) valueToString(value bpparser.Value) string {
+	if value.Variable != "" {
+		return fmt.Sprintf("$(%s)", value.Variable)
+	} else {
+		switch value.Type {
+		case bpparser.Bool:
+			return fmt.Sprintf(`"%t"`, value.BoolValue)
+		case bpparser.String:
+			return fmt.Sprintf(`"%s"`, value.StringValue)
+		case bpparser.List:
+			return fmt.Sprintf("\\\n%s\n", w.listToMkString(value.ListValue))
+		case bpparser.Map:
+			w.errorf("maps not supported in assignment")
+			return "ERROR: unsupported type map in assignment"
+		}
+	}
+
+	return ""
+}
+
+func (w *androidMkWriter) listToMkString(list []bpparser.Value) string {
+	lines := make([]string, 0, len(list))
+	for _, tok := range list {
+		lines = append(lines, fmt.Sprintf("\t\"%s\"", tok.StringValue))
+	}
+
+	return strings.Join(lines, " \\\n")
+}
+
+func (w *androidMkWriter) errorf(format string, values ...interface{}) {
+	s := fmt.Sprintf(format, values)
+	w.WriteString("# ANDROIDBP ERROR:\n")
+	for _, line := range strings.Split(s, "\n") {
+		fmt.Fprintf(w, "# %s\n", line)
+	}
+}
+
+func (w *androidMkWriter) handleComment(comment *bpparser.Comment) {
+	for _, c := range comment.Comment {
+		mkComment := strings.Replace(c, "//", "#", 1)
+		// TODO: handle /* comments?
+		fmt.Fprintf(w, "%s\n", mkComment)
+	}
+}
+
+func (w *androidMkWriter) handleModule(module *bpparser.Module) {
+	if moduleName, ok := moduleTypes[module.Type.Name]; ok {
+		w.WriteString("include $(CLEAR_VARS)\n")
+		standardProps := make([]string, 0, len(module.Properties))
+		//condProps := make([]string, len(module.Properties))
+		for _, prop := range module.Properties {
+			if mkProp, ok := standardProperties[prop.Name.Name]; ok {
+				standardProps = append(standardProps, fmt.Sprintf("%s := %s", mkProp.string,
+					w.valueToString(prop.Value)))
+			}
+		}
+
+		mkModule := strings.Join(standardProps, "\n")
+		w.WriteString(mkModule)
+
+		fmt.Fprintf(w, "include $(%s)\n\n", moduleName)
+	} else {
+		w.errorf("Unsupported module %s", module.Type.Name)
+	}
+}
+
+func (w *androidMkWriter) handleAssignment(assignment *bpparser.Assignment) {
+	assigner := ":="
+	if assignment.Assigner != "=" {
+		assigner = assignment.Assigner
+	}
+	fmt.Fprintf(w, "%s %s %s\n", assignment.Name.Name, assigner,
+		w.valueToString(assignment.OrigValue))
+}
+
+func (w *androidMkWriter) iter() <-chan interface{} {
+	ch := make(chan interface{}, len(w.file.Comments)+len(w.file.Defs))
+	go func() {
+		commIdx := 0
+		defsIdx := 0
+		for defsIdx < len(w.file.Defs) || commIdx < len(w.file.Comments) {
+			if defsIdx == len(w.file.Defs) {
+				ch <- w.file.Comments[commIdx]
+				commIdx++
+			} else if commIdx == len(w.file.Comments) {
+				ch <- w.file.Defs[defsIdx]
+				defsIdx++
+			} else {
+				commentsPos := 0
+				defsPos := 0
+
+				def := w.file.Defs[defsIdx]
+				switch def := def.(type) {
+				case *bpparser.Module:
+					defsPos = def.LbracePos.Line
+				case *bpparser.Assignment:
+					defsPos = def.Pos.Line
+				}
+
+				comment := w.file.Comments[commIdx]
+				commentsPos = comment.Pos.Line
+
+				if commentsPos < defsPos {
+					commIdx++
+					ch <- comment
+				} else {
+					defsIdx++
+					ch <- def
+				}
+			}
+		}
+		close(ch)
+	}()
+	return ch
+}
+
+func (w *androidMkWriter) write() {
+	outFilePath := fmt.Sprintf("%s/Android.mk.out", w.path)
+	fmt.Printf("Writing %s\n", outFilePath)
+
+	f, err := os.Create(outFilePath)
+	if err != nil {
+		panic(err)
+	}
+
+	defer func() {
+		if err := f.Close(); err != nil {
+			panic(err)
+		}
+	}()
+
+	w.Writer = bufio.NewWriter(f)
+
+	for block := range w.iter() {
+		switch block := block.(type) {
+		case *bpparser.Module:
+			w.handleModule(block)
+		case *bpparser.Assignment:
+			w.handleAssignment(block)
+		case bpparser.Comment:
+			w.handleComment(&block)
+		}
+	}
+
+	if err = w.Flush(); err != nil {
+		panic(err)
+	}
+}
+
+func main() {
+	if len(os.Args) < 2 {
+		fmt.Println("No filename supplied")
+		return
+	}
+
+	reader, err := os.Open(os.Args[1])
+	if err != nil {
+		fmt.Println(err.Error())
+		return
+	}
+
+	scope := bpparser.NewScope(nil)
+	file, errs := bpparser.Parse(os.Args[1], reader, scope)
+	if len(errs) > 0 {
+		fmt.Println("%d errors parsing %s", len(errs), os.Args[1])
+		fmt.Println(errs)
+		return
+	}
+
+	writer := &androidMkWriter{
+		file: file,
+		path: path.Dir(os.Args[1]),
+	}
+
+	writer.write()
+}