blob: d5e33c262816029f85597ee8ef850c04839a92fa [file] [log] [blame]
// Copyright 2018 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.collect.ImmutableSet;
import com.google.common.eventbus.Subscribe;
import com.google.common.flogger.GoogleLogger;
import com.google.devtools.build.lib.actions.BuildFailedException;
import com.google.devtools.build.lib.actions.TestExecException;
import com.google.devtools.build.lib.analysis.AnalysisAndExecutionResult;
import com.google.devtools.build.lib.analysis.BuildView;
import com.google.devtools.build.lib.analysis.BuildView.BuildConfigurationsCreated;
import com.google.devtools.build.lib.analysis.BuildView.ExecutionSetup;
import com.google.devtools.build.lib.analysis.ViewCreationFailedException;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.config.CoreOptions;
import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
import com.google.devtools.build.lib.analysis.test.CoverageArtifactsKnownEvent;
import com.google.devtools.build.lib.buildtool.buildevent.NoAnalyzeEvent;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.RepositoryMapping;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.cmdline.TargetParsingException;
import com.google.devtools.build.lib.cmdline.TargetPattern;
import com.google.devtools.build.lib.cmdline.TargetPattern.Parser;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.pkgcache.LoadingFailedException;
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 com.google.devtools.build.lib.server.FailureDetails.BuildConfiguration.Code;
import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
import com.google.devtools.build.lib.skyframe.BuildResultListener;
import com.google.devtools.build.lib.skyframe.RepositoryMappingValue.RepositoryMappingResolutionException;
import com.google.devtools.build.lib.skyframe.SkyframeBuildView.BuildDriverKeyTestContext;
import com.google.devtools.build.lib.skyframe.TargetPatternPhaseValue;
import com.google.devtools.build.lib.skyframe.TopLevelStatusEvents.AspectAnalyzedEvent;
import com.google.devtools.build.lib.skyframe.TopLevelStatusEvents.TestAnalyzedEvent;
import com.google.devtools.build.lib.skyframe.TopLevelStatusEvents.TopLevelTargetAnalyzedEvent;
import com.google.devtools.build.lib.util.AbruptExitException;
import com.google.devtools.build.lib.util.DetailedExitCode;
import com.google.devtools.build.lib.util.RegexFilter;
import com.google.devtools.common.options.OptionsParsingException;
import java.util.Collection;
import java.util.List;
import javax.annotation.Nullable;
/**
* Intended drop-in replacement for AnalysisPhaseRunner after we're done with merging Skyframe's
* analysis and execution phases. This is part of https://github.com/bazelbuild/bazel/issues/14057.
* Internal: b/147350683.
*
* <p>TODO(leba): Consider removing this class altogether to reduce complexity.
*/
public final class AnalysisAndExecutionPhaseRunner {
private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
private AnalysisAndExecutionPhaseRunner() {}
@Nullable
static AnalysisAndExecutionResult execute(
CommandEnvironment env,
BuildRequest request,
BuildOptions buildOptions,
TargetPatternPhaseValue loadingResult,
ExecutionSetup executionSetupCallback,
BuildConfigurationsCreated buildConfigurationCreatedCallback,
BuildDriverKeyTestContext buildDriverKeyTestContext)
throws BuildFailedException,
InterruptedException,
ViewCreationFailedException,
AbruptExitException,
InvalidConfigurationException,
TestExecException,
RepositoryMappingResolutionException {
// Compute the heuristic instrumentation filter if needed.
if (request.needsInstrumentationFilter()) {
try (SilentCloseable c = Profiler.instance().profile("Compute instrumentation filter")) {
String instrumentationFilter =
InstrumentationFilterSupport.computeInstrumentationFilter(
env.getReporter(),
// TODO(ulfjack): Expensive. Make this part of the TargetPatternPhaseValue or write
// a new SkyFunction to compute it?
loadingResult.getTestsToRun(env.getReporter(), env.getPackageManager()));
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(CoreOptions.class).instrumentationFilter =
new RegexFilter.RegexFilterConverter().convert(instrumentationFilter);
} catch (OptionsParsingException e) {
throw new InvalidConfigurationException(Code.HEURISTIC_INSTRUMENTATION_FILTER_INVALID, e);
}
}
}
// Exit if there are any pending exceptions from modules.
env.throwPendingException();
AnalysisAndExecutionResult analysisAndExecutionResult = null;
if (request.getBuildOptions().performAnalysisPhase) {
Profiler.instance().markPhase(ProfilePhase.ANALYZE_AND_EXECUTE);
try (SilentCloseable c = Profiler.instance().profile("runAnalysisAndExecutionPhase");
TopLevelTargetAnalysisWatcher watcher =
TopLevelTargetAnalysisWatcher.createAndRegisterWithEventBus(
env.getRuntime().getBlazeModules(), env, request, buildOptions)) {
analysisAndExecutionResult =
runAnalysisAndExecutionPhase(
env,
request,
loadingResult,
buildOptions,
executionSetupCallback,
buildConfigurationCreatedCallback,
buildDriverKeyTestContext);
}
BuildResultListener buildResultListener = env.getBuildResultListener();
if (request.shouldRunTests()) {
AnalysisPhaseRunner.reportTargetsWithTests(
env, buildResultListener.getAnalyzedTargets(), buildResultListener.getAnalyzedTests());
} else {
AnalysisPhaseRunner.reportTargets(env, buildResultListener.getAnalyzedTargets());
}
AnalysisPhaseRunner.postAbortedEventsForSkippedTargets(
env, buildResultListener.getSkippedTargets());
} else {
env.getReporter().handle(Event.progress("Loading complete."));
env.getReporter().post(new NoAnalyzeEvent());
logger.atInfo().log("No analysis requested, so finished");
FailureDetail failureDetail =
BuildView.createAnalysisFailureDetail(loadingResult, /* skyframeAnalysisResult= */ null);
if (failureDetail != null) {
throw new BuildFailedException(
failureDetail.getMessage(), DetailedExitCode.of(failureDetail));
}
}
return analysisAndExecutionResult;
}
static TargetPatternPhaseValue evaluateTargetPatterns(
CommandEnvironment env, final BuildRequest request, final TargetValidator validator)
throws LoadingFailedException, TargetParsingException, InterruptedException {
boolean keepGoing = request.getKeepGoing();
TargetPatternPhaseValue result =
env.getSkyframeExecutor()
.loadTargetPatternsWithFilters(
env.getReporter(),
request.getTargets(),
env.getRelativeWorkingDirectory(),
request.getLoadingOptions(),
request.getLoadingPhaseThreadCount(),
keepGoing,
request.shouldRunTests());
if (validator != null) {
Collection<Target> targets =
result.getTargets(env.getReporter(), env.getSkyframeExecutor().getPackageManager());
validator.validateTargets(targets, keepGoing);
}
return result;
}
/**
* Performs all phases of the build: Setup, Loading, Analysis & Execution.
*
* <p>Postcondition: On success, populates the BuildRequest's set of targets to build.
*
* @return null if the build were successful; a useful error message if errors were encountered
* and request.keepGoing.
* @throws InterruptedException if the current thread was interrupted.
* @throws ViewCreationFailedException if analysis failed for any reason.
* @throws InvalidConfigurationException if the configuration can't be determined.
* @throws BuildFailedException if action execution failed.
* @throws TestExecException if test execution failed.
*/
private static AnalysisAndExecutionResult runAnalysisAndExecutionPhase(
CommandEnvironment env,
BuildRequest request,
TargetPatternPhaseValue loadingResult,
BuildOptions targetOptions,
ExecutionSetup executionSetupCallback,
BuildConfigurationsCreated buildConfigurationCreatedCallback,
BuildDriverKeyTestContext buildDriverKeyTestContext)
throws InterruptedException,
InvalidConfigurationException,
ViewCreationFailedException,
BuildFailedException,
TestExecException,
RepositoryMappingResolutionException,
AbruptExitException {
env.getReporter().handle(Event.progress("Loading complete. Analyzing..."));
ImmutableSet<Label> explicitTargetPatterns =
getExplicitTargetPatterns(
env,
request.getTargets(),
request.getKeepGoing(),
request.getLoadingPhaseThreadCount());
BuildView view =
new BuildView(
env.getDirectories(),
env.getRuntime().getRuleClassProvider(),
env.getSkyframeExecutor(),
env.getRuntime().getCoverageReportActionFactory(request));
// TODO(b/199053098) TestFilteringCompleteEvent.
return (AnalysisAndExecutionResult)
view.update(
loadingResult,
targetOptions,
explicitTargetPatterns,
request.getAspects(),
request.getAspectsParameters(),
request.getViewOptions(),
request.getKeepGoing(),
request.getViewOptions().skipIncompatibleExplicitTargets,
request.getCheckForActionConflicts(),
env.getQuiescingExecutors(),
request.getTopLevelArtifactContext(),
request.reportIncompatibleTargets(),
env.getReporter(),
env.getEventBus(),
env.getRuntime().getBugReporter(),
/* includeExecutionPhase= */ true,
request.getBuildOptions().skymeldAnalysisOverlapPercentage,
env.getLocalResourceManager(),
env.getBuildResultListener(),
executionSetupCallback,
buildConfigurationCreatedCallback,
buildDriverKeyTestContext,
env.getAdditionalConfigurationChangeEvent());
}
/**
* Turns target patterns from the command line into parsed equivalents for single targets.
*
* <p>Globbing targets like ":all" and "..." are ignored here and will not be in the returned set.
*
* @param env the action's environment.
* @param requestedTargetPatterns the list of target patterns specified on the command line.
* @param keepGoing --keep_going command line option
* @param loadingPhaseThreads no of threads to be used in execution
* @return the set of stringified labels of target patterns that represent single targets. The
* stringified labels are in the "unambiguous canonical form".
* @throws ViewCreationFailedException if a pattern fails to parse for some reason.
*/
private static ImmutableSet<Label> getExplicitTargetPatterns(
CommandEnvironment env,
List<String> requestedTargetPatterns,
boolean keepGoing,
int loadingPhaseThreads)
throws ViewCreationFailedException, RepositoryMappingResolutionException,
InterruptedException {
ImmutableSet.Builder<Label> explicitTargetPatterns = ImmutableSet.builder();
// TODO(andreisolo): Don't re-compute these here as they should be already computed inside the
// TargetPatternPhaseValue
RepositoryMapping mainRepoMapping =
env.getSkyframeExecutor()
.getMainRepoMapping(keepGoing, loadingPhaseThreads, env.getReporter());
TargetPattern.Parser parser =
new Parser(env.getRelativeWorkingDirectory(), RepositoryName.MAIN, mainRepoMapping);
for (String requestedTargetPattern : requestedTargetPatterns) {
if (requestedTargetPattern.startsWith("-")) {
// Excluded patterns are by definition not explicitly requested so we can move on to the
// next target pattern.
continue;
}
// Parse the pattern. This should always work because this is at least the second time we're
// doing it. The previous time is in runAnalysisAndExecutionPhase(). Still, if parsing does
// fail we propagate the exception up.
TargetPattern parsedPattern;
try {
parsedPattern = parser.parse(requestedTargetPattern);
} catch (TargetParsingException e) {
throw new ViewCreationFailedException(
"Failed to parse target pattern even though it was previously parsed successfully",
e.getDetailedExitCode().getFailureDetail(),
e);
}
if (parsedPattern.getType() == TargetPattern.Type.SINGLE_TARGET) {
explicitTargetPatterns.add(parsedPattern.getSingleTargetLabel());
}
}
return ImmutableSet.copyOf(explicitTargetPatterns.build());
}
private static class TopLevelTargetAnalysisWatcher implements AutoCloseable {
private final Iterable<BlazeModule> blazeModules;
private final CommandEnvironment env;
private final BuildRequest buildRequest;
private final BuildOptions buildOptions;
private TopLevelTargetAnalysisWatcher(
Iterable<BlazeModule> blazeModules,
CommandEnvironment env,
BuildRequest buildRequest,
BuildOptions buildOptions) {
this.blazeModules = blazeModules;
this.env = env;
this.buildRequest = buildRequest;
this.buildOptions = buildOptions;
}
/** Creates an AnalysisOperationWatcher and registers it with the provided eventBus. */
public static TopLevelTargetAnalysisWatcher createAndRegisterWithEventBus(
Iterable<BlazeModule> blazeModules,
CommandEnvironment env,
BuildRequest buildRequest,
BuildOptions buildOptions) {
TopLevelTargetAnalysisWatcher watcher =
new TopLevelTargetAnalysisWatcher(blazeModules, env, buildRequest, buildOptions);
env.getEventBus().register(watcher);
return watcher;
}
@Subscribe
public void handleTopLevelTargetAnalysisConcluded(TopLevelTargetAnalyzedEvent e)
throws ViewCreationFailedException, InterruptedException {
for (BlazeModule blazeModule : blazeModules) {
blazeModule.afterTopLevelTargetAnalysis(
env, buildRequest, buildOptions, e.configuredTarget());
}
}
@Subscribe
public void handleAspectAnalyzed(AspectAnalyzedEvent e) {
for (BlazeModule blazeModule : blazeModules) {
blazeModule.afterSingleAspectAnalysis(buildRequest, e.configuredAspect());
}
}
@Subscribe
public void handleTestAnalyzed(TestAnalyzedEvent e) {
for (BlazeModule blazeModule : blazeModules) {
blazeModule.afterSingleTestAnalysis(buildRequest, e.configuredTarget());
}
}
@Subscribe
public void handleKnownCoverageArtifacts(CoverageArtifactsKnownEvent e) {
for (BlazeModule blazeModule : blazeModules) {
blazeModule.coverageArtifactsKnown(e.coverageArtifacts());
}
}
@Override
public void close() {
env.getEventBus().unregister(this);
}
}
}