| // 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.common.collect.ImmutableSet; | |
| 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.AnalysisResult; | |
| import com.google.devtools.build.lib.analysis.BuildInfoEvent; | |
| import com.google.devtools.build.lib.analysis.BuildView; | |
| 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.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.PostAnalysisQueryBuildTool.PostAnalysisQueryCommandLineException; | |
| 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.NoSuchPackageException; | |
| import com.google.devtools.build.lib.packages.NoSuchTargetException; | |
| 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.LoadingResult; | |
| 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.BlazeRuntime; | |
| import com.google.devtools.build.lib.runtime.CommandEnvironment; | |
| import com.google.devtools.build.lib.skyframe.BuildConfigurationValue; | |
| 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.common.options.OptionsParsingException; | |
| import java.util.Collection; | |
| import java.util.Map; | |
| import java.util.Objects; | |
| import java.util.Set; | |
| import java.util.concurrent.TimeUnit; | |
| import java.util.logging.Level; | |
| import java.util.logging.Logger; | |
| import java.util.regex.Pattern; | |
| import java.util.stream.Stream; | |
| /** | |
| * 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 class BuildTool { | |
| private static Logger logger = Logger.getLogger(BuildTool.class.getName()); | |
| protected CommandEnvironment env; | |
| protected 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, PostAnalysisQueryCommandLineException { | |
| try (SilentCloseable c = Profiler.instance().profile("validateOptions")) { | |
| validateOptions(request); | |
| } | |
| BuildOptions buildOptions; | |
| try (SilentCloseable c = Profiler.instance().profile("createBuildOptions")) { | |
| buildOptions = runtime.createBuildOptions(request); | |
| } | |
| // Sync the package manager before sending the BuildStartingEvent in runLoadingPhase() | |
| try (SilentCloseable c = Profiler.instance().profile("setupPackageCache")) { | |
| env.setupPackageCache(request, DefaultsPackage.getDefaultsPackageContent(buildOptions)); | |
| } | |
| ExecutionTool executionTool = null; | |
| boolean catastrophe = false; | |
| try { | |
| try (SilentCloseable c = Profiler.instance().profile("BuildStartingEvent")) { | |
| 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; | |
| Profiler.instance().markPhase(ProfilePhase.LOAD); | |
| try (SilentCloseable c = Profiler.instance().profile("evaluateTargetPatterns")) { | |
| loadingResult = evaluateTargetPatterns(request, validator); | |
| } | |
| env.setWorkspaceName(loadingResult.getWorkspaceName()); | |
| executionTool = new ExecutionTool(env, request); | |
| if (needsExecutionPhase(request.getBuildOptions())) { | |
| try (SilentCloseable closeable = Profiler.instance().profile("ExecutionTool.init")) { | |
| 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; | |
| try (SilentCloseable c = Profiler.instance().profile("createConfigurations")) { | |
| configurations = | |
| env.getSkyframeExecutor() | |
| .createConfigurations( | |
| env.getReporter(), | |
| runtime.getConfigurationFragmentFactories(), | |
| buildOptions, | |
| request.getMultiCpus(), | |
| request.getKeepGoing()); | |
| } | |
| 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) { | |
| Profiler.instance().markPhase(ProfilePhase.ANALYZE); | |
| AnalysisResult analysisResult; | |
| try (SilentCloseable c = Profiler.instance().profile("runAnalysisPhase")) { | |
| analysisResult = runAnalysisPhase(request, loadingResult, configurations); | |
| } | |
| // 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); | |
| try (SilentCloseable c = Profiler.instance().profile("validateLicensingForTargets")) { | |
| validateLicensingForTargets(analysisResult.getTargetsToBuild(), request.getKeepGoing()); | |
| } | |
| } | |
| result.setBuildConfigurationCollection(configurations); | |
| result.setActualTargets(analysisResult.getTargetsToBuild()); | |
| result.setTestTargets(analysisResult.getTargetsToTest()); | |
| reportTargets(analysisResult); | |
| for (ConfiguredTarget target : analysisResult.getTargetsToSkip()) { | |
| BuildConfiguration config = | |
| env.getSkyframeExecutor() | |
| .getConfiguration(env.getReporter(), target.getConfigurationKey()); | |
| 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)); | |
| } | |
| try (SilentCloseable c = Profiler.instance().profile("postProcessAnalysisResult")) { | |
| postProcessAnalysisResult(request, analysisResult); | |
| } | |
| // Execution phase. | |
| if (needsExecutionPhase(request.getBuildOptions())) { | |
| executionTool.executeBuild( | |
| request.getId(), | |
| analysisResult, | |
| result, | |
| 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); | |
| } | |
| // Will return after profiler line below. | |
| } | |
| Profiler.instance().markPhase(ProfilePhase.FINISH); | |
| } 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())); | |
| } | |
| } | |
| } | |
| /** | |
| * This class is meant to be overridden by classes that want to perform the Analysis phase and | |
| * then process the results in some interesting way. See {@link CqueryBuildTool} as an example. | |
| */ | |
| protected void postProcessAnalysisResult( | |
| BuildRequest request, | |
| AnalysisResult analysisResult) | |
| throws InterruptedException, ViewCreationFailedException, | |
| PostAnalysisQueryCommandLineException { | |
| } | |
| 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()); | |
| 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 (PostAnalysisQueryCommandLineException 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 maybeSetStopOnFirstFailure(BuildRequest request, BuildResult result) { | |
| if (shouldStopOnFailure(request)) { | |
| result.setStopOnFirstFailure(true); | |
| } | |
| } | |
| private boolean shouldStopOnFailure(BuildRequest request) { | |
| return !(request.getKeepGoing() && request.getExecutionOptions().testKeepGoing); | |
| } | |
| private final LoadingResult evaluateTargetPatterns( | |
| final BuildRequest request, final TargetValidator validator) | |
| throws LoadingFailedException, TargetParsingException, InterruptedException { | |
| initializeOutputFilter(request); | |
| final boolean keepGoing = request.getKeepGoing(); | |
| LoadingCallback callback = new LoadingCallback() { | |
| @Override | |
| public void notifyTargets(Collection<Target> targets) throws LoadingFailedException { | |
| if (validator != null) { | |
| validator.validateTargets(targets, keepGoing); | |
| } | |
| } | |
| }; | |
| LoadingResult result = | |
| env.getSkyframeExecutor().loadTargetPatterns( | |
| 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...")); | |
| BuildView view = new BuildView(env.getDirectories(), runtime.getRuleClassProvider(), | |
| env.getSkyframeExecutor(), runtime.getCoverageReportActionFactory(request)); | |
| AnalysisResult analysisResult = | |
| view.update( | |
| loadingResult, | |
| configurations, | |
| request.getAspects(), | |
| request.getViewOptions(), | |
| request.getKeepGoing(), | |
| request.getLoadingPhaseThreadCount(), | |
| 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())); | |
| ImmutableSet<BuildConfigurationValue.Key> configurationKeys = | |
| Stream.concat( | |
| analysisResult | |
| .getTargetsToBuild() | |
| .stream() | |
| .map(ConfiguredTarget::getConfigurationKey) | |
| .distinct(), | |
| analysisResult.getTargetsToTest() == null | |
| ? Stream.empty() | |
| : analysisResult | |
| .getTargetsToTest() | |
| .stream() | |
| .map(ConfiguredTarget::getConfigurationKey) | |
| .distinct()) | |
| .filter(Objects::nonNull) | |
| .distinct() | |
| .collect(ImmutableSet.toImmutableSet()); | |
| Map<BuildConfigurationValue.Key, BuildConfiguration> configurationMap = | |
| env.getSkyframeExecutor().getConfigurations(env.getReporter(), configurationKeys); | |
| env.getEventBus() | |
| .post( | |
| new TestFilteringCompleteEvent( | |
| analysisResult.getTargetsToBuild(), | |
| analysisResult.getTargetsToTest(), | |
| configurationMap)); | |
| 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); | |
| InterruptedException ie = null; | |
| try { | |
| env.getSkyframeExecutor().notifyCommandComplete(); | |
| } catch (InterruptedException e) { | |
| env.getReporter().handle(Event.error("Build interrupted during command completion")); | |
| ie = e; | |
| } | |
| // 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()))); | |
| if (ie != null) { | |
| if (exitCondition.equals(ExitCode.SUCCESS)) { | |
| result.setExitCondition(ExitCode.INTERRUPTED); | |
| } else if (!exitCondition.equals(ExitCode.INTERRUPTED)) { | |
| logger.log( | |
| Level.WARNING, | |
| "Suppressed interrupted exception during stop request because already failing with exit" | |
| + " code " | |
| + exitCondition, | |
| ie); | |
| } | |
| } | |
| } | |
| 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) { | |
| 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 (!targetLicense.getLicense().checkCompatibility( | |
| distribs, target, targetLicense.getLabel(), 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 (!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(); | |
| } | |
| } |