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/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"