aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/pkgcache/LoadingPhaseRunner.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/pkgcache/LoadingPhaseRunner.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/pkgcache/LoadingPhaseRunner.java661
1 files changed, 661 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/pkgcache/LoadingPhaseRunner.java b/src/main/java/com/google/devtools/build/lib/pkgcache/LoadingPhaseRunner.java
new file mode 100644
index 0000000000..4d8eea607f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/pkgcache/LoadingPhaseRunner.java
@@ -0,0 +1,661 @@
+// Copyright 2014 Google Inc. 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.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.ImmutableList;
+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.ResolvedTargets;
+import com.google.devtools.build.lib.cmdline.TargetParsingException;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible;
+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.NoSuchTargetException;
+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.PackageIdentifier;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.packages.TestSize;
+import com.google.devtools.build.lib.packages.TestTargetUtils;
+import com.google.devtools.build.lib.packages.TestTimeout;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.common.options.Converters.CommaSeparatedOptionListConverter;
+import com.google.devtools.common.options.Option;
+import com.google.devtools.common.options.OptionsBase;
+
+import java.util.ArrayList;
+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:
+ * <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.
+ */
+public class LoadingPhaseRunner {
+
+ /**
+ * Loading phase options.
+ */
+ public static class Options extends OptionsBase {
+
+ @Option(name = "loading_phase_threads",
+ defaultValue = "200",
+ category = "undocumented",
+ help = "Number of parallel threads to use for the loading phase.")
+ public int loadingPhaseThreads;
+
+ @Option(name = "build_tests_only",
+ defaultValue = "false",
+ category = "what",
+ help = "If specified, only *_test and test_suite rules will be built "
+ + "and other targets specified on the command line will be ignored. "
+ + "By default everything that was requested will be built.")
+ public boolean buildTestsOnly;
+
+ @Option(name = "compile_one_dependency",
+ defaultValue = "false",
+ category = "what",
+ help = "Compile a single dependency of the argument files. "
+ + "This is useful for syntax checking source files in IDEs, "
+ + "for example, by rebuilding a single target that depends on "
+ + "the source file to detect errors as early as possible in the "
+ + "edit/build/test cycle. This argument affects the way all "
+ + "non-flag arguments are interpreted; instead of being targets "
+ + "to build they are source filenames. For each source filename "
+ + "an arbitrary target that depends on it will be built.")
+ public boolean compileOneDependency;
+
+ @Option(name = "test_tag_filters",
+ converter = CommaSeparatedOptionListConverter.class,
+ defaultValue = "",
+ category = "what",
+ help = "Specifies a comma-separated list of test tags. Each tag can be optionally " +
+ "preceded with '-' to specify excluded tags. Only those test targets will be " +
+ "found that contain at least one included tag and do not contain any excluded " +
+ "tags. This option affects --build_tests_only behavior and the test command."
+ )
+ public List<String> testTagFilterList;
+
+ @Option(name = "test_size_filters",
+ converter = TestSize.TestSizeFilterConverter.class,
+ defaultValue = "",
+ category = "what",
+ help = "Specifies a comma-separated list of test sizes. Each size can be optionally " +
+ "preceded with '-' to specify excluded sizes. Only those test targets will be " +
+ "found that contain at least one included size and do not contain any excluded " +
+ "sizes. This option affects --build_tests_only behavior and the test command."
+ )
+ public Set<TestSize> testSizeFilterSet;
+
+ @Option(name = "test_timeout_filters",
+ converter = TestTimeout.TestTimeoutFilterConverter.class,
+ defaultValue = "",
+ category = "what",
+ help = "Specifies a comma-separated list of test timeouts. Each timeout can be " +
+ "optionally preceded with '-' to specify excluded timeouts. Only those test " +
+ "targets will be found that contain at least one included timeout and do not " +
+ "contain any excluded timeouts. This option affects --build_tests_only behavior " +
+ "and the test command."
+ )
+ public Set<TestTimeout> testTimeoutFilterSet;
+
+ @Option(name = "test_lang_filters",
+ converter = CommaSeparatedOptionListConverter.class,
+ defaultValue = "",
+ category = "what",
+ help = "Specifies a comma-separated list of test languages. Each language can be " +
+ "optionally preceded with '-' to specify excluded languages. Only those " +
+ "test targets will be found that are written in the specified languages. " +
+ "The name used for each language should be the same as the language prefix in the " +
+ "*_test rule, e.g. one of 'cc', 'java', 'py', etc." +
+ "This option affects --build_tests_only behavior and the test command."
+ )
+ public List<String> testLangFilterList;
+ }
+
+ /**
+ * A callback interface to notify the caller about specific events.
+ * TODO(bazel-team): maybe we should use the EventBus instead?
+ */
+ public interface Callback {
+ /**
+ * Called after the target patterns have been resolved to give the caller a chance to validate
+ * the list before proceeding.
+ */
+ void notifyTargets(Collection<Target> targets) throws LoadingFailedException;
+
+ /**
+ * Called after loading has finished, to notify the caller about the visited packages.
+ *
+ * <p>The set of visited packages is the set of packages in the transitive closure of the
+ * union of the top level targets.
+ */
+ void notifyVisitedPackages(Set<PackageIdentifier> visitedPackages);
+ }
+
+ /**
+ * The result of the loading phase, i.e., whether there were errors, and which targets were
+ * successfully loaded, plus some related metadata.
+ */
+ public static final class LoadingResult {
+ private final boolean hasTargetPatternError;
+ private final boolean hasLoadingError;
+ private final ImmutableSet<Target> targetsToAnalyze;
+ private final ImmutableSet<Target> testsToRun;
+ private final ImmutableMap<PackageIdentifier, Path> packageRoots;
+ // TODO(bazel-team): consider moving this to LoadedPackageProvider
+ private final ImmutableSet<PackageIdentifier> visitedPackages;
+
+ public LoadingResult(boolean hasTargetPatternError, boolean hasLoadingError,
+ Collection<Target> targetsToAnalyze, Collection<Target> testsToRun,
+ ImmutableMap<PackageIdentifier, Path> packageRoots,
+ Set<PackageIdentifier> visitedPackages) {
+ this.hasTargetPatternError = hasTargetPatternError;
+ this.hasLoadingError = hasLoadingError;
+ this.targetsToAnalyze =
+ targetsToAnalyze == null ? null : ImmutableSet.copyOf(targetsToAnalyze);
+ this.testsToRun = testsToRun == null ? null : ImmutableSet.copyOf(testsToRun);
+ this.packageRoots = packageRoots;
+ this.visitedPackages = ImmutableSet.copyOf(visitedPackages);
+ }
+
+ /** Whether there were errors during target pattern evaluation. */
+ public boolean hasTargetPatternError() {
+ return hasTargetPatternError;
+ }
+
+ /** Whether there were errors during the loading phase. */
+ public boolean hasLoadingError() {
+ return hasLoadingError;
+ }
+
+ /** Successfully loaded targets that should be built. */
+ public Collection<Target> getTargets() {
+ return targetsToAnalyze;
+ }
+
+ /** Successfully loaded targets that should be run as tests. Must be a subset of the targets. */
+ public Collection<Target> getTestsToRun() {
+ return testsToRun;
+ }
+
+ /**
+ * The map from package names to the package root where each package was found; this is used to
+ * set up the symlink tree.
+ */
+ public ImmutableMap<PackageIdentifier, Path> getPackageRoots() {
+ return packageRoots;
+ }
+
+ /**
+ * Returns all packages that were visited during this loading phase.
+ *
+ * <p>We use this to decide when to evict ConfiguredTarget nodes from the graph.
+ */
+ @ThreadCompatible
+ private ImmutableSet<PackageIdentifier> getVisitedPackages() {
+ return visitedPackages;
+ }
+ }
+
+ 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;
+ private final TransitivePackageLoader pkgLoader;
+
+ public LoadingPhaseRunner(PackageManager packageManager,
+ Set<String> ruleNames) {
+ this.packageManager = packageManager;
+ this.targetPatternEvaluator = packageManager.getTargetPatternEvaluator();
+ this.ruleNames = ruleNames;
+ this.pkgLoader = packageManager.newTransitiveLoader();
+ }
+
+ public TargetPatternEvaluator getTargetPatternEvaluator() {
+ return targetPatternEvaluator;
+ }
+
+ public void updatePatternEvaluator(PathFragment relativeWorkingDirectory) {
+ targetPatternEvaluator.updateOffset(relativeWorkingDirectory);
+ }
+
+ /**
+ * This method only exists for the benefit of InfoCommand, which needs to construct
+ * a {@code BuildConfigurationCollection} without running a full loading phase. Don't
+ * add any more clients; instead, we should change info so that it doesn't need the configuration.
+ */
+ public LoadedPackageProvider loadForConfigurations(EventHandler eventHandler,
+ Set<Label> labelsToLoad, boolean keepGoing) throws InterruptedException {
+ // Use a new Label Visitor here to avoid erasing the cache on the existing one.
+ TransitivePackageLoader transitivePackageLoader = packageManager.newTransitiveLoader();
+ boolean loadingSuccessful = transitivePackageLoader.sync(
+ eventHandler, ImmutableSet.<Target>of(),
+ labelsToLoad, keepGoing, /*parallelThreads=*/10,
+ /*maxDepth=*/Integer.MAX_VALUE);
+ return loadingSuccessful ? packageManager : null;
+ }
+
+ /**
+ * 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.
+ */
+ public LoadingResult execute(EventHandler eventHandler, EventBus eventBus,
+ List<String> targetPatterns, Options options,
+ ListMultimap<String, Label> labelsToLoadUnconditionally, boolean keepGoing,
+ boolean determineTests, @Nullable Callback 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<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());
+ }
+
+ maybeReportDeprecation(eventHandler, targets.getTargets());
+
+ // Load the transitive closure of all targets.
+ LoadingResult result = doLoadingPhase(eventHandler, eventBus, targets.getTargets(),
+ testsToRun, labelsToLoadUnconditionally, keepGoing, options.loadingPhaseThreads,
+ targets.hasError());
+
+ if (callback != null) {
+ callback.notifyVisitedPackages(result.getVisitedPackages());
+ }
+
+ return result;
+ }
+
+ /**
+ * 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.
+ *
+ * @param targetsToLoad the list of command-line target patterns specified by the user
+ * @param testsToRun the tests to run as a subset of the targets to load
+ * @param labelsToLoadUnconditionally the labels to load unconditionally (presumably for the build
+ * configuration)
+ * @param keepGoing if true, don't throw ViewCreationFailedException if some
+ * targets could not be loaded, just skip thm.
+ */
+ private LoadingResult doLoadingPhase(EventHandler eventHandler, EventBus eventBus,
+ ImmutableSet<Target> targetsToLoad, Collection<Target> testsToRun,
+ ListMultimap<String, Label> labelsToLoadUnconditionally, boolean keepGoing,
+ int loadingPhaseThreads, boolean hasError)
+ throws InterruptedException, LoadingFailedException {
+ eventHandler.handle(Event.progress("Loading..."));
+ Stopwatch timer = Stopwatch.createStarted();
+ LOG.info("Starting loading phase");
+
+ Set<Label> labelsToLoad = ImmutableSet.copyOf(labelsToLoadUnconditionally.values());
+
+ // For each label in {@code targetsToLoad}, ensure that the target to which
+ // it refers exists, and also every target in its transitive closure of label
+ // dependencies. Success guarantees that a call to
+ // {@code getConfiguredTarget} for the same targets will not fail; the
+ // configuration process is intolerant of missing packages/targets. Before
+ // calling getConfiguredTarget(), clients must ensure that all necessary
+ // packages/targets have been visited since the last sync/clear.
+ boolean loadingSuccessful = pkgLoader.sync(eventHandler, targetsToLoad, labelsToLoad,
+ keepGoing, loadingPhaseThreads, Integer.MAX_VALUE);
+
+ ImmutableSet<Target> targetsToAnalyze;
+ if (loadingSuccessful) {
+ // Success: all loaded targets will be analyzed.
+ targetsToAnalyze = targetsToLoad;
+ } else if (keepGoing) {
+ // Keep going: filter out the error-free targets and only continue with those.
+ targetsToAnalyze = filterErrorFreeTargets(eventBus, targetsToLoad,
+ pkgLoader, labelsToLoadUnconditionally);
+
+ // Tell the user about the subset of successful targets.
+ int requested = targetsToLoad.size();
+ int loaded = targetsToAnalyze.size();
+ if (0 < loaded && loaded < requested) {
+ String message = String.format("Loading succeeded for only %d of %d targets", loaded,
+ requested);
+ eventHandler.handle(Event.info(message));
+ LOG.info(message);
+ }
+ } else {
+ throw new LoadingFailedException("Loading failed; build aborted");
+ }
+
+ Set<Target> filteredTargets = targetsToAnalyze;
+ try {
+ // We use strict test_suite expansion here to match the analysis-time checks.
+ ResolvedTargets<Target> expandedResult = TestTargetUtils.expandTestSuites(
+ packageManager, eventHandler, targetsToAnalyze, /*strict=*/true, /*keepGoing=*/true);
+ targetsToAnalyze = expandedResult.getTargets();
+ filteredTargets = Sets.difference(filteredTargets, targetsToAnalyze);
+ if (expandedResult.hasError()) {
+ if (!keepGoing) {
+ throw new LoadingFailedException("Could not expand test suite target");
+ }
+ loadingSuccessful = false;
+ }
+ } catch (TargetParsingException e) {
+ // This shouldn't happen, because we've already loaded the targets successfully.
+ throw (AssertionError) (new AssertionError("Unexpected target failure").initCause(e));
+ }
+
+ // Perform some operations on the set of packages containing the collected targets.
+ ImmutableMap<PackageIdentifier, Path> packageRoots = collectPackageRoots(
+ pkgLoader.getErrorFreeVisitedPackages());
+
+ Set<PackageIdentifier> visitedPackageNames = pkgLoader.getVisitedPackageNames();
+
+ // Clear some targets from the cache to free memory.
+ packageManager.partiallyClear();
+
+ eventBus.post(new LoadingPhaseCompleteEvent(
+ targetsToAnalyze, filteredTargets, packageManager.getStatistics(),
+ timer.stop().elapsed(TimeUnit.MILLISECONDS)));
+ LOG.info("Loading phase finished");
+
+ // testsToRun can contain targets that aren't analyzed, but the BuildView ignores those.
+ return new LoadingResult(hasError, !loadingSuccessful, targetsToAnalyze, testsToRun,
+ packageRoots, visitedPackageNames);
+ }
+
+ private Collection<Target> getTargetsForLabels(Collection<Label> labels) {
+ Set<Target> result = new HashSet<>();
+
+ for (Label label : labels) {
+ try {
+ result.add(packageManager.getLoadedTarget(label));
+ } catch (NoSuchPackageException e) {
+ Package pkg = Preconditions.checkNotNull(e.getPackage());
+ try {
+ result.add(pkg.getTarget(label.getName()));
+ } catch (NoSuchTargetException ex) {
+ throw new IllegalStateException(ex);
+ }
+ } catch (NoSuchThingException e) {
+ throw new IllegalStateException(e); // The target should have been loaded
+ }
+ }
+
+ return result;
+ }
+
+ private ImmutableSet<Target> filterErrorFreeTargets(
+ EventBus eventBus, Collection<Target> targetsToLoad,
+ TransitivePackageLoader pkgLoader,
+ ListMultimap<String, Label> labelsToLoadUnconditionally) throws LoadingFailedException {
+ // Error out if any of the labels needed for the configuration could not be loaded.
+ Collection<Label> labelsToLoad = new ArrayList<>(labelsToLoadUnconditionally.values());
+ for (Target target : targetsToLoad) {
+ labelsToLoad.add(target.getLabel());
+ }
+ Multimap<Label, Label> rootCauses = pkgLoader.getRootCauses(labelsToLoad);
+ for (Map.Entry<String, Label> entry : labelsToLoadUnconditionally.entries()) {
+ if (rootCauses.containsKey(entry.getValue())) {
+ throw new LoadingFailedException("Failed to load required " + entry.getKey()
+ + " target: '" + entry.getValue() + "'");
+ }
+ }
+
+ // Post root causes for command-line targets that could not be loaded.
+ for (Map.Entry<Label, Label> entry : rootCauses.entries()) {
+ eventBus.post(new LoadingFailureEvent(entry.getKey(), entry.getValue()));
+ }
+
+ return ImmutableSet.copyOf(Sets.difference(ImmutableSet.copyOf(targetsToLoad),
+ ImmutableSet.copyOf(getTargetsForLabels(rootCauses.keySet()))));
+ }
+
+ /**
+ * Returns a map of collected package names to root paths.
+ */
+ private static ImmutableMap<PackageIdentifier, Path> collectPackageRoots(
+ Collection<Package> packages) {
+ // Make a map of the package names to their root paths.
+ ImmutableMap.Builder<PackageIdentifier, Path> packageRoots = ImmutableMap.builder();
+ for (Package pkg : packages) {
+ packageRoots.put(pkg.getPackageIdentifier(), pkg.getSourceRoot());
+ }
+ return packageRoots.build();
+ }
+
+ /**
+ * 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 Options#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_AND_OBSOLETE, 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, Options options, boolean keepGoing)
+ throws TargetParsingException, InterruptedException {
+ // Parse the targets to get the tests.
+ ResolvedTargets.Builder<Target> testTargetsBuilder = ResolvedTargets.builder();
+ for (String targetPattern : targetPatterns) {
+ if (targetPattern.startsWith("-")) {
+ ResolvedTargets<Target> someNegativeTargets = targetPatternEvaluator.parseTargetPatternList(
+ eventHandler, ImmutableList.of(targetPattern.substring(1)),
+ FilteringPolicies.FILTER_TESTS, keepGoing);
+ ResolvedTargets<Target> moreNegativeTargets = TestTargetUtils.expandTestSuites(
+ packageManager, eventHandler, someNegativeTargets.getTargets(), /*strict=*/false,
+ keepGoing);
+ testTargetsBuilder.filter(Predicates.not(Predicates.in(moreNegativeTargets.getTargets())));
+ testTargetsBuilder.mergeError(moreNegativeTargets.hasError());
+ } else {
+ ResolvedTargets<Target> somePositiveTargets = targetPatternEvaluator.parseTargetPatternList(
+ eventHandler, ImmutableList.of(targetPattern),
+ FilteringPolicies.FILTER_TESTS, keepGoing);
+ ResolvedTargets<Target> morePositiveTargets = TestTargetUtils.expandTestSuites(
+ packageManager, eventHandler, somePositiveTargets.getTargets(), /*strict=*/false,
+ keepGoing);
+ testTargetsBuilder.addAll(morePositiveTargets.getTargets());
+ testTargetsBuilder.mergeError(morePositiveTargets.hasError());
+ }
+ }
+ testTargetsBuilder.filter(getTestFilter(eventHandler, options));
+ return testTargetsBuilder.build();
+ }
+
+ /**
+ * Convert the options into a test filter.
+ */
+ private Predicate<Target> getTestFilter(EventHandler eventHandler, Options options) {
+ Predicate<Target> testFilter = Predicates.alwaysTrue();
+ if (!options.testSizeFilterSet.isEmpty()) {
+ testFilter = Predicates.and(testFilter,
+ TestTargetUtils.testSizeFilter(options.testSizeFilterSet));
+ }
+ if (!options.testTimeoutFilterSet.isEmpty()) {
+ testFilter = Predicates.and(testFilter,
+ TestTargetUtils.testTimeoutFilter(options.testTimeoutFilterSet));
+ }
+ if (!options.testTagFilterList.isEmpty()) {
+ testFilter = Predicates.and(testFilter,
+ TestTargetUtils.tagFilter(options.testTagFilterList));
+ }
+ if (!options.testLangFilterList.isEmpty()) {
+ testFilter = Predicates.and(testFilter,
+ TestTargetUtils.testLangFilter(options.testLangFilterList, eventHandler, ruleNames));
+ }
+ return testFilter;
+ }
+
+ /**
+ * Emit a warning when a deprecated target is mentioned on the command line.
+ *
+ * <p>Note that this does not stop us from emitting "target X depends on deprecated target Y"
+ * style warnings for the same target and it is a good thing; <i>depending</i> on a target and
+ * <i>wanting</i> to build it are different things.
+ */
+ private void maybeReportDeprecation(EventHandler eventHandler, Collection<Target> targets) {
+ for (Rule rule : Iterables.filter(targets, Rule.class)) {
+ if (rule.isAttributeValueExplicitlySpecified("deprecation")) {
+ eventHandler.handle(Event.warn(rule.getLocation(), String.format(
+ "target '%s' is deprecated: %s", rule.getLabel(),
+ NonconfigurableAttributeMapper.of(rule).get("deprecation", Type.STRING))));
+ }
+ }
+ }
+}