blob: ea49db939a7233e5c80e8e36e55528196102f04c [file] [log] [blame]
Bob Badoura99ac622021-10-25 16:21:00 -07001// 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
17import (
18 "fmt"
19 "strings"
20)
21
22// JoinResolutionSets returns a new ResolutionSet combining the resolutions from
23// multiple resolution sets. All sets must be derived from the same license
24// graph.
25//
26// e.g. combine "restricted", "reciprocal", and "proprietary" resolutions.
27func JoinResolutionSets(resolutions ...*ResolutionSet) *ResolutionSet {
28 if len(resolutions) < 1 {
29 panic(fmt.Errorf("attempt to join 0 resolution sets"))
30 }
31 rmap := make(map[*TargetNode]actionSet)
32 for _, r := range resolutions {
33 if len(r.resolutions) < 1 {
34 continue
35 }
36 for attachesTo, as := range r.resolutions {
37 if as.isEmpty() {
38 continue
39 }
40 if _, ok := rmap[attachesTo]; !ok {
41 rmap[attachesTo] = as.copy()
42 continue
43 }
44 rmap[attachesTo].addSet(as)
45 }
46 }
47 return &ResolutionSet{rmap}
48}
49
50// ResolutionSet describes an immutable set of targets and the license
51// conditions each target must satisfy or "resolve" in a specific context.
52//
53// Ultimately, the purpose of recording the license metadata and building a
54// license graph is to identify, describe, and verify the necessary actions or
55// operations for compliance policy.
56//
57// i.e. What is the source-sharing policy? Has it been met? Meet it.
58//
59// i.e. Are there incompatible policy requirements? Such as a source-sharing
60// policy applied to code that policy also says may not be shared? If so, stop
61// and remove the dependencies that create the situation.
62//
63// The ResolutionSet is the base unit for mapping license conditions to the
64// targets triggering some necessary action per policy. Different ResolutionSet
65// values may be calculated for different contexts.
66//
67// e.g. Suppose an unencumbered binary links in a notice .a library.
68//
69// An "unencumbered" condition would originate from the binary, and a "notice"
70// condition would originate from the .a library. A ResolutionSet for the
71// context of the Notice policy might apply both conditions to the binary while
72// preserving the origin of each condition. By applying the notice condition to
73// the binary, the ResolutionSet stipulates the policy that the release of the
74// unencumbered binary must provide suitable notice for the .a library.
75//
76// The resulting ResolutionSet could be used for building a notice file, for
77// validating that a suitable notice has been built into the distribution, or
78// for reporting what notices need to be given.
79//
80// Resolutions for different contexts may be combined in a new ResolutionSet
81// using JoinResolutions(...).
82//
83// See: resolve.go for:
84// * ResolveBottomUpConditions(...)
85// * ResolveTopDownForCondition(...)
86// See also: policy.go for:
87// * ResolveSourceSharing(...)
88// * ResolveSourcePrivacy(...)
89type ResolutionSet struct {
90 // resolutions maps names of target with applicable conditions to the set of conditions that apply.
91 resolutions map[*TargetNode]actionSet
92}
93
94// String returns a string representation of the set.
95func (rs *ResolutionSet) String() string {
96 var sb strings.Builder
97 fmt.Fprintf(&sb, "{")
98 sep := ""
99 for attachesTo, as := range rs.resolutions {
100 fmt.Fprintf(&sb, "%s%s -> %s", sep, attachesTo.name, as.String())
101 sep = ", "
102 }
103 fmt.Fprintf(&sb, "}")
104 return sb.String()
105}
106
107// AttachesTo identifies the list of targets triggering action to resolve
108// conditions. (unordered)
109func (rs *ResolutionSet) AttachesTo() TargetNodeList {
110 targets := make(TargetNodeList, 0, len(rs.resolutions))
111 for attachesTo := range rs.resolutions {
112 targets = append(targets, attachesTo)
113 }
114 return targets
115}
116
117// ActsOn identifies the list of targets to act on (share, give notice etc.)
118// to resolve conditions. (unordered)
119func (rs *ResolutionSet) ActsOn() TargetNodeList {
120 tset := make(map[*TargetNode]bool)
121 for _, as := range rs.resolutions {
122 for actsOn := range as {
123 tset[actsOn] = true
124 }
125 }
126 targets := make(TargetNodeList, 0, len(tset))
127 for target := range tset {
128 targets = append(targets, target)
129 }
130 return targets
131}
132
133// Origins identifies the list of targets originating conditions to resolve.
134// (unordered)
135func (rs *ResolutionSet) Origins() TargetNodeList {
136 tset := make(map[*TargetNode]bool)
137 for _, as := range rs.resolutions {
138 for _, cs := range as {
139 for _, origins := range cs.conditions {
140 for origin := range origins {
141 tset[origin] = true
142 }
143 }
144 }
145 }
146 targets := make(TargetNodeList, 0, len(tset))
147 for target := range tset {
148 targets = append(targets, target)
149 }
150 return targets
151}
152
153// Resolutions returns the list of resolutions that `attachedTo`
154// target must resolve. Returns empty list if no conditions apply.
155//
156// Panics if `attachedTo` does not appear in the set.
157func (rs *ResolutionSet) Resolutions(attachedTo *TargetNode) ResolutionList {
158 as, ok := rs.resolutions[attachedTo]
159 if !ok {
160 return ResolutionList{}
161 }
162 result := make(ResolutionList, 0, len(as))
163 for actsOn, cs := range as {
164 result = append(result, Resolution{attachedTo, actsOn, cs.Copy()})
165 }
166 return result
167}
168
169// ResolutionsByActsOn returns the list of resolutions that must `actOn` to
170// resolvee. Returns empty list if no conditions apply.
171//
172// Panics if `actOn` does not appear in the set.
173func (rs *ResolutionSet) ResolutionsByActsOn(actOn *TargetNode) ResolutionList {
174 c := 0
175 for _, as := range rs.resolutions {
176 if _, ok := as[actOn]; ok {
177 c++
178 }
179 }
180 result := make(ResolutionList, 0, c)
181 for attachedTo, as := range rs.resolutions {
182 if cs, ok := as[actOn]; ok {
183 result = append(result, Resolution{attachedTo, actOn, cs.Copy()})
184 }
185 }
186 return result
187}
188
189// AttachesToByOrigin identifies the list of targets requiring action to
190// resolve conditions originating at `origin`. (unordered)
191func (rs *ResolutionSet) AttachesToByOrigin(origin *TargetNode) TargetNodeList {
192 tset := make(map[*TargetNode]bool)
193 for attachesTo, as := range rs.resolutions {
194 for _, cs := range as {
195 if cs.HasAnyByOrigin(origin) {
196 tset[attachesTo] = true
197 break
198 }
199 }
200 }
201 targets := make(TargetNodeList, 0, len(tset))
202 for target := range tset {
203 targets = append(targets, target)
204 }
205 return targets
206}
207
208// AttachesToTarget returns true if the set contains conditions that
209// are `attachedTo`.
210func (rs *ResolutionSet) AttachesToTarget(attachedTo *TargetNode) bool {
211 _, isPresent := rs.resolutions[attachedTo]
212 return isPresent
213}
214
215// AnyByNameAttachToTarget returns true if the set contains conditions matching
216// `names` that attach to `attachedTo`.
217func (rs *ResolutionSet) AnyByNameAttachToTarget(attachedTo *TargetNode, names ...ConditionNames) bool {
218 as, isPresent := rs.resolutions[attachedTo]
219 if !isPresent {
220 return false
221 }
222 for _, cs := range as {
223 for _, cn := range names {
224 for _, name := range cn {
225 _, isPresent = cs.conditions[name]
226 if isPresent {
227 return true
228 }
229 }
230 }
231 }
232 return false
233}
234
235// AllByNameAttachTo returns true if the set contains at least one condition
236// matching each element of `names` for `attachedTo`.
237func (rs *ResolutionSet) AllByNameAttachToTarget(attachedTo *TargetNode, names ...ConditionNames) bool {
238 as, isPresent := rs.resolutions[attachedTo]
239 if !isPresent {
240 return false
241 }
242 for _, cn := range names {
243 found := false
244 asloop:
245 for _, cs := range as {
246 for _, name := range cn {
247 _, isPresent = cs.conditions[name]
248 if isPresent {
249 found = true
250 break asloop
251 }
252 }
253 }
254 if !found {
255 return false
256 }
257 }
258 return true
259}
260
261// IsEmpty returns true if the set contains no conditions to resolve.
262func (rs *ResolutionSet) IsEmpty() bool {
263 for _, as := range rs.resolutions {
264 if !as.isEmpty() {
265 return false
266 }
267 }
268 return true
269}
270
271// compliance-only ResolutionSet methods
272
273// newResolutionSet constructs a new, empty instance of resolutionSetImp for graph `lg`.
274func newResolutionSet() *ResolutionSet {
275 return &ResolutionSet{make(map[*TargetNode]actionSet)}
276}
277
278// addConditions attaches all of the license conditions in `as` to `attachTo` to act on the originating node if not already applied.
279func (rs *ResolutionSet) addConditions(attachTo *TargetNode, as actionSet) {
280 _, ok := rs.resolutions[attachTo]
281 if !ok {
282 rs.resolutions[attachTo] = as.copy()
283 return
284 }
285 rs.resolutions[attachTo].addSet(as)
286}
287
288// add attaches all of the license conditions in `as` to `attachTo` to act on `attachTo` if not already applied.
289func (rs *ResolutionSet) addSelf(attachTo *TargetNode, as actionSet) {
290 for _, cs := range as {
291 if cs.IsEmpty() {
292 return
293 }
294 _, ok := rs.resolutions[attachTo]
295 if !ok {
296 rs.resolutions[attachTo] = make(actionSet)
297 }
298 _, ok = rs.resolutions[attachTo][attachTo]
299 if !ok {
300 rs.resolutions[attachTo][attachTo] = newLicenseConditionSet()
301 }
302 rs.resolutions[attachTo][attachTo].AddSet(cs)
303 }
304}