feat(pypi): pip.defaults API for customizing repo selection 2/n (#2988)

WIP: stacked on #2987

This is adding `constraint_values` attribute to `pip.configure` and is
threading it all the way down to the generation of `BUILD.bazel` file of
for
config settings used in the hub repository.

Out of scope:
- Passing `flag_values` or target settings. I am torn about it - doing
it in
this PR would flesh out the design more, but at the same time it might
become
  harder to review.
- `whl_target_platforms` and `select_whls` is still unchanged, not sure
if it
  is related to this attribute addition.

Work towards #2747
Work towards #2548 
Work towards #260

---------

Co-authored-by: Richard Levasseur <richardlev@gmail.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9897dc9..da3dcc8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -64,7 +64,7 @@
 ### Added
 * (pypi) To configure the environment for `requirements.txt` evaluation, use the newly added
   developer preview of the `pip.default` tag class. Only `rules_python` and root modules can use
-  this feature.
+  this feature. You can also configure `constraint_values` using `pip.default`.
 
 {#v0-0-0-removed}
 ### Removed
diff --git a/python/private/pypi/config_settings.bzl b/python/private/pypi/config_settings.bzl
index 3e828e5..7edc578 100644
--- a/python/private/pypi/config_settings.bzl
+++ b/python/private/pypi/config_settings.bzl
@@ -111,8 +111,8 @@
         glibc_versions = [],
         muslc_versions = [],
         osx_versions = [],
-        target_platforms = [],
         name = None,
+        platform_constraint_values = {},
         **kwargs):
     """Generate all of the pip config settings.
 
@@ -126,8 +126,10 @@
             configure config settings for.
         osx_versions (list[str]): The list of OSX OS versions to configure
             config settings for.
-        target_platforms (list[str]): The list of "{os}_{cpu}" for deriving
-            constraint values for each condition.
+        platform_constraint_values: {type}`dict[str, list[str]]` the constraint
+            values to use instead of the default ones. Key are platform names
+            (a human-friendly platform string). Values are lists of
+            `constraint_value` label strings.
         **kwargs: Other args passed to the underlying implementations, such as
             {obj}`native`.
     """
@@ -135,22 +137,17 @@
     glibc_versions = [""] + glibc_versions
     muslc_versions = [""] + muslc_versions
     osx_versions = [""] + osx_versions
-    target_platforms = [("", ""), ("osx", "universal2")] + [
-        t.split("_", 1)
-        for t in target_platforms
-    ]
+    target_platforms = {
+        "": [],
+        # TODO @aignas 2025-06-15: allowing universal2 and platform specific wheels in one
+        # closure is making things maybe a little bit too complicated.
+        "osx_universal2": ["@platforms//os:osx"],
+    } | platform_constraint_values
 
     for python_version in python_versions:
-        for os, cpu in target_platforms:
-            constraint_values = []
-            suffix = ""
-            if os:
-                constraint_values.append("@platforms//os:" + os)
-                suffix += "_" + os
-            if cpu:
-                suffix += "_" + cpu
-                if cpu != "universal2":
-                    constraint_values.append("@platforms//cpu:" + cpu)
+        for platform_name, constraint_values in target_platforms.items():
+            suffix = "_{}".format(platform_name) if platform_name else ""
+            os, _, cpu = platform_name.partition("_")
 
             _dist_config_settings(
                 suffix = suffix,
diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl
index 97b6825..78511b4 100644
--- a/python/private/pypi/extension.bzl
+++ b/python/private/pypi/extension.bzl
@@ -372,7 +372,7 @@
         ),
     )
 
-def _configure(config, *, platform, os_name, arch_name, override = False, env = {}):
+def _configure(config, *, platform, os_name, arch_name, constraint_values, env = {}, override = False):
     """Set the value in the config if the value is provided"""
     config.setdefault("platforms", {})
     if platform:
