androidbp: Implement static/shared property copying

During mutation, make deep copies of the blueprint modules, then start
modifying the property lists to apply the static/shared properties.

Change-Id: Idc7063f4d1cf7d173ae10418e3437f3e2b914f59
diff --git a/androidbp/cmd/androidbp.go b/androidbp/cmd/androidbp.go
index ef7a364..ca33cb8 100644
--- a/androidbp/cmd/androidbp.go
+++ b/androidbp/cmd/androidbp.go
@@ -18,43 +18,6 @@
 
 var recursiveSubdirRegex *regexp.Regexp = regexp.MustCompile("(.+)/\\*\\*/(.+)")
 
-type Module struct {
-	bpmod      *bpparser.Module
-	bpname     string
-	mkname     string
-	isHostRule bool
-}
-
-func newModule(mod *bpparser.Module) *Module {
-	return &Module{
-		bpmod:  mod,
-		bpname: mod.Type.Name,
-	}
-}
-
-func (m *Module) translateRuleName() error {
-	var name string
-	if translation, ok := moduleTypeToRule[m.bpname]; ok {
-		name = translation
-	} else {
-		return fmt.Errorf("Unknown module type %q", m.bpname)
-	}
-
-	if m.isHostRule {
-		if trans, ok := targetToHostModuleRule[name]; ok {
-			name = trans
-		} else {
-			return fmt.Errorf("No corresponding host rule for %q", name)
-		}
-	} else {
-		m.isHostRule = strings.Contains(name, "HOST")
-	}
-
-	m.mkname = name
-
-	return nil
-}
-
 type androidMkWriter struct {
 	io.Writer
 
@@ -85,6 +48,22 @@
 	}
 }
 
+func appendValueToValue(dest bpparser.Value, src bpparser.Value) (bpparser.Value, error) {
+	if src.Type != dest.Type {
+		return bpparser.Value{}, fmt.Errorf("ERROR: source and destination types don't match")
+	}
+	switch dest.Type {
+	case bpparser.List:
+		dest.ListValue = append(dest.ListValue, src.ListValue...)
+		return dest, nil
+	case bpparser.String:
+		dest.StringValue += src.StringValue
+		return dest, nil
+	default:
+		return bpparser.Value{}, fmt.Errorf("ERROR: unsupported append with type %s", dest.Type.String())
+	}
+}
+
 func getTopOfAndroidTree(wd string) (string, error) {
 	if !filepath.IsAbs(wd) {
 		return "", errors.New("path must be absolute: " + wd)
@@ -257,15 +236,6 @@
 	}, nil
 }
 
-func modulePropBool(module *bpparser.Module, name string) bool {
-	for _, prop := range module.Properties {
-		if name == prop.Name.Name {
-			return prop.Value.BoolValue
-		}
-	}
-	return false
-}
-
 func (w *androidMkWriter) writeModule(moduleRule string, props []string,
 	disabledBuilds map[string]bool, isHostRule bool) {
 	disabledConditionals := disabledTargetConditionals
@@ -331,8 +301,41 @@
 			newModule(module.bpmod),
 			newModule(module.bpmod),
 		}
+
+		ccLinkageCopy := func(props Properties, prop *bpparser.Property) error {
+			for _, p := range prop.Value.MapValue {
+				err := props.AppendToProp(p.Name.Name, p)
+				if err != nil {
+					return err
+				}
+			}
+			props.DeleteProp(prop.Name.Name)
+			return nil
+		}
+		ccLinkageDelete := func(props Properties, prop *bpparser.Property) error {
+			props.DeleteProp(prop.Name.Name)
+			return nil
+		}
+
 		modules[0].bpname = "cc_library_shared"
+		err := modules[0].IterateArchPropertiesWithName("shared", ccLinkageCopy)
+		if err != nil {
+			return nil, err
+		}
+		err = modules[0].IterateArchPropertiesWithName("static", ccLinkageDelete)
+		if err != nil {
+			return nil, err
+		}
+
 		modules[1].bpname = "cc_library_static"
