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