xref: /aosp_15_r20/build/make/tools/compliance/policy_walk.go (revision 9e94795a3d4ef5c1d47486f9a02bb378756cea8a)
1*9e94795aSAndroid Build Coastguard Worker// Copyright 2021 Google LLC
2*9e94795aSAndroid Build Coastguard Worker//
3*9e94795aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License");
4*9e94795aSAndroid Build Coastguard Worker// you may not use this file except in compliance with the License.
5*9e94795aSAndroid Build Coastguard Worker// You may obtain a copy of the License at
6*9e94795aSAndroid Build Coastguard Worker//
7*9e94795aSAndroid Build Coastguard Worker//      http://www.apache.org/licenses/LICENSE-2.0
8*9e94795aSAndroid Build Coastguard Worker//
9*9e94795aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software
10*9e94795aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS,
11*9e94795aSAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*9e94795aSAndroid Build Coastguard Worker// See the License for the specific language governing permissions and
13*9e94795aSAndroid Build Coastguard Worker// limitations under the License.
14*9e94795aSAndroid Build Coastguard Worker
15*9e94795aSAndroid Build Coastguard Workerpackage compliance
16*9e94795aSAndroid Build Coastguard Worker
17*9e94795aSAndroid Build Coastguard Worker// EdgeContextProvider is an interface for injecting edge-specific context
18*9e94795aSAndroid Build Coastguard Worker// into walk paths.
19*9e94795aSAndroid Build Coastguard Workertype EdgeContextProvider interface {
20*9e94795aSAndroid Build Coastguard Worker	// Context returns the context for `edge` when added to `path`.
21*9e94795aSAndroid Build Coastguard Worker	Context(lg *LicenseGraph, path TargetEdgePath, edge *TargetEdge) interface{}
22*9e94795aSAndroid Build Coastguard Worker}
23*9e94795aSAndroid Build Coastguard Worker
24*9e94795aSAndroid Build Coastguard Worker// NoEdgeContext implements EdgeContextProvider for walks that use no context.
25*9e94795aSAndroid Build Coastguard Workertype NoEdgeContext struct{}
26*9e94795aSAndroid Build Coastguard Worker
27*9e94795aSAndroid Build Coastguard Worker// Context returns nil.
28*9e94795aSAndroid Build Coastguard Workerfunc (ctx NoEdgeContext) Context(lg *LicenseGraph, path TargetEdgePath, edge *TargetEdge) interface{} {
29*9e94795aSAndroid Build Coastguard Worker	return nil
30*9e94795aSAndroid Build Coastguard Worker}
31*9e94795aSAndroid Build Coastguard Worker
32*9e94795aSAndroid Build Coastguard Worker// ApplicableConditionsContext provides the subset of conditions in `universe`
33*9e94795aSAndroid Build Coastguard Worker// that apply to each edge in a path.
34*9e94795aSAndroid Build Coastguard Workertype ApplicableConditionsContext struct {
35*9e94795aSAndroid Build Coastguard Worker	universe LicenseConditionSet
36*9e94795aSAndroid Build Coastguard Worker}
37*9e94795aSAndroid Build Coastguard Worker
38*9e94795aSAndroid Build Coastguard Worker// Context returns the LicenseConditionSet applicable to the edge.
39*9e94795aSAndroid Build Coastguard Workerfunc (ctx ApplicableConditionsContext) Context(lg *LicenseGraph, path TargetEdgePath, edge *TargetEdge) interface{} {
40*9e94795aSAndroid Build Coastguard Worker	universe := ctx.universe
41*9e94795aSAndroid Build Coastguard Worker	if len(path) > 0 {
42*9e94795aSAndroid Build Coastguard Worker		universe = path[len(path)-1].ctx.(LicenseConditionSet)
43*9e94795aSAndroid Build Coastguard Worker	}
44*9e94795aSAndroid Build Coastguard Worker	return conditionsAttachingAcrossEdge(lg, edge, universe)
45*9e94795aSAndroid Build Coastguard Worker}
46*9e94795aSAndroid Build Coastguard Worker
47*9e94795aSAndroid Build Coastguard Worker// VisitNode is called for each root and for each walked dependency node by
48*9e94795aSAndroid Build Coastguard Worker// WalkTopDown and WalkTopDownBreadthFirst. When VisitNode returns true, WalkTopDown will proceed to walk
49*9e94795aSAndroid Build Coastguard Worker// down the dependences of the node
50*9e94795aSAndroid Build Coastguard Workertype VisitNode func(lg *LicenseGraph, target *TargetNode, path TargetEdgePath) bool
51*9e94795aSAndroid Build Coastguard Worker
52*9e94795aSAndroid Build Coastguard Worker// WalkTopDown does a top-down walk of `lg` calling `visit` and descending
53*9e94795aSAndroid Build Coastguard Worker// into depenencies when `visit` returns true.
54*9e94795aSAndroid Build Coastguard Workerfunc WalkTopDown(ctx EdgeContextProvider, lg *LicenseGraph, visit VisitNode) {
55*9e94795aSAndroid Build Coastguard Worker	path := NewTargetEdgePath(32)
56*9e94795aSAndroid Build Coastguard Worker
57*9e94795aSAndroid Build Coastguard Worker	var walk func(fnode *TargetNode)
58*9e94795aSAndroid Build Coastguard Worker	walk = func(fnode *TargetNode) {
59*9e94795aSAndroid Build Coastguard Worker		visitChildren := visit(lg, fnode, *path)
60*9e94795aSAndroid Build Coastguard Worker		if !visitChildren {
61*9e94795aSAndroid Build Coastguard Worker			return
62*9e94795aSAndroid Build Coastguard Worker		}
63*9e94795aSAndroid Build Coastguard Worker		for _, edge := range fnode.edges {
64*9e94795aSAndroid Build Coastguard Worker			var edgeContext interface{}
65*9e94795aSAndroid Build Coastguard Worker			if ctx == nil {
66*9e94795aSAndroid Build Coastguard Worker				edgeContext = nil
67*9e94795aSAndroid Build Coastguard Worker			} else {
68*9e94795aSAndroid Build Coastguard Worker				edgeContext = ctx.Context(lg, *path, edge)
69*9e94795aSAndroid Build Coastguard Worker			}
70*9e94795aSAndroid Build Coastguard Worker			path.Push(edge, edgeContext)
71*9e94795aSAndroid Build Coastguard Worker			walk(edge.dependency)
72*9e94795aSAndroid Build Coastguard Worker			path.Pop()
73*9e94795aSAndroid Build Coastguard Worker		}
74*9e94795aSAndroid Build Coastguard Worker	}
75*9e94795aSAndroid Build Coastguard Worker
76*9e94795aSAndroid Build Coastguard Worker	for _, r := range lg.rootFiles {
77*9e94795aSAndroid Build Coastguard Worker		path.Clear()
78*9e94795aSAndroid Build Coastguard Worker		walk(lg.targets[r])
79*9e94795aSAndroid Build Coastguard Worker	}
80*9e94795aSAndroid Build Coastguard Worker}
81*9e94795aSAndroid Build Coastguard Worker
82*9e94795aSAndroid Build Coastguard Worker// WalkTopDownBreadthFirst performs a Breadth-first top down walk of `lg` calling `visit` and descending
83*9e94795aSAndroid Build Coastguard Worker// into depenencies when `visit` returns true.
84*9e94795aSAndroid Build Coastguard Workerfunc WalkTopDownBreadthFirst(ctx EdgeContextProvider, lg *LicenseGraph, visit VisitNode) {
85*9e94795aSAndroid Build Coastguard Worker	path := NewTargetEdgePath(32)
86*9e94795aSAndroid Build Coastguard Worker
87*9e94795aSAndroid Build Coastguard Worker	var walk func(fnode *TargetNode)
88*9e94795aSAndroid Build Coastguard Worker	walk = func(fnode *TargetNode) {
89*9e94795aSAndroid Build Coastguard Worker		edgesToWalk := make(TargetEdgeList, 0, len(fnode.edges))
90*9e94795aSAndroid Build Coastguard Worker		for _, edge := range fnode.edges {
91*9e94795aSAndroid Build Coastguard Worker			var edgeContext interface{}
92*9e94795aSAndroid Build Coastguard Worker			if ctx == nil {
93*9e94795aSAndroid Build Coastguard Worker				edgeContext = nil
94*9e94795aSAndroid Build Coastguard Worker			} else {
95*9e94795aSAndroid Build Coastguard Worker				edgeContext = ctx.Context(lg, *path, edge)
96*9e94795aSAndroid Build Coastguard Worker			}
97*9e94795aSAndroid Build Coastguard Worker			path.Push(edge, edgeContext)
98*9e94795aSAndroid Build Coastguard Worker			if visit(lg, edge.dependency, *path){
99*9e94795aSAndroid Build Coastguard Worker				edgesToWalk = append(edgesToWalk, edge)
100*9e94795aSAndroid Build Coastguard Worker			}
101*9e94795aSAndroid Build Coastguard Worker			path.Pop()
102*9e94795aSAndroid Build Coastguard Worker		}
103*9e94795aSAndroid Build Coastguard Worker
104*9e94795aSAndroid Build Coastguard Worker		for _, edge := range(edgesToWalk) {
105*9e94795aSAndroid Build Coastguard Worker			var edgeContext interface{}
106*9e94795aSAndroid Build Coastguard Worker			if ctx == nil {
107*9e94795aSAndroid Build Coastguard Worker				edgeContext = nil
108*9e94795aSAndroid Build Coastguard Worker			} else {
109*9e94795aSAndroid Build Coastguard Worker				edgeContext = ctx.Context(lg, *path, edge)
110*9e94795aSAndroid Build Coastguard Worker			}
111*9e94795aSAndroid Build Coastguard Worker			path.Push(edge, edgeContext)
112*9e94795aSAndroid Build Coastguard Worker			walk(edge.dependency)
113*9e94795aSAndroid Build Coastguard Worker			path.Pop()
114*9e94795aSAndroid Build Coastguard Worker		}
115*9e94795aSAndroid Build Coastguard Worker	}
116*9e94795aSAndroid Build Coastguard Worker
117*9e94795aSAndroid Build Coastguard Worker	path.Clear()
118*9e94795aSAndroid Build Coastguard Worker	rootsToWalk := make([]*TargetNode, 0, len(lg.rootFiles))
119*9e94795aSAndroid Build Coastguard Worker	for _, r := range lg.rootFiles {
120*9e94795aSAndroid Build Coastguard Worker		if visit(lg, lg.targets[r], *path){
121*9e94795aSAndroid Build Coastguard Worker			rootsToWalk = append(rootsToWalk, lg.targets[r])
122*9e94795aSAndroid Build Coastguard Worker		}
123*9e94795aSAndroid Build Coastguard Worker	}
124*9e94795aSAndroid Build Coastguard Worker
125*9e94795aSAndroid Build Coastguard Worker	for _, rnode := range(rootsToWalk) {
126*9e94795aSAndroid Build Coastguard Worker		walk(rnode)
127*9e94795aSAndroid Build Coastguard Worker	}
128*9e94795aSAndroid Build Coastguard Worker}
129*9e94795aSAndroid Build Coastguard Worker
130*9e94795aSAndroid Build Coastguard Worker// resolutionKey identifies results from walking a specific target for a
131*9e94795aSAndroid Build Coastguard Worker// specific set of conditions.
132*9e94795aSAndroid Build Coastguard Workertype resolutionKey struct {
133*9e94795aSAndroid Build Coastguard Worker	target *TargetNode
134*9e94795aSAndroid Build Coastguard Worker	cs     LicenseConditionSet
135*9e94795aSAndroid Build Coastguard Worker}
136*9e94795aSAndroid Build Coastguard Worker
137*9e94795aSAndroid Build Coastguard Worker// WalkResolutionsForCondition performs a top-down walk of the LicenseGraph
138*9e94795aSAndroid Build Coastguard Worker// resolving all distributed works for `conditions`.
139*9e94795aSAndroid Build Coastguard Workerfunc WalkResolutionsForCondition(lg *LicenseGraph, conditions LicenseConditionSet) ResolutionSet {
140*9e94795aSAndroid Build Coastguard Worker	shipped := ShippedNodes(lg)
141*9e94795aSAndroid Build Coastguard Worker
142*9e94795aSAndroid Build Coastguard Worker	// rmap maps 'attachesTo' targets to the `actsOn` targets and applicable conditions
143*9e94795aSAndroid Build Coastguard Worker	rmap := make(map[resolutionKey]ActionSet)
144*9e94795aSAndroid Build Coastguard Worker
145*9e94795aSAndroid Build Coastguard Worker	// cmap identifies previously walked target/condition pairs.
146*9e94795aSAndroid Build Coastguard Worker	cmap := make(map[resolutionKey]struct{})
147*9e94795aSAndroid Build Coastguard Worker
148*9e94795aSAndroid Build Coastguard Worker	// result accumulates the resolutions to return.
149*9e94795aSAndroid Build Coastguard Worker	result := make(ResolutionSet)
150*9e94795aSAndroid Build Coastguard Worker	WalkTopDown(ApplicableConditionsContext{conditions}, lg, func(lg *LicenseGraph, tn *TargetNode, path TargetEdgePath) bool {
151*9e94795aSAndroid Build Coastguard Worker		universe := conditions
152*9e94795aSAndroid Build Coastguard Worker		if len(path) > 0 {
153*9e94795aSAndroid Build Coastguard Worker			universe = path[len(path)-1].ctx.(LicenseConditionSet)
154*9e94795aSAndroid Build Coastguard Worker		}
155*9e94795aSAndroid Build Coastguard Worker
156*9e94795aSAndroid Build Coastguard Worker		if universe.IsEmpty() {
157*9e94795aSAndroid Build Coastguard Worker			return false
158*9e94795aSAndroid Build Coastguard Worker		}
159*9e94795aSAndroid Build Coastguard Worker		key := resolutionKey{tn, universe}
160*9e94795aSAndroid Build Coastguard Worker
161*9e94795aSAndroid Build Coastguard Worker		if _, alreadyWalked := cmap[key]; alreadyWalked {
162*9e94795aSAndroid Build Coastguard Worker			pure := true
163*9e94795aSAndroid Build Coastguard Worker			for _, p := range path {
164*9e94795aSAndroid Build Coastguard Worker				target := p.Target()
165*9e94795aSAndroid Build Coastguard Worker				tkey := resolutionKey{target, universe}
166*9e94795aSAndroid Build Coastguard Worker				if _, ok := rmap[tkey]; !ok {
167*9e94795aSAndroid Build Coastguard Worker					rmap[tkey] = make(ActionSet)
168*9e94795aSAndroid Build Coastguard Worker				}
169*9e94795aSAndroid Build Coastguard Worker				// attach prior walk outcome to ancestor
170*9e94795aSAndroid Build Coastguard Worker				for actsOn, cs := range rmap[key] {
171*9e94795aSAndroid Build Coastguard Worker					rmap[tkey][actsOn] = cs
172*9e94795aSAndroid Build Coastguard Worker				}
173*9e94795aSAndroid Build Coastguard Worker				// if prior walk produced results, copy results
174*9e94795aSAndroid Build Coastguard Worker				// to ancestor.
175*9e94795aSAndroid Build Coastguard Worker				if _, ok := result[tn]; ok && pure {
176*9e94795aSAndroid Build Coastguard Worker					if _, ok := result[target]; !ok {
177*9e94795aSAndroid Build Coastguard Worker						result[target] = make(ActionSet)
178*9e94795aSAndroid Build Coastguard Worker					}
179*9e94795aSAndroid Build Coastguard Worker					for actsOn, cs := range result[tn] {
180*9e94795aSAndroid Build Coastguard Worker						result[target][actsOn] = cs
181*9e94795aSAndroid Build Coastguard Worker					}
182*9e94795aSAndroid Build Coastguard Worker					pure = target.IsContainer()
183*9e94795aSAndroid Build Coastguard Worker				}
184*9e94795aSAndroid Build Coastguard Worker			}
185*9e94795aSAndroid Build Coastguard Worker			// if all ancestors are pure aggregates, attach
186*9e94795aSAndroid Build Coastguard Worker			// matching prior walk conditions to self. Prior walk
187*9e94795aSAndroid Build Coastguard Worker			// will not have done so if any ancestor was not an
188*9e94795aSAndroid Build Coastguard Worker			// aggregate.
189*9e94795aSAndroid Build Coastguard Worker			if pure {
190*9e94795aSAndroid Build Coastguard Worker				match := rmap[key][tn].Intersection(universe)
191*9e94795aSAndroid Build Coastguard Worker				if !match.IsEmpty() {
192*9e94795aSAndroid Build Coastguard Worker					if _, ok := result[tn]; !ok {
193*9e94795aSAndroid Build Coastguard Worker						result[tn] = make(ActionSet)
194*9e94795aSAndroid Build Coastguard Worker					}
195*9e94795aSAndroid Build Coastguard Worker					result[tn][tn] = match
196*9e94795aSAndroid Build Coastguard Worker				}
197*9e94795aSAndroid Build Coastguard Worker			}
198*9e94795aSAndroid Build Coastguard Worker			return false
199*9e94795aSAndroid Build Coastguard Worker		}
200*9e94795aSAndroid Build Coastguard Worker		// no need to walk node or dependencies if not shipped
201*9e94795aSAndroid Build Coastguard Worker		if !shipped.Contains(tn) {
202*9e94795aSAndroid Build Coastguard Worker			return false
203*9e94795aSAndroid Build Coastguard Worker		}
204*9e94795aSAndroid Build Coastguard Worker		if _, ok := rmap[key]; !ok {
205*9e94795aSAndroid Build Coastguard Worker			rmap[key] = make(ActionSet)
206*9e94795aSAndroid Build Coastguard Worker		}
207*9e94795aSAndroid Build Coastguard Worker		// add self to walk outcome
208*9e94795aSAndroid Build Coastguard Worker		rmap[key][tn] = tn.resolution
209*9e94795aSAndroid Build Coastguard Worker		cmap[key] = struct{}{}
210*9e94795aSAndroid Build Coastguard Worker		cs := tn.resolution
211*9e94795aSAndroid Build Coastguard Worker		if !cs.IsEmpty() {
212*9e94795aSAndroid Build Coastguard Worker			cs = cs.Intersection(universe)
213*9e94795aSAndroid Build Coastguard Worker			pure := true
214*9e94795aSAndroid Build Coastguard Worker			for _, p := range path {
215*9e94795aSAndroid Build Coastguard Worker				target := p.Target()
216*9e94795aSAndroid Build Coastguard Worker				tkey := resolutionKey{target, universe}
217*9e94795aSAndroid Build Coastguard Worker				if _, ok := rmap[tkey]; !ok {
218*9e94795aSAndroid Build Coastguard Worker					rmap[tkey] = make(ActionSet)
219*9e94795aSAndroid Build Coastguard Worker				}
220*9e94795aSAndroid Build Coastguard Worker				// copy current node's action into ancestor
221*9e94795aSAndroid Build Coastguard Worker				rmap[tkey][tn] = tn.resolution
222*9e94795aSAndroid Build Coastguard Worker				// conditionally put matching conditions into
223*9e94795aSAndroid Build Coastguard Worker				// result
224*9e94795aSAndroid Build Coastguard Worker				if pure && !cs.IsEmpty() {
225*9e94795aSAndroid Build Coastguard Worker					if _, ok := result[target]; !ok {
226*9e94795aSAndroid Build Coastguard Worker						result[target] = make(ActionSet)
227*9e94795aSAndroid Build Coastguard Worker					}
228*9e94795aSAndroid Build Coastguard Worker					result[target][tn] = cs
229*9e94795aSAndroid Build Coastguard Worker					pure = target.IsContainer()
230*9e94795aSAndroid Build Coastguard Worker				}
231*9e94795aSAndroid Build Coastguard Worker			}
232*9e94795aSAndroid Build Coastguard Worker			// if all ancestors are pure aggregates, attach
233*9e94795aSAndroid Build Coastguard Worker			// matching conditions to self.
234*9e94795aSAndroid Build Coastguard Worker			if pure && !cs.IsEmpty() {
235*9e94795aSAndroid Build Coastguard Worker				if _, ok := result[tn]; !ok {
236*9e94795aSAndroid Build Coastguard Worker					result[tn] = make(ActionSet)
237*9e94795aSAndroid Build Coastguard Worker				}
238*9e94795aSAndroid Build Coastguard Worker				result[tn][tn] = cs
239*9e94795aSAndroid Build Coastguard Worker			}
240*9e94795aSAndroid Build Coastguard Worker		}
241*9e94795aSAndroid Build Coastguard Worker		return true
242*9e94795aSAndroid Build Coastguard Worker	})
243*9e94795aSAndroid Build Coastguard Worker
244*9e94795aSAndroid Build Coastguard Worker	return result
245*9e94795aSAndroid Build Coastguard Worker}
246*9e94795aSAndroid Build Coastguard Worker
247*9e94795aSAndroid Build Coastguard Worker// WalkActionsForCondition performs a top-down walk of the LicenseGraph
248*9e94795aSAndroid Build Coastguard Worker// resolving all distributed works for `conditions`.
249*9e94795aSAndroid Build Coastguard Workerfunc WalkActionsForCondition(lg *LicenseGraph, conditions LicenseConditionSet) ActionSet {
250*9e94795aSAndroid Build Coastguard Worker	// amap maps 'actsOn' targets to the applicable conditions
251*9e94795aSAndroid Build Coastguard Worker	//
252*9e94795aSAndroid Build Coastguard Worker	// amap is the resulting ActionSet
253*9e94795aSAndroid Build Coastguard Worker	amap := make(ActionSet)
254*9e94795aSAndroid Build Coastguard Worker
255*9e94795aSAndroid Build Coastguard Worker	for tn := range ShippedNodes(lg) {
256*9e94795aSAndroid Build Coastguard Worker		if cs := conditions.Intersection(tn.resolution); !cs.IsEmpty() {
257*9e94795aSAndroid Build Coastguard Worker			amap[tn] = cs
258*9e94795aSAndroid Build Coastguard Worker		}
259*9e94795aSAndroid Build Coastguard Worker	}
260*9e94795aSAndroid Build Coastguard Worker
261*9e94795aSAndroid Build Coastguard Worker	return amap
262*9e94795aSAndroid Build Coastguard Worker}
263