forked from bazelbuild/bazel
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move most license checking logic into a module to make it easier to e…
…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
1 parent
992eb17
commit 3dd18be
Showing
9 changed files
with
319 additions
and
190 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
256 changes: 256 additions & 0 deletions
256
src/main/java/com/google/devtools/build/lib/bazel/LicenseCheckingModule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
Oops, something went wrong.