Snap for 7347062 from 6ec1dbf6d48e295cafc4161d8fc006abfb20281d to sc-d1-release

Change-Id: I52c8986b460dd48aa86293a2430015c7ca3f4c2e
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 26b5d82..a92d439 100644
--- a/bpmodify/bpmodify_test.go
+++ b/bpmodify/bpmodify_test.go
@@ -19,234 +19,270 @@
 	"testing"
 
 	"github.com/google/blueprint/parser"
+	"github.com/google/blueprint/proptools"
 )
 
 var testCases = []struct {
+	name      string
 	input     string
 	output    string
 	property  string
 	addSet    string
 	removeSet string
+	setString *string
 }{
 	{
-		`
-		cc_foo {
-			name: "foo",
-		}
+		name: "add",
+		input: `
+			cc_foo {
+				name: "foo",
+			}
 		`,
-		`
-		cc_foo {
-			name: "foo",
-			deps: ["bar"],
-		}
+		output: `
+			cc_foo {
+				name: "foo",
+				deps: ["bar"],
+			}
 		`,
-		"deps",
-		"bar",
-		"",
+		property: "deps",
+		addSet:   "bar",
 	},
 	{
-		`
-		cc_foo {
-			name: "foo",
-			deps: ["bar"],
-		}
+		name: "remove",
+		input: `
+			cc_foo {
+				name: "foo",
+				deps: ["bar"],
+			}
 		`,
-		`
-		cc_foo {
-			name: "foo",
-			deps: [],
-		}
+		output: `
+			cc_foo {
+				name: "foo",
+				deps: [],
+			}
 		`,
-		"deps",
-		"",
-		"bar",
+		property:  "deps",
+		removeSet: "bar",
 	},
 	{
-		`
-		cc_foo {
-			name: "foo",
-		}
+		name: "nested add",
+		input: `
+			cc_foo {
+				name: "foo",
+			}
 		`,
-		`
-		cc_foo {
-			name: "foo",
-			arch: {
-				arm: {
-					deps: [
-						"dep2",
-						"nested_dep",],
+		output: `
+			cc_foo {
+				name: "foo",
+				arch: {
+					arm: {
+						deps: [
+							"dep2",
+							"nested_dep",],
+					},
 				},
-			},
-		}
+			}
 		`,
-		"arch.arm.deps",
-		"nested_dep,dep2",
-		"",
+		property: "arch.arm.deps",
+		addSet:   "nested_dep,dep2",
 	},
 	{
-		`
-		cc_foo {
-			name: "foo",
-			arch: {
-				arm: {
-					deps: [
-						"dep2",
-						"nested_dep",
-					],
+		name: "nested remove",
+		input: `
+			cc_foo {
+				name: "foo",
+				arch: {
+					arm: {
+						deps: [
+							"dep2",
+							"nested_dep",
+						],
+					},
 				},
-			},
-		}
+			}
 		`,
-		`
-		cc_foo {
-			name: "foo",
-			arch: {
-				arm: {
-					deps: [
-					],
+		output: `
+			cc_foo {
+				name: "foo",
+				arch: {
+					arm: {
+						deps: [
+						],
+					},
 				},
-			},
-		}
+			}
 		`,
-		"arch.arm.deps",
-		"",
-		"nested_dep,dep2",
+		property:  "arch.arm.deps",
+		removeSet: "nested_dep,dep2",
 	},
 	{
-		`
-		cc_foo {
-			name: "foo",
-			arch: {
-				arm: {
-					deps: [
-						"nested_dep",
-						"dep2",
-					],
+		name: "add existing",
+		input: `
+			cc_foo {
+				name: "foo",
+				arch: {
+					arm: {
+						deps: [
+							"nested_dep",
+							"dep2",
+						],
+					},
 				},
-			},
-		}
+			}
 		`,
-		`
-		cc_foo {
-			name: "foo",
-			arch: {
-				arm: {
-					deps: [
-						"nested_dep",
-						"dep2",
-					],
+		output: `
+			cc_foo {
+				name: "foo",
+				arch: {
+					arm: {
+						deps: [
+							"nested_dep",
+							"dep2",
+						],
+					},
 				},
-			},
-		}
+			}
 		`,
-		"arch.arm.deps",
-		"dep2,dep2",
-		"",
+		property: "arch.arm.deps",
+		addSet:   "dep2,dep2",
 	},
 	{
-		`
-		cc_foo {
-			name: "foo",
-			arch: {
-				arm: {
-					deps: [
-						"nested_dep",
-						"dep2",
-					],
+		name: "remove missing",
+		input: `
+			cc_foo {
+				name: "foo",
+				arch: {
+					arm: {
+						deps: [
+							"nested_dep",
+							"dep2",
+						],
+					},
 				},
-			},
-		}
+			}
 		`,
-		`
-		cc_foo {
-			name: "foo",
-			arch: {
-				arm: {
-					deps: [
-						"nested_dep",
-						"dep2",
-					],
+		output: `
+			cc_foo {
+				name: "foo",
+				arch: {
+					arm: {
+						deps: [
+							"nested_dep",
+							"dep2",
+						],
+					},
 				},
-			},
-		}
+			}
 		`,
-		"arch.arm.deps",
-		"",
-		"dep3,dep4",
+		property:  "arch.arm.deps",
+		removeSet: "dep3,dep4",
 	},
 	{
-		`
-		cc_foo {
-			name: "foo",
-		}
+		name: "remove non existent",
+		input: `
+			cc_foo {
+				name: "foo",
+			}
 		`,
-		`
-		cc_foo {
-			name: "foo",
-		}
+		output: `
+			cc_foo {
+				name: "foo",
+			}
 		`,
-		"deps",
-		"",
-		"bar",
+		property:  "deps",
+		removeSet: "bar",
 	},
 	{
-		`
-		cc_foo {
-			name: "foo",
-			arch: {},
-		}
+		name: "remove non existent nested",
+		input: `
+			cc_foo {
+				name: "foo",
+				arch: {},
+			}
 		`,
-		`
-		cc_foo {
-			name: "foo",
-			arch: {},
-		}
+		output: `
+			cc_foo {
+				name: "foo",
+				arch: {},
+			}
 		`,
-		"arch.arm.deps",
-		"",
-		"dep3,dep4",
+		property:  "arch.arm.deps",
+		removeSet: "dep3,dep4",
 	},
 	{
-		`
-		cc_foo {
-			name: "foo",
-			versions: ["1", "2"],
-		}
+		name: "add numeric sorted",
+		input: `
+			cc_foo {
+				name: "foo",
+				versions: ["1", "2"],
+			}
 		`,
-		`
-		cc_foo {
-			name: "foo",
-			versions: [
-				"1",
-				"2",
-				"10",
-			],
-		}
+		output: `
+			cc_foo {
+				name: "foo",
+				versions: [
+					"1",
+					"2",
+					"10",
+				],
+			}
 		`,
-		"versions",
-		"10",
-		"",
+		property: "versions",
+		addSet:   "10",
 	},
 	{
-		`
-		cc_foo {
-			name: "foo",
-			deps: ["bar-v1-bar", "bar-v2-bar"],
-		}
+		name: "add mixed sorted",
+		input: `
+			cc_foo {
+				name: "foo",
+				deps: ["bar-v1-bar", "bar-v2-bar"],
+			}
 		`,
-		`
-		cc_foo {
-			name: "foo",
-			deps: [
-				"bar-v1-bar",
-				"bar-v2-bar",
-				"bar-v10-bar",
-			],
-		}
+		output: `
+			cc_foo {
+				name: "foo",
+				deps: [
+					"bar-v1-bar",
+					"bar-v2-bar",
+					"bar-v10-bar",
+				],
+			}
 		`,
-		"deps",
-		"bar-v10-bar",
-		"",
+		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"),
 	},
 }
 
@@ -260,42 +296,43 @@
 
 func TestProcessModule(t *testing.T) {
 	for i, testCase := range testCases {
-		targetedProperty.Set(testCase.property)
-		addIdents.Set(testCase.addSet)
-		removeIdents.Set(testCase.removeSet)
+		t.Run(testCase.name, func(t *testing.T) {
+			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 {
-			t.Errorf("test case %d:", i)
-			for _, err := range errs {
-				t.Errorf("  %s", err)
-			}
-			t.Errorf("failed to parse:")
-			t.Errorf("%+v", testCase)
-			continue
-		}
-
-		if inModule, ok := inAst.Defs[0].(*parser.Module); !ok {
-			t.Errorf("test case %d:", i)
-			t.Errorf("  input must only contain a single module definition: %s", testCase.input)
-			continue
-		} else {
-			_, errs := processModule(inModule, "", inAst)
+			inAst, errs := parser.ParseAndEval("", strings.NewReader(testCase.input), parser.NewScope(nil))
 			if len(errs) > 0 {
-				t.Errorf("test case %d:", i)
 				for _, err := range errs {
 					t.Errorf("  %s", err)
 				}
+				t.Errorf("failed to parse:")
+				t.Errorf("%+v", testCase)
+				t.FailNow()
 			}
-			inModuleText, _ := parser.Print(inAst)
-			inModuleString := string(inModuleText)
-			if simplifyModuleDefinition(inModuleString) != simplifyModuleDefinition(testCase.output) {
-				t.Errorf("test case %d:", i)
-				t.Errorf("expected module definition:")
-				t.Errorf("  %s", testCase.output)
-				t.Errorf("actual module definition:")
-				t.Errorf("  %s", inModuleString)
+
+			if inModule, ok := inAst.Defs[0].(*parser.Module); !ok {
+				t.Fatalf("  input must only contain a single module definition: %s", testCase.input)
+			} else {
+				_, errs := processModule(inModule, "", inAst)
+				if len(errs) > 0 {
+					t.Errorf("test case %d:", i)
+					for _, err := range errs {
+						t.Errorf("  %s", err)
+					}
+				}
+				inModuleText, _ := parser.Print(inAst)
+				inModuleString := string(inModuleText)
+				if simplifyModuleDefinition(inModuleString) != simplifyModuleDefinition(testCase.output) {
+					t.Errorf("test case %d:", i)
+					t.Errorf("expected module definition:")
+					t.Errorf("  %s", testCase.output)
+					t.Errorf("actual module definition:")
+					t.Errorf("  %s", inModuleString)
+				}
 			}
-		}
+		})
 	}
+
 }