| // Copyright 2021 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package compliance |
| |
| // EdgeContextProvider is an interface for injecting edge-specific context |
| // into walk paths. |
| type EdgeContextProvider interface { |
| // Context returns the context for `edge` when added to `path`. |
| Context(lg *LicenseGraph, path TargetEdgePath, edge *TargetEdge) interface{} |
| } |
| |
| // NoEdgeContext implements EdgeContextProvider for walks that use no context. |
| type NoEdgeContext struct{} |
| |
| // Context returns nil. |
| func (ctx NoEdgeContext) Context(lg *LicenseGraph, path TargetEdgePath, edge *TargetEdge) interface{} { |
| return nil |
| } |
| |
| // ApplicableConditionsContext provides the subset of conditions in `universe` |
| // that apply to each edge in a path. |
| type ApplicableConditionsContext struct { |
| universe LicenseConditionSet |
| } |
| |
| // Context returns the LicenseConditionSet applicable to the edge. |
| func (ctx ApplicableConditionsContext) Context(lg *LicenseGraph, path TargetEdgePath, edge *TargetEdge) interface{} { |
| universe := ctx.universe |
| if len(path) > 0 { |
| universe = path[len(path)-1].ctx.(LicenseConditionSet) |
| } |
| return conditionsAttachingAcrossEdge(lg, edge, universe) |
| } |
| |
| // VisitNode is called for each root and for each walked dependency node by |
| // WalkTopDown and WalkTopDownBreadthFirst. When VisitNode returns true, WalkTopDown will proceed to walk |
| // down the dependences of the node |
| type VisitNode func(lg *LicenseGraph, target *TargetNode, path TargetEdgePath) bool |
| |
| // WalkTopDown does a top-down walk of `lg` calling `visit` and descending |
| // into depenencies when `visit` returns true. |
| func WalkTopDown(ctx EdgeContextProvider, lg *LicenseGraph, visit VisitNode) { |
| path := NewTargetEdgePath(32) |
| |
| var walk func(fnode *TargetNode) |
| walk = func(fnode *TargetNode) { |
| visitChildren := visit(lg, fnode, *path) |
| if !visitChildren { |
| return |
| } |
| for _, edge := range fnode.edges { |
| var edgeContext interface{} |
| if ctx == nil { |
| edgeContext = nil |
| } else { |
| edgeContext = ctx.Context(lg, *path, edge) |
| } |
| path.Push(edge, edgeContext) |
| walk(edge.dependency) |
| path.Pop() |
| } |
| } |
| |
| for _, r := range lg.rootFiles { |
| path.Clear() |
| walk(lg.targets[r]) |
| } |
| } |
| |
| // WalkTopDownBreadthFirst performs a Breadth-first top down walk of `lg` calling `visit` and descending |
| // into depenencies when `visit` returns true. |
| func WalkTopDownBreadthFirst(ctx EdgeContextProvider, lg *LicenseGraph, visit VisitNode) { |
| path := NewTargetEdgePath(32) |
| |
| var walk func(fnode *TargetNode) |
| walk = func(fnode *TargetNode) { |
| edgesToWalk := make(TargetEdgeList, 0, len(fnode.edges)) |
| for _, edge := range fnode.edges { |
| var edgeContext interface{} |
| if ctx == nil { |
| edgeContext = nil |
| } else { |
| edgeContext = ctx.Context(lg, *path, edge) |
| } |
| path.Push(edge, edgeContext) |
| if visit(lg, edge.dependency, *path){ |
| edgesToWalk = append(edgesToWalk, edge) |
| } |
| path.Pop() |
| } |
| |
| for _, edge := range(edgesToWalk) { |
| var edgeContext interface{} |
| if ctx == nil { |
| edgeContext = nil |
| } else { |
| edgeContext = ctx.Context(lg, *path, edge) |
| } |
| path.Push(edge, edgeContext) |
| walk(edge.dependency) |
| path.Pop() |
| } |
| } |
| |
| path.Clear() |
| rootsToWalk := make([]*TargetNode, 0, len(lg.rootFiles)) |
| for _, r := range lg.rootFiles { |
| if visit(lg, lg.targets[r], *path){ |
| rootsToWalk = append(rootsToWalk, lg.targets[r]) |
| } |
| } |
| |
| for _, rnode := range(rootsToWalk) { |
| walk(rnode) |
| } |
| } |
| |
| // resolutionKey identifies results from walking a specific target for a |
| // specific set of conditions. |
| type resolutionKey struct { |
| target *TargetNode |
| cs LicenseConditionSet |
| } |
| |
| // WalkResolutionsForCondition performs a top-down walk of the LicenseGraph |
| // resolving all distributed works for `conditions`. |
| func WalkResolutionsForCondition(lg *LicenseGraph, conditions LicenseConditionSet) ResolutionSet { |
| shipped := ShippedNodes(lg) |
| |
| // rmap maps 'attachesTo' targets to the `actsOn` targets and applicable conditions |
| rmap := make(map[resolutionKey]ActionSet) |
| |
| // cmap identifies previously walked target/condition pairs. |
| cmap := make(map[resolutionKey]struct{}) |
| |
| // result accumulates the resolutions to return. |
| result := make(ResolutionSet) |
| WalkTopDown(ApplicableConditionsContext{conditions}, lg, func(lg *LicenseGraph, tn *TargetNode, path TargetEdgePath) bool { |
| universe := conditions |
| if len(path) > 0 { |
| universe = path[len(path)-1].ctx.(LicenseConditionSet) |
| } |
| |
| if universe.IsEmpty() { |
| return false |
| } |
| key := resolutionKey{tn, universe} |
| |
| if _, alreadyWalked := cmap[key]; alreadyWalked { |
| pure := true |
| for _, p := range path { |
| target := p.Target() |
| tkey := resolutionKey{target, universe} |
| if _, ok := rmap[tkey]; !ok { |
| rmap[tkey] = make(ActionSet) |
| } |
| // attach prior walk outcome to ancestor |
| for actsOn, cs := range rmap[key] { |
| rmap[tkey][actsOn] = cs |
| } |
| // if prior walk produced results, copy results |
| // to ancestor. |
| if _, ok := result[tn]; ok && pure { |
| if _, ok := result[target]; !ok { |
| result[target] = make(ActionSet) |
| } |
| for actsOn, cs := range result[tn] { |
| result[target][actsOn] = cs |
| } |
| pure = target.IsContainer() |
| } |
| } |
| // if all ancestors are pure aggregates, attach |
| // matching prior walk conditions to self. Prior walk |
| // will not have done so if any ancestor was not an |
| // aggregate. |
| if pure { |
| match := rmap[key][tn].Intersection(universe) |
| if !match.IsEmpty() { |
| if _, ok := result[tn]; !ok { |
| result[tn] = make(ActionSet) |
| } |
| result[tn][tn] = match |
| } |
| } |
| return false |
| } |
| // no need to walk node or dependencies if not shipped |
| if !shipped.Contains(tn) { |
| return false |
| } |
| if _, ok := rmap[key]; !ok { |
| rmap[key] = make(ActionSet) |
| } |
| // add self to walk outcome |
| rmap[key][tn] = tn.resolution |
| cmap[key] = struct{}{} |
| cs := tn.resolution |
| if !cs.IsEmpty() { |
| cs = cs.Intersection(universe) |
| pure := true |
| for _, p := range path { |
| target := p.Target() |
| tkey := resolutionKey{target, universe} |
| if _, ok := rmap[tkey]; !ok { |
| rmap[tkey] = make(ActionSet) |
| } |
| // copy current node's action into ancestor |
| rmap[tkey][tn] = tn.resolution |
| // conditionally put matching conditions into |
| // result |
| if pure && !cs.IsEmpty() { |
| if _, ok := result[target]; !ok { |
| result[target] = make(ActionSet) |
| } |
| result[target][tn] = cs |
| pure = target.IsContainer() |
| } |
| } |
| // if all ancestors are pure aggregates, attach |
| // matching conditions to self. |
| if pure && !cs.IsEmpty() { |
| if _, ok := result[tn]; !ok { |
| result[tn] = make(ActionSet) |
| } |
| result[tn][tn] = cs |
| } |
| } |
| return true |
| }) |
| |
| return result |
| } |
| |
| // WalkActionsForCondition performs a top-down walk of the LicenseGraph |
| // resolving all distributed works for `conditions`. |
| func WalkActionsForCondition(lg *LicenseGraph, conditions LicenseConditionSet) ActionSet { |
| // amap maps 'actsOn' targets to the applicable conditions |
| // |
| // amap is the resulting ActionSet |
| amap := make(ActionSet) |
| |
| for tn := range ShippedNodes(lg) { |
| if cs := conditions.Intersection(tn.resolution); !cs.IsEmpty() { |
| amap[tn] = cs |
| } |
| } |
| |
| return amap |
| } |