Enable rule transition to inspect configurable attributes' value

Fixes https://github.com/bazelbuild/bazel/issues/15157

PiperOrigin-RevId: 558262406
Change-Id: I514c95c0470a2d171a5b276f25b65a4adddf17e9
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/StarlarkRuleTransitionProviderTest.java b/src/test/java/com/google/devtools/build/lib/analysis/StarlarkRuleTransitionProviderTest.java
index 3267329..ba0bc22 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/StarlarkRuleTransitionProviderTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/StarlarkRuleTransitionProviderTest.java
@@ -292,7 +292,6 @@
     assertContainsEvent(
         "Rule transition only allowed to return a single transitioned configuration.");
   }
-
   @Test
   public void testCanDoBadStuffWithParameterizedTransitionsAndSelects() throws Exception {
     writeAllowlistFile();
@@ -339,8 +338,9 @@
     getConfiguredTarget("//test");
     assertContainsEvent(
         "No attribute 'my_configurable_attr'. "
-            + "Either this attribute does not exist for this rule or is set by a select. "
-            + "Starlark rule transitions currently cannot read attributes behind selects.");
+            + "Either this attribute does not exist for this rule or the attribute "
+            + "was not resolved because it is set by a select that reads flags the transition "
+            + "may set.");
   }
 
   @Test
@@ -1557,7 +1557,7 @@
         testTarget
             .getRuleClassObject()
             .getTransitionFactory()
-            .create(RuleTransitionData.create(testTarget));
+            .create(RuleTransitionData.create(testTarget, null, ""));
     RequiredConfigFragmentsProvider.Builder requiredFragments =
         RequiredConfigFragmentsProvider.builder();
     ruleTransition.addRequiredFragments(
diff --git a/src/test/java/com/google/devtools/build/lib/rules/config/ConfigFeatureFlagTransitionFactoryTest.java b/src/test/java/com/google/devtools/build/lib/rules/config/ConfigFeatureFlagTransitionFactoryTest.java
index ceb4711..2634388 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/config/ConfigFeatureFlagTransitionFactoryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/config/ConfigFeatureFlagTransitionFactoryTest.java
@@ -62,7 +62,7 @@
     Rule rule = scratchRule("a", "empty", "feature_flag_setter(name = 'empty', flag_values = {})");
     PatchTransition transition =
         new ConfigFeatureFlagTransitionFactory("flag_values")
-            .create(RuleTransitionData.create(rule));
+            .create(RuleTransitionData.create(rule, null, ""));
 
     BuildOptions original = getOptionsWithoutFlagFragment();
     BuildOptions converted =
@@ -88,7 +88,7 @@
             "    default_value = 'a')");
     PatchTransition transition =
         new ConfigFeatureFlagTransitionFactory("flag_values")
-            .create(RuleTransitionData.create(rule));
+            .create(RuleTransitionData.create(rule, null, ""));
 
     BuildOptions original = getOptionsWithoutFlagFragment();
     BuildOptions converted =
@@ -104,7 +104,7 @@
     Rule rule = scratchRule("a", "empty", "feature_flag_setter(name = 'empty', flag_values = {})");
     PatchTransition transition =
         new ConfigFeatureFlagTransitionFactory("flag_values")
-            .create(RuleTransitionData.create(rule));
+            .create(RuleTransitionData.create(rule, null, ""));
     Map<Label, String> originalFlagMap = ImmutableMap.of(Label.parseCanonical("//a:flag"), "value");
 
     BuildOptions original = getOptionsWithFlagFragment(originalFlagMap);
@@ -133,7 +133,7 @@
             "    default_value = 'a')");
     PatchTransition transition =
         new ConfigFeatureFlagTransitionFactory("flag_values")
