Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 84 additions & 1 deletion docs/howto/build-a-wheel.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,87 @@ It's highly recommended to disable this behavior by setting a flag in your
```
# .bazelrc
build --incompatible_default_to_explicit_init_py=true
```
```

## Setting wheel filename tags from the Python version

To automatically generate the wheel filename and `Requires-Python` metadata for
the active Python version, use make variable syntax in `abi`, `platform`,
`python_requires`, `python_tag`, and `version`:

```starlark
# BUILD.bazel
load("@rules_python//python:packaging.bzl", "py_wheel")

py_wheel(
name = "my_project_wheel",
abi = "$(PY_WHEEL_ABI_TAG)",
distribution = "my-project",
python_requires = ">=$(PY_MAJOR_DOT_MINOR)",
python_tag = "$(PY_WHEEL_PYTHON_TAG)",
version = "0.1.0",
deps = [":my_project_lib"],
)
```

When built with CPython 3.12, this produces
`my_project-0.1.0-cp312-cp312-any.whl` and sets `Requires-Python: >=3.12` in the
wheel `METADATA`. With freethreaded CPython 3.13, `$(PY_WHEEL_ABI_TAG)` expands
to `cp313t`.

### Available Python version variables

The Python toolchain provides the following make variables by default:

| Variable | Description | Example |
| :--- | :--- | :--- |
| `$(PY_WHEEL_PYTHON_TAG)` | Wheel Python tag | `cp312` |
| `$(PY_WHEEL_ABI_TAG)` | Wheel ABI tag | `cp312`, `cp313t` |
| `$(PY_WHEEL_IMPLEMENTATION_TAG)` | Wheel implementation prefix | `cp` |
| `$(PY_MAJOR_DOT_MINOR)` | Dotted `major.minor` version | `3.12` |
| `$(PY_MAJOR_MINOR_NODOT)` | Dotless `majorminor` version | `312` |
| `$(PY_MAJOR)` | Major version | `3` |
| `$(PY_MINOR)` | Minor version | `12` |
| `$(PY_ABI_FLAGS)` | Runtime ABI flags (`sys.abiflags`) | `""`, `"t"` |
| `$(PY_IMPLEMENTATION_NAME)` | Implementation name | `cpython` |

See {rule}`py_runtime_pair` for the full list of variables.

### Adding custom make variables

To define custom make variables (for example, to compute the package version or
platform tag in Starlark), write a rule that returns
`platform_common.TemplateVariableInfo` and pass it in the `toolchains` attribute
of {obj}`py_wheel`:

```starlark
# wheel_vars.bzl
def _wheel_vars_impl(ctx):
return [platform_common.TemplateVariableInfo({
"MY_VERSION": "1.2.3",
})]

wheel_vars = rule(
implementation = _wheel_vars_impl,
)
```

```starlark
# BUILD.bazel
load("@rules_python//python:packaging.bzl", "py_wheel")
load(":wheel_vars.bzl", "wheel_vars")

wheel_vars(
name = "vars",
)

py_wheel(
name = "my_project_wheel",
abi = "$(PY_WHEEL_ABI_TAG)",
distribution = "my-project",
python_tag = "$(PY_WHEEL_PYTHON_TAG)",
toolchains = [":vars"],
version = "$(MY_VERSION)",
deps = [":my_project_lib"],
)
```
3 changes: 3 additions & 0 deletions news/py-wheel-makevars.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
(packaging) {obj}`py_wheel` now expands Python toolchain make variables by
default and in `platform` and `python_requires`. {obj}`py_runtime_pair` and
{obj}`current_py_toolchain` now provide version and wheel tag make variables.
24 changes: 18 additions & 6 deletions python/current_py_toolchain.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,15 @@ def _current_py_toolchain_impl(ctx):
vars["PYTHON2"] = toolchain.py2_runtime.interpreter_path
vars["PYTHON2_ROOTPATH"] = toolchain.py2_runtime.interpreter_path

if getattr(toolchain, "template_vars", None):
template_vars = toolchain.template_vars
else:
template_vars = platform_common.TemplateVariableInfo(vars)

files = depset(direct, transitive = transitive)
return [
toolchain,
platform_common.TemplateVariableInfo(vars),
template_vars,
DefaultInfo(
runfiles = ctx.runfiles(transitive_files = files),
files = files,
Expand All @@ -53,14 +58,21 @@ def _current_py_toolchain_impl(ctx):

current_py_toolchain = rule(
doc = """
This rule exists so that the current python toolchain can be used in the `toolchains` attribute of
other rules, such as genrule. It allows exposing a python toolchain after toolchain resolution has
happened, to a rule which expects a concrete implementation of a toolchain, rather than a
This rule exists so that the current python toolchain can be used in the
`toolchains` attribute of other rules, such as genrule. It allows exposing a
python toolchain after toolchain resolution has happened, to a rule which
expects a concrete implementation of a toolchain, rather than a
toolchain_type which could be resolved to that toolchain.

:::{versionchanged} 1.4.0
From now on, we also expose `$(PYTHON2_ROOTPATH)` and `$(PYTHON3_ROOTPATH)` which are runfiles
locations equivalents of `$(PYTHON2)` and `$(PYTHON3) respectively.
From now on, we also expose `$(PYTHON2_ROOTPATH)` and `$(PYTHON3_ROOTPATH)`
which are runfiles locations equivalents of `$(PYTHON2)` and `$(PYTHON3)`
respectively.
:::