@@ -387,6 +387,7 @@
             name = platform.replace("-", "_").lower(),
             os_name = os_name,
             arch_name = arch_name,
+            constraint_values = constraint_values,
             env = env,
         )
     else:
@@ -413,6 +414,10 @@
             arch_name = cpu,
             os_name = "linux",
             platform = "linux_{}".format(cpu),
+            constraint_values = [
+                "@platforms//os:linux",
+                "@platforms//cpu:{}".format(cpu),
+            ],
             env = {"platform_version": "0"},
         )
     for cpu in [
@@ -424,17 +429,25 @@
             arch_name = cpu,
             # We choose the oldest non-EOL version at the time when we release `rules_python`.
             # See https://endoflife.date/macos
-            env = {"platform_version": "14.0"},
             os_name = "osx",
             platform = "osx_{}".format(cpu),
+            constraint_values = [
+                "@platforms//os:osx",
+                "@platforms//cpu:{}".format(cpu),
+            ],
+            env = {"platform_version": "14.0"},
         )
 
     _configure(
         defaults,
         arch_name = "x86_64",
-        env = {"platform_version": "0"},
         os_name = "windows",
         platform = "windows_x86_64",
+        constraint_values = [
+            "@platforms//os:windows",
+            "@platforms//cpu:x86_64",
+        ],
+        env = {"platform_version": "0"},
     )
     return struct(**defaults)
 
@@ -500,6 +513,7 @@
             _configure(
                 defaults,
                 arch_name = tag.arch_name,
+                constraint_values = tag.constraint_values,
                 env = tag.env,
                 os_name = tag.os_name,
                 platform = tag.platform,
@@ -679,6 +693,13 @@
             }
             for hub_name, extra_whl_aliases in extra_aliases.items()
         },
+        platform_constraint_values = {
+            hub_name: {
+                platform_name: sorted([str(Label(cv)) for cv in p.constraint_values])
+                for platform_name, p in config.platforms.items()
+            }
+            for hub_name in hub_whl_map
+        },
         whl_libraries = {
             k: dict(sorted(args.items()))
             for k, args in sorted(whl_libraries.items())
@@ -769,6 +790,7 @@
                 for key, values in whl_map.items()
             },
             packages = mods.exposed_packages.get(hub_name, []),
+            platform_constraint_values = mods.platform_constraint_values.get(hub_name, {}),
             groups = mods.hub_group_map.get(hub_name),
         )
 
@@ -790,6 +812,12 @@
 :::
 """,
     ),
+    "constraint_values": attr.label_list(
+        mandatory = True,
+        doc = """\
+The constraint_values to use in select statements.
+""",
+    ),
     "os_name": attr.string(
         doc = """\
 The OS name to be used.
diff --git a/python/private/pypi/hub_repository.bzl b/python/private/pypi/hub_repository.bzl
index 0dbc6c2..4398d7b 100644
--- a/python/private/pypi/hub_repository.bzl
+++ b/python/private/pypi/hub_repository.bzl
@@ -34,6 +34,7 @@
         },
         extra_hub_aliases = rctx.attr.extra_hub_aliases,
         requirement_cycles = rctx.attr.groups,
+        platform_constraint_values = rctx.attr.platform_constraint_values,
     )
     for path, contents in aliases.items():
         rctx.file(path, contents)
@@ -83,6 +84,10 @@
 The list of packages that will be exposed via all_*requirements macros. Defaults to whl_map keys.
 """,
         ),
+        "platform_constraint_values": attr.string_list_dict(
+            doc = "The constraint values for each platform name. The values are string canonical string Label representations",
+            mandatory = False,
+        ),
         "repo_name": attr.string(
             mandatory = True,
             doc = "The apparent name of the repo. This is needed because in bzlmod, the name attribute becomes the canonical name.",
diff --git a/python/private/pypi/render_pkg_aliases.bzl b/python/private/pypi/render_pkg_aliases.bzl
index 28f32ed..267d7ce 100644
--- a/python/private/pypi/render_pkg_aliases.bzl
+++ b/python/private/pypi/render_pkg_aliases.bzl
@@ -155,12 +155,14 @@
     # Use a dict as a simple set
     return sorted({_major_minor(v): None for v in python_versions})
 
-def render_multiplatform_pkg_aliases(*, aliases, **kwargs):
+def render_multiplatform_pkg_aliases(*, aliases, platform_constraint_values = {}, **kwargs):
     """Render the multi-platform pkg aliases.
 
     Args:
         aliases: dict[str, list(whl_config_setting)] A list of aliases that will be
           transformed from ones having `filename` to ones having `config_setting`.
