androidbp: handle suffix props, conditionals, map assignments

Change-Id: I6aec388e72d960d80943620024c2d16d51a0b095
diff --git a/androidbp/cmd/androidbp.go b/androidbp/cmd/androidbp.go
index dbca35a..2e0d25c 100644
--- a/androidbp/cmd/androidbp.go
+++ b/androidbp/cmd/androidbp.go
@@ -5,6 +5,7 @@
 	"fmt"
 	"os"
 	"path"
+	"regexp"
 	"strings"
 
 	bpparser "github.com/google/blueprint/parser"
@@ -13,11 +14,13 @@
 type androidMkWriter struct {
 	*bufio.Writer
 
-	file *bpparser.File
-	path string
+	blueprint *bpparser.File
+	path      string
+
+	mapScope map[string][]*bpparser.Property
 }
 
-func (w *androidMkWriter) valueToString(value bpparser.Value) string {
+func valueToString(value bpparser.Value) string {
 	if value.Variable != "" {
 		return fmt.Sprintf("$(%s)", value.Variable)
 	} else {
@@ -25,90 +28,216 @@
 		case bpparser.Bool:
 			return fmt.Sprintf(`"%t"`, value.BoolValue)
 		case bpparser.String:
-			return fmt.Sprintf(`"%s"`, value.StringValue)
+			return fmt.Sprintf(`"%s"`, processWildcards(value.StringValue))
 		case bpparser.List:
-			return fmt.Sprintf("\\\n%s\n", w.listToMkString(value.ListValue))
+			return fmt.Sprintf("\\\n%s\n", listToMkString(value.ListValue))
 		case bpparser.Map:
-			w.errorf("maps not supported in assignment")
-			return "ERROR: unsupported type map in assignment"
+			return fmt.Sprintf("ERROR can't convert map to string")
+		default:
+			return fmt.Sprintf("ERROR: unsupported type %d", value.Type)
 		}
 	}
-
-	return ""
 }
 
-func (w *androidMkWriter) listToMkString(list []bpparser.Value) string {
+// TODO: handle non-recursive wildcards?
+func processWildcards(s string) string {
+	re := regexp.MustCompile("(.*)/\\*\\*/(.*)")
+	submatches := re.FindAllStringSubmatch(s, -1)
+	if submatches != nil && len(submatches[0]) > 2 {
+		// Found a wildcard rule
+		return fmt.Sprintf("$(call find-files-in-subdirs, $(LOCAL_PATH), %s, %s)",
+			submatches[0][2], submatches[0][1])
+	}
+
+	return s
+}
+
+func listToMkString(list []bpparser.Value) string {
 	lines := make([]string, 0, len(list))
 	for _, tok := range list {
-		lines = append(lines, fmt.Sprintf("\t\"%s\"", tok.StringValue))
+		if tok.Type == bpparser.String {
+			lines = append(lines, fmt.Sprintf("\t\"%s\"", processWildcards(tok.StringValue)))
+		} else {
+			lines = append(lines, fmt.Sprintf("# ERROR: unsupported type %s in list",
+				tok.Type.String()))
+		}
 	}
 
 	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 translateTargetConditionals(props []*bpparser.Property,
+	disabledBuilds map[string]bool, isHostRule bool) (computedProps []string) {
+	for _, target := range props {
+		conditionals := targetScopedPropertyConditionals
+		if isHostRule {
+			conditionals = hostScopedPropertyConditionals
+		}
+
+		conditional, ok := conditionals[target.Name.Name]
+		if !ok {
+			// not found
+			conditional = fmt.Sprintf(
+				"ifeq(true, true) # ERROR: unsupported conditional host [%s]",
+				target.Name.Name)
+		}
+
+		var scopedProps []string
+		for _, targetScopedProp := range target.Value.MapValue {
+			if mkProp, ok := standardProperties[targetScopedProp.Name.Name]; ok {
+				scopedProps = append(scopedProps, fmt.Sprintf("%s += %s",
+					mkProp.string, valueToString(targetScopedProp.Value)))
+			} else if "disabled" == targetScopedProp.Name.Name {
+				if targetScopedProp.Value.BoolValue {
+					disabledBuilds[target.Name.Name] = true
+				} else {
+					delete(disabledBuilds, target.Name.Name)
+				}
+			}
+		}
+
+		if len(scopedProps) > 0 {
+			computedProps = append(computedProps, conditional)
+			computedProps = append(computedProps, scopedProps...)
+			computedProps = append(computedProps, "endif")
+		}
 	}
+
+	return
+}
+
+func translateSuffixProperties(suffixProps []*bpparser.Property,
+	suffixMap map[string]string) (computedProps []string) {
+	for _, suffixProp := range suffixProps {
+		if suffix, ok := suffixMap[suffixProp.Name.Name]; ok {
+			for _, stdProp := range suffixProp.Value.MapValue {
+				if mkProp, ok := standardProperties[stdProp.Name.Name]; ok {
+					computedProps = append(computedProps, fmt.Sprintf("%s_%s := %s", mkProp.string, suffix, valueToString(stdProp.Value)))
+				} else {
+					computedProps = append(computedProps, fmt.Sprintf("# ERROR: unsupported property %s", stdProp.Name.Name))
+				}
+			}
+		}
+	}
+	return
+}
+
+func (w *androidMkWriter) lookupMap(parent bpparser.Value) (mapValue []*bpparser.Property) {
+	if parent.Variable != "" {
+		mapValue = w.mapScope[parent.Variable]
+	} else {
+		mapValue = parent.MapValue
+	}
+	return
 }
 
 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)
+		fmt.Fprintf(w, "#%s\n", c)
 	}
 }
 
