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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy