blob: 6cb5025df3ad263e896063d3cda42c1b11f8be8b [file] [log] [blame]
// Copyright 2019 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.bazel;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Table;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.LicensesProvider;
import com.google.devtools.build.lib.analysis.LicensesProvider.TargetLicense;
import com.google.devtools.build.lib.analysis.StaticallyLinkedMarkerProvider;
import com.google.devtools.build.lib.analysis.ViewCreationFailedException;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.buildtool.BuildRequest;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
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.packages.InputFile;
import com.google.devtools.build.lib.packages.License;
import com.google.devtools.build.lib.packages.License.DistributionType;
import com.google.devtools.build.lib.packages.License.LicenseType;
import com.google.devtools.build.lib.packages.NoSuchPackageException;
import com.google.devtools.build.lib.packages.NoSuchTargetException;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.StarlarkSemanticsOptions;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.packages.TargetUtils;
import com.google.devtools.build.lib.profiler.ProfilePhase;
import com.google.devtools.build.lib.profiler.Profiler;
import com.google.devtools.build.lib.profiler.SilentCloseable;
import com.google.devtools.build.lib.runtime.BlazeModule;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
import java.util.EnumSet;
import java.util.Set;
/**
* Module responsible for checking third party license compliance.
*
* <p><b>This is outdated logic marked for removal.</b> See <a
* href="https://github.com/bazelbuild/bazel/issues/7444">#7444</a> for details.
*/
public class LicenseCheckingModule extends BlazeModule {
protected boolean shouldCheckLicenses(BuildRequest request, BuildOptions buildOptions) {
if (request.getOptions(StarlarkSemanticsOptions.class)
.incompatibleDisableThirdPartyLicenseChecking) {
return false;
}
return buildOptions.get(BuildConfiguration.Options.class).checkLicenses;
}
@Override
public void afterAnalysis(
CommandEnvironment env,
BuildRequest request,
BuildOptions buildOptions,
Iterable<ConfiguredTarget> configuredTargets)
throws InterruptedException, ViewCreationFailedException {
// Check licenses.
// We check licenses if the first target configuration has license checking enabled. Right
// now, it is not possible to have multiple target configurations with different settings
// for this flag, which allows us to take this short cut.
if (shouldCheckLicenses(request, buildOptions)) {
Profiler.instance().markPhase(ProfilePhase.LICENSE);
try (SilentCloseable c = Profiler.instance().profile("validateLicensingForTargets")) {
validateLicensingForTargets(env, configuredTargets, request.getKeepGoing());
}
}
}
/**
* Takes a set of configured targets, and checks if the distribution methods declared for the
* targets are compatible with the constraints imposed by their prerequisites' licenses.
*
* @param configuredTargets the targets to check
* @param keepGoing if false, and a licensing error is encountered, both generates an error
* message on the reporter, <em>and</em> throws an exception. If true, then just generates a
* message on the reporter.
* @throws ViewCreationFailedException if the license checking failed (and not --keep_going)
*/
private static void validateLicensingForTargets(
CommandEnvironment env, Iterable<ConfiguredTarget> configuredTargets, boolean keepGoing)
throws ViewCreationFailedException {
for (ConfiguredTarget configuredTarget : configuredTargets) {
Target target = null;
try {
target = env.getPackageManager().getTarget(env.getReporter(), configuredTarget.getLabel());
} catch (NoSuchPackageException | NoSuchTargetException | InterruptedException e) {
env.getReporter().handle(Event.error("Failed to get target to validate license"));
throw new ViewCreationFailedException(
"Build aborted due to issue getting targets to validate licenses", e);
}
if (TargetUtils.isTestRule(target)) {
continue; // Tests are exempt from license checking
}
final Set<DistributionType> distribs = target.getDistributions();
StaticallyLinkedMarkerProvider markerProvider =
configuredTarget.getProvider(StaticallyLinkedMarkerProvider.class);
boolean staticallyLinked = markerProvider != null && markerProvider.isLinkedStatically();
LicensesProvider provider = configuredTarget.getProvider(LicensesProvider.class);
if (provider != null) {
NestedSet<TargetLicense> licenses = provider.getTransitiveLicenses();
for (TargetLicense targetLicense : licenses) {
if (!checkCompatibility(
targetLicense.getLicense(),
distribs,
target,
targetLicense.getLabel(),
env.getReporter(),
staticallyLinked)) {
if (!keepGoing) {
throw new ViewCreationFailedException("Build aborted due to licensing error");
}
}
}
} else if (target instanceof InputFile) {
// Input file targets do not provide licenses because they do not
// depend on the rule where their license is taken from. This is usually
// not a problem, because the transitive collection of licenses always
// hits the rule they come from, except when the input file is a
// top-level target. Thus, we need to handle that case specially here.
//
// See FileTarget#getLicense for more information about the handling of
// license issues with File targets.
License license = target.getLicense();
if (!checkCompatibility(
license,
distribs,
target,
configuredTarget.getLabel(),
env.getReporter(),
staticallyLinked)) {
if (!keepGoing) {
throw new ViewCreationFailedException("Build aborted due to licensing error");
}
}
}
}
}
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 final Table<DistributionType, LicenseType, Object> licenseIncompatibilies =
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 final Table<DistributionType, LicenseType, Object> licenseWarnings =
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);
}
/**
* Checks if the given license is compatible with distributing a particular target in some set of
* distribution modes.
*
* @param license the license to check
* @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
*/
@VisibleForTesting
public static boolean checkCompatibility(
License license,
Set<DistributionType> dists,
Target target,
Label licensedTarget,
EventHandler eventHandler,
boolean staticallyLinked) {
Location location = (target instanceof Rule) ? ((Rule) target).getLocation() : null;
LicenseType leastRestrictiveLicense;
if (license.getLicenseTypes().contains(LicenseType.RESTRICTED_IF_STATICALLY_LINKED)) {
Set<LicenseType> tempLicenses = EnumSet.copyOf(license.getLicenseTypes());
tempLicenses.remove(LicenseType.RESTRICTED_IF_STATICALLY_LINKED);
if (staticallyLinked) {
tempLicenses.add(LicenseType.RESTRICTED);
} else {
tempLicenses.add(LicenseType.UNENCUMBERED);
}
leastRestrictiveLicense = License.leastRestrictive(tempLicenses);
} else {
leastRestrictiveLicense = License.leastRestrictive(license.getLicenseTypes());
}
for (DistributionType dt : dists) {
if (licenseIncompatibilies.contains(dt, leastRestrictiveLicense)) {
if (!license.getExceptions().contains(target.getLabel())) {
eventHandler.handle(
Event.error(
location,
"Build target '"
+ target.getLabel()
+ "' is not compatible with license '"
+ license
+ "' from target '"
+ licensedTarget
+ "'"));
return false;
}
} else if (licenseWarnings.contains(dt, leastRestrictiveLicense)) {
eventHandler.handle(
Event.warn(
location,
"Build target '"
+ target
+ "' has a potential licensing issue with a '"
+ license
+ "' license from target '"
+ licensedTarget
+ "'"));
}
}
return true;
}
}