+		err = modules[1].IterateArchPropertiesWithName("shared", ccLinkageDelete)
+		if err != nil {
+			return nil, err
+		}
+		err = modules[1].IterateArchPropertiesWithName("static", ccLinkageCopy)
+		if err != nil {
+			return nil, err
+		}
 	}
 
 	for _, mod := range modules {
@@ -340,7 +343,7 @@
 		if err != nil {
 			return nil, err
 		}
-		if mod.isHostRule || !modulePropBool(mod.bpmod, "host_supported") {
+		if mod.isHostRule || !mod.PropBool("host_supported") {
 			continue
 		}
 
diff --git a/androidbp/cmd/androidbp_test.go b/androidbp/cmd/androidbp_test.go
index 87170ed..cf5be7a 100644
--- a/androidbp/cmd/androidbp_test.go
+++ b/androidbp/cmd/androidbp_test.go
@@ -104,6 +104,33 @@
 			    LOCAL_MODULE := test
 			    include $(BUILD_HOST_STATIC_LIBRARY)`,
 	},
+	// Static and Shared properties
+	{
+		blueprint: `cc_library {
+				name: "test",
+				srcs: ["a"],
+				static: { srcs: ["c"], static_libs: ["l"], },
+				shared: { srcs: ["b"], },
+				multilib: { lib32: { shared: { cflags: ["f"], }, }, },
+			    }`,
+		androidmk: `include $(CLEAR_VARS)
+			    LOCAL_MODULE := test
+			    LOCAL_SRC_FILES := \
+			        a \
+			        b
+			    LOCAL_CFLAGS_32 := \
+			        f
+			    include $(BUILD_SHARED_LIBRARY)
+
+			    include $(CLEAR_VARS)
+			    LOCAL_MODULE := test
+			    LOCAL_SRC_FILES := \
+			        a \
+			        c
+			    LOCAL_STATIC_LIBRARIES := \
+			        l
+			    include $(BUILD_STATIC_LIBRARY)`,
+	},
 	// Manual translation
 	{
 		blueprint: `/* Android.mk:start
diff --git a/androidbp/cmd/module.go b/androidbp/cmd/module.go
new file mode 100644
index 0000000..648ff88
--- /dev/null
+++ b/androidbp/cmd/module.go
@@ -0,0 +1,121 @@
+package main
+
+import (
+	"fmt"
+	"strings"
+
+	bpparser "github.com/google/blueprint/parser"
+)
+
+type Module struct {
+	bpmod      *bpparser.Module
+	bpname     string
+	mkname     string
+	isHostRule bool
+}
+
+func newModule(mod *bpparser.Module) *Module {
+	return &Module{
+		bpmod:  mod.Copy(),
+		bpname: mod.Type.Name,
+	}
+}
+
+func (m *Module) translateRuleName() error {
+	var name string
+	if translation, ok := moduleTypeToRule[m.bpname]; ok {
+		name = translation
+	} else {
+		return fmt.Errorf("Unknown module type %q", m.bpname)
+	}
+
+	if m.isHostRule {
+		if trans, ok := targetToHostModuleRule[name]; ok {
+			name = trans
+		} else {
+			return fmt.Errorf("No corresponding host rule for %q", name)
+		}
+	} else {
+		m.isHostRule = strings.Contains(name, "HOST")
+	}
+
+	m.mkname = name
+
+	return nil
+}
+
+func (m *Module) Properties() Properties {
+	return Properties{&m.bpmod.Properties}
+}
+
+func (m *Module) PropBool(name string) bool {
+	if prop, ok := m.Properties().Prop(name); ok {
+		return prop.Value.BoolValue
+	}
+	return false
+}
+
+func (m *Module) IterateArchPropertiesWithName(name string, f func(Properties, *bpparser.Property) error) error {
+	if p, ok := m.Properties().Prop(name); ok {
+		err := f(m.Properties(), p)
+		if err != nil {
+			return err
+		}
+	}
+
+	for _, prop := range m.bpmod.Properties {
+		switch prop.Name.Name {
+		case "arch", "multilib", "target":
+			for _, sub := range prop.Value.MapValue {
+				props := Properties{&sub.Value.MapValue}
+				if p, ok := props.Prop(name); ok {
+					err := f(props, p)
+					if err != nil {
+						return err
+					}
+				}
+			}
+		}
+	}
+
+	return nil
+}
+
+type Properties struct {
+	props *[]*bpparser.Property
+}
+
+func (p Properties) Prop(name string) (*bpparser.Property, bool) {
+	for _, prop := range *p.props {
+		if name == prop.Name.Name {
+			return prop, true
+		}
+	}
+	return nil, false
+}
+
+func (p Properties) AppendToProp(name string, src *bpparser.Property) error {
+	if d, ok := p.Prop(name); ok {
+		val, err := appendValueToValue(d.Value, src.Value)
+		if err != nil {
+			return err
+		}
+
+		d.Value = val
+	} else {
+		prop := src.Copy()
+		prop.Name.Name = name
+		*p.props = append(*p.props, prop)
+	}
+
+	return nil
+}
+
+func (p Properties) DeleteProp(name string) {
+	for i, prop := range *p.props {
+		if prop.Name.Name == name {
+			*p.props = append((*p.props)[0:i], (*p.props)[i+1:]...)
+			return
+		}
+	}
+}