| """Utilities for reading values from pyproject.toml.""" |
| |
| load("@toml.bzl", "toml") |
| load(":version.bzl", "version") |
| |
| def read_pyproject(module_ctx, pyproject): |
| """Read a pyproject.toml file and return the relevant fields. |
| |
| The file is parsed with a pure-Starlark TOML decoder; no Python |
| interpreter is required. The raw `requires-python` value is returned |
| as-is so that callers can decide how to interpret it. |
| |
| Args: |
| module_ctx: the module extension context (needs `path` and `read`). |
| pyproject: {type}`Label` pointing at the pyproject.toml file. |
| |
| Returns: |
| {type}`struct` with the attributes: |
| * `requires_python`: {type}`str | None` the raw `requires-python` |
| value (e.g. `"==3.13.9"`), or `None` if it is not set. |
| """ |
| data = toml.decode(module_ctx.read(module_ctx.path(pyproject), watch = "yes")) |
| return struct( |
| requires_python = data.get("project", {}).get("requires-python"), |
| ) |
| |
| def version_from_requires_python(requires_python): |
| """Derive a concrete Python version from a `requires-python` value. |
| |
| Currently only an exact `==X.Y.Z` specifier is supported. The value is |
| validated and normalized via {obj}`//python/private:version.bzl` so that |
| malformed input fails in a consistent way. Broader specifier support |
| (e.g. `>=`, `X.Y`) can be layered on here in the future. |
| |
| Args: |
| requires_python: {type}`str` the raw `requires-python` value. |
| |
| Returns: |
| {type}`str` the normalized version string (e.g. `"3.13.9"`). |
| """ |
| if not requires_python: |
| fail("`requires-python` must be specified") |
| |
| if not requires_python.startswith("=="): |
| fail("`requires-python` must pin an exact version with `==`, got: {}".format(requires_python)) |
| |
| bare_version = requires_python[len("=="):].strip() |
| |
| # Parse strictly so malformed versions fail cleanly, then normalize. |
| version.parse(bare_version, strict = True) |
| return version.normalize(bare_version) |