|  | // Copyright 2014 The Bazel Authors. 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.cmdline.Label; | 
|  | import com.google.devtools.build.lib.cmdline.LabelSyntaxException; | 
|  | 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 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 = DistributionType.valueOf(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 (LabelSyntaxException 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; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * 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(); | 
|  | } | 
|  | } |