blob: 3e730889edafbf6ed3d2bf12ab87d7c6f74c8fc9 [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
48// WalkTopDown. When VisitNode returns true, WalkTopDown will proceed to walk
49// 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
Bob Badour103eb0f2022-01-10 13:50:57 -080082// resolutionKey identifies results from walking a specific target for a
83// specific set of conditions.
84type resolutionKey struct {
85 target *TargetNode
86 cs LicenseConditionSet
87}
88
Bob Badour9ee7d032021-10-25 16:51:48 -070089// WalkResolutionsForCondition performs a top-down walk of the LicenseGraph
Bob Badour103eb0f2022-01-10 13:50:57 -080090// resolving all distributed works for `conditions`.
91func WalkResolutionsForCondition(lg *LicenseGraph, conditions LicenseConditionSet) ResolutionSet {
Bob Badour9ee7d032021-10-25 16:51:48 -070092 shipped := ShippedNodes(lg)
93
94 // rmap maps 'attachesTo' targets to the `actsOn` targets and applicable conditions
Bob Badour103eb0f2022-01-10 13:50:57 -080095 rmap := make(map[resolutionKey]ActionSet)
Bob Badour9ee7d032021-10-25 16:51:48 -070096
Bob Badour103eb0f2022-01-10 13:50:57 -080097 // cmap identifies previously walked target/condition pairs.
98 cmap := make(map[resolutionKey]struct{})
99
100 // result accumulates the resolutions to return.
101 result := make(ResolutionSet)
102 WalkTopDown(ApplicableConditionsContext{conditions}, lg, func(lg *LicenseGraph, tn *TargetNode, path TargetEdgePath) bool {
103 universe := conditions
104 if len(path) > 0 {
105 universe = path[len(path)-1].ctx.(LicenseConditionSet)
106 }
107
108 if universe.IsEmpty() {
109 return false
110 }
111 key := resolutionKey{tn, universe}
112
113 if _, alreadyWalked := cmap[key]; alreadyWalked {
114 pure := true
115 for _, p := range path {
116 target := p.Target()
117 tkey := resolutionKey{target, universe}
118 if _, ok := rmap[tkey]; !ok {
119 rmap[tkey] = make(ActionSet)
120 }
121 // attach prior walk outcome to ancestor
122 for actsOn, cs := range rmap[key] {
123 rmap[tkey][actsOn] = cs
124 }
125 // if prior walk produced results, copy results
126 // to ancestor.
127 if _, ok := result[tn]; ok && pure {
128 if _, ok := result[target]; !ok {
129 result[target] = make(ActionSet)
130 }
131 for actsOn, cs := range result[tn] {
132 result[target][actsOn] = cs
133 }
134 pure = target.IsContainer()
135 }
136 }
137 // if all ancestors are pure aggregates, attach
138 // matching prior walk conditions to self. Prior walk
139 // will not have done so if any ancestor was not an
140 // aggregate.
141 if pure {
142 match := rmap[key][tn].Intersection(universe)
143 if !match.IsEmpty() {
144 if _, ok := result[tn]; !ok {
145 result[tn] = make(ActionSet)
146 }
147 result[tn][tn] = match
148 }
149 }
150 return false
151 }
152 // no need to walk node or dependencies if not shipped
153 if !shipped.Contains(tn) {
154 return false
155 }
156 if _, ok := rmap[key]; !ok {
157 rmap[key] = make(ActionSet)
158 }
159 // add self to walk outcome
160 rmap[key][tn] = tn.resolution
161 cmap[key] = struct{}{}
162 cs := tn.resolution
163 if !cs.IsEmpty() {
164 cs = cs.Intersection(universe)
165 pure := true
166 for _, p := range path {
167 target := p.Target()
168 tkey := resolutionKey{target, universe}
169 if _, ok := rmap[tkey]; !ok {
170 rmap[tkey] = make(ActionSet)
171 }
172 // copy current node's action into ancestor
173 rmap[tkey][tn] = tn.resolution
174 // conditionally put matching conditions into
175 // result
176 if pure && !cs.IsEmpty() {
177 if _, ok := result[target]; !ok {
178 result[target] = make(ActionSet)
179 }
180 result[target][tn] = cs
181 pure = target.IsContainer()
182 }
183 }
184 // if all ancestors are pure aggregates, attach
185 // matching conditions to self.
186 if pure && !cs.IsEmpty() {
187 if _, ok := result[tn]; !ok {
188 result[tn] = make(ActionSet)
189 }
190 result[tn][tn] = cs
191 }
192 }
193 return true
194 })
195
196 return result
197}
198
199// WalkActionsForCondition performs a top-down walk of the LicenseGraph
200// resolving all distributed works for `conditions`.
201func WalkActionsForCondition(lg *LicenseGraph, conditions LicenseConditionSet) ActionSet {
202 shipped := ShippedNodes(lg)
203
204 // cmap identifies previously walked target/condition pairs.
205 cmap := make(map[resolutionKey]struct{})
206
207 // amap maps 'actsOn' targets to the applicable conditions
208 //
209 // amap is the resulting ActionSet
210 amap := make(ActionSet)
211 WalkTopDown(ApplicableConditionsContext{conditions}, lg, func(lg *LicenseGraph, tn *TargetNode, path TargetEdgePath) bool {
212 universe := conditions
213 if len(path) > 0 {
214 universe = path[len(path)-1].ctx.(LicenseConditionSet)
215 }
216 if universe.IsEmpty() {
217 return false
218 }
219 key := resolutionKey{tn, universe}
220 if _, ok := cmap[key]; ok {
Bob Badour9ee7d032021-10-25 16:51:48 -0700221 return false
222 }
223 if !shipped.Contains(tn) {
224 return false
225 }
Bob Badour103eb0f2022-01-10 13:50:57 -0800226 cs := universe.Intersection(tn.resolution)
227 if !cs.IsEmpty() {
228 if _, ok := amap[tn]; ok {
229 amap[tn] = cs
230 } else {
231 amap[tn] = amap[tn].Union(cs)
Bob Badour9ee7d032021-10-25 16:51:48 -0700232 }
233 }
Bob Badour103eb0f2022-01-10 13:50:57 -0800234 return true
Bob Badour9ee7d032021-10-25 16:51:48 -0700235 })
236
Bob Badour103eb0f2022-01-10 13:50:57 -0800237 return amap
Bob Badour9ee7d032021-10-25 16:51:48 -0700238}