Bob Badour | a99ac62 | 2021-10-25 16:21:00 -0700 | [diff] [blame] | 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 | |
| 15 | package compliance |
| 16 | |
| 17 | import ( |
| 18 | "fmt" |
| 19 | "strings" |
| 20 | ) |
| 21 | |
Bob Badour | a99ac62 | 2021-10-25 16:21:00 -0700 | [diff] [blame] | 22 | // ResolutionSet describes an immutable set of targets and the license |
| 23 | // conditions each target must satisfy or "resolve" in a specific context. |
| 24 | // |
| 25 | // Ultimately, the purpose of recording the license metadata and building a |
| 26 | // license graph is to identify, describe, and verify the necessary actions or |
| 27 | // operations for compliance policy. |
| 28 | // |
| 29 | // i.e. What is the source-sharing policy? Has it been met? Meet it. |
| 30 | // |
| 31 | // i.e. Are there incompatible policy requirements? Such as a source-sharing |
| 32 | // policy applied to code that policy also says may not be shared? If so, stop |
| 33 | // and remove the dependencies that create the situation. |
| 34 | // |
| 35 | // The ResolutionSet is the base unit for mapping license conditions to the |
| 36 | // targets triggering some necessary action per policy. Different ResolutionSet |
| 37 | // values may be calculated for different contexts. |
| 38 | // |
| 39 | // e.g. Suppose an unencumbered binary links in a notice .a library. |
| 40 | // |
| 41 | // An "unencumbered" condition would originate from the binary, and a "notice" |
| 42 | // condition would originate from the .a library. A ResolutionSet for the |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 43 | // context of the Notice policy might attach both conditions to the binary to |
| 44 | // act on the origin of each condition. By attaching the notice condition to |
Bob Badour | a99ac62 | 2021-10-25 16:21:00 -0700 | [diff] [blame] | 45 | // the binary, the ResolutionSet stipulates the policy that the release of the |
| 46 | // unencumbered binary must provide suitable notice for the .a library. |
| 47 | // |
| 48 | // The resulting ResolutionSet could be used for building a notice file, for |
| 49 | // validating that a suitable notice has been built into the distribution, or |
| 50 | // for reporting what notices need to be given. |
| 51 | // |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 52 | // The action is defined by the context. In the above example, the action is |
| 53 | // providing notice for the module acted on. In another context, the action |
| 54 | // might be sharing the source-code or preserving the privacy of the module |
| 55 | // acted on. |
| 56 | type ResolutionSet map[*TargetNode]ActionSet |
| 57 | |
| 58 | // AttachesTo identifies the list of targets triggering action to resolve |
| 59 | // conditions. (unordered) |
| 60 | func (rs ResolutionSet) AttachesTo() TargetNodeList { |
| 61 | result := make(TargetNodeList, 0, len(rs)) |
| 62 | for attachesTo := range rs { |
| 63 | result = append(result, attachesTo) |
| 64 | } |
| 65 | return result |
Bob Badour | a99ac62 | 2021-10-25 16:21:00 -0700 | [diff] [blame] | 66 | } |
| 67 | |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 68 | // AttachesToTarget returns true if the set contains conditions that |
| 69 | // are `attachedTo`. |
| 70 | func (rs ResolutionSet) AttachesToTarget(target *TargetNode) bool { |
| 71 | _, isPresent := rs[target] |
| 72 | return isPresent |
| 73 | } |
| 74 | |
Bob Badour | 085a2c2 | 2022-09-21 19:36:59 -0700 | [diff] [blame] | 75 | // IsPureAggregate returns true if `target`, which must be in |
| 76 | // `AttachesTo()` resolves to a pure aggregate in the resolution. |
| 77 | func (rs ResolutionSet) IsPureAggregate(target *TargetNode) bool { |
| 78 | _, isPresent := rs[target] |
| 79 | if !isPresent { |
| 80 | panic(fmt.Errorf("ResolutionSet.IsPureAggregate(%s): not attached to %s", target.Name(), target.Name())) |
| 81 | } |
| 82 | return target.pure |
| 83 | } |
| 84 | |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 85 | // Resolutions returns the list of resolutions that `attachedTo` |
| 86 | // target must resolve. Returns empty list if no conditions apply. |
| 87 | func (rs ResolutionSet) Resolutions(attachesTo *TargetNode) ResolutionList { |
| 88 | as, ok := rs[attachesTo] |
| 89 | if !ok { |
| 90 | return nil |
| 91 | } |
| 92 | result := make(ResolutionList, 0, len(as)) |
| 93 | for actsOn, cs := range as { |
| 94 | result = append(result, Resolution{attachesTo, actsOn, cs}) |
| 95 | } |
| 96 | return result |
| 97 | } |
| 98 | |
Bob Badour | c817845 | 2022-01-31 13:05:53 -0800 | [diff] [blame] | 99 | // AllActions returns the set of actions required to resolve the set omitting |
| 100 | // the attachment. |
| 101 | func (rs ResolutionSet) AllActions() ActionSet { |
| 102 | result := make(ActionSet) |
| 103 | for _, as := range rs { |
| 104 | for actsOn, cs := range as { |
| 105 | if _, ok := result[actsOn]; ok { |
| 106 | result[actsOn] = cs.Union(result[actsOn]) |
| 107 | } else { |
| 108 | result[actsOn] = cs |
| 109 | } |
| 110 | } |
| 111 | } |
| 112 | return result |
| 113 | } |
| 114 | |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 115 | // String returns a human-readable string representation of the set. |
| 116 | func (rs ResolutionSet) String() string { |
Bob Badour | a99ac62 | 2021-10-25 16:21:00 -0700 | [diff] [blame] | 117 | var sb strings.Builder |
| 118 | fmt.Fprintf(&sb, "{") |
| 119 | sep := "" |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 120 | for attachesTo, as := range rs { |
| 121 | fmt.Fprintf(&sb, "%s%s -> %s", sep, attachesTo.Name(), as.String()) |
Bob Badour | a99ac62 | 2021-10-25 16:21:00 -0700 | [diff] [blame] | 122 | sep = ", " |
| 123 | } |
| 124 | fmt.Fprintf(&sb, "}") |
| 125 | return sb.String() |
| 126 | } |
| 127 | |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 128 | // ActionSet identifies a set of targets to act on and the license conditions |
| 129 | // the action will resolve. |
| 130 | type ActionSet map[*TargetNode]LicenseConditionSet |
Bob Badour | a99ac62 | 2021-10-25 16:21:00 -0700 | [diff] [blame] | 131 | |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 132 | // String returns a human-readable string representation of the set. |
| 133 | func (as ActionSet) String() string { |
| 134 | var sb strings.Builder |
| 135 | fmt.Fprintf(&sb, "{") |
| 136 | sep := "" |
Bob Badour | a99ac62 | 2021-10-25 16:21:00 -0700 | [diff] [blame] | 137 | for actsOn, cs := range as { |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 138 | fmt.Fprintf(&sb, "%s%s%s", sep, actsOn.Name(), cs.String()) |
| 139 | sep = ", " |
Bob Badour | a99ac62 | 2021-10-25 16:21:00 -0700 | [diff] [blame] | 140 | } |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 141 | fmt.Fprintf(&sb, "}") |
| 142 | return sb.String() |
Bob Badour | a99ac62 | 2021-10-25 16:21:00 -0700 | [diff] [blame] | 143 | } |