Skip to content

Commit 9f63485

Browse files
authored
Merge branch 'main' into feat/extension-eval-without-python
2 parents d4b19b4 + 67e233f commit 9f63485

File tree

7 files changed

+444
-8
lines changed

7 files changed

+444
-8
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ Unreleased changes template.
6464
* 3.12.9
6565
* 3.13.2
6666
* (pypi) Use `xcrun xcodebuild --showsdks` to find XCode root.
67+
* (pypi) The `bzlmod` extension will now generate smaller lock files for when
68+
using `experimental_index_url`.
6769
* (toolchains) Remove all but `3.8.20` versions of the Python `3.8` interpreter who has
6870
reached EOL. If users still need other versions of the `3.8` interpreter, please supply
6971
the URLs manually {bzl:ob}`python.toolchain` or {bzl:obj}`python_register_toolchains` calls.
@@ -128,6 +130,9 @@ Unreleased changes template.
128130

129131
{#v1-3-0-added}
130132
### Added
133+
* (python) {attr}`python.defaults` has been added to allow users to
134+
set the default python version in the root module by reading the
135+
default version number from a file or an environment variable.
131136
* {obj}`//python/bin:python`: convenience target for directly running an
132137
interpreter. {obj}`--//python/bin:python_src` can be used to specify a
133138
binary whose interpreter to use.

examples/multi_python_versions/MODULE.bazel

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ local_path_override(
1010
)
1111

1212
python = use_extension("@rules_python//python/extensions:python.bzl", "python")
13+
python.defaults(
14+
# The environment variable takes precedence if set.
15+
python_version = "3.9",
16+
python_version_env = "BAZEL_PYTHON_VERSION",
17+
)
1318
python.toolchain(
1419
configure_coverage_tool = True,
1520
# Only set when you have mulitple toolchain versions.

python/private/pypi/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ bzl_library(
9595
":whl_config_setting_bzl",
9696
":whl_library_bzl",
9797
":whl_repo_name_bzl",
98+
":whl_target_platforms_bzl",
9899
"//python/private:full_version_bzl",
99100
"//python/private:normalize_name_bzl",
100101
"//python/private:semver_bzl",

python/private/pypi/extension.bzl

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ load(":simpleapi_download.bzl", "simpleapi_download")
3232
load(":whl_config_setting.bzl", "whl_config_setting")
3333
load(":whl_library.bzl", "whl_library")
3434
load(":whl_repo_name.bzl", "pypi_repo_name", "whl_repo_name")
35+
load(":whl_target_platforms.bzl", "whl_target_platforms")
3536

3637
def _major_minor_version(version):
3738
version = semver(version)
@@ -278,9 +279,18 @@ def _whl_repos(*, requirement, whl_library_args, download_only, netrc, auth_patt
278279

279280
# Pure python wheels or sdists may need to have a platform here
280281
target_platforms = None
281-
if distribution.filename.endswith("-any.whl") or not distribution.filename.endswith(".whl"):
282-
if multiple_requirements_for_whl:
283-
target_platforms = requirement.target_platforms
282+
if distribution.filename.endswith(".whl") and not distribution.filename.endswith("-any.whl"):
283+
parsed_whl = parse_whl_name(distribution.filename)
284+
whl_platforms = whl_target_platforms(
285+
platform_tag = parsed_whl.platform_tag,
286+
)
287+
args["experimental_target_platforms"] = [
288+
p
289+
for p in requirement.target_platforms
290+
if [None for wp in whl_platforms if p.endswith(wp.target_platform)]
291+
]
292+
elif multiple_requirements_for_whl:
293+
target_platforms = requirement.target_platforms
284294

285295
repo_name = whl_repo_name(
286296
distribution.filename,

python/private/python.bzl

Lines changed: 153 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,47 @@ def parse_modules(*, module_ctx, _fail = fail):
7878

7979
config = _get_toolchain_config(modules = module_ctx.modules, _fail = _fail)
8080

81+
default_python_version = None
82+
for mod in module_ctx.modules:
83+
defaults_attr_structs = _create_defaults_attr_structs(mod = mod)
84+
default_python_version_env = None
85+
default_python_version_file = None
86+
87+
# Only the root module and rules_python are allowed to specify the default
88+
# toolchain for a couple reasons:
89+
# * It prevents submodules from specifying different defaults and only
90+
# one of them winning.
91+
# * rules_python needs to set a soft default in case the root module doesn't,
92+
# e.g. if the root module doesn't use Python itself.
93+
# * The root module is allowed to override the rules_python default.
94+
if mod.is_root or (mod.name == "rules_python" and not default_python_version):
95+
for defaults_attr in defaults_attr_structs:
96+
default_python_version = _one_or_the_same(
97+
default_python_version,
98+
defaults_attr.python_version,
99+
onerror = _fail_multiple_defaults_python_version,
100+
)
101+
default_python_version_env = _one_or_the_same(
102+
default_python_version_env,
103+
defaults_attr.python_version_env,
104+
onerror = _fail_multiple_defaults_python_version_env,
105+
)
106+
default_python_version_file = _one_or_the_same(
107+
default_python_version_file,
108+
defaults_attr.python_version_file,
109+
onerror = _fail_multiple_defaults_python_version_file,
110+
)
111+
if default_python_version_file:
112+
default_python_version = _one_or_the_same(
113+
default_python_version,
114+
module_ctx.read(default_python_version_file, watch = "yes").strip(),
115+
)
116+
if default_python_version_env:
117+
default_python_version = module_ctx.getenv(
118+
default_python_version_env,
119+
default_python_version,
120+
)
121+
81122
seen_versions = {}
82123
for mod in module_ctx.modules:
83124
module_toolchain_versions = []
@@ -104,7 +145,13 @@ def parse_modules(*, module_ctx, _fail = fail):
104145
# * rules_python needs to set a soft default in case the root module doesn't,
105146
# e.g. if the root module doesn't use Python itself.
106147
# * The root module is allowed to override the rules_python default.
107-
is_default = toolchain_attr.is_default
148+
if default_python_version:
149+
is_default = default_python_version == toolchain_version
150+
if toolchain_attr.is_default and not is_default:
151+
fail("The 'is_default' attribute doesn't work if you set " +
152+
"the default Python version with the `defaults` tag.")
153+
else:
154+
is_default = toolchain_attr.is_default
108155

109156
# Also only the root module should be able to decide ignore_root_user_error.
110157
# Modules being depended upon don't know the final environment, so they aren't
@@ -115,7 +162,7 @@ def parse_modules(*, module_ctx, _fail = fail):
115162
fail("Toolchains in the root module must have consistent 'ignore_root_user_error' attributes")
116163

117164
ignore_root_user_error = toolchain_attr.ignore_root_user_error
118-
elif mod.name == "rules_python" and not default_toolchain:
165+
elif mod.name == "rules_python" and not default_toolchain and not default_python_version:
119166
# We don't do the len() check because we want the default that rules_python
120167
# sets to be clearly visible.
121168
is_default = toolchain_attr.is_default
@@ -282,6 +329,19 @@ def _python_impl(module_ctx):
282329
else:
283330
return None
284331

332+
def _one_or_the_same(first, second, *, onerror = None):
333+
if not first:
334+
return second
335+
if not second or second == first:
336+
return first
337+
if onerror:
338+
return onerror(first, second)
339+
else:
340+
fail("Unique value needed, got both '{}' and '{}', which are different".format(
341+
first,
342+
second,
343+
))
344+
285345
def _fail_duplicate_module_toolchain_version(version, module):
286346
fail(("Duplicate module toolchain version: module '{module}' attempted " +
287347
"to use version '{version}' multiple times in itself").format(
@@ -305,6 +365,30 @@ def _warn_duplicate_global_toolchain_version(version, first, second_toolchain_na
305365
version = version,
306366
))
307367

368+
def _fail_multiple_defaults_python_version(first, second):
369+
fail(("Multiple python_version entries in defaults: " +
370+
"First default was python_version '{first}'. " +
371+
"Second was python_version '{second}'").format(
372+
first = first,
373+
second = second,
374+
))
375+
376+
def _fail_multiple_defaults_python_version_file(first, second):
377+
fail(("Multiple python_version_file entries in defaults: " +
378+
"First default was python_version_file '{first}'. " +
379+
"Second was python_version_file '{second}'").format(
380+
first = first,
381+
second = second,
382+
))
383+
384+
def _fail_multiple_defaults_python_version_env(first, second):
385+
fail(("Multiple python_version_env entries in defaults: " +
386+
"First default was python_version_env '{first}'. " +
387+
"Second was python_version_env '{second}'").format(
388+
first = first,
389+
second = second,
390+
))
391+
308392
def _fail_multiple_default_toolchains(first, second):
309393
fail(("Multiple default toolchains: only one toolchain " +
310394
"can have is_default=True. First default " +
@@ -526,6 +610,21 @@ def _get_toolchain_config(*, modules, _fail = fail):
526610
register_all_versions = register_all_versions,
527611
)
528612

613+
def _create_defaults_attr_structs(*, mod):
614+
arg_structs = []
615+
616+
for tag in mod.tags.defaults:
617+
arg_structs.append(_create_defaults_attr_struct(tag = tag))
618+
619+
return arg_structs
620+
621+
def _create_defaults_attr_struct(*, tag):
622+
return struct(
623+
python_version = getattr(tag, "python_version", None),
624+
python_version_env = getattr(tag, "python_version_env", None),
625+
python_version_file = getattr(tag, "python_version_file", None),
626+
)
627+
529628
def _create_toolchain_attr_structs(*, mod, config, seen_versions):
530629
arg_structs = []
531630

@@ -570,6 +669,49 @@ def _get_bazel_version_specific_kwargs():
570669

571670
return kwargs
572671

672+
_defaults = tag_class(
673+
doc = """Tag class to specify the default Python version.""",
674+
attrs = {
675+
"python_version": attr.string(
676+
mandatory = False,
677+
doc = """\
678+
String saying what the default Python version should be. If the string
679+
matches the {attr}`python_version` attribute of a toolchain, this
680+
toolchain is the default version. If this attribute is set, the
681+
{attr}`is_default` attribute of the toolchain is ignored.
682+
683+
:::{versionadded} VERSION_NEXT_FEATURE
684+
:::
685+
""",
686+
),
687+
"python_version_env": attr.string(
688+
mandatory = False,
689+
doc = """\
690+
Environment variable saying what the default Python version should be.
691+
If the string matches the {attr}`python_version` attribute of a
692+
toolchain, this toolchain is the default version. If this attribute is
693+
set, the {attr}`is_default` attribute of the toolchain is ignored.
694+
695+
:::{versionadded} VERSION_NEXT_FEATURE
696+
:::
697+
""",
698+
),
699+
"python_version_file": attr.label(
700+
mandatory = False,
701+
allow_single_file = True,
702+
doc = """\
703+
File saying what the default Python version should be. If the contents
704+
of the file match the {attr}`python_version` attribute of a toolchain,
705+
this toolchain is the default version. If this attribute is set, the
706+
{attr}`is_default` attribute of the toolchain is ignored.
707+
708+
:::{versionadded} VERSION_NEXT_FEATURE
709+
:::
710+
""",
711+
),
712+
},
713+
)
714+
573715
_toolchain = tag_class(
574716
doc = """Tag class used to register Python toolchains.
575717
Use this tag class to register one or more Python toolchains. This class
@@ -653,7 +795,14 @@ error to run with root access instead.
653795
),
654796
"is_default": attr.bool(
655797
mandatory = False,
656-
doc = "Whether the toolchain is the default version",
798+
doc = """\
799+
Whether the toolchain is the default version.
800+
801+
:::{versionchanged} VERSION_NEXT_FEATURE
802+
This setting is ignored if the default version is set using the `defaults`
803+
tag class.
804+
:::
805+
""",
657806
),
658807
"python_version": attr.string(
659808
mandatory = True,
@@ -852,6 +1001,7 @@ python = module_extension(
8521001
""",
8531002
implementation = _python_impl,
8541003
tag_classes = {
1004+
"defaults": _defaults,
8551005
"override": _override,
8561006
"single_version_override": _single_version_override,
8571007
"single_version_platform_override": _single_version_platform_override,

0 commit comments

Comments
 (0)