Tags propagation: added incompatible flag

reverted rollback of tags propagation: https://github.com/bazelbuild/bazel/commit/29eecb56c5f0f26b4751ec9eb79954412fb2991c

Tags declared on targets are not propagated to actions and therefore are not taken into consideration by bazel. This causes some issues, for instance, target marked with a tag 'no-remote' will still be executed remotely.
As it was agreed in the design doc (see #7766 for a link), set of tags to be propagated to actions as a first iteration.
This change is responsible for that first step for the Starlark Rules.

RELNOTES: tags 'no-remote', 'no-cache', 'no-remote-cache', 'no-remote-exec', 'no-sandbox' are propagated now to the actions from targets when '--ncompatible_allow_tags_propagation' flag set to true. See #8830.

Closes #7766
Related to #8830

Closes #8829.

PiperOrigin-RevId: 258521443
diff --git a/src/main/java/com/google/devtools/build/lib/actions/ExecutionRequirements.java b/src/main/java/com/google/devtools/build/lib/actions/ExecutionRequirements.java
index e632839..330dffa 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/ExecutionRequirements.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/ExecutionRequirements.java
@@ -18,11 +18,23 @@
 import com.google.common.base.Function;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.packages.Rule;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 /**
  * Strings used to express requirements on action execution environments.
+ *
+ * <ol>
+ *   If you are adding a new execution requirement, pay attention to the following:
+ *   <li>If its name starts with one of the supported prefixes, then it can be also used as a tag on
+ *       a target and will be propagated to the execution requirements, see for prefixes {@link
+ *       com.google.devtools.build.lib.packages.TargetUtils#getExecutionInfo(Rule)}
+ *   <li>If this is a potentially conflicting execution requirements, e.g. you are adding a pair
+ *       'requires-x' and 'block-x', you MUST take care of a potential conflict in the Executor that
+ *       is using new execution requirements. As an example, see {@link
+ *       Spawns#requiresNetwork(com.google.devtools.build.lib.actions.Spawn, boolean)}.
+ * </ol>
  */
 public class ExecutionRequirements {
 
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java
index 29b7e5b..16dea75 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java
@@ -558,15 +558,14 @@
     if (EvalUtils.toBoolean(useDefaultShellEnv)) {
       builder.useDefaultShellEnvironment();
     }
-    if (executionRequirementsUnchecked != Runtime.NONE) {
-      builder.setExecutionInfo(
-          TargetUtils.filter(
-              SkylarkDict.castSkylarkDictOrNoneToDict(
-                  executionRequirementsUnchecked,
-                  String.class,
-                  String.class,
-                  "execution_requirements")));
-    }
+
+    ImmutableMap<String, String> executionInfo =
+        TargetUtils.getFilteredExecutionInfo(
+            executionRequirementsUnchecked,
+            ruleContext.getRule(),
+            starlarkSemantics.incompatibleAllowTagsPropagation());
+    builder.setExecutionInfo(executionInfo);
+
     if (inputManifestsUnchecked != Runtime.NONE) {
       for (RunfilesSupplier supplier : SkylarkList.castSkylarkListOrNoneToList(
           inputManifestsUnchecked, RunfilesSupplier.class, "runfiles suppliers")) {
diff --git a/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java b/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java
index 868697d..73879e3 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java
@@ -177,6 +177,21 @@
   public boolean incompatibleBzlDisallowLoadAfterStatement;
 
   @Option(
+      name = "incompatible_allow_tags_propagation",
+      defaultValue = "false",
+      documentationCategory = OptionDocumentationCategory.STARLARK_SEMANTICS,
+      effectTags = {OptionEffectTag.BUILD_FILE_SEMANTICS},
+      metadataTags = {
+        OptionMetadataTag.INCOMPATIBLE_CHANGE,
+        OptionMetadataTag.TRIGGERED_BY_ALL_INCOMPATIBLE_CHANGES
+      },
+      help =
+          "If set to true, tags will be propagated from a target to the actions' execution"
+              + " requirements; otherwise tags are not propagated. See"
+              + " https://github.com/bazelbuild/bazel/issues/8830 for details.")
+  public boolean incompatibleAllowTagsPropagation;
+
+  @Option(
       name = "incompatible_depset_union",
       defaultValue = "true",
       documentationCategory = OptionDocumentationCategory.STARLARK_SEMANTICS,
@@ -697,6 +712,7 @@
             .incompatibleDisallowSplitEmptySeparator(incompatibleDisallowSplitEmptySeparator)
             .incompatibleDisallowDictLookupUnhashableKeys(
                 incompatibleDisallowDictLookupUnhashableKeys)
+            .incompatibleAllowTagsPropagation(incompatibleAllowTagsPropagation)
             .build();
     return INTERNER.intern(semantics);
   }
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 acb0352..3ab123a 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
@@ -24,6 +24,8 @@
 import com.google.common.collect.Maps;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.syntax.SkylarkDict;
 import com.google.devtools.build.lib.syntax.Type;
 import com.google.devtools.build.lib.util.Pair;
 import java.util.ArrayList;
@@ -46,18 +48,15 @@
   // some internal tags that we don't allow to be set on targets. We also don't want to
   // exhaustively enumerate all the legal values here. Right now, only a ~small set of tags is
   // recognized by Bazel.
-  private static final Predicate<String> LEGAL_EXEC_INFO_KEYS = new Predicate<String>() {
-    @Override
-    public boolean apply(String tag) {
-      return tag.startsWith("block-")
-          || tag.startsWith("requires-")
-          || tag.startsWith("no-")
-          || tag.startsWith("supports-")
-          || tag.startsWith("disable-")
-          || tag.equals("local")
-          || tag.startsWith("cpu:");
-    }
-  };
+  private static boolean legalExecInfoKeys(String tag) {
+    return tag.startsWith("block-")
+        || tag.startsWith("requires-")
+        || tag.startsWith("no-")
+        || tag.startsWith("supports-")
+        || tag.startsWith("disable-")
+        || tag.equals("local")
+        || tag.startsWith("cpu:");
+  }
 
   private TargetUtils() {} // Uninstantiable.
 
@@ -220,19 +219,15 @@
   }
 
   /**
-   * Returns the execution info. These include execution requirement tags ('block-*', 'requires-*',
-   * 'no-*', 'supports-*', 'disable-*', 'local', and 'cpu:*') as keys with empty values.
+   * Returns the execution info from the tags declared on the target. These include only some tags
+   * {@link #legalExecInfoKeys} as keys with empty values.
    */
   public static Map<String, String> getExecutionInfo(Rule rule) {
     // tags may contain duplicate values.
     Map<String, String> map = new HashMap<>();
     for (String tag :
         NonconfigurableAttributeMapper.of(rule).get(CONSTRAINTS_ATTR, Type.STRING_LIST)) {
-      // We don't want to pollute the execution info with random things, and we also need to reserve
-      // some internal tags that we don't allow to be set on targets. We also don't want to
-      // exhaustively enumerate all the legal values here. Right now, only a ~small set of tags is
-      // recognized by Bazel.
-      if (LEGAL_EXEC_INFO_KEYS.apply(tag)) {
+      if (legalExecInfoKeys(tag)) {
         map.put(tag, "");
       }
     }
@@ -240,11 +235,48 @@
   }
 
   /**
+   * Returns the execution info, obtained from the rule's tags and the execution requirements
+   * provided. Only supported tags are included into the execution info, see {@link
+   * #legalExecInfoKeys}.
+   *
+   * @param executionRequirementsUnchecked execution_requirements of a rule, expected to be of a
+   *     {@code SkylarkDict<String, String>} type, null or {@link
+   *     com.google.devtools.build.lib.syntax.Runtime#NONE}
+   * @param rule a rule instance to get tags from
+   * @param incompatibleAllowTagsPropagation if set to true, tags will be propagated from a target
+   *     to the actions' execution requirements, for more details {@see
+   *     SkylarkSematicOptions#incompatibleAllowTagsPropagation}
+   */
+  public static ImmutableMap<String, String> getFilteredExecutionInfo(
+      Object executionRequirementsUnchecked, Rule rule, boolean incompatibleAllowTagsPropagation)
+      throws EvalException {
+    Map<String, String> checkedExecutionRequirements =
+        TargetUtils.filter(
+            SkylarkDict.castSkylarkDictOrNoneToDict(
+                executionRequirementsUnchecked,
+                String.class,
+                String.class,
+                "execution_requirements"));
+
+    Map<String, String> executionInfoBuilder = new HashMap<>();
+    // adding filtered execution requirements to the execution info map
+    executionInfoBuilder.putAll(checkedExecutionRequirements);
+
+    if (incompatibleAllowTagsPropagation) {
+      Map<String, String> checkedTags = getExecutionInfo(rule);
+      // merging filtered tags to the execution info map avoiding duplicates
+      checkedTags.forEach(executionInfoBuilder::putIfAbsent);
+    }
+
+    return ImmutableMap.copyOf(executionInfoBuilder);
+  }
+
+  /**
    * Returns the execution info. These include execution requirement tags ('block-*', 'requires-*',
    * 'no-*', 'supports-*', 'disable-*', 'local', and 'cpu:*') as keys with empty values.
    */
   public static Map<String, String> filter(Map<String, String> executionInfo) {
-    return Maps.filterKeys(executionInfo, LEGAL_EXEC_INFO_KEYS);
+    return Maps.filterKeys(executionInfo, TargetUtils::legalExecInfoKeys);
   }
 
   /**
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java b/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java
index 87b8c5f..f2181f4 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java
@@ -60,6 +60,7 @@
     INCOMPATIBLE_OBJC_FRAMEWORK_CLEANUP(StarlarkSemantics::incompatibleObjcFrameworkCleanup),
     INCOMPATIBLE_DISALLOW_RULE_EXECUTION_PLATFORM_CONSTRAINTS_ALLOWED(
         StarlarkSemantics::incompatibleDisallowRuleExecutionPlatformConstraintsAllowed),
+    INCOMPATIBLE_ALLOW_TAGS_PROPAGATION(StarlarkSemantics::incompatibleAllowTagsPropagation),
     NONE(null);
 
     // Using a Function here makes the enum definitions far cleaner, and, since this is
@@ -210,6 +211,8 @@
 
   public abstract boolean incompatibleDisallowDictLookupUnhashableKeys();
 
+  public abstract boolean incompatibleAllowTagsPropagation();
+
   @Memoized
   @Override
   public abstract int hashCode();
@@ -287,6 +290,7 @@
           .incompatibleRestrictStringEscapes(false)
           .incompatibleDisallowSplitEmptySeparator(false)
           .incompatibleDisallowDictLookupUnhashableKeys(false)
+          .incompatibleAllowTagsPropagation(false)
           .build();
 
   /** Builder for {@link StarlarkSemantics}. All fields are mandatory. */
@@ -312,6 +316,8 @@
 
     public abstract Builder experimentalStarlarkUnusedInputsList(boolean value);
 
+    public abstract Builder incompatibleAllowTagsPropagation(boolean value);
+
     public abstract Builder incompatibleBzlDisallowLoadAfterStatement(boolean value);
 
     public abstract Builder incompatibleDepsetIsNotIterable(boolean value);
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTest.java b/src/test/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTest.java
index 5826f5a..0788a89 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTest.java
@@ -115,6 +115,14 @@
   }
 
   @Test
+  public void testExecutionInfoCopied() {
+    SpawnAction copyFromWelcomeToDestination =
+        createCopyFromWelcomeToDestination(ImmutableMap.of());
+    Map<String, String> executionInfo = copyFromWelcomeToDestination.getExecutionInfo();
+    assertThat(executionInfo).containsExactly("local", "");
+  }
+
+  @Test
   public void testBuilder() throws Exception {
     Artifact input = getSourceArtifact("input");
     Artifact output = getBinArtifactWithNoOwner("output");
@@ -288,7 +296,7 @@
     assertThat(info.getMnemonic()).isEqualTo("Dummy");
 
     SpawnInfo spawnInfo = info.getExtension(SpawnInfo.spawnInfo);
-    assertThat(spawnInfo).isNotNull();
+    assertThat(info.hasExtension(SpawnInfo.spawnInfo)).isTrue();
 
     assertThat(spawnInfo.getArgumentList())
         .containsExactlyElementsIn(action.getArguments());
diff --git a/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java b/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
index fbd3c0a..995e0f7 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
@@ -133,6 +133,7 @@
         "--experimental_platforms_api=" + rand.nextBoolean(),
         "--experimental_starlark_config_transitions=" + rand.nextBoolean(),
         "--experimental_starlark_unused_inputs_list=" + rand.nextBoolean(),
+        "--incompatible_allow_tags_propagation=" + rand.nextBoolean(),
         "--incompatible_bzl_disallow_load_after_statement=" + rand.nextBoolean(),
         "--incompatible_depset_for_libraries_to_link_getter=" + rand.nextBoolean(),
         "--incompatible_depset_is_not_iterable=" + rand.nextBoolean(),
@@ -188,6 +189,7 @@
         .experimentalPlatformsApi(rand.nextBoolean())
         .experimentalStarlarkConfigTransitions(rand.nextBoolean())
         .experimentalStarlarkUnusedInputsList(rand.nextBoolean())
+        .incompatibleAllowTagsPropagation(rand.nextBoolean())
         .incompatibleBzlDisallowLoadAfterStatement(rand.nextBoolean())
         .incompatibleDepsetForLibrariesToLinkGetter(rand.nextBoolean())
         .incompatibleDepsetIsNotIterable(rand.nextBoolean())
diff --git a/src/test/java/com/google/devtools/build/lib/packages/TargetUtilsTest.java b/src/test/java/com/google/devtools/build/lib/packages/TargetUtilsTest.java
index eb49366..058524e 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/TargetUtilsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/TargetUtilsTest.java
@@ -18,6 +18,8 @@
 import com.google.common.base.Predicate;
 import com.google.common.collect.Lists;
 import com.google.devtools.build.lib.packages.util.PackageLoadingTestCase;
+import com.google.devtools.build.lib.syntax.Runtime;
+import com.google.devtools.build.lib.syntax.SkylarkDict;
 import java.util.Map;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -96,4 +98,178 @@
     execInfo = TargetUtils.getExecutionInfo(tag1b);
     assertThat(execInfo).containsExactly("local", "", "cpu:4", "");
   }
+
+  @Test
+  public void testExecutionInfo_withPrefixSupports() throws Exception {
+    scratch.file(
+        "tests/BUILD",
+        "sh_binary(name = 'with-prefix-supports', srcs=['sh.sh'], tags=['supports-workers',"
+            + " 'supports-whatever', 'my-tag'])");
+
+    Rule withSupportsPrefix = (Rule) getTarget("//tests:with-prefix-supports");
+
+    Map<String, String> execInfo = TargetUtils.getExecutionInfo(withSupportsPrefix);
+    assertThat(execInfo).containsExactly("supports-whatever", "", "supports-workers", "");
+  }
+
+  @Test
+  public void testExecutionInfo_withPrefixDisable() throws Exception {
+    scratch.file(
+        "tests/BUILD",
+        "sh_binary(name = 'with-prefix-disable', srcs=['sh.sh'], tags=['disable-local-prefetch',"
+            + " 'disable-something-else', 'another-tag'])");
+
+    Rule withDisablePrefix = (Rule) getTarget("//tests:with-prefix-disable");
+
+    Map<String, String> execInfo = TargetUtils.getExecutionInfo(withDisablePrefix);
+    assertThat(execInfo)
+        .containsExactly("disable-local-prefetch", "", "disable-something-else", "");
+  }
+
+  @Test
+  public void testExecutionInfo_withPrefixNo() throws Exception {
+    scratch.file(
+        "tests/BUILD",
+        "sh_binary(name = 'with-prefix-no', srcs=['sh.sh'], tags=['no-remote-imaginary-flag',"
+            + " 'no-sandbox', 'unknown'])");
+
+    Rule withNoPrefix = (Rule) getTarget("//tests:with-prefix-no");
+
+    Map<String, String> execInfo = TargetUtils.getExecutionInfo(withNoPrefix);
+    assertThat(execInfo).containsExactly("no-remote-imaginary-flag", "", "no-sandbox", "");
+  }
+
+  @Test
+  public void testExecutionInfo_withPrefixRequires() throws Exception {
+    scratch.file(
+        "tests/BUILD",
+        "sh_binary(name = 'with-prefix-requires', srcs=['sh.sh'], tags=['requires-network',"
+            + " 'requires-sunlight', 'test-only'])");
+
+    Rule withRequiresPrefix = (Rule) getTarget("//tests:with-prefix-requires");
+
+    Map<String, String> execInfo = TargetUtils.getExecutionInfo(withRequiresPrefix);
+    assertThat(execInfo).containsExactly("requires-network", "", "requires-sunlight", "");
+  }
+
+  @Test
+  public void testExecutionInfo_withPrefixBlock() throws Exception {
+    scratch.file(
+        "tests/BUILD",
+        "sh_binary(name = 'with-prefix-block', srcs=['sh.sh'], tags=['block-some-feature',"
+            + " 'block-network', 'wrong-tag'])");
+
+    Rule withBlockPrefix = (Rule) getTarget("//tests:with-prefix-block");
+
+    Map<String, String> execInfo = TargetUtils.getExecutionInfo(withBlockPrefix);
+    assertThat(execInfo).containsExactly("block-network", "", "block-some-feature", "");
+  }
+
+  @Test
+  public void testExecutionInfo_withPrefixCpu() throws Exception {
+    scratch.file(
+        "tests/BUILD",
+        "sh_binary(name = 'with-prefix-cpu', srcs=['sh.sh'], tags=['cpu:123', 'wrong-tag'])");
+
+    Rule withCpuPrefix = (Rule) getTarget("//tests:with-prefix-cpu");
+
+    Map<String, String> execInfo = TargetUtils.getExecutionInfo(withCpuPrefix);
+    assertThat(execInfo).containsExactly("cpu:123", "");
+  }
+
+  @Test
+  public void testExecutionInfo_withLocalTag() throws Exception {
+    scratch.file(
+        "tests/BUILD",
+        "sh_binary(name = 'with-local-tag', srcs=['sh.sh'], tags=['local', 'some-tag'])");
+
+    Rule withLocal = (Rule) getTarget("//tests:with-local-tag");
+
+    Map<String, String> execInfo = TargetUtils.getExecutionInfo(withLocal);
+    assertThat(execInfo).containsExactly("local", "");
+  }
+
+  @Test
+  public void testFilteredExecutionInfo_FromUncheckedExecRequirements() throws Exception {
+    scratch.file("tests/BUILD", "sh_binary(name = 'no-tag', srcs=['sh.sh'])");
+
+    Rule noTag = (Rule) getTarget("//tests:no-tag");
+
+    Map<String, String> execInfo =
+        TargetUtils.getFilteredExecutionInfo(
+            SkylarkDict.of(null, "supports-worker", "1"), noTag, /* allowTagsPropagation */ true);
+    assertThat(execInfo).containsExactly("supports-worker", "1");
+
+    execInfo =
+        TargetUtils.getFilteredExecutionInfo(
+            SkylarkDict.of(null, "some-custom-tag", "1", "no-cache", "1"),
+            noTag,
+            /* allowTagsPropagation */ true);
+    assertThat(execInfo).containsExactly("no-cache", "1");
+  }
+
+  @Test
+  public void testFilteredExecutionInfo() throws Exception {
+    scratch.file(
+        "tests/BUILD",
+        "sh_binary(name = 'tag1', srcs=['sh.sh'], tags=['supports-workers', 'no-cache'])");
+    Rule tag1 = (Rule) getTarget("//tests:tag1");
+    SkylarkDict<String, String> executionRequirementsUnchecked =
+        SkylarkDict.of(null, "no-remote", "1");
+
+    Map<String, String> execInfo =
+        TargetUtils.getFilteredExecutionInfo(
+            executionRequirementsUnchecked, tag1, /* allowTagsPropagation */ true);
+
+    assertThat(execInfo).containsExactly("no-cache", "", "supports-workers", "", "no-remote", "1");
+  }
+
+  @Test
+  public void testFilteredExecutionInfo_withDuplicateTags() throws Exception {
+    scratch.file(
+        "tests/BUILD",
+        "sh_binary(name = 'tag1', srcs=['sh.sh'], tags=['supports-workers', 'no-cache'])");
+    Rule tag1 = (Rule) getTarget("//tests:tag1");
+    SkylarkDict<String, String> executionRequirementsUnchecked =
+        SkylarkDict.of(null, "no-cache", "1");
+
+    Map<String, String> execInfo =
+        TargetUtils.getFilteredExecutionInfo(
+            executionRequirementsUnchecked, tag1, /* allowTagsPropagation */ true);
+
+    assertThat(execInfo).containsExactly("no-cache", "1", "supports-workers", "");
+  }
+
+  @Test
+  public void testFilteredExecutionInfo_WithNullUncheckedExecRequirements() throws Exception {
+    scratch.file(
+        "tests/BUILD",
+        "sh_binary(name = 'tag1', srcs=['sh.sh'], tags=['supports-workers', 'no-cache'])");
+    Rule tag1 = (Rule) getTarget("//tests:tag1");
+
+    Map<String, String> execInfo =
+        TargetUtils.getFilteredExecutionInfo(null, tag1, /* allowTagsPropagation */ true);
+    assertThat(execInfo).containsExactly("no-cache", "", "supports-workers", "");
+
+    execInfo =
+        TargetUtils.getFilteredExecutionInfo(Runtime.NONE, tag1, /* allowTagsPropagation */ true);
+    assertThat(execInfo).containsExactly("no-cache", "", "supports-workers", "");
+  }
+
+  @Test
+  public void testFilteredExecutionInfoWhenIncompatibleFlagDisabled() throws Exception {
+    // when --incompatible_allow_tags_propagation=false
+    scratch.file(
+        "tests/BUILD",
+        "sh_binary(name = 'tag1', srcs=['sh.sh'], tags=['supports-workers', 'no-cache'])");
+    Rule tag1 = (Rule) getTarget("//tests:tag1");
+    SkylarkDict<String, String> executionRequirementsUnchecked =
+        SkylarkDict.of(null, "no-remote", "1");
+
+    Map<String, String> execInfo =
+        TargetUtils.getFilteredExecutionInfo(
+            executionRequirementsUnchecked, tag1, /* allowTagsPropagation */ false);
+
+    assertThat(execInfo).containsExactly("no-remote", "1");
+  }
 }
diff --git a/src/test/shell/bazel/BUILD b/src/test/shell/bazel/BUILD
index 0d19873..b918b54 100644
--- a/src/test/shell/bazel/BUILD
+++ b/src/test/shell/bazel/BUILD
@@ -711,6 +711,14 @@
 )
 
 sh_test(
+    name = "tags_propagation_skylark_test",
+    size = "large",
+    srcs = ["tags_propagation_skylark_test.sh"],
+    data = [":test-deps"],
+    tags = ["no_windows"],
+)
+
+sh_test(
     name = "disk_cache_test",
     size = "small",
     srcs = ["disk_cache_test.sh"],
diff --git a/src/test/shell/bazel/tags_propagation_skylark_test.sh b/src/test/shell/bazel/tags_propagation_skylark_test.sh
new file mode 100755
index 0000000..8ae1568
--- /dev/null
+++ b/src/test/shell/bazel/tags_propagation_skylark_test.sh
@@ -0,0 +1,175 @@
+#!/bin/bash
+#
+# Copyright 2019 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.
+
+# Tests target's tags propagation with rules defined in Skylark.
+# Tests for https://github.com/bazelbuild/bazel/issues/7766
+
+# Load the test setup defined in the parent directory
+CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+source "${CURRENT_DIR}/../integration_test_setup.sh" \
+  || { echo "integration_test_setup.sh not found!" >&2; exit 1; }
+
+# Test a basic skylark ctx.actions.run_shell rule which has tags, that should be propagated
+function test_tags_propagated_to_run_shell() {
+  mkdir -p test
+  cat << EOF >> test/BUILD
+load(":skylark.bzl", "test_rule")
+
+test_rule(
+    name = "test",
+    out = "output.txt",
+    tags = ["no-cache", "no-remote", "local"]
+)
+EOF
+
+  cat << 'EOF' >> test/skylark.bzl
+def _test_impl(ctx):
+  ctx.actions.run_shell(outputs = [ctx.outputs.out],
+                        command = ["touch", ctx.outputs.out.path])
+  files_to_build = depset([ctx.outputs.out])
+  return DefaultInfo(
+      files = files_to_build,
+  )
+
+test_rule = rule(
+    implementation=_test_impl,
+    attrs = {
+        "out": attr.output(mandatory = True),
+    },
+)
+EOF
+
+  bazel aquery --incompatible_allow_tags_propagation '//test:test' > output1 2> $TEST_log \
+      || fail "should have generated output successfully"
+
+  assert_contains "ExecutionInfo: {local: '', no-cache: '', no-remote: ''}" output1
+}
+
+# Test a basic skylark ctx.actions.run rule which has tags, that should be propagated
+function test_tags_propagated_to_run() {
+  mkdir -p test
+  cat << EOF >> test/BUILD
+load(":skylark.bzl", "test_rule")
+
+test_rule(
+    name = "test",
+    out = "output.txt",
+    tags = ["no-cache", "no-remote", "no-sandbox", "requires-network", "local"]
+)
+EOF
+
+  cat << 'EOF' >> test/skylark.bzl
+def _test_impl(ctx):
+  ctx.actions.run(
+      outputs = [ctx.outputs.out],
+      executable = 'dummy')
+  files_to_build = depset([ctx.outputs.out])
+  return DefaultInfo(
+      files = files_to_build,
+  )
+
+test_rule = rule(
+    implementation=_test_impl,
+    attrs = {
+        "out": attr.output(mandatory = True),
+    },
+)
+EOF
+
+  bazel aquery --incompatible_allow_tags_propagation '//test:test' > output1 2> $TEST_log \
+      || fail "should have generated output successfully"
+
+  assert_contains "ExecutionInfo: {local: '', no-cache: '', no-remote: '', no-sandbox: '', requires-network: ''}" output1
+}
+
+# Test a basic skylark ctx.actions.run rule which has tags, that should be propagated,
+# when the rule also has execution_info
+function test_tags_propagated_to_run_with_exec_info_in_rule() {
+  mkdir -p test
+  cat << EOF >> test/BUILD
+load(":skylark.bzl", "test_rule")
+
+test_rule(
+    name = "test",
+    out = "output.txt",
+    tags = ["no-cache", "no-remote", "custom-tag-1", "requires-network", "local"]
+)
+EOF
+
+  cat << 'EOF' >> test/skylark.bzl
+def _test_impl(ctx):
+  ctx.actions.run(
+      outputs = [ctx.outputs.out],
+      executable = 'dummy',
+      execution_requirements = {"requires-x": "", "custom-tag-whatever": "", "no-cache": "1"})
+  files_to_build = depset([ctx.outputs.out])
+  return DefaultInfo(
+      files = files_to_build,
+  )
+
+test_rule = rule(
+    implementation=_test_impl,
+    attrs = {
+        "out": attr.output(mandatory = True),
+    },
+)
+EOF
+
+  bazel aquery --incompatible_allow_tags_propagation '//test:test' > output1 2> $TEST_log \
+      || fail "should have generated output successfully"
+
+  assert_contains "ExecutionInfo: {local: '', no-cache: 1, no-remote: '', requires-network: '', requires-x: ''}" output1
+}
+
+# Test a basic skylark ctx.actions.run rule which has tags, that should not be propagated
+# as --incompatible_allow_tags_propagation flag set to false
+function test_tags_not_propagated_to_run_when_incompatible_flag_off() {
+  mkdir -p test
+  cat << EOF >> test/BUILD
+load(":skylark.bzl", "test_rule")
+
+test_rule(
+    name = "test",
+    out = "output.txt",
+    tags = ["no-cache", "no-remote", "no-sandbox", "requires-network", "local"]
+)
+EOF
+
+  cat << 'EOF' >> test/skylark.bzl
+def _test_impl(ctx):
+  ctx.actions.run(
+      outputs = [ctx.outputs.out],
+      executable = 'dummy')
+  files_to_build = depset([ctx.outputs.out])
+  return DefaultInfo(
+      files = files_to_build,
+  )
+
+test_rule = rule(
+    implementation=_test_impl,
+    attrs = {
+        "out": attr.output(mandatory = True),
+    },
+)
+EOF
+
+  bazel aquery --incompatible_allow_tags_propagation=false '//test:test' > output1 2> $TEST_log \
+      || fail "should have generated output successfully"
+
+  assert_not_contains "ExecutionInfo: {}" output1
+}
+
+run_suite "tags propagation: skylark rule tests"