+        platform_constraint_values: {type}`dict[str, list[str]]` contains all of the
+            target platforms and their appropriate `constraint_values`.
         **kwargs: extra arguments passed to render_pkg_aliases.
 
     Returns:
@@ -187,18 +189,22 @@
         muslc_versions = flag_versions.get("muslc_versions", []),
         osx_versions = flag_versions.get("osx_versions", []),
         python_versions = _major_minor_versions(flag_versions.get("python_versions", [])),
-        target_platforms = flag_versions.get("target_platforms", []),
+        platform_constraint_values = platform_constraint_values,
         visibility = ["//:__subpackages__"],
     )
     return contents
 
-def _render_config_settings(**kwargs):
+def _render_config_settings(platform_constraint_values, **kwargs):
     return """\
 load("@rules_python//python/private/pypi:config_settings.bzl", "config_settings")
 
 {}""".format(render.call(
         "config_settings",
         name = repr("config_settings"),
+        platform_constraint_values = render.dict(
+            platform_constraint_values,
+            value_repr = render.list,
+        ),
         **_repr_dict(value_repr = render.list, **kwargs)
     ))
 
diff --git a/tests/pypi/config_settings/config_settings_tests.bzl b/tests/pypi/config_settings/config_settings_tests.bzl
index f111d0c..9551d42 100644
--- a/tests/pypi/config_settings/config_settings_tests.bzl
+++ b/tests/pypi/config_settings/config_settings_tests.bzl
@@ -657,13 +657,34 @@
         glibc_versions = [(2, 14), (2, 17)],
         muslc_versions = [(1, 1)],
         osx_versions = [(10, 9), (11, 0)],
-        target_platforms = [
-            "windows_x86_64",
-            "windows_aarch64",
-            "linux_x86_64",
-            "linux_ppc",
-            "linux_aarch64",
-            "osx_x86_64",
-            "osx_aarch64",
-        ],
+        platform_constraint_values = {
+            "linux_aarch64": [
+                "@platforms//cpu:aarch64",
+                "@platforms//os:linux",
+            ],
+            "linux_ppc": [
+                "@platforms//cpu:ppc",
+                "@platforms//os:linux",
+            ],
+            "linux_x86_64": [
+                "@platforms//cpu:x86_64",
+                "@platforms//os:linux",
+            ],
+            "osx_aarch64": [
+                "@platforms//cpu:aarch64",
+                "@platforms//os:osx",
+            ],
+            "osx_x86_64": [
+                "@platforms//cpu:x86_64",
+                "@platforms//os:osx",
+            ],
+            "windows_aarch64": [
+                "@platforms//cpu:aarch64",
+                "@platforms//os:windows",
+            ],
+            "windows_x86_64": [
+                "@platforms//cpu:x86_64",
+                "@platforms//os:windows",
+            ],
+        },
     )
