Skip to content

Commit

Permalink
Move most license checking logic into a module to make it easier to e…
Browse files Browse the repository at this point in the history
…ventually remove.

There's basically four groups of license-related logic in Bazel:

1) Syntactic support in BUILD files
2) Semantics that checks third_party rules have licenses() declared
3) LicenseProvider, which collects rules' transitive license declarations
4) Semantics that checks if a build's licenses are valid

This change only covers 4).

This also simplifies AnalysisPhaseRunner and License.

Part of bazelbuild#7444.

PiperOrigin-RevId: 235585865
  • Loading branch information
gregestren authored and copybara-github committed Feb 25, 2019
1 parent 992eb17 commit 3dd18be
Show file tree
Hide file tree
Showing 9 changed files with 319 additions and 190 deletions.
18 changes: 18 additions & 0 deletions src/main/java/com/google/devtools/build/lib/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,22 @@ java_library(
],
)

java_library(
name = "bazel/LicenseCheckingModule",
srcs = ["bazel/LicenseCheckingModule.java"],
deps = [
":build-base",
":events",
":packages-internal",
":runtime",
"//src/main/java/com/google/devtools/build/lib/cmdline",
"//src/main/java/com/google/devtools/build/lib/collect",
"//src/main/java/com/google/devtools/build/lib/collect/nestedset",
"//src/main/java/com/google/devtools/build/lib/profiler",
"//third_party:guava",
],
)

java_library(
name = "bazel-main",
srcs = ["bazel/Bazel.java"],
Expand Down Expand Up @@ -845,8 +861,10 @@ java_library(
exclude = [
"bazel/Bazel.java",
"bazel/BazelRepositoryModule.java",
"bazel/LicenseCheckingModule.java",
],
),
exports = [":bazel/LicenseCheckingModule"],
deps = [
":build-base",
":build-info",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public final class Bazel {
com.google.devtools.build.lib.bazel.BazelWorkspaceStatusModule.class,
com.google.devtools.build.lib.bazel.BazelDiffAwarenessModule.class,
com.google.devtools.build.lib.bazel.BazelRepositoryModule.class,
com.google.devtools.build.lib.bazel.LicenseCheckingModule.class,
com.google.devtools.build.lib.bazel.debug.WorkspaceRuleModule.class,
com.google.devtools.build.lib.bazel.coverage.BazelCoverageReportModule.class,
com.google.devtools.build.lib.skylarkdebug.module.SkylarkDebuggerModule.class,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
// 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.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 {

private boolean shouldCheckLicenses(BuildOptions buildOptions) {
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(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;
}
}
Loading

0 comments on commit 3dd18be

Please sign in to comment.