Implement build tag filtering.

If the --build_tag_filters option is specified, targets built will be filtered according to their tags (at least one included, none excluded)

--
MOS_MIGRATED_REVID=138856195
diff --git a/src/main/java/com/google/devtools/build/lib/packages/TargetUtils.java b/src/main/java/com/google/devtools/build/lib/packages/TargetUtils.java
index 43b7473..00d15cd 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/TargetUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/TargetUtils.java
@@ -14,11 +14,15 @@
 
 package com.google.devtools.build.lib.packages;
 
+import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.events.Location;
 import com.google.devtools.build.lib.syntax.Type;
+import com.google.devtools.build.lib.util.Pair;
+import java.util.Collection;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import javax.annotation.Nullable;
 
@@ -193,6 +197,34 @@
     return visitor.isExplicit();
   }
 
+  /**
+   * Returns a predicate to be used for test tag filtering, i.e., that only accepts tests that match
+   * all of the required tags and none of the excluded tags.
+   */
+  public static Predicate<Target> tagFilter(List<String> tagFilterList) {
+    Pair<Collection<String>, Collection<String>> tagLists =
+        TestTargetUtils.sortTagsBySense(tagFilterList);
+    final Collection<String> requiredTags = tagLists.first;
+    final Collection<String> excludedTags = tagLists.second;
+    return new Predicate<Target>() {
+      @Override
+      public boolean apply(Target input) {
+        if (requiredTags.isEmpty() && excludedTags.isEmpty()) {
+          return true;
+        }
+
+        if (!(input instanceof Rule)) {
+          return false;
+        }
+        // Note that test_tags are those originating from the XX_test rule,
+        // whereas the requiredTags and excludedTags originate from the command
+        // line or test_suite rule.
+        return TestTargetUtils.testMatchesFilters(((Rule) input).getRuleTags(),
+            requiredTags, excludedTags, false);
+      }
+    };
+  }
+
   private static class ExplicitEdgeVisitor implements AttributeMap.AcceptsLabelAttribute {
     private final Label expectedLabel;
     private final Rule rule;
diff --git a/src/main/java/com/google/devtools/build/lib/packages/TestTargetUtils.java b/src/main/java/com/google/devtools/build/lib/packages/TestTargetUtils.java
index 7045c16..eb5755c 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/TestTargetUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/TestTargetUtils.java
@@ -144,31 +144,6 @@
   }
 
   /**
-   * Returns a predicate to be used for test tag filtering, i.e., that only accepts tests that match
-   * all of the required tags and none of the excluded tags.
-   */
-  // TODO(bazel-team): This also applies to non-test rules, so should probably be moved to
-  // TargetUtils.
-  public static Predicate<Target> tagFilter(List<String> tagFilterList) {
-    Pair<Collection<String>, Collection<String>> tagLists = sortTagsBySense(tagFilterList);
-    final Collection<String> requiredTags = tagLists.first;
-    final Collection<String> excludedTags = tagLists.second;
-    return new Predicate<Target>() {
-      @Override
-      public boolean apply(Target input) {
-        if (!(input instanceof Rule)) {
-          return false;
-        }
-        // Note that test_tags are those originating from the XX_test rule,
-        // whereas the requiredTags and excludedTags originate from the command
-        // line or test_suite rule.
-        return testMatchesFilters(((Rule) input).getRuleTags(),
-            requiredTags, excludedTags, false);
-      }
-    };
-  }
-
-  /**
    * Separates a list of text "tags" into a Pair of Collections, where
    * the first element are the required or positive tags and the second element
    * are the excluded or negative tags.
diff --git a/src/main/java/com/google/devtools/build/lib/pkgcache/LegacyLoadingPhaseRunner.java b/src/main/java/com/google/devtools/build/lib/pkgcache/LegacyLoadingPhaseRunner.java
index 388e4b8..448faf6 100644
--- a/src/main/java/com/google/devtools/build/lib/pkgcache/LegacyLoadingPhaseRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/pkgcache/LegacyLoadingPhaseRunner.java
@@ -26,6 +26,7 @@
 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.TargetUtils;
 import com.google.devtools.build.lib.packages.TestTargetUtils;
 import com.google.devtools.build.lib.util.Preconditions;
 import com.google.devtools.build.lib.vfs.PathFragment;
@@ -116,7 +117,7 @@
     EventHandler parseFailureListener = new ParseFailureListenerImpl(eventHandler, eventBus);
     // Determine targets to build:
     ResolvedTargets<Target> targets = getTargetsToBuild(parseFailureListener,
-        targetPatterns, options.compileOneDependency, keepGoing);
+        targetPatterns, options.compileOneDependency, options.buildTagFilterList, keepGoing);
 
     ImmutableSet<Target> filteredTargets = targets.getFilteredTargets();
 
@@ -256,14 +257,22 @@
    */
   private ResolvedTargets<Target> getTargetsToBuild(EventHandler eventHandler,
       List<String> targetPatterns, boolean compileOneDependency,
-      boolean keepGoing) throws TargetParsingException, InterruptedException {
-    ResolvedTargets<Target> result =
+      List<String> buildTagFilterList, boolean keepGoing)
+      throws TargetParsingException, InterruptedException {
+    ResolvedTargets<Target> evaluated =
         targetPatternEvaluator.parseTargetPatternList(eventHandler, targetPatterns,
             FilteringPolicies.FILTER_MANUAL, keepGoing);
+
+    ResolvedTargets<Target> result = ResolvedTargets.<Target>builder()
+        .merge(evaluated)
+        .filter(TargetUtils.tagFilter(buildTagFilterList))
+        .build();
+
     if (compileOneDependency) {
       return new CompileOneDependencyTransformer(packageManager)
           .transformCompileOneDependency(eventHandler, result);
     }
+
     return result;
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/pkgcache/LoadingOptions.java b/src/main/java/com/google/devtools/build/lib/pkgcache/LoadingOptions.java
index 9ed11f3..29f0523 100644
--- a/src/main/java/com/google/devtools/build/lib/pkgcache/LoadingOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/pkgcache/LoadingOptions.java
@@ -20,7 +20,6 @@
 import com.google.devtools.common.options.Option;
 import com.google.devtools.common.options.OptionsBase;
 import com.google.devtools.common.options.OptionsParsingException;
-
 import java.util.List;
 import java.util.Set;
 
@@ -57,6 +56,18 @@
           + "an arbitrary target that depends on it will be built.")
   public boolean compileOneDependency;
 