-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)))
-			}
+func (w *androidMkWriter) writeModule(moduleRule string, props []string,
+	disabledBuilds map[string]bool, isHostRule bool) {
+	disabledConditionals := disabledTargetConditionals
+	if isHostRule {
+		disabledConditionals = disabledHostConditionals
+	}
+	for build, _ := range disabledBuilds {
+		if conditional, ok := disabledConditionals[build]; ok {
+			fmt.Fprintf(w, "%s\n", conditional)
+			defer fmt.Fprintf(w, "endif\n")
 		}
+	}
 
-		mkModule := strings.Join(standardProps, "\n")
-		w.WriteString(mkModule)
+	fmt.Fprintf(w, "include $(CLEAR_VARS)\n")
+	fmt.Fprintf(w, "%s\n", strings.Join(props, "\n"))
+	fmt.Fprintf(w, "include $(%s)\n\n", moduleRule)
+}
 
-		fmt.Fprintf(w, "include $(%s)\n\n", moduleName)
-	} else {
-		w.errorf("Unsupported module %s", module.Type.Name)
+func (w *androidMkWriter) handleModule(module *bpparser.Module) {
+	moduleRule := fmt.Sprintf(module.Type.Name)
+	if translation, ok := moduleTypeToRule[module.Type.Name]; ok {
+		moduleRule = translation
+	}
+
+	isHostRule := strings.Contains(moduleRule, "HOST")
+	hostSupported := false
+	standardProps := make([]string, 0, len(module.Properties))
+	disabledBuilds := make(map[string]bool)
+	for _, prop := range module.Properties {
+		if mkProp, ok := standardProperties[prop.Name.Name]; ok {
+			standardProps = append(standardProps, fmt.Sprintf("%s := %s", mkProp.string, valueToString(prop.Value)))
+		} else if suffixMap, ok := suffixProperties[prop.Name.Name]; ok {
+			suffixProps := w.lookupMap(prop.Value)
+			standardProps = append(standardProps, translateSuffixProperties(suffixProps, suffixMap)...)
+		} else if "target" == prop.Name.Name {
+			props := w.lookupMap(prop.Value)
+			standardProps = append(standardProps, translateTargetConditionals(props, disabledBuilds, isHostRule)...)
+		} else if "host_supported" == prop.Name.Name {
+			hostSupported = prop.Value.BoolValue
+		} else {
+			standardProps = append(standardProps, fmt.Sprintf("# ERROR: Unsupported property %s", prop.Name.Name))
+		}
+	}
+
+	// write out target build
+	w.writeModule(moduleRule, standardProps, disabledBuilds, isHostRule)
+	if hostSupported {
+		hostModuleRule := "NO CORRESPONDING HOST RULE" + moduleRule
+		if trans, ok := targetToHostModuleRule[moduleRule]; ok {
+			hostModuleRule = trans
+		}
+		w.writeModule(hostModuleRule, standardProps,
+			disabledBuilds, true)
+	}
+}
+
+func (w *androidMkWriter) handleSubdirs(value bpparser.Value) {
+	switch value.Type {
+	case bpparser.String:
+		fmt.Fprintf(w, "$(call all-makefiles-under, %s)\n", value.StringValue)
+	case bpparser.List:
+		for _, tok := range value.ListValue {
+			fmt.Fprintf(w, "$(call all-makefiles-under, %s)\n", tok.StringValue)
+		}
 	}
 }
 
 func (w *androidMkWriter) handleAssignment(assignment *bpparser.Assignment) {
-	assigner := ":="
-	if assignment.Assigner != "=" {
-		assigner = assignment.Assigner
+	if "subdirs" == assignment.Name.Name {
+		w.handleSubdirs(assignment.OrigValue)
+	} else if assignment.OrigValue.Type == bpparser.Map {
+		// maps may be assigned in Soong, but can only be translated to .mk
+		// in the context of the module
+		w.mapScope[assignment.Name.Name] = assignment.OrigValue.MapValue
+	} else {
+		assigner := ":="
+		if assignment.Assigner != "=" {
+			assigner = assignment.Assigner
+		}
+		fmt.Fprintf(w, "%s %s %s\n", assignment.Name.Name, assigner,
+			valueToString(assignment.OrigValue))
 	}
-	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))
+	ch := make(chan interface{}, len(w.blueprint.Comments)+len(w.blueprint.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]
+		for defsIdx < len(w.blueprint.Defs) || commIdx < len(w.blueprint.Comments) {
+			if defsIdx == len(w.blueprint.Defs) {
+				ch <- w.blueprint.Comments[commIdx]
 				commIdx++
-			} else if commIdx == len(w.file.Comments) {
-				ch <- w.file.Defs[defsIdx]
+			} else if commIdx == len(w.blueprint.Comments) {
+				ch <- w.blueprint.Defs[defsIdx]
 				defsIdx++
 			} else {
 				commentsPos := 0
 				defsPos := 0
 
-				def := w.file.Defs[defsIdx]
+				def := w.blueprint.Defs[defsIdx]
 				switch def := def.(type) {
 				case *bpparser.Module:
 					defsPos = def.LbracePos.Line
@@ -116,7 +245,7 @@
 					defsPos = def.Pos.Line
 				}
 
-				comment := w.file.Comments[commIdx]
+				comment := w.blueprint.Comments[commIdx]
 				commentsPos = comment.Pos.Line
 
 				if commentsPos < defsPos {
@@ -134,7 +263,7 @@
 }
 
 func (w *androidMkWriter) write() {
-	outFilePath := fmt.Sprintf("%s/Android.mk.out", w.path)
+	outFilePath := fmt.Sprintf("%s/Androidbp.mk", w.path)
 	fmt.Printf("Writing %s\n", outFilePath)
 
 	f, err := os.Create(outFilePath)
@@ -142,14 +271,12 @@
 		panic(err)
 	}
 
-	defer func() {
-		if err := f.Close(); err != nil {
-			panic(err)
-		}
-	}()
+	defer f.Close()
 
 	w.Writer = bufio.NewWriter(f)
 
+	w.WriteString("LOCAL_PATH := $(call my-dir)\n")
+
 	for block := range w.iter() {
 		switch block := block.(type) {
 		case *bpparser.Module:
@@ -179,7 +306,7 @@
 	}
 
 	scope := bpparser.NewScope(nil)
-	file, errs := bpparser.Parse(os.Args[1], reader, scope)
+	blueprint, 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)
@@ -187,8 +314,9 @@
 	}
 
 	writer := &androidMkWriter{
-		file: file,
-		path: path.Dir(os.Args[1]),
+		blueprint: blueprint,
+		path:      path.Dir(os.Args[1]),
+		mapScope:  make(map[string][]*bpparser.Property),
 	}
 
 	writer.write()
diff --git a/androidbp/cmd/soong.go b/androidbp/cmd/soong.go
index db27071..b8d7e0e 100644
--- a/androidbp/cmd/soong.go
+++ b/androidbp/cmd/soong.go
@@ -19,6 +19,7 @@
 	"manifest":     {"LOCAL_JAR_MANIFEST", bpparser.String},
 	"jarjar_rules": {"LOCAL_JARJAR_RULES", bpparser.String},
 	"certificate":  {"LOCAL_CERTIFICATE", bpparser.String},
+	"suffix":       {"LOCAL_MODULE_SUFFIX", bpparser.String},
 	//"name":             "LOCAL_PACKAGE_NAME", TODO
 
 	// ==== LIST PROPERTIES ====
@@ -62,7 +63,7 @@
 	"export_package_resources": {"LOCAL_EXPORT_PACKAGE_RESOURCES", bpparser.Bool},
 }
 
-var moduleTypes = map[string]string{
+var moduleTypeToRule = map[string]string{
 	"cc_library_shared":        "BUILD_SHARED_LIBRARY",
 	"cc_library_static":        "BUILD_STATIC_LIBRARY",
 	"cc_library_host_shared":   "BUILD_HOST_SHARED_LIBRARY",
@@ -80,3 +81,50 @@
 	"android_app":              "BUILD_PACKAGE",
 	"prebuilt":                 "BUILD_PREBUILT",
 }
+
+var suffixProperties = map[string]map[string]string{
+	"multilib": {"lib32": "32", "lib64": "64"},
+	"arch": {"arm": "arm", "arm64": "arm64", "mips": "mips", "mips64": "mips64",
+		"x86": "x86", "x86_64": "x86_64"},
+}
+
+var hostScopedPropertyConditionals = map[string]string{
+	"darwin":      "ifeq ($(HOST_OS), darwin)",
+	"not_darwin":  "ifneq($(HOST_OS), darwin)",
+	"windows":     "ifeq ($(HOST_OS), windows)",
+	"not_windows": "ifneq($(HOST_OS), windows)",
+	"linux":       "ifeq ($(HOST_OS), linux)",
+	"not_linux":   "ifneq($(HOST_OS), linux)",
+}
+
+// TODO: host target?
+var targetScopedPropertyConditionals = map[string]string{
+	"android32":     "ifeq($(TARGET_IS_64_BIT), false)",
+	"not_android32": "ifeq($(TARGET_IS_64_BIT), true)",
+	"android64":     "ifeq($(TARGET_IS_64_BIT), true)",
+	"not_android64": "ifeq($(TARGET_IS_64_BIT), false)",
+}
+
+var disabledHostConditionals = map[string]string{
+	"darwin":      "ifneq ($(HOST_OS), darwin)",
+	"not_darwin":  "ifeq($(HOST_OS), darwin)",
+	"windows":     "ifneq ($(HOST_OS), windows)",
+	"not_windows": "ifeq($(HOST_OS), windows)",
+	"linux":       "ifneq ($(HOST_OS), linux)",
+	"not_linux":   "ifeq($(HOST_OS), linux)",
+}
+
+var disabledTargetConditionals = map[string]string{
+	"android32":     "ifeq($(TARGET_IS_64_BIT), true)",
+	"not_android32": "ifeq($(TARGET_IS_64_BIT), false)",
+	"android64":     "ifeq($(TARGET_IS_64_BIT), false)",
+	"not_android64": "ifeq($(TARGET_IS_64_BIT), true)",
+}
+
+var targetToHostModuleRule = map[string]string{
+	"BUILD_SHARED_LIBRARY": "BUILD_HOST_SHARED_LIBRARY",
+	"BUILD_STATIC_LIBRARY": "BUILD_HOST_STATIC_LIBRARY",
+	"BUILD_EXECUTABLE":     "BUILD_HOST_EXECUTABLE",
+	"BUILD_NATIVE_TEST":    "BUILD_HOST_NATIVE_TEST",
+	"BUILD_JAVA_LIBRARY":   "BUILD_HOST_JAVA_LIBRARY",
+}