Skip to content

Commit a63c208

Browse files
thesayynMatt Mackay
andauthored
feat: support python_version attribute (#347)
This PR adds support python_version attribute the same way rules_python does, with the exception that we generate no `defs.bzl` file that users can just replace their with. Instead we encourage them to create their own macros that sets the desired version on py_binary/py_test rule. ### Changes are visible to end-users: yes - Searched for relevant documentation and updated as needed: yes - Breaking change (forces users to change their own code or config): no - Suggested release notes appear below: yes ### Test plan - New test cases added --------- Co-authored-by: Matt Mackay <matt@aspect.dev>
1 parent 0c59d31 commit a63c208

File tree

8 files changed

+130
-2
lines changed

8 files changed

+130
-2
lines changed

MODULE.bazel

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ bazel_dep(name = "bazel_skylib", version = "1.4.2")
1313
bazel_dep(name = "rules_python", version = "0.29.0")
1414
bazel_dep(name = "platforms", version = "0.0.7")
1515

16+
17+
# Custom python version for testing only
18+
python = use_extension("@rules_python//python/extensions:python.bzl", "python")
19+
python.toolchain(
20+
is_default = False,
21+
python_version = "3.8.12",
22+
)
23+
1624
tools = use_extension("//py:extensions.bzl", "py_tools")
1725
tools.rules_py_tools()
1826
use_repo(tools, "rules_py_tools")

WORKSPACE

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@ register_toolchains("//:container_py_toolchain")
2424

2525
load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_toolchains")
2626

27+
python_register_toolchains(
28+
name = "python_toolchain_3_8",
29+
python_version = "3.8.12",
30+
# Setting `set_python_version_constraint` will set special constraints on the registered toolchain.
31+
# This means that this toolchain registration will only be selected for `py_binary` / `py_test` targets
32+
# that have the `python_version = "3.8.12"` attribute set. Targets that have no `python_attribute` will use
33+
# the default toolchain resolved which can be seen below.
34+
set_python_version_constraint = True,
35+
)
36+
37+
# It is important to register the default toolchain at last as it will be selected for any
38+
# py_test/py_binary target even if it has python_version attribute set.
2739
python_register_toolchains(
2840
name = "python_toolchain",
2941
python_version = "3.9",

docs/rules.md

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/multi_version/BUILD.bazel

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
load("@aspect_rules_py//py:defs.bzl", "py_binary", "py_pytest_main", "py_test")
2+
3+
py_binary(
4+
name = "multi_version",
5+
srcs = ["say.py"],
6+
deps = [
7+
"@pypi_cowsay//:pkg",
8+
],
9+
python_version = "3.8.12"
10+
)
11+
py_pytest_main(
12+
name = "__test__",
13+
deps = ["@pypi_pytest//:pkg"],
14+
)
15+
16+
py_test(
17+
name = "py_version_test",
18+
srcs = [
19+
"py_version_test.py",
20+
":__test__",
21+
],
22+
main = ":__test__.py",
23+
deps = [
24+
":__test__",
25+
],
26+
python_version = "3.8.12"
27+
)
28+
py_test(
29+
name = "py_version_default_test",
30+
srcs = [
31+
"py_version_default_test.py",
32+
":__test__",
33+
],
34+
main = ":__test__.py",
35+
deps = [
36+
":__test__",
37+
],
38+
)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import sys
2+
3+
def test_default_py_version():
4+
assert sys.version_info.major == 3, "sys.version_info.major == 3"
5+
assert sys.version_info.minor == 9, "sys.version_info.minor == 9"
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import sys
2+
3+
def test_specific_py_version():
4+
assert sys.version_info.major == 3, "sys.version_info.major == 3"
5+
assert sys.version_info.minor == 8, "sys.version_info.minor == 8"
6+
assert sys.version_info.micro == 12, "sys.version_info.micro == 12"

examples/multi_version/say.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import cowsay
2+
import sys
3+
4+
cowsay.cow('hello py_binary, %s!' % sys.version)

py/private/py_binary.bzl

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,41 @@ _attrs = dict({
139139
allow_single_file = True,
140140
mandatory = True,
141141
),
142+
"python_version": attr.string(
143+
doc = """Whether to build this target and its transitive deps for a specific python version.
144+
145+
Note that setting this attribute alone will not be enough as the python toolchain for the desired version
146+
also needs to be registered in the WORKSPACE or MODULE.bazel file.
147+
148+
When using WORKSPACE, this may look like this,
149+
150+
```
151+
load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_toolchains")
152+
153+
python_register_toolchains(
154+
name = "python_toolchain_3_8",
155+
python_version = "3.8.12",
156+
# setting set_python_version_constraint makes it so that only matches py_* rule
157+
# which has this exact version set in the `python_version` attribute.
158+
set_python_version_constraint = True,
159+
)
160+
161+
# It's important to register the default toolchain last it will match any py_* target.
162+
python_register_toolchains(
163+
name = "python_toolchain",
164+
python_version = "3.9",
165+
)
166+
```
167+
168+
Configuring for MODULE.bazel may look like this:
169+
170+
```
171+
python = use_extension("@rules_python//python/extensions:python.bzl", "python")
172+
python.toolchain(python_version = "3.8.12", is_default = False)
173+
python.toolchain(python_version = "3.9", is_default = True)
174+
```
175+
"""
176+
),
142177
"_run_tmpl": attr.label(
143178
allow_single_file = True,
144179
default = "//py/private:run.tmpl.sh",
@@ -150,17 +185,33 @@ _attrs = dict({
150185
"_interpreter_version_flag": attr.label(
151186
default = "//py:interpreter_version",
152187
),
188+
# Required for py_version attribute
189+
"_allowlist_function_transition": attr.label(
190+
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
191+
),
153192
})
154193

155194
_attrs.update(**_py_library.attrs)
156195

196+
def _python_version_transition_impl(_, attr):
197+
if not attr.python_version:
198+
return {}
199+
return {"@rules_python//python/config_settings:python_version": str(attr.python_version)}
200+
201+
_python_version_transition = transition(
202+
implementation = _python_version_transition_impl,
203+
inputs = [],
204+
outputs = ["@rules_python//python/config_settings:python_version"],
205+
)
206+
157207
py_base = struct(
158208
implementation = _py_binary_rule_impl,
159209
attrs = _attrs,
160210
toolchains = [
161211
PY_TOOLCHAIN,
162212
VENV_TOOLCHAIN,
163213
],
214+
cfg = _python_version_transition
164215
)
165216

166217
py_binary = rule(
@@ -169,6 +220,7 @@ py_binary = rule(
169220
attrs = py_base.attrs,
170221
toolchains = py_base.toolchains,
171222
executable = True,
223+
cfg = py_base.cfg
172224
)
173225

174226
py_test = rule(
@@ -177,4 +229,5 @@ py_test = rule(
177229
attrs = py_base.attrs,
178230
toolchains = py_base.toolchains,
179231
test = True,
232+
cfg = py_base.cfg
180233
)

0 commit comments

Comments
 (0)