-            .create(RuleTransitionData.create(rule));
+            .create(RuleTransitionData.create(rule, null, ""));
     Map<Label, String> originalFlagMap = ImmutableMap.of(Label.parseCanonical("//a:old"), "value");
     Map<Label, String> expectedFlagMap = ImmutableMap.of(Label.parseCanonical("//a:flag"), "a");
 
@@ -201,34 +201,34 @@
     new EqualsTester()
         .addEqualityGroup(
             // transition for non flags target
-            factory.create(RuleTransitionData.create(nonflag)), NoTransition.INSTANCE)
+            factory.create(RuleTransitionData.create(nonflag, null, "")), NoTransition.INSTANCE)
         .addEqualityGroup(
             // transition with empty map
-            factory.create(RuleTransitionData.create(empty)),
+            factory.create(RuleTransitionData.create(empty, null, "")),
             // transition produced by same factory on same rule
-            factory.create(RuleTransitionData.create(empty)),
+            factory.create(RuleTransitionData.create(empty, null, "")),
             // transition produced by similar factory on same rule
-            factory2.create(RuleTransitionData.create(empty)),
+            factory2.create(RuleTransitionData.create(empty, null, "")),
             // transition produced by same factory on similar rule
-            factory.create(RuleTransitionData.create(empty2)),
+            factory.create(RuleTransitionData.create(empty2, null, "")),
             // transition produced by similar factory on similar rule
-            factory2.create(RuleTransitionData.create(empty2)))
+            factory2.create(RuleTransitionData.create(empty2, null, "")))
         .addEqualityGroup(
             // transition with flag -> a
-            factory.create(RuleTransitionData.create(flagSetterA)),
+            factory.create(RuleTransitionData.create(flagSetterA, null, "")),
             // same map, different rule
-            factory.create(RuleTransitionData.create(flagSetterA2)),
+            factory.create(RuleTransitionData.create(flagSetterA2, null, "")),
             // same map, different factory
-            factory2.create(RuleTransitionData.create(flagSetterA)))
+            factory2.create(RuleTransitionData.create(flagSetterA, null, "")))
         .addEqualityGroup(
             // transition with flag set to different value
-            factory.create(RuleTransitionData.create(flagSetterB)))
+            factory.create(RuleTransitionData.create(flagSetterB, null, "")))
         .addEqualityGroup(
             // transition with different flag set to same value
-            factory.create(RuleTransitionData.create(flag2Setter)))
+            factory.create(RuleTransitionData.create(flag2Setter, null, "")))
         .addEqualityGroup(
             // transition with more flags set
-            factory.create(RuleTransitionData.create(bothSetter)))
+            factory.create(RuleTransitionData.create(bothSetter, null, "")))
         .testEquals();
   }
 
diff --git a/src/test/java/com/google/devtools/build/lib/rules/config/FeatureFlagManualTrimmingTest.java b/src/test/java/com/google/devtools/build/lib/rules/config/FeatureFlagManualTrimmingTest.java
index 4173c5c..5531aa0 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/config/FeatureFlagManualTrimmingTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/config/FeatureFlagManualTrimmingTest.java
@@ -839,7 +839,7 @@
         getConfiguration(getConfiguredTarget("//test:toplevel_target")).getOptions();
     PatchTransition transition =
         new ConfigFeatureFlagTaggedTrimmingTransitionFactory(BaseRuleClasses.TAGGED_TRIMMING_ATTR)
-            .create(RuleTransitionData.create((Rule) getTarget("//test:dep")));
+            .create(RuleTransitionData.create((Rule) getTarget("//test:dep"), null, ""));
     BuildOptions depOptions =
         transition.patch(
             new BuildOptionsView(topLevelOptions, transition.requiresOptionFragments()),
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/SkyframeErrorProcessorTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/SkyframeErrorProcessorTest.java
index 7ea54c7..aa69a10 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/SkyframeErrorProcessorTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/SkyframeErrorProcessorTest.java
@@ -49,10 +49,11 @@
         ConfiguredTargetKey.builder()
             .setLabel(Label.parseCanonicalUnchecked("//analysis_err"))
             .build();
+    TargetAndConfiguration mockTargetAndConfiguration =
+        new TargetAndConfiguration(mock(Target.class), /* configuration= */ null);
     ConfiguredValueCreationException analysisException =
         new ConfiguredValueCreationException(
-            new TargetAndConfiguration(mock(Target.class), /* configuration= */ null),
-            "analysis exception");
+            mockTargetAndConfiguration.getTarget(), "analysis exception");
     ErrorInfo analysisErrorInfo =
         ErrorInfo.fromException(
             new ReifiedSkyFunctionException(
diff --git a/src/test/shell/integration/rule_transition_test.sh b/src/test/shell/integration/rule_transition_test.sh
new file mode 100755
index 0000000..6025381
--- /dev/null
+++ b/src/test/shell/integration/rule_transition_test.sh
@@ -0,0 +1,278 @@
+#!/bin/bash
+#
+# Copyright 2023 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.
+#
+# Test rule transition can inspect configurable attribute.
+
+# --- begin runfiles.bash initialization ---
+# Copy-pasted from Bazel's Bash runfiles library (tools/bash/runfiles/runfiles.bash).
+if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
+  if [[ -f "$0.runfiles_manifest" ]]; then
+    export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest"
+  elif [[ -f "$0.runfiles/MANIFEST" ]]; then
+    export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST"
+  elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
+    export RUNFILES_DIR="$0.runfiles"
+  fi
+fi
+if [[ -f "${RUNFILES_DIR:-/dev/null}/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
+  source "${RUNFILES_DIR}/bazel_tools/tools/bash/runfiles/runfiles.bash"
+elif [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
+  source "$(grep -m1 "^bazel_tools/tools/bash/runfiles/runfiles.bash " \
+            "$RUNFILES_MANIFEST_FILE" | cut -d ' ' -f 2-)"
+else
+  echo >&2 "ERROR: cannot find @bazel_tools//tools/bash/runfiles:runfiles.bash"
+  exit 1
+fi
+# --- end runfiles.bash initialization ---
+
+source "$(rlocation "io_bazel/src/test/shell/integration_test_setup.sh")" \
+  || { echo "integration_test_setup.sh not found!" >&2; exit 1; }
+
+function set_up() {
+  create_new_workspace
+}
+
+function create_transitions() {
+  local pkg="${1}"
+  mkdir -p "${pkg}"
+  cat > "${pkg}/def.bzl" <<EOF
+
+load("//third_party/bazel_skylib/rules:common_settings.bzl", "BuildSettingInfo")
+
+example_package = "${pkg}"
+
+def _transition_impl(settings, attr):
+    if getattr(attr, "apply_transition") and settings["//%s:transition_input_flag" % example_package]:
+        return {"//%s:transition_output_flag" % example_package: True}
+    return {"//%s:transition_output_flag" % example_package: False}
+
+example_transition = transition(
+    implementation = _transition_impl,
+    inputs = ["//%s:transition_input_flag" % example_package],
+    outputs = ["//%s:transition_output_flag" % example_package],
+)
+
+def _rule_impl(ctx):
+    print("Flag value for %s: %s" % (
+        ctx.label.name,
+        ctx.attr._transition_output_flag[BuildSettingInfo].value,
+    ))
+
+transition_attached = rule(
+    implementation = _rule_impl,
+    cfg = example_transition,
+    attrs = {
+        "apply_transition": attr.bool(default = False),
+        "deps": attr.label_list(),
+        "_transition_output_flag": attr.label(default = "//%s:transition_output_flag" % example_package),
+        "_allowlist_function_transition": attr.label(
+            default = "//tools/allowlists/function_transition_allowlist:function_transition_allowlist",
+        ),
+    },
+)
+
+transition_not_attached = rule(
+    implementation = _rule_impl,
+    attrs = {
+        "deps": attr.label_list(),
+        "_transition_output_flag": attr.label(default = "//%s:transition_output_flag" % example_package),
+    },
+)
+EOF
+}
+
+function create_rules_with_incoming_transition_and_selects() {
+  local pkg="${1}"
+  mkdir -p "${pkg}"
+  cat > "${pkg}/BUILD" <<EOF
+load(
+    "//${pkg}:def.bzl",
+    "transition_attached",
+    "transition_not_attached",
+)
+load("//third_party/bazel_skylib/rules:common_settings.bzl", "bool_flag")
+
+bool_flag(
+    name = "transition_input_flag",
+    build_setting_default = True,
+)
+
+bool_flag(
+    name = "transition_output_flag",
+    build_setting_default = False,
+)
+
+config_setting(
+    name = "select_setting",
+    flag_values = {":transition_input_flag": "True"},
+)
+
+# All should print "False" if
+# "--no//${pkg}:transition_input_flag" is
+# specified on the command line
+
+# bazel build :top_level will print the results for all of the targets below
+
+transition_attached(
+    name = "top_level",
+    apply_transition = select({
+        ":select_setting": True,
+        "//conditions:default": False,
+    }),
+    deps = [
+        ":transition_attached_dep",
+        ":transition_not_attached_dep",
+    ],
+)
+
+# Should print "False"
+transition_attached(
+    apply_transition = False,
+    name = "transition_attached_dep",
+    deps = [
+        ":transition_not_attached_dep_of_dep",
+    ],
+)
+
+# Should print "True" when building top_level, "False" otherwise
+transition_not_attached(
+    name = "transition_not_attached_dep",
+)
+
+# Should print "False"
+transition_not_attached(
+    name = "transition_not_attached_dep_of_dep",
+)
+EOF
+}
+
+function test_rule_transition_can_inspect_configure_attributes(){
+  local -r pkg="${FUNCNAME[0]}"
+  create_transitions "${pkg}"
+  create_rules_with_incoming_transition_and_selects "${pkg}"
+
+  bazel build "//${pkg}:top_level" &> $TEST_log || fail "Build failed"
+
+  expect_log 'Flag value for transition_not_attached_dep: True'
+  expect_log 'Flag value for transition_not_attached_dep_of_dep: False'
+  expect_log 'Flag value for transition_attached_dep: False'
+  expect_log 'Flag value for top_level: True'
+}
+
+function test_rule_transition_can_inspect_configure_attributes_with_flag(){
+  local -r pkg="${FUNCNAME[0]}"
+
+  create_transitions "${pkg}"
+  create_rules_with_incoming_transition_and_selects "${pkg}"
+
+  bazel build --no//${pkg}:transition_input_flag "//${pkg}:top_level" &> $TEST_log || fail "Build failed"
+
+  expect_log 'Flag value for transition_not_attached_dep: False'
+  expect_log 'Flag value for transition_not_attached_dep_of_dep: False'
+  expect_log 'Flag value for transition_attached_dep: False'
+  expect_log 'Flag value for top_level: False'
+}
+
+function test_rule_transition_can_not_inspect_configure_attribute() {
+  local -r pkg="${FUNCNAME[0]}"
+
+  # create transition definition
+  mkdir -p "${pkg}"
+  cat > "${pkg}/def.bzl" <<EOF
+
+load("//third_party/bazel_skylib/rules:common_settings.bzl", "BuildSettingInfo")
+
+example_package = "${pkg}"
+
+def _transition_impl(settings, attr):
+    if getattr(attr, "apply_transition") and settings["//%s:transition_input_flag" % example_package]:
+        return {"//%s:transition_output_flag" % example_package: True}
+    return {
+      "//%s:transition_output_flag" % example_package: False,
+      "//%s:transition_input_flag" % example_package: False
+    }
+
+example_transition = transition(
+    implementation = _transition_impl,
+    inputs = ["//%s:transition_input_flag" % example_package],
+    outputs = [
+      "//%s:transition_output_flag" % example_package,
+      "//%s:transition_input_flag" % example_package,
+    ],
+)
+
+def _rule_impl(ctx):
+    print("Flag value for %s: %s" % (
+        ctx.label.name,
+        ctx.attr._transition_output_flag[BuildSettingInfo].value,
+    ))
+
+transition_attached = rule(
+    implementation = _rule_impl,
+    cfg = example_transition,
+    attrs = {
+        "apply_transition": attr.bool(default = False),
+        "deps": attr.label_list(),
+        "_transition_output_flag": attr.label(default = "//%s:transition_output_flag" % example_package),
+        "_allowlist_function_transition": attr.label(
+            default = "//tools/allowlists/function_transition_allowlist:function_transition_allowlist",
+        ),
+    },
+)
+EOF
+
+  # create rules with transition attached
+  cat > "${pkg}/BUILD" <<EOF
+load(
+    "//${pkg}:def.bzl",
+    "transition_attached",
+)
+load("//third_party/bazel_skylib/rules:common_settings.bzl", "bool_flag")
+
+bool_flag(
+    name = "transition_input_flag",
+    build_setting_default = True,
+)
+
+bool_flag(
+    name = "transition_output_flag",
+    build_setting_default = False,
+)
+
+config_setting(
+    name = "select_setting",
+    flag_values = {":transition_input_flag": "True"},
+)
+
+# All should print "False" if
+# "--no//${pkg}:transition_input_flag" is
+# specified on the command line
+
+# bazel build :top_level will print the results for all of the targets below
+
+transition_attached(
+    name = "top_level",
+    apply_transition = select({
+        ":select_setting": True,
+        "//conditions:default": False,
+    }),
+)
+EOF
+  bazel build "//${pkg}:top_level" &> $TEST_log && fail "Build did NOT complete successfully"
+  expect_log "No attribute 'apply_transition'. Either this attribute does not exist for this rule or the attribute was not resolved because it is set by a select that reads flags the transition may set."
+}
+
+run_suite "rule transition tests"
diff --git a/src/test/shell/integration/toolchain_test.sh b/src/test/shell/integration/toolchain_test.sh
index 894d950..1a65538 100755
--- a/src/test/shell/integration/toolchain_test.sh
+++ b/src/test/shell/integration/toolchain_test.sh
@@ -1128,12 +1128,12 @@
   bazel build \
     --platforms="//${pkg}/platform:not_a_platform" \
     "//${pkg}/demo:use" &> $TEST_log && fail "Build failure expected"
-  expect_log "While resolving toolchains for target //${pkg}/demo:use: Target //${pkg}/platform:not_a_platform was referenced as a platform, but does not provide PlatformInfo"
+  expect_log "Target //${pkg}/platform:not_a_platform was referenced as a platform, but does not provide PlatformInfo"
 
   bazel build \
     --host_platform="//${pkg}/platform:not_a_platform" \
     "//${pkg}/demo:use" &> $TEST_log && fail "Build failure expected"
-  expect_log "While resolving toolchains for target //${pkg}/demo:use: Target //${pkg}/platform:not_a_platform was referenced as a platform, but does not provide PlatformInfo"
+  expect_log "Target //${pkg}/platform:not_a_platform was referenced as a platform, but does not provide PlatformInfo"
 }
 
 
@@ -1884,10 +1884,11 @@
 )
 EOF
 
+  echo "START DEBUGGING"
   bazel build \
     --platforms="//${pkg}:hello" \
     "//${pkg}:target" &> $TEST_log && fail "Build succeeded unexpectedly"
-  expect_log "While resolving toolchains for target //${pkg}:target: Target //${pkg}:hello was referenced as a platform, but does not provide PlatformInfo"
+  expect_log "Target //${pkg}:hello was referenced as a platform, but does not provide PlatformInfo"
 }