diff --git a/tests/pypi/extension/extension_tests.bzl b/tests/pypi/extension/extension_tests.bzl
index 3d205a2..231e8ca 100644
--- a/tests/pypi/extension/extension_tests.bzl
+++ b/tests/pypi/extension/extension_tests.bzl
@@ -1051,6 +1051,10 @@
                 default = [
                     _default(
                         platform = "{}_{}".format(os, cpu),
+                        constraint_values = [
+                            "@platforms//os:{}".format(os),
+                            "@platforms//cpu:{}".format(cpu),
+                        ],
                     )
                     for os, cpu in [
                         ("linux", "x86_64"),
diff --git a/tests/pypi/pkg_aliases/pkg_aliases_test.bzl b/tests/pypi/pkg_aliases/pkg_aliases_test.bzl
index 71ca811..0fbcd4e 100644
--- a/tests/pypi/pkg_aliases/pkg_aliases_test.bzl
+++ b/tests/pypi/pkg_aliases/pkg_aliases_test.bzl
@@ -419,10 +419,16 @@
             alias = _mock_alias(available_config_settings),
             config_setting = _mock_config_setting(available_config_settings),
         ),
-        target_platforms = [
-            "linux_aarch64",
-            "linux_x86_64",
-        ],
+        platform_constraint_values = {
+            "linux_aarch64": [
+                "@platforms//cpu:aarch64",
+                "@platforms//os:linux",
+            ],
+            "linux_x86_64": [
+                "@platforms//cpu:x86_64",
+                "@platforms//os:linux",
+            ],
+        },
     )
 
     got_aliases = multiplatform_whl_aliases(
@@ -448,19 +454,39 @@
                 "any": {},
                 "macosx_11_0_arm64": {
                     "osx_versions": [(11, 0)],
-                    "target_platforms": ["osx_aarch64"],
+                    "platform_constraint_values": {
+                        "osx_aarch64": [
+                            "@platforms//cpu:aarch64",
+                            "@platforms//os:osx",
+                        ],
+                    },
                 },
                 "manylinux_2_17_x86_64": {
                     "glibc_versions": [(2, 17), (2, 18)],
-                    "target_platforms": ["linux_x86_64"],
+                    "platform_constraint_values": {
+                        "linux_x86_64": [
+                            "@platforms//cpu:x86_64",
+                            "@platforms//os:linux",
+                        ],
+                    },
                 },
                 "manylinux_2_18_x86_64": {
                     "glibc_versions": [(2, 17), (2, 18)],
-                    "target_platforms": ["linux_x86_64"],
+                    "platform_constraint_values": {
+                        "linux_x86_64": [
+                            "@platforms//cpu:x86_64",
+                            "@platforms//os:linux",
+                        ],
+                    },
                 },
                 "musllinux_1_1_aarch64": {
                     "muslc_versions": [(1, 2), (1, 1), (1, 0)],
-                    "target_platforms": ["linux_aarch64"],
+                    "platform_constraint_values": {
+                        "linux_aarch64": [
+                            "@platforms//cpu:aarch64",
+                            "@platforms//os:linux",
+                        ],
+                    },
                 },
             }.items():
                 aliases = {
diff --git a/tests/pypi/render_pkg_aliases/render_pkg_aliases_test.bzl b/tests/pypi/render_pkg_aliases/render_pkg_aliases_test.bzl
index 416d50b..c262ed6 100644
--- a/tests/pypi/render_pkg_aliases/render_pkg_aliases_test.bzl
+++ b/tests/pypi/render_pkg_aliases/render_pkg_aliases_test.bzl
@@ -93,6 +93,12 @@
             },
         },
         extra_hub_aliases = {"bar_baz": ["foo"]},
+        platform_constraint_values = {
+            "linux_x86_64": [
+                "@platforms//os:linux",
+                "@platforms//cpu:x86_64",
+            ],
+        },
     )
 
     want_key = "bar_baz/BUILD.bazel"
@@ -130,8 +136,13 @@
 
 config_settings(
     name = "config_settings",
+    platform_constraint_values = {
+        "linux_x86_64": [
+            "@platforms//os:linux",
+            "@platforms//cpu:x86_64",
+        ],
+    },
     python_versions = ["3.2"],
-    target_platforms = ["linux_x86_64"],
     visibility = ["//:__subpackages__"],
 )""",
     )