| // 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.base.Preconditions; | |
| import com.google.common.base.Stopwatch; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.ImmutableSet; | |
| 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.AnalysisPhaseCompleteEvent; | |
| import com.google.devtools.build.lib.analysis.AnalysisResult; | |
| import com.google.devtools.build.lib.analysis.BuildView; | |
| import com.google.devtools.build.lib.analysis.ConfiguredAspect; | |
| import com.google.devtools.build.lib.analysis.ConfiguredTarget; | |
| import com.google.devtools.build.lib.analysis.ViewCreationFailedException; | |
| import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue; | |
| 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.buildeventstream.AbortedEvent; | |
| import com.google.devtools.build.lib.buildeventstream.BuildEventIdUtil; | |
| import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.Aborted.AbortReason; | |
| import com.google.devtools.build.lib.buildtool.buildevent.NoAnalyzeEvent; | |
| import com.google.devtools.build.lib.buildtool.buildevent.TestFilteringCompleteEvent; | |
| 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.AspectKeyCreator.AspectKey; | |
| import com.google.devtools.build.lib.skyframe.BuildConfigurationKey; | |
| import com.google.devtools.build.lib.skyframe.BuildInfoCollectionFunction; | |
| import com.google.devtools.build.lib.skyframe.PrecomputedValue; | |
| import com.google.devtools.build.lib.skyframe.RepositoryMappingValue.RepositoryMappingResolutionException; | |
| 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.skyframe.TopLevelStatusEvents.TopLevelTargetSkippedEvent; | |
| 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 java.util.Map; | |
| import java.util.Map.Entry; | |
| import java.util.Objects; | |
| import java.util.concurrent.TimeUnit; | |
| import java.util.stream.Stream; | |
| /** Performs target pattern eval, configuration creation, loading and analysis. */ | |
| public final class AnalysisPhaseRunner { | |
| private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); | |
| private AnalysisPhaseRunner() {} | |
| public static AnalysisResult execute( | |
| CommandEnvironment env, | |
| BuildRequest request, | |
| BuildOptions buildOptions, | |
| TargetValidator validator) | |
| throws BuildFailedException, InterruptedException, ViewCreationFailedException, | |
| TargetParsingException, LoadingFailedException, AbruptExitException, | |
| InvalidConfigurationException, RepositoryMappingResolutionException { | |
| // Target pattern evaluation. | |
| TargetPatternPhaseValue loadingResult; | |
| Profiler.instance().markPhase(ProfilePhase.TARGET_PATTERN_EVAL); | |
| try (SilentCloseable c = Profiler.instance().profile("evaluateTargetPatterns")) { | |
| loadingResult = evaluateTargetPatterns(env, request, validator); | |
| } | |
| env.setWorkspaceName(loadingResult.getWorkspaceName()); | |
| // 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(); | |
| AnalysisResult analysisResult = null; | |
| if (request.getBuildOptions().performAnalysisPhase) { | |
| Profiler.instance().markPhase(ProfilePhase.ANALYZE); | |
| // The build info factories are immutable during the life time of this server. However, we | |
| // sometimes clean the graph, which requires re-injecting the value, which requires a hook to | |
| // do so afterwards, and there is no such hook at the server / workspace level right now. For | |
| // simplicity, we keep the code here for now. | |
| env.getSkyframeExecutor() | |
| .injectExtraPrecomputedValues( | |
| ImmutableList.of( | |
| PrecomputedValue.injected( | |
| BuildInfoCollectionFunction.BUILD_INFO_FACTORIES, | |
| env.getRuntime().getRuleClassProvider().getBuildInfoFactoriesAsMap()))); | |
| try (SilentCloseable c = Profiler.instance().profile("runAnalysisPhase")) { | |
| analysisResult = runAnalysisPhase(env, request, loadingResult, buildOptions); | |
| } | |
| for (BlazeModule module : env.getRuntime().getBlazeModules()) { | |
| module.afterAnalysis(env, request, buildOptions, analysisResult); | |
| } | |
| if (request.shouldRunTests()) { | |
| reportTargetsWithTests( | |
| env, | |
| analysisResult.getTargetsToBuild(), | |
| Preconditions.checkNotNull(analysisResult.getTargetsToTest())); | |
| } else { | |
| reportTargets(env, analysisResult.getTargetsToBuild()); | |
| } | |
| for (ConfiguredTarget target : analysisResult.getTargetsToSkip()) { | |
| BuildConfigurationValue config = | |
| env.getSkyframeExecutor() | |
| .getConfiguration(env.getReporter(), target.getConfigurationKey()); | |
| Label label = target.getLabel(); | |
| env.getEventBus() | |
| .post( | |
| new AbortedEvent( | |
| BuildEventIdUtil.targetCompleted(label, config.getEventId()), | |
| AbortReason.SKIPPED, | |
| String.format("Target %s build was skipped.", label), | |
| label)); | |
| } | |
| } 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, null, null); | |
| if (failureDetail != null) { | |
| throw new BuildFailedException( | |
| failureDetail.getMessage(), DetailedExitCode.of(failureDetail)); | |
| } | |
| } | |
| return analysisResult; | |
| } | |
| private 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 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 static AnalysisResult runAnalysisPhase( | |
| CommandEnvironment env, | |
| BuildRequest request, | |
| TargetPatternPhaseValue loadingResult, | |
| BuildOptions targetOptions) | |
| throws InterruptedException, InvalidConfigurationException, | |
| RepositoryMappingResolutionException, ViewCreationFailedException { | |
| Stopwatch timer = Stopwatch.createStarted(); | |
| 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)); | |
| AnalysisResult analysisResult; | |
| try { | |
| analysisResult = | |
| view.update( | |
| loadingResult, | |
| targetOptions, | |
| explicitTargetPatterns, | |
| request.getAspects(), | |
| request.getAspectsParameters(), | |
| request.getViewOptions(), | |
| request.getKeepGoing(), | |
| request.getCheckForActionConflicts(), | |
| env.getQuiescingExecutors(), | |
| request.getTopLevelArtifactContext(), | |
| request.reportIncompatibleTargets(), | |
| env.getReporter(), | |
| env.getEventBus(), | |
| env.getRuntime().getBugReporter(), | |
| /* includeExecutionPhase= */ false, | |
| /* resourceManager= */ null, | |
| /* buildResultListener= */ null, | |
| /* executionSetupCallback= */ null, | |
| /* buildConfigurationsCreatedCallback= */ null, | |
| /* buildDriverKeyTestContext= */ null); | |
| } catch (BuildFailedException | TestExecException | AbruptExitException unexpected) { | |
| throw new IllegalStateException("Unexpected execution exception type: ", unexpected); | |
| } | |
| // TODO(bazel-team): Merge these into one event. | |
| env.getEventBus() | |
| .post( | |
| new AnalysisPhaseCompleteEvent( | |
| analysisResult.getTargetsToBuild(), | |
| view.getEvaluatedCounts(), | |
| view.getEvaluatedActionsCounts(), | |
| timer.stop().elapsed(TimeUnit.MILLISECONDS), | |
| view.getAndClearPkgManagerStatistics(), | |
| env.getSkyframeExecutor().wasAnalysisCacheInvalidatedAndResetBit())); | |
| ImmutableSet<BuildConfigurationKey> 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<BuildConfigurationKey, BuildConfigurationValue> configurationMap = | |
| env.getSkyframeExecutor().getConfigurations(env.getReporter(), configurationKeys); | |
| env.getEventBus() | |
| .post( | |
| new TestFilteringCompleteEvent( | |
| analysisResult.getTargetsToBuild(), | |
| analysisResult.getTargetsToTest(), | |
| analysisResult.getTargetsToSkip(), | |
| configurationMap)); | |
| postTopLevelStatusEvents(env, analysisResult, configurationMap); | |
| return analysisResult; | |
| } | |
| /** Post the appropriate {@link com.google.devtools.build.lib.skyframe.TopLevelStatusEvents}. */ | |
| private static void postTopLevelStatusEvents( | |
| CommandEnvironment env, | |
| AnalysisResult analysisResult, | |
| Map<BuildConfigurationKey, BuildConfigurationValue> configurationMap) { | |
| for (ConfiguredTarget configuredTarget : analysisResult.getTargetsToBuild()) { | |
| env.getEventBus().post(TopLevelTargetAnalyzedEvent.create(configuredTarget)); | |
| if (analysisResult.getTargetsToSkip().contains(configuredTarget)) { | |
| env.getEventBus().post(TopLevelTargetSkippedEvent.create(configuredTarget)); | |
| } | |
| if (analysisResult.getTargetsToTest() != null | |
| && analysisResult.getTargetsToTest().contains(configuredTarget)) { | |
| env.getEventBus() | |
| .post( | |
| TestAnalyzedEvent.create( | |
| configuredTarget, | |
| configurationMap.get(configuredTarget.getConfigurationKey()), | |
| /*isSkipped=*/ analysisResult.getTargetsToSkip().contains(configuredTarget))); | |
| } | |
| } | |
| for (Entry<AspectKey, ConfiguredAspect> entry : analysisResult.getAspectsMap().entrySet()) { | |
| env.getEventBus() | |
| .post( | |
| AspectAnalyzedEvent.createWithoutFurtherSymlinkPlanting( | |
| entry.getKey(), entry.getValue())); | |
| } | |
| } | |
| static void reportTargetsWithTests( | |
| CommandEnvironment env, | |
| Collection<ConfiguredTarget> targetsToBuild, | |
| Collection<ConfiguredTarget> targetsToTest) { | |
| int testCount = targetsToTest.size(); | |
| int targetCount = targetsToBuild.size() - testCount; | |
| if (targetCount == 0) { | |
| env.getReporter() | |
| .handle( | |
| Event.info( | |
| "Found " | |
| + testCount | |
| + (testCount == 1 ? " test target..." : " test targets..."))); | |
| } else { | |
| env.getReporter() | |
| .handle( | |
| Event.info( | |
| "Found " | |
| + targetCount | |
| + (targetCount == 1 ? " target and " : " targets and ") | |
| + testCount | |
| + (testCount == 1 ? " test target..." : " test targets..."))); | |
| } | |
| } | |
| static void reportTargets(CommandEnvironment env, Collection<ConfiguredTarget> targetsToBuild) { | |
| int targetCount = targetsToBuild.size(); | |
| env.getReporter() | |
| .handle( | |
| Event.info("Found " + targetCount + (targetCount == 1 ? " target..." : " targets..."))); | |
| } | |
| /** | |
| * 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 runAnalysisPhase(). 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()); | |
| } | |
| } |