Policy clarified: No need to share a "distribution medium"

Including code built from restricted sources in a distribution medium
does not require sharing the code for building the distribution medium.

Test: m cts dist

Test: m cts dist gts (requires cherry-pick to internal)

Change-Id: I7fcd889b11a97f8deaf4de9d72fdadd09deebe30
diff --git a/tools/compliance/cmd/listshare/listshare.go b/tools/compliance/cmd/listshare/listshare.go
index 31bd1b2..4ca6457 100644
--- a/tools/compliance/cmd/listshare/listshare.go
+++ b/tools/compliance/cmd/listshare/listshare.go
@@ -149,6 +149,9 @@
 	// Group the resolutions by project.
 	presolution := make(map[string]compliance.LicenseConditionSet)
 	for _, target := range shareSource.AttachesTo() {
+		if shareSource.IsPureAggregate(target) && !target.LicenseConditions().MatchesAnySet(compliance.ImpliesShared) {
+			continue
+		}
 		rl := shareSource.Resolutions(target)
 		sort.Sort(rl)
 		for _, r := range rl {
diff --git a/tools/compliance/cmd/listshare/listshare_test.go b/tools/compliance/cmd/listshare/listshare_test.go
index c1e38be..fb61583 100644
--- a/tools/compliance/cmd/listshare/listshare_test.go
+++ b/tools/compliance/cmd/listshare/listshare_test.go
@@ -194,13 +194,6 @@
 					conditions: []string{"restricted"},
 				},
 				{
-					project: "highest/apex",
-					conditions: []string{
-						"restricted",
-						"restricted_allows_dynamic_linking",
-					},
-				},
-				{
 					project: "static/binary",
 					conditions: []string{
 						"restricted_allows_dynamic_linking",
@@ -225,13 +218,6 @@
 					conditions: []string{"restricted"},
 				},
 				{
-					project: "container/zip",
-					conditions: []string{
-						"restricted",
-						"restricted_allows_dynamic_linking",
-					},
-				},
-				{
 					project:    "device/library",
 					conditions: []string{"restricted_allows_dynamic_linking"},
 				},
@@ -320,10 +306,6 @@
 					project:    "dynamic/binary",
 					conditions: []string{"restricted"},
 				},
-				{
-					project:    "highest/apex",
-					conditions: []string{"restricted"},
-				},
 			},
 		},
 		{
@@ -336,10 +318,6 @@
 					conditions: []string{"restricted"},
 				},
 				{
-					project:    "container/zip",
-					conditions: []string{"restricted"},
-				},
-				{
 					project:    "dynamic/binary",
 					conditions: []string{"restricted"},
 				},
@@ -381,10 +359,6 @@
 					project:    "bin/threelibraries",
 					conditions: []string{"restricted"},
 				},
-				{
-					project:    "container/zip",
-					conditions: []string{"restricted"},
-				},
 			},
 		},
 		{
@@ -397,10 +371,6 @@
 					conditions: []string{"restricted"},
 				},
 				{
-					project:    "container/zip",
-					conditions: []string{"restricted"},
-				},
-				{
 					project:    "lib/apache",
 					conditions: []string{"restricted"},
 				},
@@ -420,10 +390,6 @@
 					conditions: []string{"restricted"},
 				},
 				{
-					project:    "container/zip",
-					conditions: []string{"restricted"},
-				},
-				{
 					project:    "lib/apache",
 					conditions: []string{"restricted"},
 				},
@@ -447,10 +413,6 @@
 					conditions: []string{"restricted"},
 				},
 				{
-					project:    "container/zip",
-					conditions: []string{"restricted"},
-				},
-				{
 					project:    "lib/apache",
 					conditions: []string{"restricted"},
 				},
diff --git a/tools/compliance/policy_resolve.go b/tools/compliance/policy_resolve.go
index d357aec..93335a9 100644
--- a/tools/compliance/policy_resolve.go
+++ b/tools/compliance/policy_resolve.go
@@ -65,9 +65,7 @@
 	// amap identifes targets previously walked. (guarded by mu)
 	amap := make(map[*TargetNode]struct{})
 
-	// cmap identifies targets previously walked as pure aggregates. i.e. as containers
-	// (guarded by mu)
-	cmap := make(map[*TargetNode]struct{})
+	// mu guards concurrent access to amap
 	var mu sync.Mutex
 
 	var walk func(target *TargetNode, treatAsAggregate bool) LicenseConditionSet
@@ -81,19 +79,16 @@
 				if treatAsAggregate {
 					return target.resolution, true
 				}
