add Partition method to LabelListAttribute

It can be tedious to split a LabelListAttribute on all of the
configuration axes, so LabelListAttribute.Partition can be used to do
this based on a predicate function.

Test: go test ./bazel
Change-Id: I78ff8c24ce9c86bab5896290d8afe979d56545b6
diff --git a/bazel/properties.go b/bazel/properties.go
index 963e27b..7bbfea4 100644
--- a/bazel/properties.go
+++ b/bazel/properties.go
@@ -119,7 +119,7 @@
 	return dirs
 }
 
-// Add inserts the label Label at the end of the LabelList.
+// Add inserts the label Label at the end of the LabelList.Includes.
 func (ll *LabelList) Add(label *Label) {
 	if label == nil {
 		return
@@ -127,6 +127,14 @@
 	ll.Includes = append(ll.Includes, *label)
 }
 
+// AddExclude inserts the label Label at the end of the LabelList.Excludes.
+func (ll *LabelList) AddExclude(label *Label) {
+	if label == nil {
+		return
+	}
+	ll.Excludes = append(ll.Excludes, *label)
+}
+
 // Append appends the fields of other labelList to the corresponding fields of ll.
 func (ll *LabelList) Append(other LabelList) {
 	if len(ll.Includes) > 0 || len(other.Includes) > 0 {
@@ -137,6 +145,30 @@
 	}
 }
 
+// Partition splits a LabelList into two LabelLists depending on the return value
+// of the predicate.
+// This function preserves the Includes and Excludes, but it does not provide
+// that information to the partition function.
+func (ll *LabelList) Partition(predicate func(label Label) bool) (LabelList, LabelList) {
+	predicated := LabelList{}
+	unpredicated := LabelList{}
+	for _, include := range ll.Includes {
+		if predicate(include) {
+			predicated.Add(&include)
+		} else {
+			unpredicated.Add(&include)
+		}
+	}
+	for _, exclude := range ll.Excludes {
+		if predicate(exclude) {
+			predicated.AddExclude(&exclude)
+		} else {
+			unpredicated.AddExclude(&exclude)
+		}
+	}
+	return predicated, unpredicated
+}
+
 // UniqueSortedBazelLabels takes a []Label and deduplicates the labels, and returns
 // the slice in a sorted order.
 func UniqueSortedBazelLabels(originalLabels []Label) []Label {
@@ -822,6 +854,29 @@
 	}
 }
 
+// Partition splits a LabelListAttribute into two LabelListAttributes depending
+// on the return value of the predicate.
+// This function preserves the Includes and Excludes, but it does not provide
+// that information to the partition function.
+func (lla LabelListAttribute) Partition(predicate func(label Label) bool) (LabelListAttribute, LabelListAttribute) {
+	predicated := LabelListAttribute{}
+	unpredicated := LabelListAttribute{}
+
+	valuePartitionTrue, valuePartitionFalse := lla.Value.Partition(predicate)
+	predicated.SetValue(valuePartitionTrue)
+	unpredicated.SetValue(valuePartitionFalse)
+
+	for axis, selectValueLabelLists := range lla.ConfigurableValues {
+		for config, labelList := range selectValueLabelLists {
+			configPredicated, configUnpredicated := labelList.Partition(predicate)
+			predicated.SetSelectValue(axis, config, configPredicated)
+			unpredicated.SetSelectValue(axis, config, configUnpredicated)
+		}
+	}
+
+	return predicated, unpredicated
+}
+
 // OtherModuleContext is a limited context that has methods with information about other modules.
 type OtherModuleContext interface {
 	ModuleFromName(name string) (blueprint.Module, bool)
diff --git a/bazel/properties_test.go b/bazel/properties_test.go
index 7b76b74..dc4d5b0 100644
--- a/bazel/properties_test.go
+++ b/bazel/properties_test.go
@@ -310,6 +310,134 @@
 	}
 }
 
