blob: 581912a7ee9d86d4a4364f81286fb09f7dec0992 [file] [log] [blame]
Bob Badour9ee7d032021-10-25 16:51:48 -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 "regexp"
19 "strings"
20)
21
22var (
Bob Badour67d8ae32022-01-10 18:32:54 -080023 // 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 Badour9ee7d032021-10-25 16:51:48 -070032 // ImpliesUnencumbered lists the condition names representing an author attempt to disclaim copyright.
Bob Badour103eb0f2022-01-10 13:50:57 -080033 ImpliesUnencumbered = LicenseConditionSet(UnencumberedCondition)
Bob Badour9ee7d032021-10-25 16:51:48 -070034
35 // ImpliesPermissive lists the condition names representing copyrighted but "licensed without policy requirements".
Bob Badour103eb0f2022-01-10 13:50:57 -080036 ImpliesPermissive = LicenseConditionSet(PermissiveCondition)
Bob Badour9ee7d032021-10-25 16:51:48 -070037
38 // ImpliesNotice lists the condition names implying a notice or attribution policy.
Bob Badour103eb0f2022-01-10 13:50:57 -080039 ImpliesNotice = LicenseConditionSet(UnencumberedCondition | PermissiveCondition | NoticeCondition | ReciprocalCondition |
40 RestrictedCondition | RestrictedClasspathExceptionCondition | WeaklyRestrictedCondition |
41 ProprietaryCondition | ByExceptionOnlyCondition)
Bob Badour9ee7d032021-10-25 16:51:48 -070042
43 // ImpliesReciprocal lists the condition names implying a local source-sharing policy.
Bob Badour103eb0f2022-01-10 13:50:57 -080044 ImpliesReciprocal = LicenseConditionSet(ReciprocalCondition)
Bob Badour9ee7d032021-10-25 16:51:48 -070045
46 // Restricted lists the condition names implying an infectious source-sharing policy.
Bob Badour103eb0f2022-01-10 13:50:57 -080047 ImpliesRestricted = LicenseConditionSet(RestrictedCondition | RestrictedClasspathExceptionCondition | WeaklyRestrictedCondition)
Bob Badour9ee7d032021-10-25 16:51:48 -070048
49 // ImpliesProprietary lists the condition names implying a confidentiality policy.
Bob Badour103eb0f2022-01-10 13:50:57 -080050 ImpliesProprietary = LicenseConditionSet(ProprietaryCondition)
Bob Badour9ee7d032021-10-25 16:51:48 -070051
52 // ImpliesByExceptionOnly lists the condition names implying a policy for "license review and approval before use".
Bob Badour103eb0f2022-01-10 13:50:57 -080053 ImpliesByExceptionOnly = LicenseConditionSet(ProprietaryCondition | ByExceptionOnlyCondition)
Bob Badour9ee7d032021-10-25 16:51:48 -070054
55 // ImpliesPrivate lists the condition names implying a source-code privacy policy.
Bob Badour103eb0f2022-01-10 13:50:57 -080056 ImpliesPrivate = LicenseConditionSet(ProprietaryCondition)
Bob Badour9ee7d032021-10-25 16:51:48 -070057
58 // ImpliesShared lists the condition names implying a source-code sharing policy.
Bob Badour103eb0f2022-01-10 13:50:57 -080059 ImpliesShared = LicenseConditionSet(ReciprocalCondition | RestrictedCondition | RestrictedClasspathExceptionCondition | WeaklyRestrictedCondition)
Bob Badour9ee7d032021-10-25 16:51:48 -070060)
61
62var (
63 anyLgpl = regexp.MustCompile(`^SPDX-license-identifier-LGPL.*`)
64 versionedGpl = regexp.MustCompile(`^SPDX-license-identifier-GPL-\p{N}.*`)
65 genericGpl = regexp.MustCompile(`^SPDX-license-identifier-GPL$`)
66 ccBySa = regexp.MustCompile(`^SPDX-license-identifier-CC-BY.*-SA.*`)
67)
68
Bob Badour103eb0f2022-01-10 13:50:57 -080069
70// LicenseConditionSetFromNames returns a set containing the recognized `names` and
71// silently ignoring or discarding the unrecognized `names`.
72func LicenseConditionSetFromNames(tn *TargetNode, names ...string) LicenseConditionSet {
73 cs := NewLicenseConditionSet()
74 for _, name := range names {
75 if name == "restricted" {
76 if 0 == len(tn.LicenseKinds()) {
77 cs = cs.Plus(RestrictedCondition)
78 continue
79 }
80 hasLgpl := false
81 hasClasspath := false
82 hasGeneric := false
83 for _, kind := range tn.LicenseKinds() {
84 if strings.HasSuffix(kind, "-with-classpath-exception") {
85 cs = cs.Plus(RestrictedClasspathExceptionCondition)
86 hasClasspath = true
87 } else if anyLgpl.MatchString(kind) {
88 cs = cs.Plus(WeaklyRestrictedCondition)
89 hasLgpl = true
90 } else if versionedGpl.MatchString(kind) {
91 cs = cs.Plus(RestrictedCondition)
92 } else if genericGpl.MatchString(kind) {
93 hasGeneric = true
94 } else if kind == "legacy_restricted" || ccBySa.MatchString(kind) {
95 cs = cs.Plus(RestrictedCondition)
96 } else {
97 cs = cs.Plus(RestrictedCondition)
98 }
99 }
100 if hasGeneric && !hasLgpl && !hasClasspath {
101 cs = cs.Plus(RestrictedCondition)
102 }
103 continue
104 }
105 if lc, ok := RecognizedConditionNames[name]; ok {
106 cs |= LicenseConditionSet(lc)
107 }
108 }
109 return cs
110}
111
112
113// Resolution happens in three phases:
Bob Badour9ee7d032021-10-25 16:51:48 -0700114//
Bob Badour103eb0f2022-01-10 13:50:57 -0800115// 1. A bottom-up traversal propagates (restricted) license conditions up to
116// targets from dendencies as needed.
Bob Badour9ee7d032021-10-25 16:51:48 -0700117//
Bob Badour103eb0f2022-01-10 13:50:57 -0800118// 2. For each condition of interest, a top-down traversal propagates
119// (restricted) conditions down from targets into linked dependencies.
Bob Badour9ee7d032021-10-25 16:51:48 -0700120//
Bob Badour103eb0f2022-01-10 13:50:57 -0800121// 3. Finally, a walk of the shipped target nodes attaches resolutions to the
122// ancestor nodes from the root down to and including the first non-container.
Bob Badour9ee7d032021-10-25 16:51:48 -0700123//
Bob Badour103eb0f2022-01-10 13:50:57 -0800124// e.g. If a disk image contains a binary bin1 that links a library liba, the
125// notice requirement for liba gets attached to the disk image and to bin1.
126// Because liba doesn't actually get shipped as a separate artifact, but only
127// as bits in bin1, it has no actions 'attached' to it. The actions attached
128// to the image and to bin1 'act on' liba by providing notice.
Bob Badour9ee7d032021-10-25 16:51:48 -0700129//
Bob Badour103eb0f2022-01-10 13:50:57 -0800130// The behavior of the 3 phases gets controlled by the 3 functions below.
Bob Badour9ee7d032021-10-25 16:51:48 -0700131//
Bob Badour103eb0f2022-01-10 13:50:57 -0800132// The first function controls what happens during the bottom-up propagation.
133// Restricted conditions propagate up all non-toolchain dependencies; except,
134// some do not propagate up dynamic links, which may depend on whether the
135// modules are independent.
136//
137// The second function controls what happens during the top-down propagation.
138// Restricted conditions propagate down as above with the added caveat that
139// inherited restricted conditions do not propagate from pure aggregates to
140// their dependencies.
141//
142// The final function controls which conditions apply/get attached to ancestors
143// depending on the types of dependencies involved. All conditions apply across
144// normal derivation dependencies. No conditions apply across toolchain
145// dependencies. Some restricted conditions apply across dynamic link
146// dependencies.
Bob Badour9ee7d032021-10-25 16:51:48 -0700147//
148// Not all restricted licenses are create equal. Some have special rules or
149// exceptions. e.g. LGPL or "with classpath excption".
150
Bob Badour103eb0f2022-01-10 13:50:57 -0800151
152// depConditionsPropagatingToTarget returns the conditions which propagate up an
Bob Badour9ee7d032021-10-25 16:51:48 -0700153// edge from dependency to target.
154//
Bob Badour103eb0f2022-01-10 13:50:57 -0800155// This function sets the policy for the bottom-up propagation and how conditions
Bob Badour9ee7d032021-10-25 16:51:48 -0700156// flow up the graph from dependencies to targets.
157//
158// If a pure aggregation is built into a derivative work that is not a pure
159// aggregation, per policy it ceases to be a pure aggregation in the context of
160// that derivative work. The `treatAsAggregate` parameter will be false for
161// non-aggregates and for aggregates in non-aggregate contexts.
Bob Badour103eb0f2022-01-10 13:50:57 -0800162func depConditionsPropagatingToTarget(lg *LicenseGraph, e *TargetEdge, depConditions LicenseConditionSet, treatAsAggregate bool) LicenseConditionSet {
163 result := LicenseConditionSet(0x0000)
Bob Badour9ee7d032021-10-25 16:51:48 -0700164 if edgeIsDerivation(e) {
Bob Badour103eb0f2022-01-10 13:50:57 -0800165 result |= depConditions & ImpliesRestricted
Bob Badour9ee7d032021-10-25 16:51:48 -0700166 return result
167 }
168 if !edgeIsDynamicLink(e) {
169 return result
170 }
171
Bob Badour103eb0f2022-01-10 13:50:57 -0800172 result |= depConditions & LicenseConditionSet(RestrictedCondition)
173 if 0 != (depConditions & LicenseConditionSet(RestrictedClasspathExceptionCondition)) && !edgeNodesAreIndependentModules(e) {
174 result |= LicenseConditionSet(RestrictedClasspathExceptionCondition)
Bob Badour9ee7d032021-10-25 16:51:48 -0700175 }
176 return result
177}
178
Bob Badour103eb0f2022-01-10 13:50:57 -0800179// targetConditionsPropagatingToDep returns the conditions which propagate down
Bob Badour9ee7d032021-10-25 16:51:48 -0700180// an edge from target to dependency.
181//
182// This function sets the policy for the top-down traversal and how conditions
183// flow down the graph from targets to dependencies.
184//
185// If a pure aggregation is built into a derivative work that is not a pure
186// aggregation, per policy it ceases to be a pure aggregation in the context of
187// that derivative work. The `treatAsAggregate` parameter will be false for
188// non-aggregates and for aggregates in non-aggregate contexts.
Bob Badour103eb0f2022-01-10 13:50:57 -0800189func targetConditionsPropagatingToDep(lg *LicenseGraph, e *TargetEdge, targetConditions LicenseConditionSet, treatAsAggregate bool) LicenseConditionSet {
190 result := targetConditions
Bob Badour9ee7d032021-10-25 16:51:48 -0700191
192 // reverse direction -- none of these apply to things depended-on, only to targets depending-on.
Bob Badour103eb0f2022-01-10 13:50:57 -0800193 result = result.Minus(UnencumberedCondition, PermissiveCondition, NoticeCondition, ReciprocalCondition, ProprietaryCondition, ByExceptionOnlyCondition)
Bob Badour9ee7d032021-10-25 16:51:48 -0700194
195 if !edgeIsDerivation(e) && !edgeIsDynamicLink(e) {
196 // target is not a derivative work of dependency and is not linked to dependency
Bob Badour103eb0f2022-01-10 13:50:57 -0800197 result = result.Difference(ImpliesRestricted)
Bob Badour9ee7d032021-10-25 16:51:48 -0700198 return result
199 }
200 if treatAsAggregate {
201 // If the author of a pure aggregate licenses it restricted, apply restricted to immediate dependencies.
202 // Otherwise, restricted does not propagate back down to dependencies.
Bob Badour103eb0f2022-01-10 13:50:57 -0800203 if !LicenseConditionSetFromNames(e.target, e.target.proto.LicenseConditions...).MatchesAnySet(ImpliesRestricted) {
204 result = result.Difference(ImpliesRestricted)
Bob Badour9ee7d032021-10-25 16:51:48 -0700205 }
206 return result
207 }
208 if edgeIsDerivation(e) {
209 return result
210 }
Bob Badour103eb0f2022-01-10 13:50:57 -0800211 result = result.Minus(WeaklyRestrictedCondition)
212 if edgeNodesAreIndependentModules(e) {
213 result = result.Minus(RestrictedClasspathExceptionCondition)
Bob Badour9ee7d032021-10-25 16:51:48 -0700214 }
215 return result
216}
217
Bob Badour103eb0f2022-01-10 13:50:57 -0800218// conditionsAttachingAcrossEdge returns the subset of conditions in `universe`
219// that apply across edge `e`.
220//
221// This function sets the policy for attaching actions to ancestor nodes in the
222// final resolution walk.
223func conditionsAttachingAcrossEdge(lg *LicenseGraph, e *TargetEdge, universe LicenseConditionSet) LicenseConditionSet {
224 result := universe
225 if edgeIsDerivation(e) {
226 return result
227 }
228 if !edgeIsDynamicLink(e) {
229 return NewLicenseConditionSet()
230 }
231
232 result &= LicenseConditionSet(RestrictedCondition | RestrictedClasspathExceptionCondition)
233 if 0 != (result & LicenseConditionSet(RestrictedClasspathExceptionCondition)) && edgeNodesAreIndependentModules(e) {
234 result &= LicenseConditionSet(RestrictedCondition)
235 }
236 return result
237}
238
239
Bob Badour9ee7d032021-10-25 16:51:48 -0700240// edgeIsDynamicLink returns true for edges representing shared libraries
241// linked dynamically at runtime.
Bob Badour103eb0f2022-01-10 13:50:57 -0800242func edgeIsDynamicLink(e *TargetEdge) bool {
243 return e.annotations.HasAnnotation("dynamic")
Bob Badour9ee7d032021-10-25 16:51:48 -0700244}
245
246// edgeIsDerivation returns true for edges where the target is a derivative
247// work of dependency.
Bob Badour103eb0f2022-01-10 13:50:57 -0800248func edgeIsDerivation(e *TargetEdge) bool {
249 isDynamic := e.annotations.HasAnnotation("dynamic")
250 isToolchain := e.annotations.HasAnnotation("toolchain")
Bob Badour9ee7d032021-10-25 16:51:48 -0700251 return !isDynamic && !isToolchain
252}
253
254// edgeNodesAreIndependentModules returns true for edges where the target and
255// dependency are independent modules.
Bob Badour103eb0f2022-01-10 13:50:57 -0800256func edgeNodesAreIndependentModules(e *TargetEdge) bool {
257 return e.target.PackageName() != e.dependency.PackageName()
Bob Badour9ee7d032021-10-25 16:51:48 -0700258}