aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/packages/License.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/packages/License.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/packages/License.java327
1 files changed, 327 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/packages/License.java b/src/main/java/com/google/devtools/build/lib/packages/License.java
new file mode 100644
index 0000000000..fe63c9c2e0
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/packages/License.java
@@ -0,0 +1,327 @@
+// Copyright 2014 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.packages;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableTable;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Table;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.syntax.Label.SyntaxException;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Support for license and distribution checking.
+ */
+@Immutable @ThreadSafe
+public final class License {
+
+ private final Set<LicenseType> licenseTypes;
+ private final Set<Label> exceptions;
+
+ /**
+ * The error that's thrown if a build file contains an invalid license string.
+ */
+ public static class LicenseParsingException extends Exception {
+ public LicenseParsingException(String s) {
+ super(s);
+ }
+ }
+
+ /**
+ * LicenseType is the basis of the License lattice - stricter licenses should
+ * be declared before less-strict licenses in the enum.
+ *
+ * <p>Note that the order is important for the purposes of finding the least
+ * restrictive license.
+ */
+ public enum LicenseType {
+ BY_EXCEPTION_ONLY,
+ RESTRICTED,
+ RESTRICTED_IF_STATICALLY_LINKED,
+ RECIPROCAL,
+ NOTICE,
+ PERMISSIVE,
+ UNENCUMBERED,
+ NONE
+ }
+
+ /**
+ * Gets the least restrictive license type from the list of licenses declared
+ * for a target. For the purposes of license checking, the license type set of
+ * a declared license can be reduced to its least restrictive member.
+ *
+ * @param types a collection of license types
+ * @return the least restrictive license type
+ */
+ @VisibleForTesting
+ static LicenseType leastRestrictive(Collection<LicenseType> types) {
+ return types.isEmpty() ? LicenseType.BY_EXCEPTION_ONLY : Collections.max(types);
+ }
+
+ /**
+ * An instance of LicenseType.None with no exceptions, used for packages
+ * outside of third_party which have no license clause in their BUILD files.
+ */
+ public static final License NO_LICENSE =
+ new License(ImmutableSet.of(LicenseType.NONE), Collections.<Label>emptySet());
+
+ /**
+ * A default instance of Distributions which is used for packages which
+ * have no "distribs" declaration. If nothing is declared, we opt for the
+ * most permissive kind of distribution, which is the internal-only distrib.
+ */
+ public static final Set<DistributionType> DEFAULT_DISTRIB =
+ Collections.singleton(DistributionType.INTERNAL);
+
+ /**
+ * The types of distribution that are supported.
+ */
+ public enum DistributionType {
+ INTERNAL,
+ WEB,
+ CLIENT,
+ EMBEDDED
+ }
+
+ /**
+ * Parses a set of strings declaring distribution types.
+ *
+ * @param distStrings strings containing distribution declarations from BUILD
+ * files
+ * @return a new, unmodifiable set of DistributionTypes
+ * @throws LicenseParsingException
+ */
+ public static Set<DistributionType> parseDistributions(Collection<String> distStrings)
+ throws LicenseParsingException {
+ if (distStrings.isEmpty()) {
+ return Collections.unmodifiableSet(EnumSet.of(DistributionType.INTERNAL));
+ } else {
+ Set<DistributionType> result = EnumSet.noneOf(DistributionType.class);
+ for (String distStr : distStrings) {
+ try {
+ DistributionType dist = Enum.valueOf(DistributionType.class, distStr.toUpperCase());
+ result.add(dist);
+ } catch (IllegalArgumentException e) {
+ throw new LicenseParsingException("Invalid distribution type '" + distStr + "'");
+ }
+ }
+ return Collections.unmodifiableSet(result);
+ }
+ }
+
+ private static final Object MARKER = new Object();
+
+ /**
+ * The license incompatibility set. This contains the set of
+ * (Distribution,License) pairs that should generate errors.
+ */
+ private static Table<DistributionType, LicenseType, Object> LICENSE_INCOMPATIBILIES =
+ createLicenseIncompatibilitySet();
+
+ private static Table<DistributionType, LicenseType, Object> createLicenseIncompatibilitySet() {
+ Table<DistributionType, LicenseType, Object> result = HashBasedTable.create();
+ result.put(DistributionType.CLIENT, LicenseType.RESTRICTED, MARKER);
+ result.put(DistributionType.EMBEDDED, LicenseType.RESTRICTED, MARKER);
+ result.put(DistributionType.INTERNAL, LicenseType.BY_EXCEPTION_ONLY, MARKER);
+ result.put(DistributionType.CLIENT, LicenseType.BY_EXCEPTION_ONLY, MARKER);
+ result.put(DistributionType.WEB, LicenseType.BY_EXCEPTION_ONLY, MARKER);
+ result.put(DistributionType.EMBEDDED, LicenseType.BY_EXCEPTION_ONLY, MARKER);
+ return ImmutableTable.copyOf(result);
+ }
+
+ /**
+ * The license warning set. This contains the set of
+ * (Distribution,License) pairs that should generate warnings when the user
+ * requests verbose license checking.
+ */
+ private static Table<DistributionType, LicenseType, Object> LICENSE_WARNINGS =
+ createLicenseWarningsSet();
+
+ private static Table<DistributionType, LicenseType, Object> createLicenseWarningsSet() {
+ Table<DistributionType, LicenseType, Object> result = HashBasedTable.create();
+ result.put(DistributionType.CLIENT, LicenseType.RECIPROCAL, MARKER);
+ result.put(DistributionType.EMBEDDED, LicenseType.RECIPROCAL, MARKER);
+ result.put(DistributionType.CLIENT, LicenseType.NOTICE, MARKER);
+ result.put(DistributionType.EMBEDDED, LicenseType.NOTICE, MARKER);
+ return ImmutableTable.copyOf(result);
+ }
+
+ private License(Set<LicenseType> licenseTypes, Set<Label> exceptions) {
+ // Defensive copy is done in .of()
+ this.licenseTypes = licenseTypes;
+ this.exceptions = exceptions;
+ }
+
+ public static License of(Collection<LicenseType> licenses, Collection<Label> exceptions) {
+ Set<LicenseType> licenseSet = ImmutableSet.copyOf(licenses);
+ Set<Label> exceptionSet = ImmutableSet.copyOf(exceptions);
+
+ if (exceptionSet.isEmpty() && licenseSet.equals(ImmutableSet.of(LicenseType.NONE))) {
+ return License.NO_LICENSE;
+ }
+
+ return new License(licenseSet, exceptionSet);
+ }
+ /**
+ * Computes a license which can be used to check if a package is compatible
+ * with some kinds of distribution. The list of licenses is scanned for the
+ * least restrictive, and the exceptions are added.
+ *
+ * @param licStrings the list of license strings declared for the package
+ * @throws LicenseParsingException if there are any parsing problems
+ */
+ public static License parseLicense(List<String> licStrings) throws LicenseParsingException {
+ /*
+ * The semantics of comparison for licenses depends on a stable iteration
+ * order for both license types and exceptions. For licenseTypes, it will be
+ * the comparison order from the enumerated types; for exceptions, it will
+ * be lexicographic order achieved using TreeSets.
+ */
+ Set<LicenseType> licenseTypes = EnumSet.noneOf(LicenseType.class);
+ Set<Label> exceptions = Sets.newTreeSet();
+ for (String str : licStrings) {
+ if (str.startsWith("exception=")) {
+ try {
+ Label label = Label.parseAbsolute(str.substring("exception=".length()));
+ exceptions.add(label);
+ } catch (SyntaxException e) {
+ throw new LicenseParsingException(e.getMessage());
+ }
+ } else {
+ try {
+ licenseTypes.add(LicenseType.valueOf(str.toUpperCase()));
+ } catch (IllegalArgumentException e) {
+ throw new LicenseParsingException("invalid license type: '" + str + "'");
+ }
+ }
+ }
+
+ return License.of(licenseTypes, exceptions);
+ }
+
+ /**
+ * Checks if this license is compatible with distributing a particular target
+ * in some set of distribution modes.
+ *
+ * @param dists the modes of distribution
+ * @param target the target which is being checked, and which will be used for
+ * checking exceptions
+ * @param licensedTarget the target which declared the license being checked.
+ * @param eventHandler a reporter where any licensing issues discovered should be
+ * reported
+ * @param staticallyLinked whether the target is statically linked under this command
+ * @return true if the license is compatible with the distributions
+ */
+ public boolean checkCompatibility(Set<DistributionType> dists,
+ Target target, Label licensedTarget, EventHandler eventHandler,
+ boolean staticallyLinked) {
+ Location location = (target instanceof Rule) ? ((Rule) target).getLocation() : null;
+
+ LicenseType leastRestrictiveLicense;
+ if (licenseTypes.contains(LicenseType.RESTRICTED_IF_STATICALLY_LINKED)) {
+ Set<LicenseType> tempLicenses = EnumSet.copyOf(licenseTypes);
+ tempLicenses.remove(LicenseType.RESTRICTED_IF_STATICALLY_LINKED);
+ if (staticallyLinked) {
+ tempLicenses.add(LicenseType.RESTRICTED);
+ } else {
+ tempLicenses.add(LicenseType.UNENCUMBERED);
+ }
+ leastRestrictiveLicense = leastRestrictive(tempLicenses);
+ } else {
+ leastRestrictiveLicense = leastRestrictive(licenseTypes);
+ }
+ for (DistributionType dt : dists) {
+ if (LICENSE_INCOMPATIBILIES.contains(dt, leastRestrictiveLicense)) {
+ if (!exceptions.contains(target.getLabel())) {
+ eventHandler.handle(Event.error(location, "Build target '" + target.getLabel()
+ + "' is not compatible with license '" + this + "' from target '"
+ + licensedTarget + "'"));
+ return false;
+ }
+ } else if (LICENSE_WARNINGS.contains(dt, leastRestrictiveLicense)) {
+ eventHandler.handle(
+ Event.warn(location, "Build target '" + target
+ + "' has a potential licensing issue "
+ + "with a '" + this + "' license from target '" + licensedTarget + "'"));
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @return an immutable set of {@link LicenseType}s contained in this {@code
+ * License}
+ */
+ public Set<LicenseType> getLicenseTypes() {
+ return licenseTypes;
+ }
+
+ /**
+ * @return an immutable set of {@link Label}s that describe exceptions to the
+ * {@code License}
+ */
+ public Set<Label> getExceptions() {
+ return exceptions;
+ }
+
+ /**
+ * A simple toString implementation which generates a canonical form of the
+ * license. (The order of license types is guaranteed to be canonical by
+ * EnumSet, and the order of exceptions is guaranteed to be lexicographic
+ * order by TreeSet.)
+ */
+ @Override
+ public String toString() {
+ if (exceptions.isEmpty()) {
+ return licenseTypes.toString().toLowerCase();
+ } else {
+ return licenseTypes.toString().toLowerCase() + " with exceptions " + exceptions.toString();
+ }
+ }
+
+ /**
+ * A simple equals implementation leveraging the support built into Set that
+ * delegates to its contents.
+ */
+ @Override
+ public boolean equals(Object o) {
+ return o == this ||
+ o instanceof License &&
+ ((License) o).licenseTypes.equals(this.licenseTypes) &&
+ ((License) o).exceptions.equals(this.exceptions);
+ }
+
+ /**
+ * A simple hashCode implementation leveraging the support built into Set that
+ * delegates to its contents.
+ */
+ @Override
+ public int hashCode() {
+ return licenseTypes.hashCode() * 43 + exceptions.hashCode();
+ }
+}