1// 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 17// 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 47// VisitNode is called for each root and for each walked dependency node by 48// WalkTopDown and WalkTopDownBreadthFirst. When VisitNode returns true, WalkTopDown will proceed to walk 49// down the dependences of the node 50type VisitNode func(lg *LicenseGraph, target *TargetNode, path TargetEdgePath) bool 51 52// WalkTopDown does a top-down walk of `lg` calling `visit` and descending 53// into depenencies when `visit` returns true. 54func WalkTopDown(ctx EdgeContextProvider, lg *LicenseGraph, visit VisitNode) { 55 path := NewTargetEdgePath(32) 56 57 var walk func(fnode *TargetNode) 58 walk = func(fnode *TargetNode) { 59 visitChildren := visit(lg, fnode, *path) 60 if !visitChildren { 61 return 62 } 63 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) 71 walk(edge.dependency) 72 path.Pop() 73 } 74 } 75 76 for _, r := range lg.rootFiles { 77 path.Clear() 78 walk(lg.targets[r]) 79 } 80} 81 82// 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 130// resolutionKey identifies results from walking a specific target for a 131// specific set of conditions. 132type resolutionKey struct { 133 target *TargetNode 134 cs LicenseConditionSet 135} 136 137// WalkResolutionsForCondition performs a top-down walk of the LicenseGraph 138// resolving all distributed works for `conditions`. 139func WalkResolutionsForCondition(lg *LicenseGraph, conditions LicenseConditionSet) ResolutionSet { 140 shipped := ShippedNodes(lg) 141 142 // rmap maps 'attachesTo' targets to the `actsOn` targets and applicable conditions 143 rmap := make(map[resolutionKey]ActionSet) 144 145 // 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 { 250 // amap maps 'actsOn' targets to the applicable conditions 251 // 252 // amap is the resulting ActionSet 253 amap := make(ActionSet) 254 255 for tn := range ShippedNodes(lg) { 256 if cs := conditions.Intersection(tn.resolution); !cs.IsEmpty() { 257 amap[tn] = cs 258 } 259 } 260 261 return amap 262} 263