blob: d785f51ee60742284a7af493b91a39ae59a8d424 [file] [log] [blame]
// 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.buildtool;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Stopwatch;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.BuildFailedException;
import com.google.devtools.build.lib.actions.TestExecException;
import com.google.devtools.build.lib.analysis.AnalysisPhaseCompleteEvent;
import com.google.devtools.build.lib.analysis.AnalysisUtils;
import com.google.devtools.build.lib.analysis.BuildInfoEvent;
import com.google.devtools.build.lib.analysis.BuildView;
import com.google.devtools.build.lib.analysis.BuildView.AnalysisResult;
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.MakeEnvironmentEvent;
import com.google.devtools.build.lib.analysis.StaticallyLinkedMarkerProvider;
import com.google.devtools.build.lib.analysis.TargetAndConfiguration;
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.BuildConfigurationCollection;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.config.DefaultsPackage;
import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
import com.google.devtools.build.lib.buildeventstream.AbortedEvent;
import com.google.devtools.build.lib.buildeventstream.BuildEventId;
import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.Aborted.AbortReason;
import com.google.devtools.build.lib.buildtool.buildevent.BuildCompleteEvent;
import com.google.devtools.build.lib.buildtool.buildevent.BuildInterruptedEvent;
import com.google.devtools.build.lib.buildtool.buildevent.BuildStartingEvent;
import com.google.devtools.build.lib.buildtool.buildevent.NoAnalyzeEvent;
import com.google.devtools.build.lib.buildtool.buildevent.NoExecutionEvent;
import com.google.devtools.build.lib.buildtool.buildevent.TestFilteringCompleteEvent;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.TargetParsingException;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.OutputFilter;
import com.google.devtools.build.lib.events.Reporter;
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.Target;
import com.google.devtools.build.lib.packages.TargetUtils;
import com.google.devtools.build.lib.pkgcache.LoadingCallback;
import com.google.devtools.build.lib.pkgcache.LoadingFailedException;
import com.google.devtools.build.lib.pkgcache.LoadingPhaseRunner;
import com.google.devtools.build.lib.pkgcache.LoadingResult;
import com.google.devtools.build.lib.profiler.ProfilePhase;
import com.google.devtools.build.lib.profiler.Profiler;
import com.google.devtools.build.lib.query2.ConfiguredTargetQueryEnvironment;
import com.google.devtools.build.lib.query2.engine.QueryException;
import com.google.devtools.build.lib.query2.engine.TargetLiteral;
import com.google.devtools.build.lib.query2.engine.ThreadSafeOutputFormatterCallback;
import com.google.devtools.build.lib.runtime.BlazeRuntime;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
import com.google.devtools.build.lib.skyframe.SkyframeExecutorWrappingWalkableGraph;
import com.google.devtools.build.lib.util.AbruptExitException;
import com.google.devtools.build.lib.util.ExitCode;
import com.google.devtools.build.lib.util.RegexFilter;
import com.google.devtools.build.skyframe.WalkableGraph;
import com.google.devtools.common.options.OptionsParsingException;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import java.util.regex.Pattern;
/**
* Provides the bulk of the implementation of the 'blaze build' command.
*
* <p>The various concrete build command classes handle the command options and request
* setup, then delegate the handling of the request (the building of targets) to this class.
*
* <p>The main entry point is {@link #buildTargets}.
*
* <p>Most of analysis is handled in {@link BuildView}, and execution in {@link ExecutionTool}.
*/
public final class BuildTool {
private static final Logger logger = Logger.getLogger(BuildTool.class.getName());
private final CommandEnvironment env;
private final BlazeRuntime runtime;
/**
* Constructs a BuildTool.
*
* @param env a reference to the command environment of the currently executing command
*/
public BuildTool(CommandEnvironment env) {
this.env = env;
this.runtime = env.getRuntime();
}
/**
* The crux of the build system. Builds the targets specified in the request using the specified
* Executor.
*
* <p>Performs loading, analysis and execution for the specified set of targets, honoring the
* configuration options in the BuildRequest. Returns normally iff successful, throws an exception
* otherwise.
*
* <p>Callers must ensure that {@link #stopRequest} is called after this method, even if it
* throws.
*
* <p>The caller is responsible for setting up and syncing the package cache.
*
* <p>During this function's execution, the actualTargets and successfulTargets fields of the
* request object are set.
*
* @param request the build request that this build tool is servicing, which specifies various
* options; during this method's execution, the actualTargets and successfulTargets fields of
* the request object are populated
* @param result the build result that is the mutable result of this build
* @param validator target validator
*/
public void buildTargets(BuildRequest request, BuildResult result, TargetValidator validator)
throws BuildFailedException, InterruptedException, ViewCreationFailedException,
TargetParsingException, LoadingFailedException, AbruptExitException,
InvalidConfigurationException, TestExecException,
ConfiguredTargetQueryCommandLineException {
validateOptions(request);
BuildOptions buildOptions = runtime.createBuildOptions(request);
// Sync the package manager before sending the BuildStartingEvent in runLoadingPhase()
env.setupPackageCache(request, DefaultsPackage.getDefaultsPackageContent(buildOptions));
ExecutionTool executionTool = null;
boolean catastrophe = false;
try {
env.getEventBus().post(new BuildStartingEvent(env, request));
logger.info("Build identifier: " + request.getId());
// Error out early if multi_cpus is set, but we're not in build or test command.
if (!request.getMultiCpus().isEmpty()) {
getReporter().handle(Event.warn(
"The --experimental_multi_cpu option is _very_ experimental and only intended for "
+ "internal testing at this time. If you do not work on the build tool, then you "
+ "should stop now!"));
if (!"build".equals(request.getCommandName()) && !"test".equals(request.getCommandName())) {
throw new InvalidConfigurationException(
"The experimental setting to select multiple CPUs is only supported for 'build' and "
+ "'test' right now!");
}
}
// Exit if there are any pending exceptions from modules.
env.throwPendingException();
// Target pattern evaluation.
LoadingResult loadingResult = evaluateTargetPatterns(request, validator);
env.setWorkspaceName(loadingResult.getWorkspaceName());
executionTool = new ExecutionTool(env, request);
if (needsExecutionPhase(request.getBuildOptions())) {
executionTool.init();
}
// Compute the heuristic instrumentation filter if needed.
if (request.needsInstrumentationFilter()) {
String instrumentationFilter =
InstrumentationFilterSupport.computeInstrumentationFilter(
env.getReporter(), loadingResult.getTestsToRun());
try {
// We're modifying the buildOptions in place, which is not ideal, but we also don't want
// to pay the price for making a copy. Maybe reconsider later if this turns out to be a
// problem (and the performance loss may not be a big deal).
buildOptions.get(BuildConfiguration.Options.class).instrumentationFilter =
new RegexFilter.RegexFilterConverter().convert(instrumentationFilter);
} catch (OptionsParsingException e) {
throw new InvalidConfigurationException(e);
}
}
// Exit if there are any pending exceptions from modules.
env.throwPendingException();
// Configuration creation.
// TODO(gregce): Consider dropping this phase and passing on-the-fly target / host configs as
// needed. This requires cleaning up the invalidation in SkyframeBuildView.setConfigurations.
BuildConfigurationCollection configurations =
env.getSkyframeExecutor()
.createConfigurations(
env.getReporter(),
runtime.getConfigurationFragmentFactories(),
buildOptions,
request.getMultiCpus(),
request.getViewOptions().keepGoing);
env.throwPendingException();
if (configurations.getTargetConfigurations().size() == 1) {
// TODO(bazel-team): This is not optimal - we retain backwards compatibility in the case
// where there's only a single configuration, but we don't send an event in the multi-config
// case. Can we do better? [multi-config]
env.getEventBus().post(new MakeEnvironmentEvent(
configurations.getTargetConfigurations().get(0).getMakeEnvironment()));
}
logger.info("Configurations created");
if (request.getBuildOptions().performAnalysisPhase) {
AnalysisResult analysisResult = runAnalysisPhase(request, loadingResult, configurations);
result.setBuildConfigurationCollection(configurations);
result.setActualTargets(analysisResult.getTargetsToBuild());
result.setTestTargets(analysisResult.getTargetsToTest());
reportTargets(analysisResult);
for (ConfiguredTarget target : analysisResult.getTargetsToSkip()) {
BuildConfiguration config = target.getConfiguration();
Label label = target.getLabel();
env.getEventBus().post(
new AbortedEvent(
BuildEventId.targetCompleted(label, config.getEventId()),
AbortReason.SKIPPED,
String.format("Target %s build was skipped.", label), label));
}
// TODO(janakr): this query will operate over the graph as constructed by analysis, but will
// also pick up any nodes that are in the graph from prior builds. This makes the results
// not reproducible at the level of a single command. Either tolerate, or wipe the analysis
// graph beforehand if this option is specified, or add another option to wipe if desired
// (SkyframeExecutor#handleConfiguredTargetChange should be sufficient).
if (request.getBuildOptions().queryExpression != null) {
if (!env.getSkyframeExecutor().hasIncrementalState()) {
throw new ConfiguredTargetQueryCommandLineException(
"Configured query is not allowed if incrementality state is not being kept");
}
try {
doConfiguredTargetQuery(request, configurations, loadingResult);
} catch (QueryException | IOException e) {
if (!request.getViewOptions().keepGoing) {
throw new ViewCreationFailedException("Error doing configured target query", e);
}
env.getReporter().error(null, "Error doing configured target query", e);
}
}
// Execution phase.
if (needsExecutionPhase(request.getBuildOptions())) {
executionTool.executeBuild(
request.getId(),
analysisResult,
result,
configurations,
analysisResult.getPackageRoots(),
request.getTopLevelArtifactContext());
} else {
getReporter().post(new NoExecutionEvent());
}
String delayedErrorMsg = analysisResult.getError();
if (delayedErrorMsg != null) {
throw new BuildFailedException(delayedErrorMsg);
}
} else {
getReporter().handle(Event.progress("Loading complete."));
getReporter().post(new NoAnalyzeEvent());
logger.info("No analysis requested, so finished");
String errorMessage = BuildView.createErrorMessage(loadingResult, null);
if (errorMessage != null) {
throw new BuildFailedException(errorMessage);
}
// Return.
}
} catch (RuntimeException e) {
// Print an error message for unchecked runtime exceptions. This does not concern Error
// subclasses such as OutOfMemoryError.
request.getOutErr().printErrLn(
"Unhandled exception thrown during build; message: " + e.getMessage());
catastrophe = true;
throw e;
} catch (Error e) {
catastrophe = true;
throw e;
} catch (InvalidConfigurationException e) {
// TODO(gregce): With "global configurations" we cannot tie a configuration creation failure
// to a single target and have to halt the entire build. Once configurations are genuinely
// created as part of the analysis phase they should report their error on the level of the
// target(s) that triggered them.
catastrophe = true;
throw e;
} finally {
if (executionTool != null) {
executionTool.shutdown();
}
if (!catastrophe) {
// Delete dirty nodes to ensure that they do not accumulate indefinitely.
long versionWindow = request.getViewOptions().versionWindowForDirtyNodeGc;
if (versionWindow != -1) {
env.getSkyframeExecutor().deleteOldNodes(versionWindow);
}
// The workspace status actions will not run with certain flags, or if an error
// occurs early in the build. Tell a lie so that the event is not missing.
// If multiple build_info events are sent, only the first is kept, so this does not harm
// successful runs (which use the workspace status action).
env.getEventBus()
.post(
new BuildInfoEvent(
env.getBlazeWorkspace().getWorkspaceStatusActionFactory()
.createDummyWorkspaceStatus()));
}
}
}
private void reportExceptionError(Exception e) {
if (e.getMessage() != null) {
getReporter().handle(Event.error(e.getMessage()));
}
}
/**
* The crux of the build system. Builds the targets specified in the request using the specified
* Executor.
*
* <p>Performs loading, analysis and execution for the specified set of targets, honoring the
* configuration options in the BuildRequest. Returns normally iff successful, throws an exception
* otherwise.
*
* <p>The caller is responsible for setting up and syncing the package cache.
*
* <p>During this function's execution, the actualTargets and successfulTargets
* fields of the request object are set.
*
* @param request the build request that this build tool is servicing, which specifies various
* options; during this method's execution, the actualTargets and successfulTargets fields
* of the request object are populated
* @param validator target validator
* @return the result as a {@link BuildResult} object
*/
public BuildResult processRequest(BuildRequest request, TargetValidator validator) {
BuildResult result = new BuildResult(request.getStartTime());
env.getEventBus().register(result);
maybeSetStopOnFirstFailure(request, result);
Throwable catastrophe = null;
ExitCode exitCode = ExitCode.BLAZE_INTERNAL_ERROR;
try {
buildTargets(request, result, validator);
exitCode = ExitCode.SUCCESS;
} catch (BuildFailedException e) {
if (e.isErrorAlreadyShown()) {
// The actual error has already been reported by the Builder.
} else {
reportExceptionError(e);
}
if (e.isCatastrophic()) {
result.setCatastrophe();
}
exitCode = e.getExitCode() != null ? e.getExitCode() : ExitCode.BUILD_FAILURE;
} catch (InterruptedException e) {
// We may have been interrupted by an error, or the user's interruption may have raced with
// an error, so check to see if we should report that error code instead.
exitCode = env.getPendingExitCode();
if (exitCode == null) {
exitCode = ExitCode.INTERRUPTED;
env.getReporter().handle(Event.error("build interrupted"));
env.getEventBus().post(new BuildInterruptedEvent());
} else {
// Report the exception from the environment - the exception we're handling here is just an
// interruption.
reportExceptionError(env.getPendingException());
result.setCatastrophe();
}
} catch (TargetParsingException | LoadingFailedException | ViewCreationFailedException e) {
exitCode = ExitCode.PARSING_FAILURE;
reportExceptionError(e);
} catch (ConfiguredTargetQueryCommandLineException e) {
exitCode = ExitCode.COMMAND_LINE_ERROR;
reportExceptionError(e);
} catch (TestExecException e) {
// ExitCode.SUCCESS means that build was successful. Real return code of program
// is going to be calculated in TestCommand.doTest().
exitCode = ExitCode.SUCCESS;
reportExceptionError(e);
} catch (InvalidConfigurationException e) {
exitCode = ExitCode.COMMAND_LINE_ERROR;
reportExceptionError(e);
// TODO(gregce): With "global configurations" we cannot tie a configuration creation failure
// to a single target and have to halt the entire build. Once configurations are genuinely
// created as part of the analysis phase they should report their error on the level of the
// target(s) that triggered them.
result.setCatastrophe();
} catch (AbruptExitException e) {
exitCode = e.getExitCode();
reportExceptionError(e);
result.setCatastrophe();
} catch (Throwable throwable) {
catastrophe = throwable;
Throwables.propagate(throwable);
} finally {
stopRequest(result, catastrophe, exitCode);
}
return result;
}
private void doConfiguredTargetQuery(
BuildRequest request,
BuildConfigurationCollection configurations,
LoadingResult loadingResult)
throws InterruptedException, QueryException, IOException {
// Determine the configurations.
List<TargetAndConfiguration> topLevelTargetsWithConfigs =
AnalysisUtils.getTargetsWithConfigs(
configurations,
loadingResult.getTargets(),
env.getReporter(),
runtime.getRuleClassProvider(),
env.getSkyframeExecutor());
// Currently, CTQE assumes that all top level targets take on the same default config and we
// don't have the ability to map multiple configs to multiple top level targets.
// So for now, we only allow multiple targets when they all carry the same config.
// TODO: fully support multiple top level targets
TargetAndConfiguration sampleTAndC = topLevelTargetsWithConfigs.get(0);
BuildConfiguration sampleConfig = sampleTAndC.getConfiguration();
for (TargetAndConfiguration targAndConfig : topLevelTargetsWithConfigs) {
if (!targAndConfig.getConfiguration().equals(sampleConfig)) {
throw new QueryException(
new TargetLiteral(request.getBuildOptions().queryExpression),
String.format(
"Top level targets %s and %s have different configurations (top level "
+ "targets with different configurations is not supported)",
sampleTAndC.getTarget().getLabel(), targAndConfig.getTarget().getLabel()));
}
}
WalkableGraph walkableGraph =
SkyframeExecutorWrappingWalkableGraph.of(env.getSkyframeExecutor());
ConfiguredTargetQueryEnvironment configuredTargetQueryEnvironment =
new ConfiguredTargetQueryEnvironment(
request.getViewOptions().keepGoing,
env.getReporter(),
env.getRuntime().getQueryFunctions(),
sampleConfig,
configurations.getHostConfiguration(),
env.newTargetPatternEvaluator().getOffset(),
env.getPackageManager().getPackagePath(),
() -> walkableGraph);
configuredTargetQueryEnvironment.evaluateQuery(
request.getBuildOptions().queryExpression,
new ThreadSafeOutputFormatterCallback<ConfiguredTarget>() {
@Override
public void processOutput(Iterable<ConfiguredTarget> partialResult)
throws IOException, InterruptedException {
for (ConfiguredTarget configuredTarget : partialResult) {
env.getReporter()
.getOutErr()
.printOutLn(
configuredTarget.getLabel()
+ " ("
+ configuredTarget.getConfiguration()
+ ")");
}
}
});
}
private void maybeSetStopOnFirstFailure(BuildRequest request, BuildResult result) {
if (shouldStopOnFailure(request)) {
result.setStopOnFirstFailure(true);
}
}
private boolean shouldStopOnFailure(BuildRequest request) {
return !(request.getViewOptions().keepGoing && request.getExecutionOptions().testKeepGoing);
}
private final LoadingResult evaluateTargetPatterns(
final BuildRequest request, final TargetValidator validator)
throws LoadingFailedException, TargetParsingException, InterruptedException {
Profiler.instance().markPhase(ProfilePhase.LOAD);
initializeOutputFilter(request);
final boolean keepGoing = request.getViewOptions().keepGoing;
LoadingCallback callback = new LoadingCallback() {
@Override
public void notifyTargets(Collection<Target> targets) throws LoadingFailedException {
if (validator != null) {
validator.validateTargets(targets, keepGoing);
}
}
};
LoadingPhaseRunner loadingPhaseRunner = env.getSkyframeExecutor().getLoadingPhaseRunner(
runtime.getPackageFactory().getRuleClassNames(),
request.getLoadingOptions().useSkyframeTargetPatternEvaluator);
LoadingResult result =
loadingPhaseRunner.execute(
getReporter(),
request.getTargets(),
env.getRelativeWorkingDirectory(),
request.getLoadingOptions(),
keepGoing,
request.shouldRunTests(),
callback);
return result;
}
/**
* Initializes the output filter to the value given with {@code --output_filter}.
*/
private void initializeOutputFilter(BuildRequest request) {
Pattern outputFilter = request.getBuildOptions().outputFilter;
if (outputFilter != null) {
getReporter().setOutputFilter(OutputFilter.RegexOutputFilter.forPattern(outputFilter));
}
}
/**
* Performs the initial phases 0-2 of the build: Setup, Loading and Analysis.
* <p>
* Postcondition: On success, populates the BuildRequest's set of targets to
* build.
*
* @return null if loading / analysis phases were successful; a useful error
* message if loading or analysis phase errors were encountered and
* request.keepGoing.
* @throws InterruptedException if the current thread was interrupted.
* @throws ViewCreationFailedException if analysis failed for any reason.
*/
private AnalysisResult runAnalysisPhase(BuildRequest request, LoadingResult loadingResult,
BuildConfigurationCollection configurations)
throws InterruptedException, ViewCreationFailedException {
Stopwatch timer = Stopwatch.createStarted();
getReporter().handle(Event.progress("Loading complete. Analyzing..."));
Profiler.instance().markPhase(ProfilePhase.ANALYZE);
BuildView view = new BuildView(env.getDirectories(), runtime.getRuleClassProvider(),
env.getSkyframeExecutor(), runtime.getCoverageReportActionFactory(request));
AnalysisResult analysisResult =
view.update(
loadingResult,
configurations,
request.getAspects(),
request.getViewOptions(),
request.getTopLevelArtifactContext(),
env.getReporter(),
env.getEventBus());
// TODO(bazel-team): Merge these into one event.
env.getEventBus().post(new AnalysisPhaseCompleteEvent(analysisResult.getTargetsToBuild(),
view.getTargetsVisited(), timer.stop().elapsed(TimeUnit.MILLISECONDS),
view.getAndClearPkgManagerStatistics()));
env.getEventBus().post(new TestFilteringCompleteEvent(analysisResult.getTargetsToBuild(),
analysisResult.getTargetsToTest()));
// 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.
boolean checkLicenses = configurations.getTargetConfigurations().get(0).checkLicenses();
if (checkLicenses) {
Profiler.instance().markPhase(ProfilePhase.LICENSE);
validateLicensingForTargets(analysisResult.getTargetsToBuild(),
request.getViewOptions().keepGoing);
}
return analysisResult;
}
private static boolean needsExecutionPhase(BuildRequestOptions options) {
return options.performAnalysisPhase && options.performExecutionPhase;
}
/**
* Stops processing the specified request.
*
* <p>This logs the build result, cleans up and stops the clock.
*
* @param crash Any unexpected RuntimeException or Error. May be null
* @param exitCondition A suggested exit condition from either the build logic or
* a thrown exception somewhere along the way.
*/
public void stopRequest(BuildResult result, Throwable crash, ExitCode exitCondition) {
Preconditions.checkState((crash == null) || !exitCondition.equals(ExitCode.SUCCESS));
result.setUnhandledThrowable(crash);
result.setExitCondition(exitCondition);
// The stop time has to be captured before we send the BuildCompleteEvent.
result.setStopTime(runtime.getClock().currentTimeMillis());
env.getEventBus()
.post(new BuildCompleteEvent(result, ImmutableList.of(BuildEventId.buildToolLogs())));
}
private void reportTargets(AnalysisResult analysisResult) {
Collection<ConfiguredTarget> targetsToBuild = analysisResult.getTargetsToBuild();
Collection<ConfiguredTarget> targetsToTest = analysisResult.getTargetsToTest();
if (targetsToTest != null) {
int testCount = targetsToTest.size();
int targetCount = targetsToBuild.size() - testCount;
if (targetCount == 0) {
getReporter().handle(Event.info("Found "
+ testCount + (testCount == 1 ? " test target..." : " test targets...")));
} else {
getReporter().handle(Event.info("Found "
+ targetCount + (targetCount == 1 ? " target and " : " targets and ")
+ testCount + (testCount == 1 ? " test target..." : " test targets...")));
}
} else {
int targetCount = targetsToBuild.size();
getReporter().handle(Event.info("Found "
+ targetCount + (targetCount == 1 ? " target..." : " targets...")));
}
}
/**
* Validates the options for this BuildRequest.
*
* <p>Issues warnings for the use of deprecated options, and warnings or errors for any option
* settings that conflict.
*/
@VisibleForTesting
public void validateOptions(BuildRequest request) throws InvalidConfigurationException {
for (String issue : request.validateOptions()) {
getReporter().handle(Event.warn(issue));
}
}
/**
* 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 void validateLicensingForTargets(Iterable<ConfiguredTarget> configuredTargets,
boolean keepGoing) throws ViewCreationFailedException {
for (ConfiguredTarget configuredTarget : configuredTargets) {
final Target target = configuredTarget.getTarget();
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 (!targetLicense.getLicense().checkCompatibility(
distribs, target, targetLicense.getLabel(), getReporter(), staticallyLinked)) {
if (!keepGoing) {
throw new ViewCreationFailedException("Build aborted due to licensing error");
}
}
}
} else if (configuredTarget.getTarget() 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 = configuredTarget.getTarget().getLicense();
if (!license.checkCompatibility(distribs, target, configuredTarget.getLabel(),
getReporter(), staticallyLinked)) {
if (!keepGoing) {
throw new ViewCreationFailedException("Build aborted due to licensing error");
}
}
}
}
}
private Reporter getReporter() {
return env.getReporter();
}
private static class ConfiguredTargetQueryCommandLineException extends Exception {
ConfiguredTargetQueryCommandLineException(String message) {
super(message);
}
}
}