| // 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.pkgcache; | |
| import com.google.common.base.Stopwatch; | |
| import com.google.common.collect.ImmutableSet; | |
| import com.google.common.collect.ListMultimap; | |
| import com.google.common.collect.Sets; | |
| import com.google.common.eventbus.EventBus; | |
| import com.google.devtools.build.lib.cmdline.Label; | |
| import com.google.devtools.build.lib.cmdline.ResolvedTargets; | |
| import com.google.devtools.build.lib.cmdline.TargetParsingException; | |
| import com.google.devtools.build.lib.events.DelegatingEventHandler; | |
| import com.google.devtools.build.lib.events.Event; | |
| import com.google.devtools.build.lib.events.EventHandler; | |
| import com.google.devtools.build.lib.packages.NoSuchPackageException; | |
| import com.google.devtools.build.lib.packages.Target; | |
| import com.google.devtools.build.lib.packages.TestTargetUtils; | |
| import com.google.devtools.build.lib.util.Preconditions; | |
| import com.google.devtools.build.lib.vfs.PathFragment; | |
| import java.util.HashSet; | |
| import java.util.List; | |
| import java.util.Set; | |
| import java.util.concurrent.TimeUnit; | |
| import java.util.logging.Logger; | |
| import javax.annotation.Nullable; | |
| /** | |
| * Implements the loading phase; responsible for: | |
| * <ul> | |
| * <li>target pattern evaluation | |
| * <li>test suite expansion | |
| * <li>loading the labels needed to construct the build configuration | |
| * <li>loading the labels needed for the analysis with the build configuration | |
| * <li>loading the transitive closure of the targets and the configuration labels | |
| * </ul> | |
| * | |
| * <p>In order to ensure correctness of incremental loading and of full cache hits, this class is | |
| * very restrictive about access to its internal state and to its collaborators. In particular, none | |
| * of the collaborators of this class may change in incompatible ways, such as changing the relative | |
| * working directory for the target pattern parser, without notifying this class. | |
| * | |
| * <p>For full caching, this class tracks the exact values of all inputs to the loading phase. To | |
| * maximize caching, it is vital that these change as rarely as possible. | |
| * | |
| * <p>The Skyframe-based re-implementation of this class is in TargetPatternPhaseFunction. | |
| */ | |
| public final class LegacyLoadingPhaseRunner extends LoadingPhaseRunner { | |
| private static final class ParseFailureListenerImpl extends DelegatingEventHandler | |
| implements ParseFailureListener { | |
| private final EventBus eventBus; | |
| private ParseFailureListenerImpl(EventHandler delegate, EventBus eventBus) { | |
| super(delegate); | |
| this.eventBus = eventBus; | |
| } | |
| @Override | |
| public void parsingError(String targetPattern, String message) { | |
| if (eventBus != null) { | |
| eventBus.post(new ParsingFailedEvent(targetPattern, message)); | |
| } | |
| } | |
| } | |
| private static final Logger LOG = Logger.getLogger(LoadingPhaseRunner.class.getName()); | |
| private final PackageManager packageManager; | |
| private final TargetPatternEvaluator targetPatternEvaluator; | |
| private final Set<String> ruleNames; | |
| public LegacyLoadingPhaseRunner(PackageManager packageManager, | |
| Set<String> ruleNames) { | |
| this.packageManager = packageManager; | |
| this.targetPatternEvaluator = packageManager.newTargetPatternEvaluator(); | |
| this.ruleNames = ruleNames; | |
| } | |
| /** | |
| * Performs target pattern evaluation, test suite expansion (if requested), and loads the | |
| * transitive closure of the resulting targets as well as of the targets needed to use the given | |
| * build configuration provider. | |
| */ | |
| @Override | |
| public LoadingResult execute( | |
| EventHandler eventHandler, | |
| EventBus eventBus, | |
| List<String> targetPatterns, | |
| PathFragment relativeWorkingDirectory, | |
| LoadingOptions options, | |
| ListMultimap<String, Label> labelsToLoadUnconditionally, | |
| boolean keepGoing, | |
| boolean determineTests, | |
| @Nullable LoadingCallback callback) | |
| throws TargetParsingException, LoadingFailedException, InterruptedException { | |
| LOG.info("Starting pattern evaluation"); | |
| Stopwatch timer = Stopwatch.createStarted(); | |
| if (options.buildTestsOnly && options.compileOneDependency) { | |
| throw new LoadingFailedException("--compile_one_dependency cannot be used together with " | |
| + "the --build_tests_only option or the 'bazel test' command "); | |
| } | |
| targetPatternEvaluator.updateOffset(relativeWorkingDirectory); | |
| EventHandler parseFailureListener = new ParseFailureListenerImpl(eventHandler, eventBus); | |
| // Determine targets to build: | |
| ResolvedTargets<Target> targets = getTargetsToBuild(parseFailureListener, | |
| targetPatterns, options.compileOneDependency, keepGoing); | |
| ImmutableSet<Target> filteredTargets = targets.getFilteredTargets(); | |
| boolean buildTestsOnly = options.buildTestsOnly; | |
| ImmutableSet<Target> testsToRun = null; | |
| ImmutableSet<Target> testFilteredTargets = ImmutableSet.of(); | |
| // Now we have a list of targets to build. If the --build_tests_only option was specified or we | |
| // want to run tests, we need to determine the list of targets to test. For that, we remove | |
| // manual tests and apply the command line filters. Also, if --build_tests_only is specified, | |
| // then the list of filtered targets will be set as build list as well. | |
| if (determineTests || buildTestsOnly) { | |
| // Parse the targets to get the tests. | |
| ResolvedTargets<Target> testTargets = determineTests(parseFailureListener, | |
| targetPatterns, options, keepGoing); | |
| if (testTargets.getTargets().isEmpty() && !testTargets.getFilteredTargets().isEmpty()) { | |
| eventHandler.handle(Event.warn("All specified test targets were excluded by filters")); | |
| } | |
| if (buildTestsOnly) { | |
| // Replace original targets to build with test targets, so that only targets that are | |
| // actually going to be built are loaded in the loading phase. Note that this has a side | |
| // effect that any test_suite target requested to be built is replaced by the set of *_test | |
| // targets it represents; for example, this affects the status and the summary reports. | |
| Set<Target> allFilteredTargets = new HashSet<>(); | |
| allFilteredTargets.addAll(targets.getTargets()); | |
| allFilteredTargets.addAll(targets.getFilteredTargets()); | |
| allFilteredTargets.removeAll(testTargets.getTargets()); | |
| allFilteredTargets.addAll(testTargets.getFilteredTargets()); | |
| testFilteredTargets = ImmutableSet.copyOf(allFilteredTargets); | |
| filteredTargets = ImmutableSet.of(); | |
| targets = ResolvedTargets.<Target>builder() | |
| .merge(testTargets) | |
| .mergeError(targets.hasError()) | |
| .build(); | |
| if (determineTests) { | |
| testsToRun = testTargets.getTargets(); | |
| } | |
| } else /*if (determineTests)*/ { | |
| testsToRun = testTargets.getTargets(); | |
| targets = ResolvedTargets.<Target>builder() | |
| .merge(targets) | |
| // Avoid merge() here which would remove the filteredTargets from the targets. | |
| .addAll(testsToRun) | |
| .mergeError(testTargets.hasError()) | |
| .build(); | |
| // filteredTargets is correct in this case - it cannot contain tests that got back in | |
| // through test_suite expansion, because the test determination would also filter those out. | |
| // However, that's not obvious, and it might be better to explicitly recompute it. | |
| } | |
| if (testsToRun != null) { | |
| // Note that testsToRun can still be null here, if buildTestsOnly && !shouldRunTests. | |
| Preconditions.checkState(targets.getTargets().containsAll(testsToRun)); | |
| } | |
| } | |
| eventBus.post(new TargetParsingCompleteEvent(targets.getTargets(), | |
| filteredTargets, testFilteredTargets, | |
| timer.stop().elapsed(TimeUnit.MILLISECONDS))); | |
| if (targets.hasError()) { | |
| eventHandler.handle(Event.warn("Target pattern parsing failed. Continuing anyway")); | |
| } | |
| if (callback != null) { | |
| callback.notifyTargets(targets.getTargets()); | |
| } | |
| LoadingPhaseRunner.maybeReportDeprecation(eventHandler, targets.getTargets()); | |
| return doSimpleLoadingPhase(eventHandler, eventBus, targets, testsToRun, keepGoing); | |
| } | |
| /** | |
| * Perform test_suite expansion and emits necessary events and logging messages for legacy | |
| * support. | |
| */ | |
| private LoadingResult doSimpleLoadingPhase( | |
| EventHandler eventHandler, | |
| EventBus eventBus, | |
| ResolvedTargets<Target> targets, | |
| ImmutableSet<Target> testsToRun, | |
| boolean keepGoing) | |
| throws InterruptedException, LoadingFailedException { | |
| Stopwatch timer = preLoadingLogging(eventHandler); | |
| ImmutableSet<Target> targetsToLoad = targets.getTargets(); | |
| ResolvedTargets<Target> expandedResult; | |
| try { | |
| expandedResult = expandTestSuites(eventHandler, targetsToLoad, keepGoing); | |
| } catch (TargetParsingException e) { | |
| throw new LoadingFailedException("Loading failed; build aborted", e); | |
| } | |
| postLoadingLogging(eventBus, targetsToLoad, expandedResult.getTargets(), timer); | |
| return new LoadingResult(targets.hasError(), expandedResult.hasError(), | |
| expandedResult.getTargets(), testsToRun, getWorkspaceName(eventHandler)); | |
| } | |
| private Stopwatch preLoadingLogging(EventHandler eventHandler) { | |
| eventHandler.handle(Event.progress("Loading...")); | |
| LOG.info("Starting loading phase"); | |
| return Stopwatch.createStarted(); | |
| } | |
| private void postLoadingLogging(EventBus eventBus, ImmutableSet<Target> originalTargetsToLoad, | |
| ImmutableSet<Target> expandedTargetsToLoad, Stopwatch timer) { | |
| Set<Target> testSuiteTargets = Sets.difference(originalTargetsToLoad, expandedTargetsToLoad); | |
| eventBus.post(new LoadingPhaseCompleteEvent( | |
| expandedTargetsToLoad, ImmutableSet.copyOf(testSuiteTargets), | |
| packageManager.getStatistics(), timer.stop().elapsed(TimeUnit.MILLISECONDS))); | |
| LOG.info("Loading phase finished"); | |
| } | |
| private ResolvedTargets<Target> expandTestSuites(EventHandler eventHandler, | |
| ImmutableSet<Target> targets, boolean keepGoing) | |
| throws LoadingFailedException, TargetParsingException { | |
| // We use strict test_suite expansion here to match the analysis-time checks. | |
| ResolvedTargets<Target> expandedResult = TestTargetUtils.expandTestSuites( | |
| packageManager, eventHandler, targets, /*strict=*/true, /*keepGoing=*/true); | |
| if (expandedResult.hasError() && !keepGoing) { | |
| throw new LoadingFailedException("Could not expand test suite target"); | |
| } | |
| return expandedResult; | |
| } | |
| /** | |
| * Interpret the command-line arguments. | |
| * | |
| * @param targetPatterns the list of command-line target patterns specified by the user | |
| * @param compileOneDependency if true, enables alternative interpretation of targetPatterns; see | |
| * {@link LoadingOptions#compileOneDependency} | |
| * @throws TargetParsingException if parsing failed and !keepGoing | |
| */ | |
| private ResolvedTargets<Target> getTargetsToBuild(EventHandler eventHandler, | |
| List<String> targetPatterns, boolean compileOneDependency, | |
| boolean keepGoing) throws TargetParsingException, InterruptedException { | |
| ResolvedTargets<Target> result = | |
| targetPatternEvaluator.parseTargetPatternList(eventHandler, targetPatterns, | |
| FilteringPolicies.FILTER_MANUAL, keepGoing); | |
| if (compileOneDependency) { | |
| return new CompileOneDependencyTransformer(packageManager) | |
| .transformCompileOneDependency(eventHandler, result); | |
| } | |
| return result; | |
| } | |
| /** | |
| * Interpret test target labels from the command-line arguments and return the corresponding set | |
| * of targets, handling the filter flags, and expanding test suites. | |
| * | |
| * @param eventHandler the error event eventHandler | |
| * @param targetPatterns the list of command-line target patterns specified by the user | |
| * @param options the loading phase options | |
| * @param keepGoing value of the --keep_going flag | |
| */ | |
| private ResolvedTargets<Target> determineTests(EventHandler eventHandler, | |
| List<String> targetPatterns, LoadingOptions options, boolean keepGoing) | |
| throws TargetParsingException, InterruptedException { | |
| // Parse the targets to get the tests. | |
| ResolvedTargets<Target> testTargetsBuilder = targetPatternEvaluator.parseTargetPatternList( | |
| eventHandler, targetPatterns, FilteringPolicies.FILTER_TESTS, keepGoing); | |
| ResolvedTargets.Builder<Target> finalBuilder = ResolvedTargets.builder(); | |
| finalBuilder.merge(testTargetsBuilder); | |
| finalBuilder.filter(TestFilter.forOptions(options, eventHandler, ruleNames)); | |
| return finalBuilder.build(); | |
| } | |
| private String getWorkspaceName(EventHandler eventHandler) | |
| throws InterruptedException, LoadingFailedException { | |
| try { | |
| return packageManager.getPackage(eventHandler, Label.EXTERNAL_PACKAGE_IDENTIFIER) | |
| .getWorkspaceName(); | |
| } catch (NoSuchPackageException e) { | |
| throw new LoadingFailedException("Failed to load //external package", e); | |
| } | |
| } | |
| } |