com.google.devtools.build.lib.packages.License.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.lib.packages.License.java

Source

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