Skip to content

Commit cbfc9cd

Browse files
committed
Add pyproject_toml to pip.parse; revert extension_metadata root deps
1 parent a72364d commit cbfc9cd

5 files changed

Lines changed: 90 additions & 13 deletions

File tree

python/private/pypi/extension.bzl

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -367,9 +367,14 @@ You cannot use both the additive_build_content and additive_build_content_file a
367367

368368
for mod in module_ctx.modules:
369369
for pip_attr in mod.tags.parse:
370-
python_version = pip_attr.python_version or config.python_version
370+
python_version = pip_attr.python_version
371+
if not python_version and pip_attr.pyproject_toml:
372+
pyproject = read_pyproject(module_ctx, pip_attr.pyproject_toml)
373+
if pyproject.requires_python:
374+
python_version = version_from_requires_python(pyproject.requires_python)
375+
python_version = python_version or config.python_version
371376
if not python_version:
372-
_fail("pip.parse() requires either python_version attribute or pip.default(pyproject_toml=...) to be set")
377+
_fail("pip.parse() requires one of `python_version`, `pyproject_toml`, or `pip.default(pyproject_toml=...)` to be set")
373378

374379
hub_name = pip_attr.hub_name
375380
if hub_name == "pypi":
@@ -722,8 +727,10 @@ When specified, reads the `requires-python` field from pyproject.toml and uses
722727
it as the default python_version for all `pip.parse()` calls that don't
723728
explicitly specify one.
724729
730+
:::{note}
725731
The version must be specified as `==X.Y.Z` (exact version with full semver).
726732
This is designed to work with dependency management tools like Renovate.
733+
:::
727734
728735
:::{versionadded} VERSION_NEXT_FEATURE
729736
:::
@@ -897,6 +904,21 @@ find in case extra indexes are specified.
897904
""",
898905
default = True,
899906
),
907+
"pyproject_toml": attr.label(
908+
mandatory = False,
909+
doc = """\
910+
Label pointing to a pyproject.toml file to read the Python version from.
911+
When specified, the `requires-python` field is used as the `python_version`
912+
for this `pip.parse()` call, unless `python_version` is set explicitly.
913+
914+
:::{note}
915+
The version must be specified as `==X.Y.Z` (exact version with full semver).
916+
:::
917+
918+
:::{versionadded} VERSION_NEXT_FEATURE
919+
:::
920+
""",
921+
),
900922
"python_version": attr.string(
901923
mandatory = False,
902924
doc = """
@@ -910,6 +932,11 @@ a corresponding `python.toolchain()` configured.
910932
:::{seealso}
911933
The {obj}`pyproject_toml` attribute for getting the version from a project file.
912934
:::
935+
936+
:::{versionchanged} VERSION_NEXT_FEATURE
937+
No longer mandatory if the {obj}`pyproject_toml` attribute or
938+
{obj}`pip.default.pyproject_toml` is specified.
939+
:::
913940
""",
914941
),
915942
"simpleapi_skip": attr.string_list(

python/private/pyproject_utils.bzl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ def version_from_requires_python(requires_python):
3838
Returns:
3939
{type}`str` the normalized version string (e.g. `"3.13.9"`).
4040
"""
41+
if not requires_python:
42+
fail("`requires-python` must be specified")
43+
4144
if not requires_python.startswith("=="):
4245
fail("`requires-python` must pin an exact version with `==`, got: {}".format(requires_python))
4346

python/private/python.bzl

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -465,14 +465,7 @@ def _python_impl(module_ctx):
465465
)
466466

467467
if bazel_features.external_deps.extension_metadata_has_reproducible:
468-
# Build the list of direct dependencies
469-
root_direct_deps = ["pythons_hub", "python_versions"]
470-
471-
return module_ctx.extension_metadata(
472-
root_module_direct_deps = root_direct_deps,
473-
root_module_direct_dev_deps = [],
474-
reproducible = True,
475-
)
468+
return module_ctx.extension_metadata(reproducible = True)
476469
else:
477470
return None
478471

