blob: 98ed31d38bfc74f6b6ed3099046f21ac01fe6e1a [file] [log] [blame]
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001// Copyright 2014 Google Inc. All rights reserved.
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 com.google.devtools.build.lib.packages;
16
17import com.google.common.annotations.VisibleForTesting;
18import com.google.common.collect.HashBasedTable;
19import com.google.common.collect.ImmutableSet;
20import com.google.common.collect.ImmutableTable;
21import com.google.common.collect.Sets;
22import com.google.common.collect.Table;
Lukacs Berkia6434362015-09-15 13:56:14 +000023import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010024import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
25import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
26import com.google.devtools.build.lib.events.Event;
27import com.google.devtools.build.lib.events.EventHandler;
28import com.google.devtools.build.lib.events.Location;
29import com.google.devtools.build.lib.syntax.Label;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010030
31import java.util.Collection;
32import java.util.Collections;
33import java.util.EnumSet;
34import java.util.List;
35import java.util.Set;
36
37/**
38 * Support for license and distribution checking.
39 */
40@Immutable @ThreadSafe
41public final class License {
42
43 private final Set<LicenseType> licenseTypes;
44 private final Set<Label> exceptions;
45
46 /**
47 * The error that's thrown if a build file contains an invalid license string.
48 */
49 public static class LicenseParsingException extends Exception {
50 public LicenseParsingException(String s) {
51 super(s);
52 }
53 }
54
55 /**
56 * LicenseType is the basis of the License lattice - stricter licenses should
57 * be declared before less-strict licenses in the enum.
58 *
59 * <p>Note that the order is important for the purposes of finding the least
60 * restrictive license.
61 */
62 public enum LicenseType {
63 BY_EXCEPTION_ONLY,
64 RESTRICTED,
65 RESTRICTED_IF_STATICALLY_LINKED,
66 RECIPROCAL,
67 NOTICE,
68 PERMISSIVE,
69 UNENCUMBERED,
70 NONE
71 }
72
73 /**
74 * Gets the least restrictive license type from the list of licenses declared
75 * for a target. For the purposes of license checking, the license type set of
76 * a declared license can be reduced to its least restrictive member.
77 *
78 * @param types a collection of license types
79 * @return the least restrictive license type
80 */
81 @VisibleForTesting
82 static LicenseType leastRestrictive(Collection<LicenseType> types) {
83 return types.isEmpty() ? LicenseType.BY_EXCEPTION_ONLY : Collections.max(types);
84 }
85
86 /**
87 * An instance of LicenseType.None with no exceptions, used for packages
88 * outside of third_party which have no license clause in their BUILD files.
89 */
90 public static final License NO_LICENSE =
91 new License(ImmutableSet.of(LicenseType.NONE), Collections.<Label>emptySet());
92
93 /**
94 * A default instance of Distributions which is used for packages which
95 * have no "distribs" declaration. If nothing is declared, we opt for the
96 * most permissive kind of distribution, which is the internal-only distrib.
97 */
98 public static final Set<DistributionType> DEFAULT_DISTRIB =
99 Collections.singleton(DistributionType.INTERNAL);
100
101 /**
102 * The types of distribution that are supported.
103 */
104 public enum DistributionType {
105 INTERNAL,
106 WEB,
107 CLIENT,
108 EMBEDDED
109 }
110
111 /**
112 * Parses a set of strings declaring distribution types.
113 *
114 * @param distStrings strings containing distribution declarations from BUILD
115 * files
116 * @return a new, unmodifiable set of DistributionTypes
117 * @throws LicenseParsingException
118 */
119 public static Set<DistributionType> parseDistributions(Collection<String> distStrings)
120 throws LicenseParsingException {
121 if (distStrings.isEmpty()) {
122 return Collections.unmodifiableSet(EnumSet.of(DistributionType.INTERNAL));
123 } else {
124 Set<DistributionType> result = EnumSet.noneOf(DistributionType.class);
125 for (String distStr : distStrings) {
126 try {
Ulf Adams07dba942015-03-05 14:47:37 +0000127 DistributionType dist = DistributionType.valueOf(distStr.toUpperCase());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100128 result.add(dist);
129 } catch (IllegalArgumentException e) {
130 throw new LicenseParsingException("Invalid distribution type '" + distStr + "'");
131 }
132 }
133 return Collections.unmodifiableSet(result);
134 }
135 }
136
137 private static final Object MARKER = new Object();
138
139 /**
140 * The license incompatibility set. This contains the set of
141 * (Distribution,License) pairs that should generate errors.
142 */
143 private static Table<DistributionType, LicenseType, Object> LICENSE_INCOMPATIBILIES =
144 createLicenseIncompatibilitySet();
145
146 private static Table<DistributionType, LicenseType, Object> createLicenseIncompatibilitySet() {
147 Table<DistributionType, LicenseType, Object> result = HashBasedTable.create();
148 result.put(DistributionType.CLIENT, LicenseType.RESTRICTED, MARKER);
149 result.put(DistributionType.EMBEDDED, LicenseType.RESTRICTED, MARKER);
150 result.put(DistributionType.INTERNAL, LicenseType.BY_EXCEPTION_ONLY, MARKER);
151 result.put(DistributionType.CLIENT, LicenseType.BY_EXCEPTION_ONLY, MARKER);
152 result.put(DistributionType.WEB, LicenseType.BY_EXCEPTION_ONLY, MARKER);
153 result.put(DistributionType.EMBEDDED, LicenseType.BY_EXCEPTION_ONLY, MARKER);
154 return ImmutableTable.copyOf(result);
155 }
156
157 /**
158 * The license warning set. This contains the set of
159 * (Distribution,License) pairs that should generate warnings when the user
160 * requests verbose license checking.
161 */
162 private static Table<DistributionType, LicenseType, Object> LICENSE_WARNINGS =
163 createLicenseWarningsSet();
164
165 private static Table<DistributionType, LicenseType, Object> createLicenseWarningsSet() {
166 Table<DistributionType, LicenseType, Object> result = HashBasedTable.create();
167 result.put(DistributionType.CLIENT, LicenseType.RECIPROCAL, MARKER);
168 result.put(DistributionType.EMBEDDED, LicenseType.RECIPROCAL, MARKER);
169 result.put(DistributionType.CLIENT, LicenseType.NOTICE, MARKER);
170 result.put(DistributionType.EMBEDDED, LicenseType.NOTICE, MARKER);
171 return ImmutableTable.copyOf(result);
172 }
173
174 private License(Set<LicenseType> licenseTypes, Set<Label> exceptions) {
175 // Defensive copy is done in .of()
176 this.licenseTypes = licenseTypes;
177 this.exceptions = exceptions;
178 }
179
180 public static License of(Collection<LicenseType> licenses, Collection<Label> exceptions) {
181 Set<LicenseType> licenseSet = ImmutableSet.copyOf(licenses);
182 Set<Label> exceptionSet = ImmutableSet.copyOf(exceptions);
183
184 if (exceptionSet.isEmpty() && licenseSet.equals(ImmutableSet.of(LicenseType.NONE))) {
185 return License.NO_LICENSE;
186 }
187
188 return new License(licenseSet, exceptionSet);
189 }
190 /**
191 * Computes a license which can be used to check if a package is compatible
192 * with some kinds of distribution. The list of licenses is scanned for the
193 * least restrictive, and the exceptions are added.
194 *
195 * @param licStrings the list of license strings declared for the package
196 * @throws LicenseParsingException if there are any parsing problems
197 */
198 public static License parseLicense(List<String> licStrings) throws LicenseParsingException {
199 /*
200 * The semantics of comparison for licenses depends on a stable iteration
201 * order for both license types and exceptions. For licenseTypes, it will be
202 * the comparison order from the enumerated types; for exceptions, it will
203 * be lexicographic order achieved using TreeSets.
204 */
205 Set<LicenseType> licenseTypes = EnumSet.noneOf(LicenseType.class);
206 Set<Label> exceptions = Sets.newTreeSet();
207 for (String str : licStrings) {
208 if (str.startsWith("exception=")) {
209 try {
210 Label label = Label.parseAbsolute(str.substring("exception=".length()));
211 exceptions.add(label);
Lukacs Berkia6434362015-09-15 13:56:14 +0000212 } catch (LabelSyntaxException e) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100213 throw new LicenseParsingException(e.getMessage());
214 }
215 } else {
216 try {
217 licenseTypes.add(LicenseType.valueOf(str.toUpperCase()));
218 } catch (IllegalArgumentException e) {
219 throw new LicenseParsingException("invalid license type: '" + str + "'");
220 }
221 }
222 }
223
224 return License.of(licenseTypes, exceptions);
225 }
226
227 /**
228 * Checks if this license is compatible with distributing a particular target
229 * in some set of distribution modes.
230 *
231 * @param dists the modes of distribution
232 * @param target the target which is being checked, and which will be used for
233 * checking exceptions
234 * @param licensedTarget the target which declared the license being checked.
235 * @param eventHandler a reporter where any licensing issues discovered should be
236 * reported
237 * @param staticallyLinked whether the target is statically linked under this command
238 * @return true if the license is compatible with the distributions
239 */
240 public boolean checkCompatibility(Set<DistributionType> dists,
241 Target target, Label licensedTarget, EventHandler eventHandler,
242 boolean staticallyLinked) {
243 Location location = (target instanceof Rule) ? ((Rule) target).getLocation() : null;
244
245 LicenseType leastRestrictiveLicense;
246 if (licenseTypes.contains(LicenseType.RESTRICTED_IF_STATICALLY_LINKED)) {
247 Set<LicenseType> tempLicenses = EnumSet.copyOf(licenseTypes);
248 tempLicenses.remove(LicenseType.RESTRICTED_IF_STATICALLY_LINKED);
249 if (staticallyLinked) {
250 tempLicenses.add(LicenseType.RESTRICTED);
251 } else {
252 tempLicenses.add(LicenseType.UNENCUMBERED);
253 }
254 leastRestrictiveLicense = leastRestrictive(tempLicenses);
255 } else {
256 leastRestrictiveLicense = leastRestrictive(licenseTypes);
257 }
258 for (DistributionType dt : dists) {
259 if (LICENSE_INCOMPATIBILIES.contains(dt, leastRestrictiveLicense)) {
260 if (!exceptions.contains(target.getLabel())) {
261 eventHandler.handle(Event.error(location, "Build target '" + target.getLabel()
262 + "' is not compatible with license '" + this + "' from target '"
263 + licensedTarget + "'"));
264 return false;
265 }
266 } else if (LICENSE_WARNINGS.contains(dt, leastRestrictiveLicense)) {
267 eventHandler.handle(
268 Event.warn(location, "Build target '" + target
269 + "' has a potential licensing issue "
270 + "with a '" + this + "' license from target '" + licensedTarget + "'"));
271 }
272 }
273 return true;
274 }
275
276 /**
277 * @return an immutable set of {@link LicenseType}s contained in this {@code
278 * License}
279 */
280 public Set<LicenseType> getLicenseTypes() {
281 return licenseTypes;
282 }
283
284 /**
285 * @return an immutable set of {@link Label}s that describe exceptions to the
286 * {@code License}
287 */
288 public Set<Label> getExceptions() {
289 return exceptions;
290 }
291
292 /**
293 * A simple toString implementation which generates a canonical form of the
294 * license. (The order of license types is guaranteed to be canonical by
295 * EnumSet, and the order of exceptions is guaranteed to be lexicographic
296 * order by TreeSet.)
297 */
298 @Override
299 public String toString() {
300 if (exceptions.isEmpty()) {
301 return licenseTypes.toString().toLowerCase();
302 } else {
Ulf Adams07dba942015-03-05 14:47:37 +0000303 return licenseTypes.toString().toLowerCase() + " with exceptions " + exceptions;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100304 }
305 }
306
307 /**
308 * A simple equals implementation leveraging the support built into Set that
309 * delegates to its contents.
310 */
311 @Override
312 public boolean equals(Object o) {
313 return o == this ||
314 o instanceof License &&
315 ((License) o).licenseTypes.equals(this.licenseTypes) &&
316 ((License) o).exceptions.equals(this.exceptions);
317 }
318
319 /**
320 * A simple hashCode implementation leveraging the support built into Set that
321 * delegates to its contents.
322 */
323 @Override
324 public int hashCode() {
325 return licenseTypes.hashCode() * 43 + exceptions.hashCode();
326 }
327}