| // 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.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.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.ConfiguredTarget; |
| import com.google.devtools.build.lib.analysis.ViewCreationFailedException; |
| import com.google.devtools.build.lib.analysis.config.BuildConfiguration; |
| import com.google.devtools.build.lib.analysis.config.BuildOptions; |
| import com.google.devtools.build.lib.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.TargetParsingException; |
| 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.skyframe.BuildConfigurationValue; |
| import com.google.devtools.build.lib.skyframe.BuildInfoCollectionFunction; |
| import com.google.devtools.build.lib.skyframe.PrecomputedValue; |
| import com.google.devtools.build.lib.skyframe.TargetPatternPhaseValue; |
| import com.google.devtools.build.lib.util.AbruptExitException; |
| import com.google.devtools.build.lib.util.Pair; |
| 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.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 { |
| |
| // Target pattern evaluation. |
| TargetPatternPhaseValue loadingResult; |
| Profiler.instance().markPhase(ProfilePhase.LOAD); |
| 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(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, request.getMultiCpus()); |
| } |
| |
| for (BlazeModule module : env.getRuntime().getBlazeModules()) { |
| module.afterAnalysis(env, request, buildOptions, analysisResult.getTargetsToBuild()); |
| } |
| |
| reportTargets(env, analysisResult); |
| |
| for (ConfiguredTarget target : analysisResult.getTargetsToSkip()) { |
| BuildConfiguration 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"); |
| String errorMessage = BuildView.createErrorMessage(loadingResult, null, null); |
| if (errorMessage != null) { |
| throw new BuildFailedException(errorMessage); |
| } |
| } |
| |
| 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, |
| Set<String> multiCpu) |
| throws InterruptedException, InvalidConfigurationException, ViewCreationFailedException { |
| Stopwatch timer = Stopwatch.createStarted(); |
| env.getReporter().handle(Event.progress("Loading complete. Analyzing...")); |
| |
| BuildView view = |
| new BuildView( |
| env.getDirectories(), |
| env.getRuntime().getRuleClassProvider(), |
| env.getSkyframeExecutor(), |
| env.getRuntime().getCoverageReportActionFactory(request)); |
| AnalysisResult analysisResult = |
| view.update( |
| loadingResult, |
| targetOptions, |
| multiCpu, |
| request.getAspects(), |
| request.getViewOptions(), |
| request.getKeepGoing(), |
| request.getLoadingPhaseThreadCount(), |
| request.getTopLevelArtifactContext(), |
| env.getReporter(), |
| env.getEventBus()); |
| |
| Pair<Integer, Integer> tcal = view.getTargetsConfiguredAndLoaded(); |
| |
| // TODO(bazel-team): Merge these into one event. |
| env.getEventBus() |
| .post( |
| new AnalysisPhaseCompleteEvent( |
| analysisResult.getTargetsToBuild(), |
| /* targetsLoaded */ tcal.second.intValue(), |
| /* targetsConfigured */ tcal.first.intValue(), |
| timer.stop().elapsed(TimeUnit.MILLISECONDS), |
| view.getAndClearPkgManagerStatistics(), |
| view.getActionsConstructed(), |
| env.getSkyframeExecutor().wasAnalysisCacheDiscardedAndResetBit())); |
| 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 void reportTargets(CommandEnvironment env, 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) { |
| 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..."))); |
| } |
| } else { |
| int targetCount = targetsToBuild.size(); |
| env.getReporter() |
| .handle( |
| Event.info( |
| "Found " + targetCount + (targetCount == 1 ? " target..." : " targets..."))); |
| } |
| } |
| } |