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