diff options
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.java | 661 |
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)))); + } + } + } +} |