| // 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.ImmutableMap; |
| 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 com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; |
| import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization; |
| import com.google.devtools.build.lib.skylarkbuildapi.LicenseApi; |
| import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.EnumSet; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Set; |
| |
| /** Support for license and distribution checking. */ |
| @Immutable |
| @ThreadSafe |
| @AutoCodec |
| public final class License implements LicenseApi { |
| private final ImmutableSet<LicenseType> licenseTypes; |
| private final ImmutableSet<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), ImmutableSet.of()); |
| |
| /** |
| * 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(Locale.ENGLISH)); |
| 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); |
| } |
| |
| @AutoCodec.Instantiator |
| @VisibleForSerialization |
| License(ImmutableSet<LicenseType> licenseTypes, ImmutableSet<Label> exceptions) { |
| // Defensive copy is done in .of() |
| this.licenseTypes = licenseTypes; |
| this.exceptions = exceptions; |
| } |
| |
| public static License of(Collection<LicenseType> licenses, Collection<Label> exceptions) { |
| ImmutableSet<LicenseType> licenseSet = ImmutableSet.copyOf(licenses); |
| ImmutableSet<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()), ImmutableMap.of()); |
| exceptions.add(label); |
| } catch (LabelSyntaxException e) { |
| throw new LicenseParsingException(e.getMessage()); |
| } |
| } else { |
| try { |
| licenseTypes.add(LicenseType.valueOf(str.toUpperCase(Locale.ENGLISH))); |
| } 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; |
| } |
| |
| @SuppressWarnings("ReferenceEquality") |
| public boolean isSpecified() { |
| return this != License.NO_LICENSE; |
| } |
| |
| /** |
| * 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(); |
| } |
| |
| @Override |
| public void repr(SkylarkPrinter printer) { |
| printer.append("<license object>"); |
| } |
| } |