-				if _, asAggregate := cmap[target]; !asAggregate {
+				if !target.pure {
 					return target.resolution, true
 				}
 				// previously walked in a pure aggregate context,
 				// needs to walk again in non-aggregate context
-				delete(cmap, target)
 			} else {
 				target.resolution |= conditionsFn(target)
 				amap[target] = struct{}{}
 			}
-			if treatAsAggregate {
-				cmap[target] = struct{}{}
-			}
+			target.pure = treatAsAggregate
 			return target.resolution, false
 		}
 		cs, alreadyWalked := priorWalkResults()
@@ -169,11 +164,7 @@
 	// amap contains the set of targets already walked. (guarded by mu)
 	amap := make(map[*TargetNode]struct{})
 
-	// cmap contains the set of targets walked as pure aggregates. i.e. containers
-	// (guarded by mu)
-	cmap := make(map[*TargetNode]struct{})
-
-	// mu guards concurrent access to cmap
+	// mu guards concurrent access to amap
 	var mu sync.Mutex
 
 	var walk func(fnode *TargetNode, cs LicenseConditionSet, treatAsAggregate bool)
@@ -183,10 +174,8 @@
 		mu.Lock()
 		fnode.resolution |= conditionsFn(fnode)
 		fnode.resolution |= cs
+		fnode.pure = treatAsAggregate
 		amap[fnode] = struct{}{}
-		if treatAsAggregate {
-			cmap[fnode] = struct{}{}
-		}
 		cs = fnode.resolution
 		mu.Unlock()
 		// for each dependency
@@ -208,11 +197,10 @@
 							return
 						}
 						// non-aggregates don't need walking as non-aggregate a 2nd time
-						if _, asAggregate := cmap[dnode]; !asAggregate {
+						if !dnode.pure {
 							return
 						}
 						// previously walked as pure aggregate; need to re-walk as non-aggregate
-						delete(cmap, dnode)
 					}
 				}
 				// add the conditions to the dependency
diff --git a/tools/compliance/policy_shareprivacyconflicts.go b/tools/compliance/policy_shareprivacyconflicts.go
index 279e179..947bb96 100644
--- a/tools/compliance/policy_shareprivacyconflicts.go
+++ b/tools/compliance/policy_shareprivacyconflicts.go
@@ -49,7 +49,11 @@
 
 	// size is the size of the result
 	size := 0
-	for _, cs := range combined {
+	for actsOn, cs := range combined {
+		if actsOn.pure && !actsOn.LicenseConditions().MatchesAnySet(ImpliesShared) {
+			// no need to share code to build "a distribution medium"
+			continue
+		}
 		size += cs.Intersection(ImpliesShared).Len() * cs.Intersection(ImpliesPrivate).Len()
 	}
 	if size == 0 {
@@ -57,6 +61,9 @@
 	}
 	result := make([]SourceSharePrivacyConflict, 0, size)
 	for actsOn, cs := range combined {
+		if actsOn.pure { // no need to share code for "a distribution medium"
+			continue
+		}
 		pconditions := cs.Intersection(ImpliesPrivate).AsList()
 		ssconditions := cs.Intersection(ImpliesShared).AsList()
 
diff --git a/tools/compliance/readgraph.go b/tools/compliance/readgraph.go
index 7516440..7faca86 100644
--- a/tools/compliance/readgraph.go
+++ b/tools/compliance/readgraph.go
@@ -198,6 +198,9 @@
 
 	// resolution identifies the set of conditions resolved by acting on the target node.
 	resolution LicenseConditionSet
+
+	// pure indicates whether to treat the node as a pure aggregate (no internal linkage)
+	pure bool
 }
 
 // addDependencies converts the proto AnnotatedDependencies into `edges`
diff --git a/tools/compliance/resolutionset.go b/tools/compliance/resolutionset.go
index 7c8f333..1be4a34 100644
--- a/tools/compliance/resolutionset.go
+++ b/tools/compliance/resolutionset.go
@@ -72,6 +72,16 @@
 	return isPresent
 }
 
+// IsPureAggregate returns true if `target`, which must be in
+// `AttachesTo()` resolves to a pure aggregate in the resolution.
+func (rs ResolutionSet) IsPureAggregate(target *TargetNode) bool {
+	_, isPresent := rs[target]
+	if !isPresent {
+		panic(fmt.Errorf("ResolutionSet.IsPureAggregate(%s): not attached to %s", target.Name(), target.Name()))
+	}
+	return target.pure
+}
+
 // Resolutions returns the list of resolutions that `attachedTo`
 // target must resolve. Returns empty list if no conditions apply.
 func (rs ResolutionSet) Resolutions(attachesTo *TargetNode) ResolutionList {