Add support for setting string property in bpmodify

Add a -str argument to set a property to a string value.

Bug: 186723288
Test: bpmodify_test.go
Change-Id: I490e3be182693c8720020814da579e6e788b3d9f
diff --git a/bpmodify/bpmodify.go b/bpmodify/bpmodify.go
index 0190f19..29d28f0 100644
--- a/bpmodify/bpmodify.go
+++ b/bpmodify/bpmodify.go
@@ -30,6 +30,8 @@
 	targetedProperty = new(qualifiedProperty)
 	addIdents        = new(identSet)
 	removeIdents     = new(identSet)
+
+	setString *string
 )
 
 func init() {
@@ -38,6 +40,7 @@
 	flag.Var(targetedProperty, "property", "fully qualified `name` of property to modify (default \"deps\")")
 	flag.Var(addIdents, "a", "comma or whitespace separated list of identifiers to add")
 	flag.Var(removeIdents, "r", "comma or whitespace separated list of identifiers to remove")
+	flag.Var(stringPtrFlag{&setString}, "str", "set a string property")
 	flag.Usage = usage
 }
 
@@ -147,14 +150,18 @@
 		return false, []error{err}
 	}
 	if prop == nil {
-		if len(addIdents.idents) == 0 {
+		if len(addIdents.idents) > 0 {
+			// We are adding something to a non-existing list prop, so we need to create it first.
+			prop, modified, err = createRecursiveProperty(module, targetedProperty.name(), targetedProperty.prefixes(), &parser.List{})
+		} else if setString != nil {
+			// We setting a non-existent string property, so we need to create it first.
+			prop, modified, err = createRecursiveProperty(module, targetedProperty.name(), targetedProperty.prefixes(), &parser.String{})
+		} else {
 			// We cannot find an existing prop, and we aren't adding anything to the prop,
 			// which means we must be removing something from a non-existing prop,
 			// which means this is a noop.
 			return false, nil
 		}
-		// Else we are adding something to a non-existing prop, so we need to create it first.
-		prop, modified, err = createRecursiveProperty(module, targetedProperty.name(), targetedProperty.prefixes())
 		if err != nil {
 			// Here should be unreachable, but still handle it for completeness.
 			return false, []error{err}
@@ -166,16 +173,18 @@
 }
 
 func getRecursiveProperty(module *parser.Module, name string, prefixes []string) (prop *parser.Property, err error) {
-	prop, _, err = getOrCreateRecursiveProperty(module, name, prefixes, false)
+	prop, _, err = getOrCreateRecursiveProperty(module, name, prefixes, nil)
 	return prop, err
 }
 
-func createRecursiveProperty(module *parser.Module, name string, prefixes []string) (prop *parser.Property, modified bool, err error) {
-	return getOrCreateRecursiveProperty(module, name, prefixes, true)
+func createRecursiveProperty(module *parser.Module, name string, prefixes []string,
+	empty parser.Expression) (prop *parser.Property, modified bool, err error) {
+
+	return getOrCreateRecursiveProperty(module, name, prefixes, empty)
 }
 
 func getOrCreateRecursiveProperty(module *parser.Module, name string, prefixes []string,
-	createIfNotFound bool) (prop *parser.Property, modified bool, err error) {
+	empty parser.Expression) (prop *parser.Property, modified bool, err error) {
 	m := &module.Map
 	for i, prefix := range prefixes {
 		if prop, found := m.GetProperty(prefix); found {
@@ -187,7 +196,7 @@
 				return nil, false, fmt.Errorf("Expected property %q to be a map, found %s",
 					strings.Join(prefixes[:i+1], "."), prop.Value.Type())
 			}
-		} else if createIfNotFound {
+		} else if empty != nil {
 			mm := &parser.Map{}
 			m.Properties = append(m.Properties, &parser.Property{Name: prefix, Value: mm})
 			m = mm
@@ -201,8 +210,8 @@
 	if prop, found := m.GetProperty(name); found {
 		// We've found a property in the AST, which must mean we didn't modify the AST.
 		return prop, false, nil
-	} else if createIfNotFound {
-		prop = &parser.Property{Name: name, Value: &parser.List{}}
+	} else if empty != nil {
+		prop = &parser.Property{Name: name, Value: empty}
 		m.Properties = append(m.Properties, prop)
 		return prop, true, nil
 	} else {
@@ -222,26 +231,37 @@
 			paramName, moduleName)}
 	}
 
-	list, ok := value.(*parser.List)
-	if !ok {
-		return false, []error{fmt.Errorf("expected parameter %s in module %s to be list, found %s",
-			paramName, moduleName, value.Type().String())}
-	}
+	if len(addIdents.idents) > 0 || len(removeIdents.idents) > 0 {
+		list, ok := value.(*parser.List)
+		if !ok {
+			return false, []error{fmt.Errorf("expected parameter %s in module %s to be list, found %s",
+				paramName, moduleName, value.Type().String())}
+		}
 
-	wasSorted := parser.ListIsSorted(list)
+		wasSorted := parser.ListIsSorted(list)
 
-	for _, a := range addIdents.idents {
-		m := parser.AddStringToList(list, a)
-		modified = modified || m
-	}
+		for _, a := range addIdents.idents {
+			m := parser.AddStringToList(list, a)
+			modified = modified || m
+		}
 
-	for _, r := range removeIdents.idents {
-		m := parser.RemoveStringFromList(list, r)
-		modified = modified || m
-	}
+		for _, r := range removeIdents.idents {
+			m := parser.RemoveStringFromList(list, r)
+			modified = modified || m
+		}
 
-	if (wasSorted || *sortLists) && modified {
-		parser.SortList(file, list)
+		if (wasSorted || *sortLists) && modified {
+			parser.SortList(file, list)
+		}
+	} else if setString != nil {
+		str, ok := value.(*parser.String)
+		if !ok {
+			return false, []error{fmt.Errorf("expected parameter %s in module %s to be string, found %s",
+				paramName, moduleName, value.Type().String())}
+		}
+
+		str.Value = *setString
+		modified = true
 	}
 
 	return modified, nil
@@ -304,8 +324,8 @@
 		return
 	}
 
-	if len(addIdents.idents) == 0 && len(removeIdents.idents) == 0 {
-		report(fmt.Errorf("-a or -r parameter is required"))
+	if len(addIdents.idents) == 0 && len(removeIdents.idents) == 0 && setString == nil {
+		report(fmt.Errorf("-a, -r or -str parameter is required"))
 		return
 	}
 
@@ -352,6 +372,22 @@
 
 }
 
+type stringPtrFlag struct {
+	s **string
+}
+
+func (f stringPtrFlag) Set(s string) error {
+	*f.s = &s
+	return nil
+}
+
+func (f stringPtrFlag) String() string {
+	if f.s == nil || *f.s == nil {
+		return ""
+	}
+	return **f.s
+}
+
 type identSet struct {
 	idents []string
 	all    bool
diff --git a/bpmodify/bpmodify_test.go b/bpmodify/bpmodify_test.go
index 7ccd06e..a92d439 100644
--- a/bpmodify/bpmodify_test.go
+++ b/bpmodify/bpmodify_test.go
@@ -19,6 +19,7 @@
 	"testing"
 
 	"github.com/google/blueprint/parser"
+	"github.com/google/blueprint/proptools"
 )
 
 var testCases = []struct {
@@ -28,6 +29,7 @@
 	property  string
 	addSet    string
 	removeSet string
+	setString *string
 }{
 	{
 		name: "add",
@@ -249,6 +251,39 @@
 		property: "deps",
 		addSet:   "bar-v10-bar",
 	},
+	{
+		name: "set string",
+		input: `
+			cc_foo {
+				name: "foo",
+			}
+		`,
+		output: `
+			cc_foo {
+				name: "foo",
+				foo: "bar",
+			}
+		`,
+		property:  "foo",
+		setString: proptools.StringPtr("bar"),
+	},
+	{
+		name: "set existing string",
+		input: `
+			cc_foo {
+				name: "foo",
+				foo: "baz",
+			}
+		`,
+		output: `
+			cc_foo {
+				name: "foo",
+				foo: "bar",
+			}
+		`,
+		property:  "foo",
+		setString: proptools.StringPtr("bar"),
+	},
 }
 
 func simplifyModuleDefinition(def string) string {
@@ -265,6 +300,7 @@
 			targetedProperty.Set(testCase.property)
 			addIdents.Set(testCase.addSet)
 			removeIdents.Set(testCase.removeSet)
+			setString = testCase.setString
 
 			inAst, errs := parser.ParseAndEval("", strings.NewReader(testCase.input), parser.NewScope(nil))
 			if len(errs) > 0 {