bazel-contrib / bazel-contrib/rules_python
py_wheel: support PEP 639 license metadata (License-Expression / License-File)
- Dominant language
- Starlark
- Stars
- 688
- Forks
- 721
- Avg merge
- 14h 49m
- Merged PRs (30d)
- 81
Description
# 🚀 feature request
### Relevant Rules
`py_wheel` (`python/private/py_wheel.bzl`) — extending an existing rule.
### Description
`py_wheel` cannot produce a wheel whose `METADATA` conforms to PEP 639, which is now Final.
On `main` today:
- `Metadata-Version` is hardcoded to `2.1` (`python/private/py_wheel.bzl:422`); the PEP 639 fields require `2.4`.
- The only license-related attribute is the legacy free-text `license` (`:250`), emitted as the `License:` field (`:432`). PEP 639 deprecates that field.
- There is no way to emit `License-Expression`, no way to emit `License-File`, and no way to place license texts under `-.dist-info/licenses/`.
- There is no escape hatch for appending arbitrary `METADATA` lines either, so this cannot be worked around from a `BUILD` file.
The practical impact is that a `py_wheel` cannot declare its license as an SPDX expression, and cannot ship the license texts of its bundled third-party dependencies in the location PEP 639 defines. For anyone who has to produce auditable license metadata for a redistributed wheel, that is a hard blocker.
Other backends have already moved: setuptools 77.0.0 emits `License-Expression`, stores `License-File`s in `.dist-info/licenses/`, and bumps core metadata to 2.4.
### Describe the solution you'd like
Two new attributes on `py_wheel`:
- `license_expression` (string) — an SPDX license expression, emitted as `License-Expression`.
- `license_files` (label-keyed string dict) — license file targets mapped to their destination path relative to `.dist-info/licenses/`. Each entry emits one `License-File` line and packages the file at that path.
`Metadata-Version` would be raised to `2.4` only when one of the two is set, so existing users are unaffected and the change stays backwards compatible. `license` and `license_expression` would be mutually exclusive, as PEP 639 intends.
```python
py_wheel(
name = "my_wheel",
distribution = "my_package",
version = "1.0.0",
license_expression = "Apache-2.0 AND MIT",
license_files = {
"//:LICENSE": "LICENSE",
"//third_party:some_dep_license.txt": "third_party/some_dep.txt",
},
)
```
produces:
```
Metadata-Version: 2.4
Name: my_package
Version: 1.0.0
License-Expression: Apache-2.0 AND MIT
License-File: LICENSE
License-File: third_party/some_dep.txt
```
with both files packaged under `my_package-1.0.0.dist-info/licenses/`.
### Describe alternatives you've considered
- **The legacy `license` attribute** — deprecated by PEP 639, carries no SPDX semantics, and does not package license files at all.
- **`extra_distinfo_files`** — can place files inside `.dist-info/`, but `Metadata-Version` stays `2.1` and no `License-File` lines are emitted. The files end up present but undeclared, which is not PEP 639 conformant.
- **Post-processing the built wheel in a separate rule** — means unzipping and rezipping the artifact just to rewrite `METADATA`; awkward, and easy to get wrong for reproducibility.
- **Carrying a local patch to `python/private/py_wheel.bzl`** — what we do today. It works, but it has to be rebased on every `rules_python` bump, which is exactly what we would rather not carry.
I have this implemented and working locally and would be glad to send a PR. I need to clear the CLA on my employer's side first, so I am opening the issue now to check whether the approach and the attribute shape are acceptable before going down that route.
Contributor guide
Research direction
Start in python/private/py_wheel.bzl, especially the license attribute around line 250 and metadata generation around lines 422 and 432. Trace how existing extra_distinfo_files are packaged, then implement the requested PEP 639 attributes and ensure Metadata-Version, License-Expression, License-File, and the files under .dist-info/licenses/ match the issue's example while preserving existing behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- build-system
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 63/100