Skip to content

Commit a72364d

Browse files
committed
Address review: use toml.decode and version.bzl, drop repo rule and toml2json
1 parent 627bfb3 commit a72364d

13 files changed

Lines changed: 52 additions & 304 deletions

File tree

.gitattributes

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,3 @@ python/private/runtimes_manifest_workspace.bzl text eol=lf
55
python/private/runtimes_manifest.txt text eol=lf
66

77
*.bat text eol=crlf
8-
requirements_lock.txt linguist-generated=true

.gitignore

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,8 @@ user.bazelrc
4848
# CLion
4949
.clwb
5050

51-
# Python artifacts
51+
# Python cache
5252
**/__pycache__/
53-
*.egg
54-
*.egg-info
5553

5654
# MODULE.bazel.lock is ignored for now as per recommendation from upstream.
5755
# See https://github.com/bazelbuild/bazel/issues/20369

CHANGELOG.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,6 @@ and [#1975](https://github.com/bazel-contrib/rules_python/issues/1975).
145145
* (toolchains) Support dynamically fetching and registering Python runtimes
146146
from a python-build-standalone manifest file using
147147
`python.override(add_runtime_manifest_urls = ..., runtime_manifest_sha = ...)`.
148-
* (pip,python) Added `pyproject_toml` attribute to `pip.default()` and `python.defaults()`
149-
to read Python version from pyproject.toml `requires-python` field (must be `==X.Y.Z` format).
150148
* (toolchain) Added {obj}`python.override.toolchain_target_settings` to allow
151149
adding `config_setting` labels to all registered toolchains.
152150
* (windows) Full venv support for Windows is available. Set

news/3514.added.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
(pip,python) Added `pyproject_toml` attribute to `pip.default()` and `python.defaults()`
2+
to read the default Python version from the `requires-python` field of `pyproject.toml`.

python/private/BUILD.bazel

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,6 @@ bzl_library(
709709
":full_version",
710710
":pbs_manifest",
711711
":platform_info",
712-
":pyproject_repo",
713712
":pyproject_utils",
714713
":python_register_toolchains",
715714
":pythons_hub",
@@ -878,6 +877,15 @@ bzl_library(
878877
],
879878
)
880879

880+
bzl_library(
881+
name = "pyproject_utils",
882+
srcs = ["pyproject_utils.bzl"],
883+
deps = [
884+
":version",
885+
"@toml.bzl//:toml",
886+
],
887+
)
888+
881889
bzl_library(
882890
name = "bzlmod_enabled",
883891
srcs = ["bzlmod_enabled.bzl"],
@@ -943,16 +951,6 @@ bzl_library(
943951
srcs = ["py_runtime_info.bzl"],
944952
)
945953

946-
bzl_library(
947-
name = "pyproject_repo",
948-
srcs = ["pyproject_repo.bzl"],
949-
)
950-
951-
bzl_library(
952-
name = "pyproject_utils",
953-
srcs = ["pyproject_utils.bzl"],
954-
)
955-
956954
bzl_library(
957955
name = "repo_utils",
958956
srcs = ["repo_utils.bzl"],

python/private/pypi/extension.bzl

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ load("@rules_python_internal//:rules_python_config.bzl", rp_config = "config")
2020
load("@toml.bzl", "toml")
2121
load("//python/private:auth.bzl", "AUTH_ATTRS")
2222
load("//python/private:normalize_name.bzl", "normalize_name")
23-
load("//python/private:pyproject_utils.bzl", "read_pyproject_version")
23+
load("//python/private:pyproject_utils.bzl", "read_pyproject", "version_from_requires_python")
2424
load("//python/private:repo_utils.bzl", "repo_utils")
2525
load(":hub_builder.bzl", "hub_builder")
2626
load(":hub_repository.bzl", "hub_repository", "whl_config_settings_to_json")
@@ -223,13 +223,9 @@ def build_config(
223223
default_hub = tag.default_hub
224224
pyproject_toml = tag.pyproject_toml
225225
if pyproject_toml:
226-
pyproject_version = read_pyproject_version(
227-
module_ctx,
228-
pyproject_toml,
229-
logger = None,
230-
)
231-
if pyproject_version:
232-
defaults["python_version"] = pyproject_version
226+
pyproject = read_pyproject(module_ctx, pyproject_toml)
227+
if pyproject.requires_python:
228+
defaults["python_version"] = version_from_requires_python(pyproject.requires_python)
233229

234230
platform = tag.platform
235231
if platform:

python/private/pyproject_repo.bzl

Lines changed: 0 additions & 80 deletions
This file was deleted.

python/private/pyproject_utils.bzl

Lines changed: 32 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,48 @@
1-
"""Utilities for reading Python version from pyproject.toml."""
1+
"""Utilities for reading values from pyproject.toml."""
22

3-
_TOML2JSON = Label("//tools/private/toml2json:toml2json.py")
3+
load("@toml.bzl", "toml")
4+
load(":version.bzl", "version")
45

5-
def _parse_requires_python(requires_python):
6-
"""Parse and validate the requires-python field.
6+
def read_pyproject(module_ctx, pyproject):
7+
"""Read a pyproject.toml file and return the relevant fields.
8+
9+
The file is parsed with a pure-Starlark TOML decoder; no Python
10+
interpreter is required. The raw `requires-python` value is returned
11+
as-is so that callers can decide how to interpret it.
712
813
Args:
9-
requires_python: The raw requires-python string from pyproject.toml.
14+
module_ctx: the module extension context (needs `path` and `read`).
15+
pyproject: {type}`Label` pointing at the pyproject.toml file.
1016
1117
Returns:
12-
The bare version string (e.g. "3.13.9").
18+
{type}`struct` with the attributes:
19+
* `requires_python`: {type}`str | None` the raw `requires-python`
20+
value (e.g. `"==3.13.9"`), or `None` if it is not set.
1321
"""
14-
if not requires_python.startswith("=="):
15-
fail("requires-python must use '==' for exact version, got: {}".format(requires_python))
16-
17-
bare_version = requires_python[2:].strip()
22+
data = toml.decode(module_ctx.read(module_ctx.path(pyproject), watch = "yes"))
23+
return struct(
24+
requires_python = data.get("project", {}).get("requires-python"),
25+
)
1826

19-
# Validate X.Y.Z format
20-
parts = bare_version.split(".")
21-
if len(parts) != 3:
22-
fail("requires-python must be in X.Y.Z format, got: {}".format(bare_version))
23-
for part in parts:
24-
if not part.isdigit():
25-
fail("requires-python must be in X.Y.Z format, got: {}".format(bare_version))
27+
def version_from_requires_python(requires_python):
28+
"""Derive a concrete Python version from a `requires-python` value.
2629
27-
return bare_version
28-
29-
def read_pyproject_version(module_ctx, pyproject_label, logger = None):
30-
"""Reads Python version from pyproject.toml if requested.
30+
Currently only an exact `==X.Y.Z` specifier is supported. The value is
31+
validated and normalized via {obj}`//python/private:version.bzl` so that
32+
malformed input fails in a consistent way. Broader specifier support
33+
(e.g. `>=`, `X.Y`) can be layered on here in the future.
3134
3235
Args:
33-
module_ctx: The module_ctx object from the module extension.
34-
pyproject_label: Label pointing to the pyproject.toml file, or None.
35-
logger: Optional logger instance for informational messages.
36+
requires_python: {type}`str` the raw `requires-python` value.
3637
3738
Returns:
38-
The Python version string (e.g. "3.13.9") or None if pyproject_label is None.
39+
{type}`str` the normalized version string (e.g. `"3.13.9"`).
3940
"""
40-
if not pyproject_label:
41-
return None
42-
43-
pyproject_path = module_ctx.path(pyproject_label)
44-
module_ctx.read(pyproject_path, watch = "yes")
45-
46-
toml2json = module_ctx.path(_TOML2JSON)
47-
result = module_ctx.execute([
48-
"python3",
49-
str(toml2json),
50-
str(pyproject_path),
51-
])
52-
53-
if result.return_code != 0:
54-
fail("Failed to parse pyproject.toml: " + result.stderr)
55-
56-
data = json.decode(result.stdout)
57-
requires_python = data.get("project", {}).get("requires-python")
58-
if not requires_python:
59-
fail("pyproject.toml must contain [project] requires-python field")
60-
61-
version = _parse_requires_python(requires_python)
41+
if not requires_python.startswith("=="):
42+
fail("`requires-python` must pin an exact version with `==`, got: {}".format(requires_python))
6243

63-
if logger:
64-
logger.info(lambda: "Read Python version {} from {}".format(version, pyproject_label))
44+
bare_version = requires_python[len("=="):].strip()
6545

66-
return version
46+
# Parse strictly so malformed versions fail cleanly, then normalize.
47+
version.parse(bare_version, strict = True)
48+
return version.normalize(bare_version)

python/private/python.bzl

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ load(":auth.bzl", "AUTH_ATTRS")
2020
load(":full_version.bzl", "full_version")
2121
load(":pbs_manifest.bzl", "parse_runtime_manifest")
2222
load(":platform_info.bzl", "platform_info")
23-
load(":pyproject_repo.bzl", "pyproject_version_repo")
24-
load(":pyproject_utils.bzl", "read_pyproject_version")
23+
load(":pyproject_utils.bzl", "read_pyproject", "version_from_requires_python")
2524
load(":python_register_toolchains.bzl", "python_register_toolchains")
2625
load(":pythons_hub.bzl", "hub_repo")
2726
load(":repo_utils.bzl", "repo_utils")
@@ -222,20 +221,6 @@ def _python_impl(module_ctx):
222221
# For all other processing (after parsing the modules) let's use a single logger.
223222
logger = repo_utils.logger(module_ctx, "python", mod = module_ctx.modules[0])
224223

225-
# Create pyproject version repo if pyproject.toml is used
226-
created_pyproject_repo = False
227-
for mod in module_ctx.modules:
228-
if mod.is_root:
229-
for tag in mod.tags.defaults:
230-
if tag.pyproject_toml:
231-
pyproject_version_repo(
232-
name = "python_version_from_pyproject",
233-
pyproject_toml = tag.pyproject_toml,
234-
)
235-
created_pyproject_repo = True
236-
break
237-
break
238-
239224
# Host compatible runtime repos
240225
# dict[str version, struct] where struct has:
241226
# * full_python_version: str
@@ -482,8 +467,6 @@ def _python_impl(module_ctx):
482467
if bazel_features.external_deps.extension_metadata_has_reproducible:
483468
# Build the list of direct dependencies
484469
root_direct_deps = ["pythons_hub", "python_versions"]
485-
if created_pyproject_repo:
486-
root_direct_deps.append("python_version_from_pyproject")
487470

488471
return module_ctx.extension_metadata(
489472
root_module_direct_deps = root_direct_deps,
@@ -1029,13 +1012,9 @@ def _compute_default_python_version(mctx):
10291012
mctx.read(default_python_version_file, watch = "yes").strip(),
10301013
)
10311014
if pyproject_toml_label:
1032-
pyproject_version = read_pyproject_version(
1033-
mctx,
1034-
pyproject_toml_label,
1035-
logger = None,
1036-
)
1037-
if pyproject_version:
1038-
default_python_version = pyproject_version
1015+
pyproject = read_pyproject(mctx, pyproject_toml_label)
1016+
if pyproject.requires_python:
1017+
default_python_version = version_from_requires_python(pyproject.requires_python)
10391018
if default_python_version_env:
10401019
default_python_version = mctx.getenv(
10411020
default_python_version_env,

tests/tools/private/toml2json/BUILD.bazel

Lines changed: 0 additions & 10 deletions
This file was deleted.

0 commit comments

Comments
 (0)