Skip to content

Commit 639df2e

Browse files
committed
refactor: make env marker config available through target and flag
1 parent ccbe5dc commit 639df2e

File tree

6 files changed

+186
-68
lines changed

6 files changed

+186
-68
lines changed

docs/api/rules_python/python/config_settings/index.md

+12
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,18 @@ Values:
159159
:::
160160
::::
161161

162+
::::{bzl:flag} pip_env_marker_config
163+
The target that provides the values for pip env marker evaluation.
164+
165+
Default: `//python/config_settings:_pip_env_marker_default_config`
166+
167+
This flag points to a target providing {obj}`EnvMarkerInfo`, which determines
168+
the values used when environment markers are resolved at build time.
169+
170+
:::{versionadded} VERSION_NEXT_FEATURE
171+
:::
172+
::::
173+
162174
::::{bzl:flag} pip_whl
163175
Set what distributions are used in the `pip` integration.
164176

docs/pypi-dependencies.md

+31
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,31 @@ compatible indexes.
391391
This is only supported on `bzlmd`.
392392
```
393393

394+
<!--
395+
396+
TODO: uncomment this when analysis-phase dependency selection is available
397+
398+
#### Customizing requirements resolution
399+
400+
In Python packaging, packages can express dependencies with conditions
401+
using "environment markers", which represent the Python version, OS, etc.
402+
403+
While the PyPI integration provides reasonable defaults to support most
404+
platforms and environment markers, the values it uses can be customized in case
405+
more esoteric configurations are needed.
406+
407+
To customize the values used, you need to do two things:
408+
1. Define a target that returns {obj}`EnvMarkerInfo`
409+
2. Set the {obj}`//python/config_settings:pip_env_marker_config` flag to
410+
the target defined in (1).
411+
412+
The keys and values should be compatible with the [PyPA dependency specifiers
413+
specification](https://packaging.python.org/en/latest/specifications/dependency-specifiers/).
414+
This is not strictly enforced, however, so you can return a subset of keys or
415+
additional keys, which become available during dependency evalution.
416+
417+
-->
418+
394419
(bazel-downloader)=
395420
### Bazel downloader and multi-platform wheel hub repository.
396421

@@ -487,3 +512,9 @@ Bazel will call this file like `cred_helper.sh get` and use the returned JSON to
487512
into whatever HTTP(S) request it performs against `example.com`.
488513

489514
[rfc7617]: https://datatracker.ietf.org/doc/html/rfc7617
515+
516+
<!--
517+
518+
519+
520+
-->

python/config_settings/BUILD.bazel

+7
Original file line numberDiff line numberDiff line change
@@ -220,3 +220,10 @@ string_flag(
220220
define_pypi_internal_flags(
221221
name = "define_pypi_internal_flags",
222222
)
223+
224+
label_flag(
225+
name = "pip_env_marker_config",
226+
build_setting_default = ":_pip_env_marker_default_config",
227+
# NOTE: Only public because it is used in pip hub repos.
228+
visibility = ["//visibility:public"],
229+
)
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""Provider for implementing environment marker values."""
2+
3+
EnvMarkerInfo = provider(
4+
doc = """
5+
The values to use during environment marker evaluation.
6+
7+
:::{seealso}
8+
The {obj}`--//python/config_settings:pip_env_marker_config` flag.
9+
:::
10+
11+
:::{versionadded} VERSION_NEXT_FEATURE
12+
""",
13+
fields = {
14+
"env": """
15+
:type: dict[str, str]
16+
17+
The values to use for environment markers when evaluating an expression.
18+
19+
The keys and values should be compatible with the [PyPA dependency specifiers
20+
specification](https://packaging.python.org/en/latest/specifications/dependency-specifiers/)
21+
22+
Missing values will be set to the specification's defaults or computed using
23+
available toolchain information.
24+
""",
25+
},
26+
)

python/private/pypi/env_marker_setting.bzl

+43-68
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,10 @@
22

33
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
44
load("//python/private:toolchain_types.bzl", "TARGET_TOOLCHAIN_TYPE")
5+
load(":env_marker_info.bzl", "EnvMarkerInfo")
56
load(
67
":pep508_env.bzl",
78
"env_aliases",
8-
"os_name_select_map",
9-
"platform_machine_select_map",
10-
"platform_system_select_map",
11-
"sys_platform_select_map",
129
)
1310
load(":pep508_evaluate.bzl", "evaluate")
1411

@@ -39,69 +36,52 @@ def env_marker_setting(*, name, expression, **kwargs):
3936
_env_marker_setting(
4037
name = name,
4138
expression = expression,
42-
os_name = select(os_name_select_map),
43-
sys_platform = select(sys_platform_select_map),
44-
platform_machine = select(platform_machine_select_map),
45-
platform_system = select(platform_system_select_map),
46-
platform_release = select({
47-
"@platforms//os:osx": "USE_OSX_VERSION_FLAG",
48-
"//conditions:default": "",
49-
}),
5039
**kwargs
5140
)
5241

5342
def _env_marker_setting_impl(ctx):
5443
env = {}
44+
env.update(
45+
ctx.attr._env_marker_config_flag[EnvMarkerInfo].env,
46+
)
5547

5648
runtime = ctx.toolchains[TARGET_TOOLCHAIN_TYPE].py3_runtime
57-
if runtime.interpreter_version_info:
58-
version_info = runtime.interpreter_version_info
59-
env["python_version"] = "{major}.{minor}".format(
60-
major = version_info.major,
61-
minor = version_info.minor,
62-
)
63-
full_version = _format_full_version(version_info)
64-
env["python_full_version"] = full_version
65-
env["implementation_version"] = full_version
66-
else:
67-
env["python_version"] = _get_flag(ctx.attr._python_version_major_minor_flag)
68-
full_version = _get_flag(ctx.attr._python_full_version_flag)
69-
env["python_full_version"] = full_version
70-
env["implementation_version"] = full_version
71-
72-
# We assume cpython if the toolchain doesn't specify because it's most
73-
# likely to be true.
74-
env["implementation_name"] = runtime.implementation_name or "cpython"
75-
env["os_name"] = ctx.attr.os_name
76-
env["sys_platform"] = ctx.attr.sys_platform
77-
env["platform_machine"] = ctx.attr.platform_machine
78-
79-
# The `platform_python_implementation` marker value is supposed to come
80-
# from `platform.python_implementation()`, however, PEP 421 introduced
81-
# `sys.implementation.name` and the `implementation_name` env marker to
82-
# replace it. Per the platform.python_implementation docs, there's now
83-
# essentially just two possible "registered" values: CPython or PyPy.
84-
# Rather than add a field to the toolchain, we just special case the value
85-
# from `sys.implementation.name` to handle the two documented values.
86-
platform_python_impl = runtime.implementation_name
87-
if platform_python_impl == "cpython":
88-
platform_python_impl = "CPython"
89-
elif platform_python_impl == "pypy":
90-
platform_python_impl = "PyPy"
91-
env["platform_python_implementation"] = platform_python_impl
92-
93-
# NOTE: Platform release for Android will be Android version:
94-
# https://peps.python.org/pep-0738/#platform
95-
# Similar for iOS:
96-
# https://peps.python.org/pep-0730/#platform
97-
platform_release = ctx.attr.platform_release
98-
if platform_release == "USE_OSX_VERSION_FLAG":
99-
platform_release = _get_flag(ctx.attr._pip_whl_osx_version_flag)
100-
env["platform_release"] = platform_release
101-
env["platform_system"] = ctx.attr.platform_system
102-
103-
# For lack of a better option, just use an empty string for now.
104-
env["platform_version"] = ""
49+
50+
if "python_version" not in env:
51+
if runtime.interpreter_version_info:
52+
version_info = runtime.interpreter_version_info
53+
env["python_version"] = "{major}.{minor}".format(
54+
major = version_info.major,
55+
minor = version_info.minor,
56+
)
57+
full_version = _format_full_version(version_info)
58+
env["python_full_version"] = full_version
59+
env["implementation_version"] = full_version
60+
else:
61+
env["python_version"] = _get_flag(ctx.attr._python_version_major_minor_flag)
62+
full_version = _get_flag(ctx.attr._python_full_version_flag)
63+
env["python_full_version"] = full_version
64+
env["implementation_version"] = full_version
65+
66+
if "implementation_name" not in env:
67+
# We assume cpython if the toolchain doesn't specify because it's most
68+
# likely to be true.
69+
env["implementation_name"] = runtime.implementation_name or "cpython"
70+
71+
if "platform_python_implementation" not in env:
72+
# The `platform_python_implementation` marker value is supposed to come
73+
# from `platform.python_implementation()`, however, PEP 421 introduced
74+
# `sys.implementation.name` and the `implementation_name` env marker to
75+
# replace it. Per the platform.python_implementation docs, there's now
76+
# essentially just two possible "registered" values: CPython or PyPy.
77+
# Rather than add a field to the toolchain, we just special case the value
78+
# from `sys.implementation.name` to handle the two documented values.
79+
platform_python_impl = runtime.implementation_name
80+
if platform_python_impl == "cpython":
81+
platform_python_impl = "CPython"
82+
elif platform_python_impl == "pypy":
83+
platform_python_impl = "PyPy"
84+
env["platform_python_implementation"] = platform_python_impl
10585

10686
env.update(env_aliases())
10787

@@ -125,14 +105,9 @@ for the specification of behavior.
125105
mandatory = True,
126106
doc = "Environment marker expression to evaluate.",
127107
),
128-
"os_name": attr.string(),
129-
"platform_machine": attr.string(),
130-
"platform_release": attr.string(),
131-
"platform_system": attr.string(),
132-
"sys_platform": attr.string(),
133-
"_pip_whl_osx_version_flag": attr.label(
134-
default = "//python/config_settings:pip_whl_osx_version",
135-
providers = [[BuildSettingInfo], [config_common.FeatureFlagInfo]],
108+
"_env_marker_config_flag": attr.label(
109+
default = "//python/config_settings:pip_env_marker_config",
110+
providers = [EnvMarkerInfo],
136111
),
137112
"_python_full_version_flag": attr.label(
138113
default = "//python/config_settings:python_version",

python/private/pypi/flags.bzl

+67
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ unnecessary files when all that are needed are flag definitions.
2020

2121
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo", "string_flag")
2222
load("//python/private:enum.bzl", "enum")
23+
load(":env_marker_info.bzl", "EnvMarkerInfo")
24+
load(
25+
":pep508_env.bzl",
26+
"os_name_select_map",
27+
"platform_machine_select_map",
28+
"platform_system_select_map",
29+
"sys_platform_select_map",
30+
)
2331

2432
# Determines if we should use whls for third party
2533
#
@@ -82,6 +90,10 @@ def define_pypi_internal_flags(name):
8290
visibility = ["//visibility:public"],
8391
)
8492

93+
_default_env_marker_config(
94+
name = "_pip_env_marker_default_config",
95+
)
96+
8597
def _allow_wheels_flag_impl(ctx):
8698
input = ctx.attr._setting[BuildSettingInfo].value
8799
value = "yes" if input in ["auto", "only"] else "no"
@@ -97,3 +109,58 @@ This rule allows us to greatly reduce the number of config setting targets at no
97109
if we are duplicating some of the functionality of the `native.config_setting`.
98110
""",
99111
)
112+
113+
def _default_env_marker_config(**kwargs):
114+
_env_marker_config(
115+
os_name = select(os_name_select_map),
116+
sys_platform = select(sys_platform_select_map),
117+
platform_machine = select(platform_machine_select_map),
118+
platform_system = select(platform_system_select_map),
119+
platform_release = select({
120+
"@platforms//os:osx": "USE_OSX_VERSION_FLAG",
121+
"//conditions:default": "",
122+
}),
123+
**kwargs
124+
)
125+
126+
def _env_marker_config_impl(ctx):
127+
env = {}
128+
env["os_name"] = ctx.attr.os_name
129+
env["sys_platform"] = ctx.attr.sys_platform
130+
env["platform_machine"] = ctx.attr.platform_machine
131+
132+
# NOTE: Platform release for Android will be Android version:
133+
# https://peps.python.org/pep-0738/#platform
134+
# Similar for iOS:
135+
# https://peps.python.org/pep-0730/#platform
136+
platform_release = ctx.attr.platform_release
137+
if platform_release == "USE_OSX_VERSION_FLAG":
138+
platform_release = _get_flag(ctx.attr._pip_whl_osx_version_flag)
139+
env["platform_release"] = platform_release
140+
env["platform_system"] = ctx.attr.platform_system
141+
142+
# For lack of a better option, just use an empty string for now.
143+
env["platform_version"] = ""
144+
return [EnvMarkerInfo(env = env)]
145+
146+
_env_marker_config = rule(
147+
implementation = _env_marker_config_impl,
148+
attrs = {
149+
"os_name": attr.string(),
150+
"platform_machine": attr.string(),
151+
"platform_release": attr.string(),
152+
"platform_system": attr.string(),
153+
"sys_platform": attr.string(),
154+
"_pip_whl_osx_version_flag": attr.label(
155+
default = "//python/config_settings:pip_whl_osx_version",
156+
providers = [[BuildSettingInfo], [config_common.FeatureFlagInfo]],
157+
),
158+
},
159+
)
160+
161+
def _get_flag(t):
162+
if config_common.FeatureFlagInfo in t:
163+
return t[config_common.FeatureFlagInfo].value
164+
if BuildSettingInfo in t:
165+
return t[BuildSettingInfo].value
166+
fail("Should not occur: {} does not have necessary providers")

0 commit comments

Comments
 (0)