Skip to content

Commit f4596fb

Browse files
authored
fix: allow disabling exec_tools toolchain from looking up an interpreter (bazel-contrib#2194)
This allow the exec_interpreter attribute to propagate None even though it's a label with a default value. Such attributes can't _directly_ be set to None because None means to use the default. To work around that, a sentinel target is used, which allows breaking the dependency on the default and triggering the rule to use None instead. A null exec interpreter is necessary in environments that don't provide a separate Python interpreter.
1 parent 30bd94d commit f4596fb

File tree

6 files changed

+117
-4
lines changed

6 files changed

+117
-4
lines changed

python/private/BUILD.bazel

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ load("//python:py_binary.bzl", "py_binary")
1818
load("//python:py_library.bzl", "py_library")
1919
load("//python:versions.bzl", "print_toolchains_checksums")
2020
load(":py_exec_tools_toolchain.bzl", "current_interpreter_executable")
21+
load(":sentinel.bzl", "sentinel")
2122
load(":stamp.bzl", "stamp_build_setting")
2223

2324
package(
@@ -213,6 +214,7 @@ bzl_library(
213214
srcs = ["py_exec_tools_toolchain.bzl"],
214215
deps = [
215216
":py_exec_tools_info_bzl",
217+
":sentinel_bzl",
216218
":toolchain_types_bzl",
217219
"//python/private/common:providers_bzl",
218220
"@bazel_skylib//lib:paths",
@@ -294,6 +296,11 @@ bzl_library(
294296
srcs = ["repo_utils.bzl"],
295297
)
296298

299+
bzl_library(
300+
name = "sentinel_bzl",
301+
srcs = ["sentinel.bzl"],
302+
)
303+
297304
bzl_library(
298305
name = "stamp_bzl",
299306
srcs = ["stamp.bzl"],
@@ -469,3 +476,7 @@ current_interpreter_executable(
469476
# py_exec_tools_toolchain.
470477
visibility = ["//visibility:public"],
471478
)
479+
480+
sentinel(
481+
name = "sentinel",
482+
)

python/private/py_exec_tools_info.bzl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ PyExecToolsInfo = provider(
1717
doc = "Build tools used as part of building Python programs.",
1818
fields = {
1919
"exec_interpreter": """
20-
Optional Target; an interpreter valid for running in the exec configuration.
20+
:type: Target | None
21+
22+
If available, an interpreter valid for running in the exec configuration.
2123
When running it in an action, use `DefaultInfo.files_to_run` to ensure all its
2224
files are appropriately available. An exec interpreter may not be available,
2325
e.g. if all the exec tools are prebuilt binaries.
@@ -33,7 +35,9 @@ the proper target constraints are being applied when obtaining this from
3335
the toolchain.
3436
""",
3537
"precompiler": """
36-
Optional Target. The tool to use for generating pyc files. If not available,
38+
:type: Target | None
39+
40+
If available, the tool to use for generating pyc files. If not available,
3741
precompiling will not be available.
3842
3943
Must provide one of the following:

python/private/py_exec_tools_toolchain.bzl

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
load("@bazel_skylib//lib:paths.bzl", "paths")
1818
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
19+
load("//python/private:sentinel.bzl", "SentinelInfo")
1920
load("//python/private:toolchain_types.bzl", "TARGET_TOOLCHAIN_TYPE")
2021
load(":py_exec_tools_info.bzl", "PyExecToolsInfo")
2122

@@ -24,9 +25,13 @@ def _py_exec_tools_toolchain_impl(ctx):
2425
if ctx.attr._visible_for_testing[BuildSettingInfo].value:
2526
extra_kwargs["toolchain_label"] = ctx.label
2627

28+
exec_interpreter = ctx.attr.exec_interpreter
29+
if SentinelInfo in ctx.attr.exec_interpreter:
30+
exec_interpreter = None
31+
2732
return [platform_common.ToolchainInfo(
2833
exec_tools = PyExecToolsInfo(
29-
exec_interpreter = ctx.attr.exec_interpreter,
34+
exec_interpreter = exec_interpreter,
3035
precompiler = ctx.attr.precompiler,
3136
),
3237
**extra_kwargs
@@ -38,7 +43,11 @@ py_exec_tools_toolchain = rule(
3843
"exec_interpreter": attr.label(
3944
default = "//python/private:current_interpreter_executable",
4045
cfg = "exec",
41-
doc = "See PyexecToolsInfo.exec_interpreter.",
46+
doc = """
47+
The interpreter to use in the exec config. To disable, specify the
48+
special target `//python/private:sentinel`. See PyExecToolsInfo.exec_interpreter
49+
for further docs.
50+
""",
4251
),
4352
"precompiler": attr.label(
4453
allow_files = True,

python/private/sentinel.bzl

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Copyright 2024 The Bazel Authors. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""A rule to define a target to act as a singleton for label attributes.
16+
17+
Label attributes with defaults cannot accept None, otherwise they fall
18+
back to using the default. A sentinel allows detecting an intended None value.
19+
"""
20+
21+
SentinelInfo = provider(
22+
doc = "Indicates this was the sentinel target.",
23+
fields = [],
24+
)
25+
26+
def _sentinel_impl(ctx):
27+
_ = ctx # @unused
28+
return [SentinelInfo()]
29+
30+
sentinel = rule(implementation = _sentinel_impl)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Copyright 2024 The Bazel Authors. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
load(":py_exec_tools_toolchain_tests.bzl", "py_exec_tools_toolchain_test_suite")
16+
17+
py_exec_tools_toolchain_test_suite(
18+
name = "py_exec_tools_toolchain_tests",
19+
)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Copyright 2024 The Bazel Authors. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
"""Starlark tests for py_exec_tools_toolchain rule."""
15+
16+
load("@rules_testing//lib:analysis_test.bzl", "analysis_test")
17+
load("@rules_testing//lib:test_suite.bzl", "test_suite")
18+
load("//python/private:py_exec_tools_toolchain.bzl", "py_exec_tools_toolchain") # buildifier: disable=bzl-visibility
19+
20+
_tests = []
21+
22+
def _test_disable_exec_interpreter(name):
23+
py_exec_tools_toolchain(
24+
name = name + "_subject",
25+
exec_interpreter = "//python/private:sentinel",
26+
)
27+
analysis_test(
28+
name = name,
29+
target = name + "_subject",
30+
impl = _test_disable_exec_interpreter_impl,
31+
)
32+
33+
def _test_disable_exec_interpreter_impl(env, target):
34+
exec_tools = target[platform_common.ToolchainInfo].exec_tools
35+
env.expect.that_bool(exec_tools.exec_interpreter == None).equals(True)
36+
37+
_tests.append(_test_disable_exec_interpreter)
38+
39+
def py_exec_tools_toolchain_test_suite(name):
40+
test_suite(name = name, tests = _tests)

0 commit comments

Comments
 (0)