+func TestLabelListAttributePartition(t *testing.T) {
+	testCases := []struct {
+		name         string
+		input        LabelListAttribute
+		predicated   LabelListAttribute
+		unpredicated LabelListAttribute
+		predicate    func(label Label) bool
+	}{
+		{
+			name: "move all to predicated partition",
+			input: MakeLabelListAttribute(makeLabelList(
+				[]string{"keep1", "throw1", "keep2", "throw2"},
+				[]string{"keep1", "throw1", "keep2", "throw2"},
+			)),
+			predicated: MakeLabelListAttribute(makeLabelList(
+				[]string{"keep1", "throw1", "keep2", "throw2"},
+				[]string{"keep1", "throw1", "keep2", "throw2"},
+			)),
+			unpredicated: LabelListAttribute{},
+			predicate: func(label Label) bool {
+				return true
+			},
+		},
+		{
+			name: "move all to unpredicated partition",
+			input: MakeLabelListAttribute(makeLabelList(
+				[]string{"keep1", "throw1", "keep2", "throw2"},
+				[]string{"keep1", "throw1", "keep2", "throw2"},
+			)),
+			predicated: LabelListAttribute{},
+			unpredicated: MakeLabelListAttribute(makeLabelList(
+				[]string{"keep1", "throw1", "keep2", "throw2"},
+				[]string{"keep1", "throw1", "keep2", "throw2"},
+			)),
+			predicate: func(label Label) bool {
+				return false
+			},
+		},
+		{
+			name: "partition includes and excludes",
+			input: MakeLabelListAttribute(makeLabelList(
+				[]string{"keep1", "throw1", "keep2", "throw2"},
+				[]string{"keep1", "throw1", "keep2", "throw2"},
+			)),
+			predicated: MakeLabelListAttribute(makeLabelList(
+				[]string{"keep1", "keep2"},
+				[]string{"keep1", "keep2"},
+			)),
+			unpredicated: MakeLabelListAttribute(makeLabelList(
+				[]string{"throw1", "throw2"},
+				[]string{"throw1", "throw2"},
+			)),
+			predicate: func(label Label) bool {
+				return strings.HasPrefix(label.Label, "keep")
+			},
+		},
+		{
+			name: "partition excludes only",
+			input: MakeLabelListAttribute(makeLabelList(
+				[]string{},
+				[]string{"keep1", "throw1", "keep2", "throw2"},
+			)),
+			predicated: MakeLabelListAttribute(makeLabelList(
+				[]string{},
+				[]string{"keep1", "keep2"},
+			)),
+			unpredicated: MakeLabelListAttribute(makeLabelList(
+				[]string{},
+				[]string{"throw1", "throw2"},
+			)),
+			predicate: func(label Label) bool {
+				return strings.HasPrefix(label.Label, "keep")
+			},
+		},
+		{
+			name: "partition includes only",
+			input: MakeLabelListAttribute(makeLabelList(
+				[]string{"keep1", "throw1", "keep2", "throw2"},
+				[]string{},
+			)),
+			predicated: MakeLabelListAttribute(makeLabelList(
+				[]string{"keep1", "keep2"},
+				[]string{},
+			)),
+			unpredicated: MakeLabelListAttribute(makeLabelList(
+				[]string{"throw1", "throw2"},
+				[]string{},
+			)),
+			predicate: func(label Label) bool {
+				return strings.HasPrefix(label.Label, "keep")
+			},
+		},
+		{
+			name:         "empty partition",
+			input:        MakeLabelListAttribute(makeLabelList([]string{}, []string{})),
+			predicated:   LabelListAttribute{},
+			unpredicated: LabelListAttribute{},
+			predicate: func(label Label) bool {
+				return true
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			predicated, unpredicated := tc.input.Partition(tc.predicate)
+			if !predicated.Value.Equals(tc.predicated.Value) {
+				t.Errorf("expected predicated labels to be %v; got %v", tc.predicated, predicated)
+			}
+			for axis, configs := range predicated.ConfigurableValues {
+				tcConfigs, ok := tc.predicated.ConfigurableValues[axis]
+				if !ok || !reflect.DeepEqual(configs, tcConfigs) {
+					t.Errorf("expected predicated labels to be %v; got %v", tc.predicated, predicated)
+				}
+			}
+			if !unpredicated.Value.Equals(tc.unpredicated.Value) {
+				t.Errorf("expected unpredicated labels to be %v; got %v", tc.unpredicated, unpredicated)
+			}
+			for axis, configs := range unpredicated.ConfigurableValues {
+				tcConfigs, ok := tc.unpredicated.ConfigurableValues[axis]
+				if !ok || !reflect.DeepEqual(configs, tcConfigs) {
+					t.Errorf("expected unpredicated labels to be %v; got %v", tc.unpredicated, unpredicated)
+				}
+			}
+		})
+	}
+}
+
 // labelAddSuffixForTypeMapper returns a LabelMapper that adds suffix to label name for modules of
 // typ
 func labelAddSuffixForTypeMapper(suffix, typ string) LabelMapper {