bazel-contrib/rules_python

py_wheel: support PEP 639 license metadata (License-Expression / License-File)

Aperta

#4042 aperta il 13 ago 2026

 (2 commenti) (0 reazioni) (0 assegnatari)Starlark (705 fork)auto 404
help wanted

Metriche repository

Star
 (679 stelle)
Metriche merge PR
 (Metriche PR in attesa)

Descrizione

🚀 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 <name>-<version>.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-Files 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.

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.

Guida contributor