blob: 9dab05bbf03bf2569de356d302a123019e23e19c [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 (
23 // ImpliesUnencumbered lists the condition names representing an author attempt to disclaim copyright.
24 ImpliesUnencumbered = ConditionNames{"unencumbered"}
25
26 // ImpliesPermissive lists the condition names representing copyrighted but "licensed without policy requirements".
27 ImpliesPermissive = ConditionNames{"permissive"}
28
29 // ImpliesNotice lists the condition names implying a notice or attribution policy.
30 ImpliesNotice = ConditionNames{"unencumbered", "permissive", "notice", "reciprocal", "restricted", "proprietary", "by_exception_only"}
31
32 // ImpliesReciprocal lists the condition names implying a local source-sharing policy.
33 ImpliesReciprocal = ConditionNames{"reciprocal"}
34
35 // Restricted lists the condition names implying an infectious source-sharing policy.
36 ImpliesRestricted = ConditionNames{"restricted"}
37
38 // ImpliesProprietary lists the condition names implying a confidentiality policy.
39 ImpliesProprietary = ConditionNames{"proprietary"}
40
41 // ImpliesByExceptionOnly lists the condition names implying a policy for "license review and approval before use".
42 ImpliesByExceptionOnly = ConditionNames{"proprietary", "by_exception_only"}
43
44 // ImpliesPrivate lists the condition names implying a source-code privacy policy.
45 ImpliesPrivate = ConditionNames{"proprietary"}
46
47 // ImpliesShared lists the condition names implying a source-code sharing policy.
48 ImpliesShared = ConditionNames{"reciprocal", "restricted"}
49)
50
51var (
52 anyLgpl = regexp.MustCompile(`^SPDX-license-identifier-LGPL.*`)
53 versionedGpl = regexp.MustCompile(`^SPDX-license-identifier-GPL-\p{N}.*`)
54 genericGpl = regexp.MustCompile(`^SPDX-license-identifier-GPL$`)
55 ccBySa = regexp.MustCompile(`^SPDX-license-identifier-CC-BY.*-SA.*`)
56)
57
58// Resolution happens in two passes:
59//
60// 1. A bottom-up traversal propagates license conditions up to targets from
61// dendencies as needed.
62//
63// 2. For each condition of interest, a top-down traversal adjusts the attached
64// conditions pushing restricted down from targets into linked dependencies.
65//
66// The behavior of the 2 passes gets controlled by the 2 functions below.
67//
68// The first function controls what happens during the bottom-up traversal. In
69// general conditions flow up through static links but not other dependencies;
70// except, restricted sometimes flows up through dynamic links.
71//
72// In general, too, the originating target gets acted on to resolve the
73// condition (e.g. providing notice), but again restricted is special in that
74// it requires acting on (i.e. sharing source of) both the originating module
75// and the target using the module.
76//
77// The latter function controls what happens during the top-down traversal. In
78// general, only restricted conditions flow down at all, and only through
79// static links.
80//
81// Not all restricted licenses are create equal. Some have special rules or
82// exceptions. e.g. LGPL or "with classpath excption".
83
84// depActionsApplicableToTarget returns the actions which propagate up an
85// edge from dependency to target.
86//
87// This function sets the policy for the bottom-up traversal and how conditions
88// flow up the graph from dependencies to targets.
89//
90// If a pure aggregation is built into a derivative work that is not a pure
91// aggregation, per policy it ceases to be a pure aggregation in the context of
92// that derivative work. The `treatAsAggregate` parameter will be false for
93// non-aggregates and for aggregates in non-aggregate contexts.
94func depActionsApplicableToTarget(e TargetEdge, depActions actionSet, treatAsAggregate bool) actionSet {
95 result := make(actionSet)
96 if edgeIsDerivation(e) {
97 result.addSet(depActions)
98 for _, cs := range depActions.byName(ImpliesRestricted) {
99 result.add(e.Target(), cs)
100 }
101 return result
102 }
103 if !edgeIsDynamicLink(e) {
104 return result
105 }
106
107 restricted := depActions.byName(ImpliesRestricted)
108 for actsOn, cs := range restricted {
109 for _, lc := range cs.AsList() {
110 hasGpl := false
111 hasLgpl := false
112 hasClasspath := false
113 hasGeneric := false
114 hasOther := false
115 for _, kind := range lc.origin.LicenseKinds() {
116 if strings.HasSuffix(kind, "-with-classpath-exception") {
117 hasClasspath = true
118 } else if anyLgpl.MatchString(kind) {
119 hasLgpl = true
120 } else if versionedGpl.MatchString(kind) {
121 hasGpl = true
122 } else if genericGpl.MatchString(kind) {
123 hasGeneric = true
124 } else if kind == "legacy_restricted" || ccBySa.MatchString(kind) {
125 hasOther = true
126 }
127 }
128 if hasOther || hasGpl {
129 result.addCondition(actsOn, lc)
130 result.addCondition(e.Target(), lc)
131 continue
132 }
133 if hasClasspath && !edgeNodesAreIndependentModules(e) {
134 result.addCondition(actsOn, lc)
135 result.addCondition(e.Target(), lc)
136 continue
137 }
138 if hasLgpl || hasClasspath {
139 continue
140 }
141 if !hasGeneric {
142 continue
143 }
144 result.addCondition(actsOn, lc)
145 result.addCondition(e.Target(), lc)
146 }
147 }
148 return result
149}
150
151// targetConditionsApplicableToDep returns the conditions which propagate down
152// an edge from target to dependency.
153//
154// This function sets the policy for the top-down traversal and how conditions
155// flow down the graph from targets to dependencies.
156//
157// If a pure aggregation is built into a derivative work that is not a pure
158// aggregation, per policy it ceases to be a pure aggregation in the context of
159// that derivative work. The `treatAsAggregate` parameter will be false for
160// non-aggregates and for aggregates in non-aggregate contexts.
161func targetConditionsApplicableToDep(e TargetEdge, targetConditions *LicenseConditionSet, treatAsAggregate bool) *LicenseConditionSet {
162 result := targetConditions.Copy()
163
164 // reverse direction -- none of these apply to things depended-on, only to targets depending-on.
165 result.RemoveAllByName(ConditionNames{"unencumbered", "permissive", "notice", "reciprocal", "proprietary", "by_exception_only"})
166
167 if !edgeIsDerivation(e) && !edgeIsDynamicLink(e) {
168 // target is not a derivative work of dependency and is not linked to dependency
169 result.RemoveAllByName(ImpliesRestricted)
170 return result
171 }
172 if treatAsAggregate {
173 // If the author of a pure aggregate licenses it restricted, apply restricted to immediate dependencies.
174 // Otherwise, restricted does not propagate back down to dependencies.
175 restricted := result.ByName(ImpliesRestricted).AsList()
176 for _, lc := range restricted {
177 if lc.origin.name != e.e.target {
178 result.Remove(lc)
179 }
180 }
181 return result
182 }
183 if edgeIsDerivation(e) {
184 return result
185 }
186 restricted := result.ByName(ImpliesRestricted).AsList()
187 for _, lc := range restricted {
188 hasGpl := false
189 hasLgpl := false
190 hasClasspath := false
191 hasGeneric := false
192 hasOther := false
193 for _, kind := range lc.origin.LicenseKinds() {
194 if strings.HasSuffix(kind, "-with-classpath-exception") {
195 hasClasspath = true
196 } else if anyLgpl.MatchString(kind) {
197 hasLgpl = true
198 } else if versionedGpl.MatchString(kind) {
199 hasGpl = true
200 } else if genericGpl.MatchString(kind) {
201 hasGeneric = true
202 } else if kind == "legacy_restricted" || ccBySa.MatchString(kind) {
203 hasOther = true
204 }
205 }
206 if hasOther || hasGpl {
207 continue
208 }
209 if hasClasspath && !edgeNodesAreIndependentModules(e) {
210 continue
211 }
212 if hasGeneric && !hasLgpl && !hasClasspath {
213 continue
214 }
215 result.Remove(lc)
216 }
217 return result
218}
219
220// edgeIsDynamicLink returns true for edges representing shared libraries
221// linked dynamically at runtime.
222func edgeIsDynamicLink(e TargetEdge) bool {
223 return e.e.annotations.HasAnnotation("dynamic")
224}
225
226// edgeIsDerivation returns true for edges where the target is a derivative
227// work of dependency.
228func edgeIsDerivation(e TargetEdge) bool {
229 isDynamic := e.e.annotations.HasAnnotation("dynamic")
230 isToolchain := e.e.annotations.HasAnnotation("toolchain")
231 return !isDynamic && !isToolchain
232}
233
234// edgeNodesAreIndependentModules returns true for edges where the target and
235// dependency are independent modules.
236func edgeNodesAreIndependentModules(e TargetEdge) bool {
237 return e.Target().PackageName() != e.Dependency().PackageName()
238}