// 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(); } }