Skip to content

Commit 866ab7d

Browse files
committed
doc(pypi): A better error message when the wheel select hits no_match
With this change we get the current values of the python configuration values printed in addition to the message printed previously. This should help us advise users who don't have their builds configured correctly. We are adding an extra `build_setting` which we can set in order to get an error message instead of a `DEBUG` warning. This has been documented as part of our config settings and in the `no_match_error` in the `select` statement. Example output now ```console $ bazel cquery --@rules_python//python/config_settings:python_version=3.12 @dev_pip//sphinx DEBUG: /home/aignas/src/github/aignas/rules_python/python/private/config_settings.bzl:193:14: The current configuration rules_python config flags is: @@//python/config_settings:pip_whl: "auto" @@//python/config_settings:pip_whl_glibc_version: "" @@//python/config_settings:pip_whl_muslc_version: "" @@//python/config_settings:pip_whl_osx_arch: "arch" @@//python/config_settings:pip_whl_osx_version: "" @@//python/config_settings:py_freethreaded: "no" @@//python/config_settings:py_linux_libc: "glibc" @@//python/config_settings:python_version: "3.12" If the value is missing, then the default value is being used, see documentation: https://rules-python.readthedocs.io/en/latest/api/rules_python/python/config_settings ERROR: /home/aignas/.cache/bazel/_bazel_aignas/6f0de8c9128ee8d5dbf27ba6dcc48bdd/external/+pip+dev_pip/sphinx/BUILD.bazel:6:12: configurable attribute "actual" in @@+pip+dev_pip//sphinx:_no_matching_repository doesn't match this configuration: No matching wheel for current configuration's Python version. The current build configuration's Python version doesn't match any of the Python wheels available for this distribution. This distribution supports the following Python configuration settings: //_config:is_cp3.11_py3_none_any //_config:is_cp3.13_py3_none_any To determine the current configuration's Python version, run: `bazel config <config id>` (shown further below) For the current configuration value see the debug message above that is printing the current flag values. If you can't see the message, then re-run the build to make it a failure instead by running the build with: --@@//python/config_settings:current_config=fail However, the command above will hide the `bazel config <config id>` message. This instance of @@+pip+dev_pip//sphinx:_no_matching_repository has configuration identifier 29ffcf8. To inspect its configuration, run: bazel config 29ffcf8. For more help, see https://bazel.build/docs/configurable-attributes#faq-select-choose-condition. ERROR: Analysis of target '@@+pip+dev_pip//sphinx:sphinx' failed; build aborted: Analysis failed INFO: Elapsed time: 0.112s INFO: 0 processes. ERROR: Build did NOT complete successfully ``` Fixes #2466
1 parent 66a8b5b commit 866ab7d

File tree

6 files changed

+176
-66
lines changed

6 files changed

