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