:::{versionchanged} VERSION_NEXT_FEATURE
Exposes make variables from the underlying toolchain's `template_vars`
(see {rule}`py_runtime_pair`).
:::
""",
implementation = _current_py_toolchain_impl,
Expand Down
3 changes: 2 additions & 1 deletion python/packaging.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ def py_wheel(
Wheels are Python distribution format defined in {pep}`PEP 427`.

This macro packages a set of targets into a single wheel.
It wraps the [py_wheel rule](#py_wheel_rule).
It wraps the [py_wheel rule](#py_wheel_rule). See
{doc}`/howto/build-a-wheel` for a usage guide.

Currently only pure-python wheels are supported.

Expand Down
2 changes: 2 additions & 0 deletions python/private/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,7 @@ bzl_library(
":common_labels",
":reexports",
"//python:py_runtime_info",
"//python/private/pypi:python_tag",
"@bazel_skylib//rules:common_settings",
],
)
Expand Down Expand Up @@ -730,6 +731,7 @@ bzl_library(
":py_package",
":rule_builders",
":stamp_impl",
":toolchain_types",
":transition_labels",
":version",
],
Expand Down
90 changes: 81 additions & 9 deletions python/private/py_runtime_pair_rule.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,53 @@

load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
load("//python:py_runtime_info.bzl", "PyRuntimeInfo")
load("//python/private/pypi:python_tag.bzl", "python_tag")
load(":common_labels.bzl", "labels")
load(":reexports.bzl", "BuiltinPyRuntimeInfo")

def _get_template_vars(py3_runtime):
vars = {}
if py3_runtime.interpreter:
vars["PYTHON3"] = py3_runtime.interpreter.path
vars["PYTHON3_ROOTPATH"] = py3_runtime.interpreter.short_path
elif py3_runtime.interpreter_path:
vars["PYTHON3"] = py3_runtime.interpreter_path
vars["PYTHON3_ROOTPATH"] = py3_runtime.interpreter_path

# BuiltinPyRuntimeInfo (used in Bazel 7) lacks rules_python-specific
# fields like abi_flags, implementation_name, and
# interpreter_version_info.
if hasattr(py3_runtime, "abi_flags"):
abi_flags = py3_runtime.abi_flags
vars["PY_ABI_FLAGS"] = abi_flags

impl_name = py3_runtime.implementation_name
impl_tag = None
if impl_name:
impl_tag = python_tag(impl_name)
vars["PY_IMPLEMENTATION_NAME"] = impl_name
vars["PY_WHEEL_IMPLEMENTATION_TAG"] = impl_tag

version_info = py3_runtime.interpreter_version_info
if (
version_info and
version_info.major != None and
version_info.minor != None
):
major = str(version_info.major)
minor = str(version_info.minor)
major_minor_nodot = "{}{}".format(major, minor)
vars["PY_MAJOR"] = major
vars["PY_MINOR"] = minor
vars["PY_MAJOR_DOT_MINOR"] = "{}.{}".format(major, minor)
vars["PY_MAJOR_MINOR_NODOT"] = major_minor_nodot
if impl_tag:
py_tag = "{}{}".format(impl_tag, major_minor_nodot)
vars["PY_WHEEL_PYTHON_TAG"] = py_tag
vars["PY_WHEEL_ABI_TAG"] = "{}{}".format(py_tag, abi_flags)

return platform_common.TemplateVariableInfo(vars)

def _py_runtime_pair_impl(ctx):
if ctx.attr.py2_runtime != None:
py2_runtime = _get_py_runtime_info(ctx.attr.py2_runtime)
Expand All @@ -44,11 +88,17 @@ def _py_runtime_pair_impl(ctx):
if ctx.attr._visible_for_testing[BuildSettingInfo].value:
extra_kwargs["toolchain_label"] = ctx.label

return [platform_common.ToolchainInfo(
py2_runtime = py2_runtime,
py3_runtime = py3_runtime,
**extra_kwargs
)]
template_vars = _get_template_vars(py3_runtime)

return [
platform_common.ToolchainInfo(
py2_runtime = py2_runtime,
py3_runtime = py3_runtime,
template_vars = template_vars,
**extra_kwargs
),
template_vars,
]

def _get_py_runtime_info(target):
# Prior to Bazel 7, the builtin PyRuntimeInfo object must be used because
Expand All @@ -70,15 +120,15 @@ py_runtime_pair = rule(
"py2_runtime": attr.label(
providers = [[PyRuntimeInfo]] + _MaybeBuiltinPyRuntimeInfo,
cfg = "target",
doc = """\
doc = """
The runtime to use for Python 2 targets. Must have `python_version` set to
`PY2`.
""",
),
"py3_runtime": attr.label(
providers = [[PyRuntimeInfo]] + _MaybeBuiltinPyRuntimeInfo,
cfg = "target",
doc = """\
doc = """
The runtime to use for Python 3 targets. Must have `python_version` set to
`PY3`.
""",
Expand All @@ -88,7 +138,7 @@ The runtime to use for Python 3 targets. Must have `python_version` set to
),
},
fragments = ["py"],
doc = """\
doc = """
A toolchain rule for Python.

This wraps up to two Python runtimes, one for Python 2 and one for Python 3.
Expand All @@ -103,8 +153,26 @@ This rule returns a {obj}`ToolchainInfo` provider with fields:

* `py2_runtime`: {type}`PyRuntimeInfo | None`, runtime information for a
Python 2 runtime.
* `py3_runtime`: {type}`PyRuntimeInfo | None`. runtime information for a
* `py3_runtime`: {type}`PyRuntimeInfo | None`, runtime information for a
Python 3 runtime.
* `template_vars`: {type}`TemplateVariableInfo`, make variables exposed by this
toolchain.

It also returns a `TemplateVariableInfo` provider with these make variables
(when the corresponding runtime values are set):

* `$(PYTHON3)`: Python 3 interpreter path.
* `$(PYTHON3_ROOTPATH)`: Python 3 interpreter runfiles path.
* `$(PY_ABI_FLAGS)`: Runtime ABI flags (`sys.abiflags`), e.g. `""` or `"t"`.
* `$(PY_IMPLEMENTATION_NAME)`: Implementation name (`sys.implementation.name`),
e.g. `cpython`.
* `$(PY_MAJOR)`: Major version, e.g. `3`.
* `$(PY_MINOR)`: Minor version, e.g. `12`.
* `$(PY_MAJOR_DOT_MINOR)`: Dotted major and minor version, e.g. `3.12`.
* `$(PY_MAJOR_MINOR_NODOT)`: Dotless major and minor version, e.g. `312`.
* `$(PY_WHEEL_ABI_TAG)`: Wheel ABI tag, e.g. `cp312` or `cp313t`.
* `$(PY_WHEEL_IMPLEMENTATION_TAG)`: Wheel implementation tag, e.g. `cp`.
* `$(PY_WHEEL_PYTHON_TAG)`: Wheel Python tag, e.g. `cp312`.

Example usage:

Expand Down Expand Up @@ -145,5 +213,9 @@ toolchain(

register_toolchains("//my_pkg:my_toolchain")
```

:::{versionchanged} VERSION_NEXT_FEATURE
Provides `TemplateVariableInfo` and `ToolchainInfo.template_vars`.
:::
""",
)
Loading
Loading