Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -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 | "regexp" |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 19 | ) |
| 20 | |
| 21 | var ( |
Bob Badour | 67d8ae3 | 2022-01-10 18:32:54 -0800 | [diff] [blame] | 22 | // RecognizedAnnotations identifies the set of annotations that have |
| 23 | // meaning for compliance policy. |
| 24 | RecognizedAnnotations = map[string]string{ |
| 25 | // used in readgraph.go to avoid creating 1000's of copies of the below 3 strings. |
| 26 | "static": "static", |
| 27 | "dynamic": "dynamic", |
| 28 | "toolchain": "toolchain", |
| 29 | } |
| 30 | |
Bob Badour | e6fdd14 | 2021-12-09 22:10:43 -0800 | [diff] [blame] | 31 | // SafePathPrefixes maps the path prefixes presumed not to contain any |
| 32 | // proprietary or confidential pathnames to whether to strip the prefix |
| 33 | // from the path when used as the library name for notices. |
| 34 | SafePathPrefixes = map[string]bool{ |
Colin Cross | 35f79c3 | 2022-01-27 15:18:52 -0800 | [diff] [blame] | 35 | "external/": true, |
| 36 | "art/": false, |
| 37 | "build/": false, |
| 38 | "cts/": false, |
| 39 | "dalvik/": false, |
| 40 | "developers/": false, |
Bob Badour | e6fdd14 | 2021-12-09 22:10:43 -0800 | [diff] [blame] | 41 | "development/": false, |
Colin Cross | 35f79c3 | 2022-01-27 15:18:52 -0800 | [diff] [blame] | 42 | "frameworks/": false, |
| 43 | "packages/": true, |
| 44 | "prebuilts/": false, |
| 45 | "sdk/": false, |
| 46 | "system/": false, |
| 47 | "test/": false, |
| 48 | "toolchain/": false, |
| 49 | "tools/": false, |
Bob Badour | e6fdd14 | 2021-12-09 22:10:43 -0800 | [diff] [blame] | 50 | } |
| 51 | |
| 52 | // SafePrebuiltPrefixes maps the regular expression to match a prebuilt |
| 53 | // containing the path of a safe prefix to the safe prefix. |
| 54 | SafePrebuiltPrefixes = make(map[*regexp.Regexp]string) |
| 55 | |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 56 | // ImpliesUnencumbered lists the condition names representing an author attempt to disclaim copyright. |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 57 | ImpliesUnencumbered = LicenseConditionSet(UnencumberedCondition) |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 58 | |
| 59 | // ImpliesPermissive lists the condition names representing copyrighted but "licensed without policy requirements". |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 60 | ImpliesPermissive = LicenseConditionSet(PermissiveCondition) |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 61 | |
| 62 | // ImpliesNotice lists the condition names implying a notice or attribution policy. |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 63 | ImpliesNotice = LicenseConditionSet(UnencumberedCondition | PermissiveCondition | NoticeCondition | ReciprocalCondition | |
Bob Badour | 10f5c48 | 2022-09-20 21:44:17 -0700 | [diff] [blame^] | 64 | RestrictedCondition | WeaklyRestrictedCondition | ProprietaryCondition | ByExceptionOnlyCondition) |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 65 | |
| 66 | // ImpliesReciprocal lists the condition names implying a local source-sharing policy. |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 67 | ImpliesReciprocal = LicenseConditionSet(ReciprocalCondition) |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 68 | |
| 69 | // Restricted lists the condition names implying an infectious source-sharing policy. |
Bob Badour | 10f5c48 | 2022-09-20 21:44:17 -0700 | [diff] [blame^] | 70 | ImpliesRestricted = LicenseConditionSet(RestrictedCondition | WeaklyRestrictedCondition) |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 71 | |
| 72 | // ImpliesProprietary lists the condition names implying a confidentiality policy. |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 73 | ImpliesProprietary = LicenseConditionSet(ProprietaryCondition) |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 74 | |
| 75 | // ImpliesByExceptionOnly lists the condition names implying a policy for "license review and approval before use". |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 76 | ImpliesByExceptionOnly = LicenseConditionSet(ProprietaryCondition | ByExceptionOnlyCondition) |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 77 | |
| 78 | // ImpliesPrivate lists the condition names implying a source-code privacy policy. |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 79 | ImpliesPrivate = LicenseConditionSet(ProprietaryCondition) |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 80 | |
| 81 | // ImpliesShared lists the condition names implying a source-code sharing policy. |
Bob Badour | 10f5c48 | 2022-09-20 21:44:17 -0700 | [diff] [blame^] | 82 | ImpliesShared = LicenseConditionSet(ReciprocalCondition | RestrictedCondition | WeaklyRestrictedCondition) |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 83 | ) |
| 84 | |
| 85 | var ( |
| 86 | anyLgpl = regexp.MustCompile(`^SPDX-license-identifier-LGPL.*`) |
| 87 | versionedGpl = regexp.MustCompile(`^SPDX-license-identifier-GPL-\p{N}.*`) |
| 88 | genericGpl = regexp.MustCompile(`^SPDX-license-identifier-GPL$`) |
| 89 | ccBySa = regexp.MustCompile(`^SPDX-license-identifier-CC-BY.*-SA.*`) |
| 90 | ) |
| 91 | |
Bob Badour | e6fdd14 | 2021-12-09 22:10:43 -0800 | [diff] [blame] | 92 | func init() { |
| 93 | for prefix := range SafePathPrefixes { |
| 94 | if prefix == "prebuilts/" { |
| 95 | continue |
| 96 | } |
| 97 | r := regexp.MustCompile("^prebuilts/[^ ]*/" + prefix) |
| 98 | SafePrebuiltPrefixes[r] = prefix |
| 99 | } |
| 100 | } |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 101 | |
| 102 | // LicenseConditionSetFromNames returns a set containing the recognized `names` and |
| 103 | // silently ignoring or discarding the unrecognized `names`. |
| 104 | func LicenseConditionSetFromNames(tn *TargetNode, names ...string) LicenseConditionSet { |
| 105 | cs := NewLicenseConditionSet() |
| 106 | for _, name := range names { |
| 107 | if name == "restricted" { |
| 108 | if 0 == len(tn.LicenseKinds()) { |
| 109 | cs = cs.Plus(RestrictedCondition) |
| 110 | continue |
| 111 | } |
| 112 | hasLgpl := false |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 113 | hasGeneric := false |
| 114 | for _, kind := range tn.LicenseKinds() { |
Bob Badour | 10f5c48 | 2022-09-20 21:44:17 -0700 | [diff] [blame^] | 115 | if anyLgpl.MatchString(kind) { |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 116 | cs = cs.Plus(WeaklyRestrictedCondition) |
| 117 | hasLgpl = true |
| 118 | } else if versionedGpl.MatchString(kind) { |
| 119 | cs = cs.Plus(RestrictedCondition) |
| 120 | } else if genericGpl.MatchString(kind) { |
| 121 | hasGeneric = true |
| 122 | } else if kind == "legacy_restricted" || ccBySa.MatchString(kind) { |
| 123 | cs = cs.Plus(RestrictedCondition) |
| 124 | } else { |
| 125 | cs = cs.Plus(RestrictedCondition) |
| 126 | } |
| 127 | } |
Bob Badour | 10f5c48 | 2022-09-20 21:44:17 -0700 | [diff] [blame^] | 128 | if hasGeneric && !hasLgpl { |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 129 | cs = cs.Plus(RestrictedCondition) |
| 130 | } |
| 131 | continue |
| 132 | } |
| 133 | if lc, ok := RecognizedConditionNames[name]; ok { |
| 134 | cs |= LicenseConditionSet(lc) |
| 135 | } |
| 136 | } |
| 137 | return cs |
| 138 | } |
| 139 | |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 140 | // Resolution happens in three phases: |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 141 | // |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 142 | // 1. A bottom-up traversal propagates (restricted) license conditions up to |
| 143 | // targets from dendencies as needed. |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 144 | // |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 145 | // 2. For each condition of interest, a top-down traversal propagates |
| 146 | // (restricted) conditions down from targets into linked dependencies. |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 147 | // |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 148 | // 3. Finally, a walk of the shipped target nodes attaches resolutions to the |
| 149 | // ancestor nodes from the root down to and including the first non-container. |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 150 | // |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 151 | // e.g. If a disk image contains a binary bin1 that links a library liba, the |
| 152 | // notice requirement for liba gets attached to the disk image and to bin1. |
| 153 | // Because liba doesn't actually get shipped as a separate artifact, but only |
| 154 | // as bits in bin1, it has no actions 'attached' to it. The actions attached |
| 155 | // to the image and to bin1 'act on' liba by providing notice. |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 156 | // |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 157 | // The behavior of the 3 phases gets controlled by the 3 functions below. |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 158 | // |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 159 | // The first function controls what happens during the bottom-up propagation. |
| 160 | // Restricted conditions propagate up all non-toolchain dependencies; except, |
| 161 | // some do not propagate up dynamic links, which may depend on whether the |
| 162 | // modules are independent. |
| 163 | // |
| 164 | // The second function controls what happens during the top-down propagation. |
| 165 | // Restricted conditions propagate down as above with the added caveat that |
| 166 | // inherited restricted conditions do not propagate from pure aggregates to |
| 167 | // their dependencies. |
| 168 | // |
| 169 | // The final function controls which conditions apply/get attached to ancestors |
| 170 | // depending on the types of dependencies involved. All conditions apply across |
| 171 | // normal derivation dependencies. No conditions apply across toolchain |
| 172 | // dependencies. Some restricted conditions apply across dynamic link |
| 173 | // dependencies. |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 174 | // |
| 175 | // Not all restricted licenses are create equal. Some have special rules or |
| 176 | // exceptions. e.g. LGPL or "with classpath excption". |
| 177 | |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 178 | // depConditionsPropagatingToTarget returns the conditions which propagate up an |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 179 | // edge from dependency to target. |
| 180 | // |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 181 | // This function sets the policy for the bottom-up propagation and how conditions |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 182 | // flow up the graph from dependencies to targets. |
| 183 | // |
| 184 | // If a pure aggregation is built into a derivative work that is not a pure |
| 185 | // aggregation, per policy it ceases to be a pure aggregation in the context of |
| 186 | // that derivative work. The `treatAsAggregate` parameter will be false for |
| 187 | // non-aggregates and for aggregates in non-aggregate contexts. |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 188 | func depConditionsPropagatingToTarget(lg *LicenseGraph, e *TargetEdge, depConditions LicenseConditionSet, treatAsAggregate bool) LicenseConditionSet { |
| 189 | result := LicenseConditionSet(0x0000) |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 190 | if edgeIsDerivation(e) { |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 191 | result |= depConditions & ImpliesRestricted |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 192 | return result |
| 193 | } |
| 194 | if !edgeIsDynamicLink(e) { |
| 195 | return result |
| 196 | } |
| 197 | |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 198 | result |= depConditions & LicenseConditionSet(RestrictedCondition) |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 199 | return result |
| 200 | } |
| 201 | |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 202 | // targetConditionsPropagatingToDep returns the conditions which propagate down |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 203 | // an edge from target to dependency. |
| 204 | // |
| 205 | // This function sets the policy for the top-down traversal and how conditions |
| 206 | // flow down the graph from targets to dependencies. |
| 207 | // |
| 208 | // If a pure aggregation is built into a derivative work that is not a pure |
| 209 | // aggregation, per policy it ceases to be a pure aggregation in the context of |
| 210 | // that derivative work. The `treatAsAggregate` parameter will be false for |
| 211 | // non-aggregates and for aggregates in non-aggregate contexts. |
Bob Badour | c817845 | 2022-01-31 13:05:53 -0800 | [diff] [blame] | 212 | func targetConditionsPropagatingToDep(lg *LicenseGraph, e *TargetEdge, targetConditions LicenseConditionSet, treatAsAggregate bool, conditionsFn TraceConditions) LicenseConditionSet { |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 213 | result := targetConditions |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 214 | |
| 215 | // reverse direction -- none of these apply to things depended-on, only to targets depending-on. |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 216 | result = result.Minus(UnencumberedCondition, PermissiveCondition, NoticeCondition, ReciprocalCondition, ProprietaryCondition, ByExceptionOnlyCondition) |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 217 | |
| 218 | if !edgeIsDerivation(e) && !edgeIsDynamicLink(e) { |
| 219 | // target is not a derivative work of dependency and is not linked to dependency |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 220 | result = result.Difference(ImpliesRestricted) |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 221 | return result |
| 222 | } |
| 223 | if treatAsAggregate { |
| 224 | // If the author of a pure aggregate licenses it restricted, apply restricted to immediate dependencies. |
| 225 | // Otherwise, restricted does not propagate back down to dependencies. |
Bob Badour | c817845 | 2022-01-31 13:05:53 -0800 | [diff] [blame] | 226 | if !conditionsFn(e.target).MatchesAnySet(ImpliesRestricted) { |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 227 | result = result.Difference(ImpliesRestricted) |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 228 | } |
| 229 | return result |
| 230 | } |
| 231 | if edgeIsDerivation(e) { |
| 232 | return result |
| 233 | } |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 234 | result = result.Minus(WeaklyRestrictedCondition) |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 235 | return result |
| 236 | } |
| 237 | |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 238 | // conditionsAttachingAcrossEdge returns the subset of conditions in `universe` |
| 239 | // that apply across edge `e`. |
| 240 | // |
| 241 | // This function sets the policy for attaching actions to ancestor nodes in the |
| 242 | // final resolution walk. |
| 243 | func conditionsAttachingAcrossEdge(lg *LicenseGraph, e *TargetEdge, universe LicenseConditionSet) LicenseConditionSet { |
| 244 | result := universe |
| 245 | if edgeIsDerivation(e) { |
| 246 | return result |
| 247 | } |
| 248 | if !edgeIsDynamicLink(e) { |
| 249 | return NewLicenseConditionSet() |
| 250 | } |
| 251 | |
Bob Badour | 10f5c48 | 2022-09-20 21:44:17 -0700 | [diff] [blame^] | 252 | result &= LicenseConditionSet(RestrictedCondition) |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 253 | return result |
| 254 | } |
| 255 | |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 256 | // edgeIsDynamicLink returns true for edges representing shared libraries |
| 257 | // linked dynamically at runtime. |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 258 | func edgeIsDynamicLink(e *TargetEdge) bool { |
| 259 | return e.annotations.HasAnnotation("dynamic") |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 260 | } |
| 261 | |
| 262 | // edgeIsDerivation returns true for edges where the target is a derivative |
| 263 | // work of dependency. |
Bob Badour | 103eb0f | 2022-01-10 13:50:57 -0800 | [diff] [blame] | 264 | func edgeIsDerivation(e *TargetEdge) bool { |
| 265 | isDynamic := e.annotations.HasAnnotation("dynamic") |
| 266 | isToolchain := e.annotations.HasAnnotation("toolchain") |
Bob Badour | 9ee7d03 | 2021-10-25 16:51:48 -0700 | [diff] [blame] | 267 | return !isDynamic && !isToolchain |
| 268 | } |