+  @Option(name = "build_tag_filters",
+      converter = CommaSeparatedOptionListConverter.class,
+      defaultValue = "",
+      category = "what",
+      help = "Specifies a comma-separated list of tags. Each tag can be optionally "
+          + "preceded with '-' to specify excluded tags. Only those targets will be built that "
+          + "contain at least one included tag and do not contain any excluded tags. This option "
+          + "does not affect the set of tests executed with the 'test' command; those are be "
+          + "governed by the test filtering options, for example '--test_tag_filters'"
+      )
+  public List<String> buildTagFilterList;
+
   @Option(name = "test_tag_filters",
       converter = CommaSeparatedOptionListConverter.class,
       defaultValue = "",
diff --git a/src/main/java/com/google/devtools/build/lib/pkgcache/TestFilter.java b/src/main/java/com/google/devtools/build/lib/pkgcache/TestFilter.java
index 84df151..e00dc3d 100644
--- a/src/main/java/com/google/devtools/build/lib/pkgcache/TestFilter.java
+++ b/src/main/java/com/google/devtools/build/lib/pkgcache/TestFilter.java
@@ -17,6 +17,7 @@
 import com.google.common.base.Predicates;
 import com.google.devtools.build.lib.events.EventHandler;
 import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.packages.TargetUtils;
 import com.google.devtools.build.lib.packages.TestSize;
 import com.google.devtools.build.lib.packages.TestTargetUtils;
 import com.google.devtools.build.lib.packages.TestTimeout;
@@ -48,7 +49,7 @@
     }
     if (!options.testTagFilterList.isEmpty()) {
       testFilter = Predicates.and(testFilter,
-          TestTargetUtils.tagFilter(options.testTagFilterList));
+          TargetUtils.tagFilter(options.testTagFilterList));
     }
     if (!options.testLangFilterList.isEmpty()) {
       testFilter = Predicates.and(testFilter,
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
index ec33024..ebb660a 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
@@ -1827,6 +1827,7 @@
       SkyKey key = TargetPatternPhaseValue.key(ImmutableList.copyOf(targetPatterns),
           relativeWorkingDirectory.getPathString(), options.compileOneDependency,
           options.buildTestsOnly, determineTests,
+          ImmutableList.copyOf(options.buildTagFilterList),
           TestFilter.forOptions(options, eventHandler, ruleClassNames));
       EvaluationResult<TargetPatternPhaseValue> evalResult;
       eventBus.post(new LoadingPhaseStartedEvent(packageProgress));
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseFunction.java
index 60600ca..7a87ba2 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseFunction.java
@@ -25,7 +25,6 @@
 import com.google.devtools.build.lib.packages.TargetUtils;
 import com.google.devtools.build.lib.pkgcache.CompileOneDependencyTransformer;
 import com.google.devtools.build.lib.pkgcache.FilteringPolicies;
-import com.google.devtools.build.lib.pkgcache.LoadingOptions;
 import com.google.devtools.build.lib.pkgcache.LoadingPhaseRunner;
 import com.google.devtools.build.lib.pkgcache.TargetProvider;
 import com.google.devtools.build.lib.pkgcache.TestFilter;
@@ -73,8 +72,7 @@
     }
 
     // Determine targets to build:
-    ResolvedTargets<Target> targets = getTargetsToBuild(env,
-        options.getTargetPatterns(), options.getOffset(), options.getCompileOneDependency());
+    ResolvedTargets<Target> targets = getTargetsToBuild(env, options);
 
     // 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
@@ -185,16 +183,14 @@
   /**
    * 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}
+   * @param options the command-line arguments in structured form
    */
   private static ResolvedTargets<Target> getTargetsToBuild(
-      Environment env, List<String> targetPatterns, String offset, boolean compileOneDependency)
-      throws InterruptedException {
+      Environment env, TargetPatternList options) throws InterruptedException {
     List<SkyKey> patternSkyKeys = new ArrayList<>();
     for (TargetPatternSkyKeyOrException keyOrException :
-        TargetPatternValue.keys(targetPatterns, FilteringPolicies.FILTER_MANUAL, offset)) {
+        TargetPatternValue.keys(options.getTargetPatterns(), FilteringPolicies.FILTER_MANUAL,
+            options.getOffset())) {
       try {
         patternSkyKeys.add(keyOrException.getSkyKey());
       } catch (TargetParsingException e) {
@@ -231,8 +227,10 @@
       }
     }
 
-    ResolvedTargets<Target> result = builder.build();
-    if (compileOneDependency) {
+    ResolvedTargets<Target> result = builder
+        .filter(TargetUtils.tagFilter(options.getBuildTargetFilter()))
+        .build();
+    if (options.getCompileOneDependency()) {
       TargetProvider targetProvider = new EnvironmentBackedRecursivePackageProvider(env);
       try {
         return new CompileOneDependencyTransformer(targetProvider)
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseValue.java
index ca88813..7238ddb 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseValue.java
@@ -24,12 +24,10 @@
 import com.google.devtools.build.lib.util.Preconditions;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
-
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.Serializable;
 import java.util.Objects;
-
 import javax.annotation.Nullable;
 
 /**
@@ -131,6 +129,7 @@
   @ThreadSafe
   public static SkyKey key(ImmutableList<String> targetPatterns, String offset,
       boolean compileOneDependency, boolean buildTestsOnly, boolean determineTests,
+      ImmutableList<String> buildTargetFilter,
       @Nullable TestFilter testFilter) {
     return SkyKey.create(
         SkyFunctions.TARGET_PATTERN_PHASE,
@@ -140,6 +139,7 @@
             compileOneDependency,
             buildTestsOnly,
             determineTests,
+            buildTargetFilter,
             testFilter));
   }
 
@@ -154,16 +154,18 @@
     private final boolean compileOneDependency;
     private final boolean buildTestsOnly;
     private final boolean determineTests;
+    private final ImmutableList<String> buildTargetFilter;
     @Nullable private final TestFilter testFilter;
 
     public TargetPatternList(ImmutableList<String> targetPatterns, String offset,
         boolean compileOneDependency, boolean buildTestsOnly, boolean determineTests,
-        @Nullable TestFilter testFilter) {
+        ImmutableList<String> buildTargetFilter, @Nullable TestFilter testFilter) {
       this.targetPatterns = Preconditions.checkNotNull(targetPatterns);
       this.offset = Preconditions.checkNotNull(offset);
       this.compileOneDependency = compileOneDependency;
       this.buildTestsOnly = buildTestsOnly;
       this.determineTests = determineTests;
+      this.buildTargetFilter = Preconditions.checkNotNull(buildTargetFilter);
       this.testFilter = testFilter;
       if (buildTestsOnly || determineTests) {
         Preconditions.checkNotNull(testFilter);
@@ -190,6 +192,10 @@
       return determineTests;
     }
 
+    public ImmutableList<String> getBuildTargetFilter() {
+      return buildTargetFilter;
+    }
+
     public TestFilter getTestFilter() {
       return testFilter;
     }
@@ -228,6 +234,7 @@
           && other.compileOneDependency == compileOneDependency
           && other.buildTestsOnly == buildTestsOnly
           && other.determineTests == determineTests
+          && other.buildTargetFilter.equals(buildTargetFilter)
           && Objects.equals(other.testFilter, testFilter);
     }
   }