tests/pypi/extension/extension_tests.bzl

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ load(":pip_parse.bzl", _parse = "pip_parse")
2424

2525
_tests = []
2626

27-
def _pypi_mock_mctx(*modules, os_name = "unittest", arch_name = "exotic", environ = {}, read = None):
27+
def _pypi_mock_mctx(*modules, os_name = "unittest", arch_name = "exotic", environ = {}, read = None, mock_files = {}):
2828
_ = read # @unused
2929
return mocks.mctx(
3030
modules = list(modules),
@@ -36,7 +36,7 @@ def _pypi_mock_mctx(*modules, os_name = "unittest", arch_name = "exotic", enviro
3636
simple==0.0.1 \
3737
--hash=sha256:deadbeef \
3838
--hash=sha256:deadbaaf""",
39-
},
39+
} | mock_files,
4040
)
4141

4242
def _default(
@@ -177,6 +177,58 @@ def _test_simple(env):
177177

178178
_tests.append(_test_simple)
179179

180+
def _test_pip_parse_pyproject_toml(env):
181+
# pip.parse() reads the version from pyproject.toml's requires-python when
182+
# python_version is not set explicitly.
183+
pypi = _parse_modules(
184+
env,
185+
module_ctx = _pypi_mock_mctx(
186+
_mod(
187+
name = "rules_python",
188+
parse = [
189+
_parse(
190+
hub_name = "pypi",
191+
pyproject_toml = "pyproject.toml",
192+
simpleapi_skip = ["simple"],
193+
requirements_lock = "requirements.txt",
194+
),
195+
],
196+
),
197+
os_name = "linux",
198+
arch_name = "x86_64",
199+
mock_files = {
200+
"pyproject.toml": "[project]\nrequires-python = \"==3.15.19\"\n",
201+
},
202+
),
203+
available_interpreters = {
204+
"python_3_15_19_host": "unit_test_interpreter_target",
205+
},
206+
minor_mapping = {"3.15": "3.15.19"},
207+
)
208+
209+
# Resolves identically to passing python_version = "3.15.19" explicitly:
210+
# the full version drives interpreter selection, hub naming uses major.minor.
211+
pypi.exposed_packages().contains_exactly({"pypi": ["simple"]})
212+
pypi.hub_whl_map().contains_exactly({"pypi": {
213+
"simple": {
214+
"pypi_315_simple": [
215+
whl_config_setting(
216+
version = "3.15",
217+
),
218+
],
219+
},
220+
}})
221+
pypi.whl_libraries().contains_exactly({
222+
"pypi_315_simple": {
223+
"config_load": "@pypi//:config.bzl",
224+
"dep_template": "@pypi//{name}:{target}",
225+
"python_interpreter_target": "unit_test_interpreter_target",
226+
"requirement": "simple==0.0.1 --hash=sha256:deadbeef --hash=sha256:deadbaaf",
227+
},
228+
})
229+
230+
_tests.append(_test_pip_parse_pyproject_toml)
231+
180232
def _test_simple_isolated(env):
181233
"""Simulate `isolate = True` with parse_modules.
182234

tests/pypi/extension/pip_parse.bzl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
def pip_parse(
44
*,
55
hub_name,
6-
python_version,
6+
python_version = None,
77
add_libdir_to_library_search_path = False,
88
auth_patterns = {},
99
download_only = False,
@@ -19,6 +19,7 @@ def pip_parse(
1919
netrc = None,
2020
parse_all_requirements_files = True,
2121
pip_data_exclude = None,
22+
pyproject_toml = None,
2223
python_interpreter = None,
2324
python_interpreter_target = None,
2425
quiet = True,
@@ -52,6 +53,7 @@ def pip_parse(
5253
netrc = netrc,
5354
parse_all_requirements_files = parse_all_requirements_files,
5455
pip_data_exclude = pip_data_exclude,
56+
pyproject_toml = pyproject_toml,
5557
python_interpreter = python_interpreter,
5658
python_interpreter_target = python_interpreter_target,
5759
python_version = python_version,

0 commit comments

Comments
 (0)