2c51bd1d49
Go's tooling expects that all files in the same package are in the same directory. Move the policy/*.go files to policy_*.go. Test: go test ./... Change-Id: I7e81936ddd20a5ffb4770ae23bdb6e411d6924cc
238 lines
6.9 KiB
Go
238 lines
6.9 KiB
Go
// 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. 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])
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
shipped := ShippedNodes(lg)
|
|
|
|
// cmap identifies previously walked target/condition pairs.
|
|
cmap := make(map[resolutionKey]struct{})
|
|
|
|
// amap maps 'actsOn' targets to the applicable conditions
|
|
//
|
|
// amap is the resulting ActionSet
|
|
amap := make(ActionSet)
|
|
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 _, ok := cmap[key]; ok {
|
|
return false
|
|
}
|
|
if !shipped.Contains(tn) {
|
|
return false
|
|
}
|
|
cs := universe.Intersection(tn.resolution)
|
|
if !cs.IsEmpty() {
|
|
if _, ok := amap[tn]; ok {
|
|
amap[tn] = cs
|
|
} else {
|
|
amap[tn] = amap[tn].Union(cs)
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
|
|
return amap
|
|
}
|