• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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