-
-
Notifications
You must be signed in to change notification settings - Fork 635
Description
🚀 feature request
Apologies if this already exists, I did try my best to search. This largely a request for for guidance as to how to document the current behavior...although I would argue that it "feels" like a bug to me. Happy to open a PR updating the existing example(s) if my understanding below is correct!
Affected Rule
The issue is caused by the rule: `py_library`Description
Some Python packages are only available for certain versions of Python. Therefore, it is common to end up with py_library in certain packages that call requirements(...) on packages that will only correctly resolve if the final py_binary that requests them specifies the correct python_version.
This mostly works great with the "new" Bzlmod-based versions of rules_python. For example, if the default python version is set to 3.13.2 and we have two pip.parse calls (one for 3.13.2 and one for 3.11, for example), then if there is a package that's only available for >=3.13, then I can safely add requirement("some_bleeding_edge_package") to my py_library and so long as I don't deps on that py_library from the wrong py_binary rule, everything will work as expected.
However, the converse is not true. If I instead end up with a package that only supports (e.g.) 3.11, then whenever I create the py_library with deps = [requirement("some_ancient_sdk")], I get a configuration-related error.
While Bazel experts might be able to easily figure out what's going wrong here, users moving to a multi-version setup for the first time currently might encounter this and end up thinking their setup is just not correct.
🔬 Minimal Reproduction
Applying the following patch to the existing multi_python_versions example works:
diff --git a/examples/multi_python_versions/libs/my_lib/BUILD.bazel b/examples/multi_python_versions/libs/my_lib/BUILD.bazel
index 7ff62249..aa51aa8d 100644
--- a/examples/multi_python_versions/libs/my_lib/BUILD.bazel
+++ b/examples/multi_python_versions/libs/my_lib/BUILD.bazel
@@ -1,4 +1,5 @@
load("@pypi//:requirements.bzl", "requirement")
+load("@rules_python//python:py_binary.bzl", "py_binary")
load("@rules_python//python:py_library.bzl", "py_library")
py_library(
@@ -7,3 +8,22 @@ py_library(
visibility = ["@//tests:__pkg__"],
deps = [requirement("websockets")],
)
+
+py_library(
+ name = "lib_39_only",
+ srcs = ["lib_39_only.py"],
+ deps = [requirement("wrapt")],
+)
+
+py_binary(
+ name = "run_39_only",
+ srcs = [
+ "__init__.py",
+ "run_39_only.py",
+ ],
+ python_version = "3.9",
+ deps = [
+ ":lib_39_only",
+ ":my_lib",
+ ],
+)
diff --git a/examples/multi_python_versions/libs/my_lib/lib_39_only.py b/examples/multi_python_versions/libs/my_lib/lib_39_only.py
new file mode 100644
index 00000000..43bc6d92
--- /dev/null
+++ b/examples/multi_python_versions/libs/my_lib/lib_39_only.py
@@ -0,0 +1,5 @@
+import wrapt
+
+
+def example() -> None:
+ assert wrapt.apply_patch is not None
diff --git a/examples/multi_python_versions/libs/my_lib/run_39_only.py b/examples/multi_python_versions/libs/my_lib/run_39_only.py
new file mode 100644
index 00000000..3a58de88
--- /dev/null
+++ b/examples/multi_python_versions/libs/my_lib/run_39_only.py
@@ -0,0 +1,6 @@
+import sys
+from libs.my_lib import lib_39_only
+
+print(sys.version)
+lib_39_only.example()
+print("Done")
diff --git a/examples/multi_python_versions/requirements/requirements_lock_3_9.txt b/examples/multi_python_versions/requirements/requirements_lock_3_9.txt
index 3c696a86..864c9b4a 100644
--- a/examples/multi_python_versions/requirements/requirements_lock_3_9.txt
+++ b/examples/multi_python_versions/requirements/requirements_lock_3_9.txt
@@ -4,6 +4,7 @@
#
# bazel run //requirements:requirements_3_9.update
#
+wrapt==1.17.3
websockets==11.0.3 ; python_full_version > "3.9.1" \
--hash=sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd \
--hash=sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f \however, the additional patch (moving the "extra" dep to 3.10 instead of 3.9)
diff --git a/examples/multi_python_versions/libs/my_lib/BUILD.bazel b/examples/multi_python_versions/libs/my_lib/BUILD.bazel
index aa51aa8d..7c76ad35 100644
--- a/examples/multi_python_versions/libs/my_lib/BUILD.bazel
+++ b/examples/multi_python_versions/libs/my_lib/BUILD.bazel
@@ -21,7 +21,7 @@ py_binary(
"__init__.py",
"run_39_only.py",
],
- python_version = "3.9",
+ python_version = "3.10",
deps = [
":lib_39_only",
":my_lib",
diff --git a/examples/multi_python_versions/requirements/requirements_lock_3_10.txt b/examples/multi_python_versions/requirements/requirements_lock_3_10.txt
index 3a845322..41629112 100644
--- a/examples/multi_python_versions/requirements/requirements_lock_3_10.txt
+++ b/examples/multi_python_versions/requirements/requirements_lock_3_10.txt
@@ -4,6 +4,7 @@
#
# bazel run //requirements:requirements_3_10.update
#
+wrapt==1.17.3
websockets==11.0.3 ; python_full_version > "3.9.1" \
--hash=sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd \
--hash=sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f \
diff --git a/examples/multi_python_versions/requirements/requirements_lock_3_9.txt b/examples/multi_python_versions/requirements/requirements_lock_3_9.txt
index 864c9b4a..3c696a86 100644
--- a/examples/multi_python_versions/requirements/requirements_lock_3_9.txt
+++ b/examples/multi_python_versions/requirements/requirements_lock_3_9.txt
@@ -4,7 +4,6 @@
#
# bazel run //requirements:requirements_3_9.update
#
-wrapt==1.17.3
websockets==11.0.3 ; python_full_version > "3.9.1" \
--hash=sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd \
--hash=sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f \causes the build to break.
🔥 Exception or Error
❯ bazel test //...
ERROR: /home/bruno/.cache/bazel/_bazel_bruno/04c300f6ffb35a37c4dae4e56b1c63c2/external/rules_python++pip+pypi/wrapt/BUILD.bazel:5:12: configurable attribute "actual" in @@rules_python++pip+pypi//wrapt:_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_cp310
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:
--@@rules_python+//python/config_settings:current_config=fail
However, the command above will hide the `bazel config <config id>` message.
This instance of @@rules_python++pip+pypi//wrapt:_no_matching_repository has configuration identifier 53d54b7. To inspect its configuration, run: bazel config 53d54b7.
For more help, see https://bazel.build/docs/configurable-attributes#faq-select-choose-condition.
Use --verbose_failures to see the command lines of failed build steps.
ERROR: Analysis of target '//libs/my_lib:lib_39_only' failed; build aborted: Analysis failed
INFO: Elapsed time: 0.120s, Critical Path: 0.00s
INFO: 1 process: 1 internal.
ERROR: Build did NOT complete successfully
ERROR: No test targets were found, yet testing was requested
Proposed "solution"
Of course, for users using a single requirements.in, this is not an issue, since for such users all packages for which we will explicitly be calling requirement are going to be visible to all version-specific toolchains. However, the magic of being able to support multiple Python versions is partially that we can use packages that are version-specific.
Ideally, @rules_python would only request that a py_library actually resolve its (transitive) deps after transitions are applied, allowing a naive bazel build //... to succeed without having to pepper target_compatible_with everywhere.
Alternative solutions
Based on my (likely very naive) understanding of the rules_python internals, supporting this kind of behavior as requested above is probably very difficult if not impossible.
Of course, fancy industrial users probably can just work around this by hosting their own internal PyPI mirror and "tricking" rules_python by offering a "fake"/"empty" version of any missing packages. However, I'd love for the rules_python docs to reflect a more typical onboarding journey, like that a small new company might take. With that in mind, I'd be happy to consider that manually-marking all py_library that directly deps on a version-specific package is just the "correct solution".
For example, continuing from above, adding appropriate target_compatible_with to the py_library using the "restricted" package works:
diff --git a/examples/multi_python_versions/MODULE.bazel b/examples/multi_python_versions/MODULE.bazel
index 4e4a0473..7efff51d 100644
--- a/examples/multi_python_versions/MODULE.bazel
+++ b/examples/multi_python_versions/MODULE.bazel
@@ -2,6 +2,7 @@ module(
name = "multi_python_versions",
)
+bazel_dep(name = "platforms", version = "1.0.0")
bazel_dep(name = "bazel_skylib", version = "1.7.1")
bazel_dep(name = "rules_python", version = "0.0.0")
local_path_override(
diff --git a/examples/multi_python_versions/libs/my_lib/BUILD.bazel b/examples/multi_python_versions/libs/my_lib/BUILD.bazel
index 7c76ad35..9a299d52 100644
--- a/examples/multi_python_versions/libs/my_lib/BUILD.bazel
+++ b/examples/multi_python_versions/libs/my_lib/BUILD.bazel
@@ -12,6 +12,10 @@ py_library(
py_library(
name = "lib_39_only",
srcs = ["lib_39_only.py"],
+ target_compatible_with = select({
+ "@rules_python//python/config_settings:is_python_3.10": [],
+ "//conditions:default": ["@platforms//:incompatible"],
+ }),
deps = [requirement("wrapt")],
)
Is it correct to update the docs to make this explicit? Or is this something that actually can be "fixed" in principle?
🌍 Your Environment
Operating System:
This should be OS independent (personally repro'd on both Linux and Mac).
Output of bazel version:
Bazelisk version: v1.26.0
Build label: 8.4.2
Build target: @@//src/main/java/com/google/devtools/build/lib/bazel:BazelServer
Build time: Wed Oct 01 16:46:46 2025 (1759337206)
Build timestamp: 1759337206
Build timestamp as int: 1759337206
Rules_python version:
HEAD