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. 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// 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 89// WalkResolutionsForCondition performs a top-down walk of the LicenseGraph 90// resolving all distributed works for `conditions`. 91func WalkResolutionsForCondition(lg *LicenseGraph, conditions LicenseConditionSet) ResolutionSet { 92 shipped := ShippedNodes(lg) 93 94 // rmap maps 'attachesTo' targets to the `actsOn` targets and applicable conditions 95 rmap := make(map[resolutionKey]ActionSet) 96 97 // 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 { 221 return false 222 } 223 if !shipped.Contains(tn) { 224 return false 225 } 226 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) 232 } 233 } 234 return true 235 }) 236 237 return amap 238} 239