+176
-66
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ Unreleased changes template.
7070
* (pypi) Using {bzl:obj}`pip_parse.experimental_requirement_cycles` and
7171
{bzl:obj}`pip_parse.use_hub_alias_dependencies` together now works when
7272
using WORKSPACE files.
73+
* The error messages when the wheel distributions do not match anything
74+
are now printing more details and include the currently active flag
75+
values. Fixes [#2466](https://github.com/bazelbuild/rules_python/issues/2466).
7376

7477
[pep-695]: https://peps.python.org/pep-0695/
7578

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,3 +240,21 @@ instead.
240240
:::
241241

242242
::::
243+
244+
::::{bzl:flag} current_config
245+
Fail the build if the current build configuration does not match the
246+
{obj}`pip.parse` defined wheels.
247+
248+
Values:
249+
* `fail`: Will fail in the build action ensuring that we get the error
250+
message no matter the action cache.
251+
* ``: The default value, that will just print a warning.
252+
253+
:::{seealso}
254+
{obj}`pip.parse`
255+
:::
256+
257+
:::{versionadded} 1.1.0
258+
:::
259+
260+
::::

python/config_settings/BUILD.bazel

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@ filegroup(
2929
construct_config_settings(
3030
name = "construct_config_settings",
3131
default_version = DEFAULT_PYTHON_VERSION,
32+
documented_flags = [
33+
":pip_whl",
34+
":pip_whl_glibc_version",
35+
":pip_whl_muslc_version",
36+
":pip_whl_osx_arch",
37+
":pip_whl_osx_version",
38+
":py_freethreaded",
39+
":py_linux_libc",
40+
],
3241
minor_mapping = MINOR_MAPPING,
3342
versions = PYTHON_VERSIONS,
3443
)

python/private/config_settings.bzl

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,21 @@
1717

1818
load("@bazel_skylib//lib:selects.bzl", "selects")
1919
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
20+
load("//python/private:text_util.bzl", "render")
2021
load(":semver.bzl", "semver")
2122

2223
_PYTHON_VERSION_FLAG = Label("//python/config_settings:python_version")
2324
_PYTHON_VERSION_MAJOR_MINOR_FLAG = Label("//python/config_settings:python_version_major_minor")
2425

25-
def construct_config_settings(*, name, default_version, versions, minor_mapping): # buildifier: disable=function-docstring
26+
_DEBUG_ENV_MESSAGE_TEMPLATE = """\
27+
The current configuration rules_python config flags is:
28+
{flags}
29+
30+
If the value is missing, then the default value is being used, see documentation:
31+
{docs_url}/python/config_settings
32+
"""
33+
34+
def construct_config_settings(*, name, default_version, versions, minor_mapping, documented_flags): # buildifier: disable=function-docstring
2635
"""Create a 'python_version' config flag and construct all config settings used in rules_python.
2736
2837
This mainly includes the targets that are used in the toolchain and pip hub
@@ -33,6 +42,8 @@ def construct_config_settings(*, name, default_version, versions, minor_mapping)
3342
default_version: {type}`str` the default value for the `python_version` flag.
3443
versions: {type}`list[str]` A list of versions to build constraint settings for.
3544
minor_mapping: {type}`dict[str, str]` A mapping from `X.Y` to `X.Y.Z` python versions.
45+
documented_flags: {type}`list[str]` The labels of the documented settings
46+
that affect build configuration.
3647
"""
3748
_ = name # @unused
3849
_python_version_flag(
@@ -44,6 +55,7 @@ def construct_config_settings(*, name, default_version, versions, minor_mapping)
4455
_python_version_major_minor_flag(
4556
name = _PYTHON_VERSION_MAJOR_MINOR_FLAG.name,
4657
build_setting_default = "",
58+
python_version_flag = _PYTHON_VERSION_FLAG,
4759
visibility = ["//visibility:public"],
4860
)
4961

@@ -101,6 +113,25 @@ def construct_config_settings(*, name, default_version, versions, minor_mapping)
101113
visibility = ["//visibility:public"],
102114
)
103115

116+
_current_config(
117+
name = "current_config",
118+
build_setting_default = "",
119+
settings = documented_flags + [_PYTHON_VERSION_FLAG.name],
120+
visibility = ["//visibility:private"],
121+
)
122+
native.config_setting(
123+
name = "is_not_matching_current_config",
124+
# We use the rule above instead of @platforms//:incompatible so that the
125+
# printing of the current env always happens when the _current_config rule
126+
# is executed.
127+
#
128+
# NOTE: This should in practise only happen if there is a missing compatible
129+
# `whl_library` in the hub repo created by `pip.parse`.
130+
flag_values = {"current_config": "will-never-match"},
131+
# Only public so that PyPI hub repo can access it
132+
visibility = ["//visibility:public"],
133+
)
134+
104135
def _python_version_flag_impl(ctx):
105136
value = ctx.build_setting_value
106137
return [
@@ -122,7 +153,7 @@ _python_version_flag = rule(
122153
)
123154

124155
def _python_version_major_minor_flag_impl(ctx):
125-
input = ctx.attr._python_version_flag[config_common.FeatureFlagInfo].value
156+
input = _flag_value(ctx.attr.python_version_flag)
126157
if input:
127158
version = semver(input)
128159
value = "{}.{}".format(version.major, version.minor)
@@ -135,8 +166,45 @@ _python_version_major_minor_flag = rule(
135166
implementation = _python_version_major_minor_flag_impl,
136167
build_setting = config.string(flag = False),
137168
attrs = {
138-
"_python_version_flag": attr.label(
139-
default = _PYTHON_VERSION_FLAG,
140-
),
169+
"python_version_flag": attr.label(mandatory = True),
170+
},
171+
)
172+
173+
def _flag_value(s):
174+
if config_common.FeatureFlagInfo in s:
175+
return s[config_common.FeatureFlagInfo].value
176+
else:
177+
return s[BuildSettingInfo].value
178+
179+
def _print_current_config_impl(ctx):
180+
flags = "\n".join([
181+
"{}: \"{}\"".format(k, v)
182+
for k, v in sorted({
183+
str(setting.label): _flag_value(setting)
184+
for setting in ctx.attr.settings
185+
}.items())
186+
])
187+
188+
msg = ctx.attr._template.format(
189+
docs_url = "https://rules-python.readthedocs.io/en/latest/api/rules_python",
190+
flags = render.indent(flags).lstrip(),
191+
)
192+
if ctx.build_setting_value and ctx.build_setting_value != "fail":
193+
fail("Only 'fail' and empty build setting values are allowed for {}".format(
194+
str(ctx.label),
195+
))
196+
elif ctx.build_setting_value:
197+
fail(msg)
198+
else:
199+
print(msg) # buildifier: disable=print
200+
201+
return [config_common.FeatureFlagInfo(value = "")]
202+
203+
_current_config = rule(
204+
implementation = _print_current_config_impl,
205+
build_setting = config.string(flag = True),
206+
attrs = {
207+
"settings": attr.label_list(mandatory = True),
208+
"_template": attr.string(default = _DEBUG_ENV_MESSAGE_TEMPLATE),
141209
},
142210
)

python/private/pypi/pkg_aliases.bzl

Lines changed: 32 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ load(":whl_target_platforms.bzl", "whl_target_platforms")
3636
# it. It is more of an internal consistency check.
3737
_VERSION_NONE = (0, 0)
3838

39-
_CONFIG_SETTINGS_PKG = str(Label("//python/config_settings:BUILD.bazel")).partition(":")[0]
40-
4139
_NO_MATCH_ERROR_TEMPLATE = """\
4240
No matching wheel for current configuration's Python version.
4341
@@ -49,37 +47,18 @@ configuration settings:
4947
To determine the current configuration's Python version, run:
5048
`bazel config <config id>` (shown further below)
5149
52-
and look for one of:
53-
{settings_pkg}:python_version
54-
{settings_pkg}:pip_whl
55-
{settings_pkg}:pip_whl_glibc_version
56-
{settings_pkg}:pip_whl_muslc_version
57-
{settings_pkg}:pip_whl_osx_arch
58-
{settings_pkg}:pip_whl_osx_version
59-
{settings_pkg}:py_freethreaded
60-
{settings_pkg}:py_linux_libc
61-
62-
If the value is missing, then the default value is being used, see documentation:
63-
{docs_url}/python/config_settings"""
64-
65-
def _no_match_error(actual):
66-
if type(actual) != type({}):
67-
return None
68-
69-
if "//conditions:default" in actual:
70-
return None
71-
72-
return _NO_MATCH_ERROR_TEMPLATE.format(
73-
config_settings = render.indent(
74-
"\n".join(sorted([
75-
value
76-
for key in actual
77-
for value in (key if type(key) == "tuple" else [key])
78-
])),
79-
).lstrip(),
80-
settings_pkg = _CONFIG_SETTINGS_PKG,
81-
docs_url = "https://rules-python.readthedocs.io/en/latest/api/rules_python",
82-
)
50+
For the current configuration value see the debug message above that is
51+
printing the current flag values. If you can't see the message, then re-run the
52+
build to make it a failure instead by running the build with:
53+
--{current_flags}=fail
54+
55+
However, the command above will hide the `bazel config <config id>` message.
56+
"""
57+
58+
_LABEL_NONE = Label("//python:none")
59+
_LABEL_CURRENT_CONFIG = Label("//python/config_settings:current_config")
60+
_LABEL_CURRENT_CONFIG_NO_MATCH = Label("//python/config_settings:is_not_matching_current_config")
61+
_INCOMPATIBLE = "_no_matching_repository"
8362

8463
def pkg_aliases(
8564
*,
@@ -120,7 +99,25 @@ def pkg_aliases(
12099
}
121100

122101
actual = multiplatform_whl_aliases(aliases = actual, **kwargs)
123-
no_match_error = _no_match_error(actual)
102+
if type(actual) == type({}) and "//conditions:default" not in actual:
103+
native.alias(
104+
name = _INCOMPATIBLE,
105+
actual = select(
106+
{_LABEL_CURRENT_CONFIG_NO_MATCH: _LABEL_NONE},
107+
no_match_error = _NO_MATCH_ERROR_TEMPLATE.format(
108+
config_settings = render.indent(
109+
"\n".join(sorted([
110+
value
111+
for key in actual
112+
for value in (key if type(key) == "tuple" else [key])
113+
])),
114+
).lstrip(),
115+
current_flags = str(_LABEL_CURRENT_CONFIG),
116+
),
117+
),
118+
visibility = ["//visibility:private"],
119+
)
120+
actual["//conditions:default"] = _INCOMPATIBLE
124121

125122
for name, target_name in target_names.items():
126123
if type(actual) == type(""):
@@ -134,10 +131,9 @@ def pkg_aliases(
134131
v: "@{repo}//:{target_name}".format(
135132
repo = repo,
136133
target_name = name,
137-
)
134+
) if repo != _INCOMPATIBLE else repo
138135
for v, repo in actual.items()
139136
},
140-
no_match_error = no_match_error,
141137
)
142138
else:
143139
fail("The `actual` arg must be a dictionary or a string")

0 commit comments

Comments
 (0)