// 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.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimap;
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.PackageIdentifier;
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.NoSuchThingException;
import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper;
import com.google.devtools.build.lib.packages.Package;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.packages.TestTargetUtils;
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import javax.annotation.Nullable;
/**
* Implements the loading phase; responsible for:
*
*
target pattern evaluation
*
test suite expansion
*
loading the labels needed to construct the build configuration
*
loading the labels needed for the analysis with the build configuration
*
loading the transitive closure of the targets and the configuration labels
*
*
*
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.
*
*
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.
*
*
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 ruleNames;
public LegacyLoadingPhaseRunner(PackageManager packageManager,
Set ruleNames) {
this.packageManager = packageManager;
this.targetPatternEvaluator = packageManager.newTargetPatternEvaluator();
this.ruleNames = ruleNames;
}
@Override
public TargetPatternEvaluator getTargetPatternEvaluator() {
return targetPatternEvaluator;
}
@Override
public void updatePatternEvaluator(PathFragment relativeWorkingDirectory) {
targetPatternEvaluator.updateOffset(relativeWorkingDirectory);
}
/**
* 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 targetPatterns, LoadingOptions options,
ListMultimap labelsToLoadUnconditionally, boolean keepGoing,
boolean enableLoading, 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 ");
}
EventHandler parseFailureListener = new ParseFailureListenerImpl(eventHandler, eventBus);
// Determine targets to build:
ResolvedTargets targets = getTargetsToBuild(parseFailureListener,
targetPatterns, options.compileOneDependency, keepGoing);
ImmutableSet filteredTargets = targets.getFilteredTargets();
boolean buildTestsOnly = options.buildTestsOnly;
ImmutableSet testsToRun = null;
ImmutableSet 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 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 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.builder()
.merge(testTargets)
.mergeError(targets.hasError())
.build();
if (determineTests) {
testsToRun = testTargets.getTargets();
}
} else /*if (determineTests)*/ {
testsToRun = testTargets.getTargets();
targets = ResolvedTargets.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());
if (enableLoading) {
return doLoadingPhase(eventHandler, eventBus, targets, testsToRun,
labelsToLoadUnconditionally, keepGoing, options.loadingPhaseThreads, callback);
} else {
return doSimpleLoadingPhase(eventHandler, eventBus, targets, testsToRun, keepGoing);
}
}
private void freeMemoryAfterLoading(LoadingCallback callback, Set visitedPackages) {
if (callback != null) {
callback.notifyVisitedPackages(visitedPackages);
}
// Clear some targets from the cache to free memory.
packageManager.partiallyClear();
}
/**
* Simplified version of {@code doLoadingPhase} method. This method does not load targets.
* It only does test_suite expansion and emits necessary events and logging messages for legacy
* support.
*/
private LoadingResult doSimpleLoadingPhase(EventHandler eventHandler, EventBus eventBus,
ResolvedTargets targets, ImmutableSet testsToRun, boolean keepGoing)
throws LoadingFailedException {
Stopwatch timer = preLoadingLogging(eventHandler);
ImmutableSet targetsToLoad = targets.getTargets();
ResolvedTargets 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, ImmutableMap.of());
}
/**
* Visit the transitive closure of the targets, populating the package cache
* and ensuring that all labels can be resolved and all rules were free from
* errors.
*/
private LoadingResult doLoadingPhase(EventHandler eventHandler, EventBus eventBus,
ResolvedTargets targets, ImmutableSet testsToRun,
ListMultimap labelsToLoadUnconditionally, boolean keepGoing,
int loadingPhaseThreads, @Nullable LoadingCallback callback)
throws InterruptedException, LoadingFailedException {
Stopwatch timer = preLoadingLogging(eventHandler);
TransitivePackageLoader pkgLoader = packageManager.newTransitiveLoader();
BaseLoadingResult baseResult = performLoadingOfTargets(eventHandler, eventBus, pkgLoader,
targets.getTargets(), labelsToLoadUnconditionally, keepGoing, loadingPhaseThreads);
ResolvedTargets expandedResult;
try {
expandedResult = expandTestSuites(eventHandler, baseResult.getTargets(), keepGoing);
} catch (TargetParsingException e) {
// This shouldn't happen, because we've already loaded the targets successfully.
throw (AssertionError) (new AssertionError("Unexpected target failure").initCause(e));
}
freeMemoryAfterLoading(callback, pkgLoader.getVisitedPackageNames());
postLoadingLogging(eventBus, baseResult.getTargets(), expandedResult.getTargets(), timer);
LoadingResult loadingResult = new LoadingResult(targets.hasError(),
!baseResult.isSuccesful() || expandedResult.hasError(),
expandedResult.getTargets(), testsToRun, baseResult.roots);
return loadingResult;
}
private Stopwatch preLoadingLogging(EventHandler eventHandler) {
eventHandler.handle(Event.progress("Loading..."));
LOG.info("Starting loading phase");
return Stopwatch.createStarted();
}
private void postLoadingLogging(EventBus eventBus, ImmutableSet originalTargetsToLoad,
ImmutableSet expandedTargetsToLoad, Stopwatch timer) {
Set testSuiteTargets = Sets.difference(originalTargetsToLoad, expandedTargetsToLoad);
eventBus.post(new LoadingPhaseCompleteEvent(expandedTargetsToLoad, testSuiteTargets,
packageManager.getStatistics(), timer.stop().elapsed(TimeUnit.MILLISECONDS)));
LOG.info("Loading phase finished");
}
private BaseLoadingResult performLoadingOfTargets(EventHandler eventHandler, EventBus eventBus,
TransitivePackageLoader pkgLoader, ImmutableSet targetsToLoad,
ListMultimap labelsToLoadUnconditionally, boolean keepGoing,
int loadingPhaseThreads) throws InterruptedException, LoadingFailedException {
Set