| // 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(), | 
 |             analysisResult.getAspects()); | 
 |       } | 
 |  | 
 |       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..."))); | 
 |     } | 
 |   } | 
 | } |