Skip to content

Commit 694fb48

Browse files
Jan Winkler (jwi)janwinkler1
authored andcommitted
feat: enable pyproject.toml as single source of truth for Python version
1 parent c9783c8 commit 694fb48

32 files changed

Lines changed: 799 additions & 29 deletions

.bazelrc.deleted_packages

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,7 @@ common --deleted_packages=tests/modules/other/nspkg_single
4545
common --deleted_packages=tests/modules/other/simple_v1
4646
common --deleted_packages=tests/modules/other/simple_v2
4747
common --deleted_packages=tests/modules/other/with_external_data
48+
common --deleted_packages=tests/pyproject/compile_requirements_test
49+
common --deleted_packages=tests/pyproject/pip_integration_test
50+
common --deleted_packages=tests/pyproject/priority_test
51+
common --deleted_packages=tests/pyproject/python_toolchain_test

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
python/features.bzl export-subst
22
tools/publish/*.txt linguist-generated=true
3+
requirements_lock.txt linguist-generated=true

.gitignore

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

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

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

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ END_UNRELEASED_TEMPLATE
7979
### Fixed
8080
* (runfiles) Fixed `CurrentRepository()` raising `ValueError` on Windows.
8181
([#3579](https://github.com/bazel-contrib/rules_python/issues/3579))
82+
* (pip) Made `config_settings` optional in `pip.default()` when not using platform-specific configurations.
8283
* (tests) No more coverage warnings are being printed if there are no sources.
8384
([#2762](https://github.com/bazel-contrib/rules_python/issues/2762))
8485
* (gazelle) Ancestor `conftest.py` files are added in addition to sibling `conftest.py`.
@@ -95,6 +96,8 @@ END_UNRELEASED_TEMPLATE
9596

9697
{#v0-0-0-added}
9798
### Added
99+
* (pip,python) Added `pyproject_toml` attribute to `pip.default()` and `python.defaults()`
100+
to read Python version from pyproject.toml `requires-python` field (must be `==X.Y.Z` format).
98101
* (binaries/tests) {obj}`--debugger`: allows specifying an extra dependency
99102
to add to binaries/tests for custom debuggers.
100103
* (binaries/tests) Build information is now included in binaries and tests.

python/private/BUILD.bazel

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ package(
2929

3030
licenses(["notice"])
3131

32-
exports_files(["runtime_env_toolchain_interpreter.sh"])
32+
exports_files([
33+
"runtime_env_toolchain_interpreter.sh",
34+
"pyproject_version_extractor.py",
35+
])
3336

3437
filegroup(
3538
name = "distribution",
@@ -272,6 +275,8 @@ bzl_library(
272275
deps = [
273276
":full_version_bzl",
274277
":platform_info_bzl",
278+
":pyproject_repo_bzl",
279+
":pyproject_utils_bzl",
275280
":python_register_toolchains_bzl",
276281
":pythons_hub_bzl",
277282
":repo_utils_bzl",
@@ -752,6 +757,16 @@ bzl_library(
752757
],
753758
)
754759

760+
bzl_library(
761+
name = "pyproject_repo_bzl",
762+
srcs = ["pyproject_repo.bzl"],
763+
)
764+
765+
bzl_library(
766+
name = "pyproject_utils_bzl",
767+
srcs = ["pyproject_utils.bzl"],
768+
)
769+
755770
# Needed to define bzl_library targets for docgen. (We don't define the
756771
# bzl_library target here because it'd give our users a transitive dependency
757772
# on Skylib.)

python/private/pypi/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ bzl_library(
127127
":whl_library_bzl",
128128
"//python/private:auth_bzl",
129129
"//python/private:normalize_name_bzl",
130+
"//python/private:pyproject_utils_bzl",
130131
"//python/private:repo_utils_bzl",
131132
"@bazel_features//:features",
132133
"@pythons_hub//:interpreters_bzl",

python/private/pypi/extension.bzl

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ load("@pythons_hub//:versions.bzl", "MINOR_MAPPING")
1919
load("@rules_python_internal//:rules_python_config.bzl", rp_config = "config")
2020
load("//python/private:auth.bzl", "AUTH_ATTRS")
2121
load("//python/private:normalize_name.bzl", "normalize_name")
22+
load("//python/private:pyproject_utils.bzl", "read_pyproject_version")
2223
load("//python/private:repo_utils.bzl", "repo_utils")
2324
load(":evaluate_markers.bzl", EVALUATE_MARKERS_SRCS = "SRCS")
2425
load(":hub_builder.bzl", "hub_builder")
@@ -86,12 +87,23 @@ def build_config(
8687
"""
8788
defaults = {
8889
"platforms": {},
90+
"python_version": None,
8991
}
9092
for mod in module_ctx.modules:
9193
if not (mod.is_root or mod.name == "rules_python"):
9294
continue
9395

9496
for tag in mod.tags.default:
97+
pyproject_toml = getattr(tag, "pyproject_toml", None)
98+
if pyproject_toml:
99+
pyproject_version = read_pyproject_version(
100+
module_ctx,
101+
pyproject_toml,
102+
logger = None,
103+
)
104+
if pyproject_version:
105+
defaults["python_version"] = pyproject_version
106+
95107
platform = tag.platform
96108
if platform:
97109
specific_config = defaults["platforms"].setdefault(platform, {})
@@ -125,6 +137,7 @@ def build_config(
125137
return struct(
126138
auth_patterns = defaults.get("auth_patterns", {}),
127139
netrc = defaults.get("netrc", None),
140+
python_version = defaults.get("python_version", None),
128141
platforms = {
129142
name: _plat(**values)
130143
for name, values in defaults["platforms"].items()
@@ -155,6 +168,8 @@ def parse_modules(
155168
Returns:
156169
A struct with the following attributes:
157170
"""
171+
config = build_config(module_ctx = module_ctx, enable_pipstar = enable_pipstar, enable_pipstar_extract = enable_pipstar_extract)
172+
158173
whl_mods = {}
159174
for mod in module_ctx.modules:
160175
for whl_mod in mod.tags.whl_mods:
@@ -186,8 +201,6 @@ You cannot use both the additive_build_content and additive_build_content_file a
186201
srcs_exclude_glob = whl_mod.srcs_exclude_glob,
187202
)
188203

189-
config = build_config(module_ctx = module_ctx, enable_pipstar = enable_pipstar, enable_pipstar_extract = enable_pipstar_extract)
190-
191204
# TODO @aignas 2025-06-03: Merge override API with the builder?
192205
_overriden_whl_set = {}
193206
whl_overrides = {}
@@ -228,6 +241,10 @@ You cannot use both the additive_build_content and additive_build_content_file a
228241

229242
for mod in module_ctx.modules:
230243
for pip_attr in mod.tags.parse:
244+
python_version = pip_attr.python_version or config.python_version
245+
if not python_version:
246+
_fail("pip.parse() requires either python_version attribute or pip.default(pyproject_toml=...) to be set")
247+
231248
hub_name = pip_attr.hub_name
232249
if hub_name not in pip_hub_map:
233250
builder = hub_builder(
@@ -264,6 +281,7 @@ You cannot use both the additive_build_content and additive_build_content_file a
264281
builder.pip_parse(
265282
module_ctx,
266283
pip_attr = pip_attr,
284+
python_version = python_version,
267285
)
268286

269287
# Keeps track of all the hub's whl repos across the different versions.
@@ -409,7 +427,7 @@ Either this or {attr}`env` `platform_machine` key should be specified.
409427
""",
410428
),
411429
"config_settings": attr.label_list(
412-
mandatory = True,
430+
mandatory = False,
413431
doc = """\
414432
The list of labels to `config_setting` targets that need to be matched for the platform to be
415433
selected.
@@ -471,6 +489,22 @@ If you are defining custom platforms in your project and don't want things to cl
471489
[isolation] feature.
472490
473491
[isolation]: https://bazel.build/rules/lib/globals/module#use_extension.isolate
492+
""",
493+
),
494+
"pyproject_toml": attr.label(
495+
mandatory = False,
496+
allow_single_file = True,
497+
doc = """\
498+
Label pointing to pyproject.toml file to read the default Python version from.
499+
When specified, reads the `requires-python` field from pyproject.toml and uses
500+
it as the default python_version for all `pip.parse()` calls that don't
501+
explicitly specify one.
502+
503+
The version must be specified as `==X.Y.Z` (exact version with full semver).
504+
This is designed to work with dependency management tools like Renovate.
505+
506+
:::{versionadded} 1.8.0
507+
:::
474508
""",
475509
),
476510
"whl_abi_tags": attr.string_list(
@@ -651,7 +685,7 @@ find in case extra indexes are specified.
651685
default = True,
652686
),
653687
"python_version": attr.string(
654-
mandatory = True,
688+
mandatory = False,
655689
doc = """
656690
The Python version the dependencies are targetting, in Major.Minor format
657691
(e.g., "3.11") or patch level granularity (e.g. "3.11.1").

python/private/pypi/hub_builder.bzl

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,8 @@ def _build(self):
147147
whl_libraries = self._whl_libraries,
148148
)
149149

150-
def _pip_parse(self, module_ctx, pip_attr):
151-
python_version = pip_attr.python_version
150+
def _pip_parse(self, module_ctx, pip_attr, python_version = None):
151+
python_version = python_version or pip_attr.python_version
152152
if python_version in self._platforms:
153153
fail((
154154
"Duplicate pip python version '{version}' for hub " +
@@ -197,8 +197,9 @@ def _pip_parse(self, module_ctx, pip_attr):
197197
self,
198198
module_ctx,
199199
pip_attr = pip_attr,
200-
enable_pipstar = self._config.enable_pipstar or self._get_index_urls.get(pip_attr.python_version),
201-
enable_pipstar_extract = self._config.enable_pipstar_extract or self._get_index_urls.get(pip_attr.python_version),
200+
python_version = python_version,
201+
enable_pipstar = self._config.enable_pipstar or self._get_index_urls.get(python_version),
202+
enable_pipstar_extract = self._config.enable_pipstar_extract or self._get_index_urls.get(python_version),
202203
)
203204

204205
### end of PUBLIC methods
@@ -410,11 +411,11 @@ def _set_get_index_urls(self, pip_attr):
410411
)
411412
return True
412413

413-
def _detect_interpreter(self, pip_attr):
414+
def _detect_interpreter(self, pip_attr, python_version):
414415
python_interpreter_target = pip_attr.python_interpreter_target
415416
if python_interpreter_target == None and not pip_attr.python_interpreter:
416417
python_name = "python_{}_host".format(
417-
pip_attr.python_version.replace(".", "_"),
418+
python_version.replace(".", "_"),
418419
)
419420
if python_name not in self._available_interpreters:
420421
fail((
@@ -424,7 +425,7 @@ def _detect_interpreter(self, pip_attr):
424425
"Expected to find {python_name} among registered versions:\n {labels}"
425426
).format(
426427
hub_name = self.name,
427-
version = pip_attr.python_version,
428+
version = python_version,
428429
python_name = python_name,
429430
labels = " \n".join(self._available_interpreters),
430431
))
@@ -488,17 +489,17 @@ def _platforms(module_ctx, *, python_version, config, target_platforms):
488489
)
489490
return platforms
490491

491-
def _evaluate_markers(self, pip_attr, enable_pipstar):
492+
def _evaluate_markers(self, pip_attr, python_version, enable_pipstar):
492493
if self._evaluate_markers_fn:
493494
return self._evaluate_markers_fn
494495

495496
if enable_pipstar:
496497
return lambda _, requirements: evaluate_markers_star(
497498
requirements = requirements,
498-
platforms = self._platforms[pip_attr.python_version],
499+
platforms = self._platforms[python_version],
499500
)
500501

501-
interpreter = _detect_interpreter(self, pip_attr)
502+
interpreter = _detect_interpreter(self, pip_attr, python_version)
502503

503504
# NOTE @aignas 2024-08-02: , we will execute any interpreter that we find either
504505
# in the PATH or if specified as a label. We will configure the env
@@ -518,7 +519,7 @@ def _evaluate_markers(self, pip_attr, enable_pipstar):
518519
module_ctx,
519520
requirements = {
520521
k: {
521-
p: self._platforms[pip_attr.python_version][p].triple
522+
p: self._platforms[python_version][p].triple
522523
for p in plats
523524
}
524525
for k, plats in requirements.items()
@@ -534,6 +535,7 @@ def _create_whl_repos(
534535
module_ctx,
535536
*,
536537
pip_attr,
538+
python_version,
537539
enable_pipstar = False,
538540
enable_pipstar_extract = False):
539541
"""create all of the whl repositories
@@ -542,11 +544,12 @@ def _create_whl_repos(
542544
self: the builder.
543545
module_ctx: {type}`module_ctx`.
544546
pip_attr: {type}`struct` - the struct that comes from the tag class iteration.
547+
python_version: {type}`str` - the resolved python version for this pip.parse call.
545548
enable_pipstar: {type}`bool` - enable the pipstar or not.
546549
enable_pipstar_extract: {type}`bool` - enable the pipstar extraction or not.
547550
"""
548551
logger = self._logger
549-
platforms = self._platforms[pip_attr.python_version]
552+
platforms = self._platforms[python_version]
550553
requirements_by_platform = parse_requirements(
551554
module_ctx,
552555
requirements_by_platform = requirements_files_by_platform(
@@ -558,15 +561,15 @@ def _create_whl_repos(
558561
extra_pip_args = pip_attr.extra_pip_args,
559562
platforms = sorted(platforms), # here we only need keys
560563
python_version = full_version(
561-
version = pip_attr.python_version,
564+
version = python_version,
562565
minor_mapping = self._minor_mapping,
563566
),
564567
logger = logger,
565568
),
566569
platforms = platforms,
567570
extra_pip_args = pip_attr.extra_pip_args,
568-
get_index_urls = self._get_index_urls.get(pip_attr.python_version),
569-
evaluate_markers = _evaluate_markers(self, pip_attr, enable_pipstar),
571+
get_index_urls = self._get_index_urls.get(python_version),
572+
evaluate_markers = _evaluate_markers(self, pip_attr, python_version, enable_pipstar),
570573
logger = logger,
571574
)
572575

@@ -588,7 +591,7 @@ def _create_whl_repos(
588591
enable_pipstar = enable_pipstar,
589592
)
590593

591-
interpreter = _detect_interpreter(self, pip_attr)
594+
interpreter = _detect_interpreter(self, pip_attr, python_version)
592595

593596
for whl in requirements_by_platform:
594597
whl_library_args = common_args | _whl_library_args(
@@ -602,17 +605,17 @@ def _create_whl_repos(
602605
whl_library_args = whl_library_args,
603606
download_only = pip_attr.download_only,
604607
netrc = self._config.netrc or pip_attr.netrc,
605-
use_downloader = _use_downloader(self, pip_attr.python_version, whl.name),
608+
use_downloader = _use_downloader(self, python_version, whl.name),
606609
auth_patterns = self._config.auth_patterns or pip_attr.auth_patterns,
607-
python_version = _major_minor_version(pip_attr.python_version),
610+
python_version = _major_minor_version(python_version),
608611
is_multiple_versions = whl.is_multiple_versions,
609612
interpreter = interpreter,
610613
enable_pipstar = enable_pipstar,
611614
enable_pipstar_extract = enable_pipstar_extract,
612615
)
613616
_add_whl_library(
614617
self,
615-
python_version = pip_attr.python_version,
618+
python_version = python_version,
616619
whl = whl,
617620
repo = repo,
618621
enable_pipstar = enable_pipstar,

0 commit comments

Comments
 (0)