feat(pypi): enable pipstar by default (#3225)

Before this PR we were using our python helpers to parse the whl
METADATA for a
particular host (or experimentally - target) platform and the fixed
dependency
list would be written to the `BUILD.bazel` files materialized by the
`whl_library` repository rule.

This PR adds extra plumbing to leverage the Starlark implementation of
the
METADATA file parsing (a.k.a. pipstar) which moves the evaluation to
analysis
phase as we will get the target platform details via the
`env_marker_config_setting` from the `toolchain` used for the
dependencies.

Since users are normally working with a subset of platforms that the
METADATA
would pull in, we are writing the list of packages from the requirements
file to
a `bzl` file so that `pipstar` knows which packages to consider when
parsing the
METADATA. This will ensure that `bazel query` continues to work.

Since just passing the list of packages to the `whl_library` would cause
refetches of all of the `whl_library` dependencies when we add or remove
a
package, we pass a path where to load the symbol called `packages` from,
which
means that only the analysis phase will be affected because the macros
will be
re-evaluated if one adds or removes a package.

We still need to download the correct platform-specific wheels for the
right
platform in order to fully resolve the referenced tickets. With this PR
we are
deprecating the `experimental_target_platforms` attribute since it has
no longer
any effect.

Fixes #2949
Work towards #260
Work towards #2241

---------

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 34f658c..cc59e38 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -74,6 +74,14 @@
 * (toolchains) `py_runtime` and `PyRuntimeInfo` reject Python 2 settings.
   Setting `py_runtime.python_version = "PY2"` or non-None
   `PyRuntimeInfo.py2_runtime` is an error.
+* (pypi) `pipstar` flag has been flipped to be enabled by default, to turn it
+  off use `RULES_PYTHON_ENABLE_PIPSTAR=0` environment variable. If you do, please
+  add a comment to
+  [#2949](https://github.com/bazel-contrib/rules_python/issues/2949).
+  With this release we are deprecating {obj}`pip.parse.experimental_target_platforms` and
+  {obj}`pip_repository.experimental_target_platforms`. For users using `WORKSPACE` and
+  vendoring the `requirements.bzl` file, please re-vendor so that downstream is unaffected
+  when the APIs get removed.
 
 {#v0-0-0-fixed}
 ### Fixed
diff --git a/examples/pip_parse_vendored/requirements.bzl b/examples/pip_parse_vendored/requirements.bzl
index ead5c49..f555157 100644
--- a/examples/pip_parse_vendored/requirements.bzl
+++ b/examples/pip_parse_vendored/requirements.bzl
@@ -4,7 +4,7 @@
 """
 
 load("@rules_python//python:pip.bzl", "pip_utils")
-load("@rules_python//python/pip_install:pip_repository.bzl", "group_library", "whl_library")
+load("@rules_python//python/pip_install:pip_repository.bzl", "whl_config_repo", "whl_library")
 
 all_requirements = [
     "@my_project_pip_deps_vendored_certifi//:pkg",
@@ -91,12 +91,17 @@
         for requirement in group_requirements
     }
 
-    group_repo = "my_project_pip_deps_vendored__groups"
-    group_library(
-        name = group_repo,
+    config_repo = "my_project_pip_deps_vendored__config"
+    whl_config_repo(
+        name = "my_project_pip_deps_vendored__config",
         repo_prefix = "my_project_pip_deps_vendored_",
         groups = all_requirement_groups,
+        whl_map = {
+            p: ""
+            for p in all_whl_requirements_by_package
+        },
     )
+    config_load = "@{}//:config.bzl".format(config_repo)
 
     # Install wheels which may be participants in a group
     whl_config = dict(_config)
@@ -112,5 +117,6 @@
             group_name = group_name,
             group_deps = group_deps,
             annotation = _get_annotation(requirement),
+            config_load = config_load,
             **whl_config
         )
diff --git a/python/pip_install/BUILD.bazel b/python/pip_install/BUILD.bazel
index 09bc46e..665375c 100644
--- a/python/pip_install/BUILD.bazel
+++ b/python/pip_install/BUILD.bazel
@@ -22,9 +22,9 @@
     name = "pip_repository_bzl",
     srcs = ["pip_repository.bzl"],
     deps = [
-        "//python/private/pypi:group_library_bzl",
         "//python/private/pypi:package_annotation_bzl",
         "//python/private/pypi:pip_repository_bzl",
+        "//python/private/pypi:whl_config_repo_bzl",
         "//python/private/pypi:whl_library_bzl",
     ],
 )
diff --git a/python/pip_install/pip_repository.bzl b/python/pip_install/pip_repository.bzl
index 18deee1..f9c3c9f 100644
--- a/python/pip_install/pip_repository.bzl
+++ b/python/pip_install/pip_repository.bzl
@@ -14,13 +14,14 @@
 
 ""
 
-load("//python/private/pypi:group_library.bzl", _group_library = "group_library")
 load("//python/private/pypi:package_annotation.bzl", _package_annotation = "package_annotation")
 load("//python/private/pypi:pip_repository.bzl", _pip_repository = "pip_repository")
+load("//python/private/pypi:whl_config_repo.bzl", _whl_config_repo = "whl_config_repo")
 load("//python/private/pypi:whl_library.bzl", _whl_library = "whl_library")
 
 # Re-exports for backwards compatibility
-group_library = _group_library
+group_library = _whl_config_repo
 pip_repository = _pip_repository
 whl_library = _whl_library
+whl_config_repo = _whl_config_repo
 package_annotation = _package_annotation
diff --git a/python/private/internal_config_repo.bzl b/python/private/internal_config_repo.bzl
index 109e68a..0c62106 100644
--- a/python/private/internal_config_repo.bzl
+++ b/python/private/internal_config_repo.bzl
@@ -22,7 +22,7 @@
 load(":repo_utils.bzl", "repo_utils")
 
 _ENABLE_PIPSTAR_ENVVAR_NAME = "RULES_PYTHON_ENABLE_PIPSTAR"
-_ENABLE_PIPSTAR_DEFAULT = "0"
+_ENABLE_PIPSTAR_DEFAULT = "1"
 _ENABLE_DEPRECATION_WARNINGS_ENVVAR_NAME = "RULES_PYTHON_DEPRECATION_WARNINGS"
 _ENABLE_DEPRECATION_WARNINGS_DEFAULT = "0"
 
diff --git a/python/private/pypi/BUILD.bazel b/python/private/pypi/BUILD.bazel
index b9650c8..7d5314d 100644
--- a/python/private/pypi/BUILD.bazel
+++ b/python/private/pypi/BUILD.bazel
@@ -158,14 +158,6 @@
 )
 
 bzl_library(
-    name = "group_library_bzl",
-    srcs = ["group_library.bzl"],
-    deps = [
-        ":generate_group_library_build_bazel_bzl",
-    ],
-)
-
-bzl_library(
     name = "hub_builder_bzl",
     srcs = ["hub_builder.bzl"],
     visibility = ["//:__subpackages__"],
@@ -409,6 +401,15 @@
 )
 
 bzl_library(
+    name = "whl_config_repo_bzl",
+    srcs = ["whl_config_repo.bzl"],
+    deps = [
+        ":generate_group_library_build_bazel_bzl",
+        "//python/private:text_util_bzl",
+    ],
+)
+
+bzl_library(
     name = "whl_config_setting_bzl",
     srcs = ["whl_config_setting.bzl"],
 )
diff --git a/python/private/pypi/config.bzl.tmpl.bzlmod b/python/private/pypi/config.bzl.tmpl
similarity index 79%
rename from python/private/pypi/config.bzl.tmpl.bzlmod
rename to python/private/pypi/config.bzl.tmpl
index c3ada70..1037153 100644
--- a/python/private/pypi/config.bzl.tmpl.bzlmod
+++ b/python/private/pypi/config.bzl.tmpl
@@ -2,8 +2,6 @@
 
 NOTE: This is internal `rules_python` API and if you would like to depend on it, please raise an issue
 with your usecase. This may change in between rules_python versions without any notice.
-
-@generated by rules_python pip.parse bzlmod extension.
 """
 
-whl_map = %%WHL_MAP%%
+packages = %%PACKAGES%%
diff --git a/python/private/pypi/generate_whl_library_build_bazel.bzl b/python/private/pypi/generate_whl_library_build_bazel.bzl
index 3764e72..e207f6d 100644
--- a/python/private/pypi/generate_whl_library_build_bazel.bzl
+++ b/python/private/pypi/generate_whl_library_build_bazel.bzl
@@ -72,6 +72,7 @@
             "requires",
             "metadata_name",
             "metadata_version",
+            "packages",
             "include",
         ]
     else:
@@ -82,17 +83,13 @@
             "target_platforms",
             "default_python_version",
         ]
-        dep_template = kwargs.get("dep_template")
-        loads.append(
-            """load("{}", "{}")""".format(
-                dep_template.format(
-                    name = "",
-                    target = "config.bzl",
-                ),
-                "whl_map",
-            ),
-        )
-        kwargs["include"] = "whl_map"
+        packages_load = kwargs.pop("config_load")
+        if not kwargs.get("requires_dist"):
+            # no deps, we can leave the extra loads out
+            pass
+        else:
+            loads.append("""load("{}", "{}")""".format(packages_load, "packages"))
+            kwargs["include"] = "packages"
 
     for arg in unsupported_args:
         if kwargs.get(arg):
diff --git a/python/private/pypi/group_library.bzl b/python/private/pypi/group_library.bzl
deleted file mode 100644
index ff800e2..0000000
--- a/python/private/pypi/group_library.bzl
+++ /dev/null
@@ -1,40 +0,0 @@
-# Copyright 2024 The Bazel Authors. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""group_library implementation for WORKSPACE setups."""
-
-load(":generate_group_library_build_bazel.bzl", "generate_group_library_build_bazel")
-
-def _group_library_impl(rctx):
-    build_file_contents = generate_group_library_build_bazel(
-        repo_prefix = rctx.attr.repo_prefix,
-        groups = rctx.attr.groups,
-    )
-    rctx.file("BUILD.bazel", build_file_contents)
-
-group_library = repository_rule(
-    attrs = {
-        "groups": attr.string_list_dict(
-            doc = "A mapping of group names to requirements within that group.",
-        ),
-        "repo_prefix": attr.string(
-            doc = "Prefix used for the whl_library created components of each group",
-        ),
-    },
-    implementation = _group_library_impl,
-    doc = """
-Create a package containing only wrapper py_library and whl_library rules for implementing dependency groups.
-This is an implementation detail of dependency groups and should not be used alone.
-    """,
-)
diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl
index b6088e4..58d35f2 100644
--- a/python/private/pypi/hub_builder.bzl
+++ b/python/private/pypi/hub_builder.bzl
@@ -451,6 +451,7 @@
     # attrs.
     whl_library_args = dict(
         dep_template = "@{}//{{name}}:{{target}}".format(self.name),
+        config_load = "@{}//:config.bzl".format(self.name),
     )
     maybe_args = dict(
         # The following values are safe to omit if they have false like values
diff --git a/python/private/pypi/hub_repository.bzl b/python/private/pypi/hub_repository.bzl
index 1d572d0..f915aa1 100644
--- a/python/private/pypi/hub_repository.bzl
+++ b/python/private/pypi/hub_repository.bzl
@@ -50,7 +50,7 @@
         "config.bzl",
         rctx.attr._config_template,
         substitutions = {
-            "%%WHL_MAP%%": render.dict(rctx.attr.whl_map, value_repr = lambda x: "None"),
+            "%%PACKAGES%%": render.dict(rctx.attr.whl_map, value_repr = lambda x: "None"),
         },
     )
     rctx.template("requirements.bzl", rctx.attr._requirements_bzl_template, substitutions = {
@@ -100,7 +100,7 @@
 """,
         ),
         "_config_template": attr.label(
-            default = ":config.bzl.tmpl.bzlmod",
+            default = ":config.bzl.tmpl",
         ),
         "_requirements_bzl_template": attr.label(
             default = ":requirements.bzl.tmpl.bzlmod",
diff --git a/python/private/pypi/pip_repository.bzl b/python/private/pypi/pip_repository.bzl
index 2cf20cd..e9a4c44 100644
--- a/python/private/pypi/pip_repository.bzl
+++ b/python/private/pypi/pip_repository.bzl
@@ -24,6 +24,17 @@
 load(":render_pkg_aliases.bzl", "render_pkg_aliases")
 load(":requirements_files_by_platform.bzl", "requirements_files_by_platform")
 
+_CONFIG_REPO_TEMPLATE = """"{name}__config"
+    whl_config_repo(
+        name = "{name}__config",
+        repo_prefix = "{name}_",
+        groups = all_requirement_groups,
+        whl_map = {{
+            p: ""
+            for p in all_whl_requirements_by_package
+        }},
+    )"""
+
 def _get_python_interpreter_attr(rctx):
     """A helper function for getting the `python_interpreter` attribute or it's default
 
@@ -156,7 +167,7 @@
     imports = [
         # NOTE: Maintain the order consistent with `buildifier`
         'load("@rules_python//python:pip.bzl", "pip_utils")',
-        'load("@rules_python//python/pip_install:pip_repository.bzl", "group_library", "whl_library")',
+        'load("@rules_python//python/pip_install:pip_repository.bzl", "whl_config_repo", "whl_library")',
     ]
 
     annotations = {}
@@ -193,7 +204,7 @@
     aliases = render_pkg_aliases(
         aliases = {
             pkg: rctx.attr.name + "_" + pkg
-            for pkg in bzl_packages or []
+            for pkg in bzl_packages
         },
         extra_hub_aliases = rctx.attr.extra_hub_aliases,
         requirement_cycles = requirement_cycles,
@@ -202,14 +213,22 @@
         rctx.file(path, contents)
 
     rctx.file("BUILD.bazel", _BUILD_FILE_CONTENTS)
+    if rctx.attr.use_hub_alias_dependencies:
+        rctx.template(
+            "config.bzl",
+            rctx.attr._config_template,
+            substitutions = {
+                "%%PACKAGES%%": render.dict({
+                    pkg: None
+                    for pkg in bzl_packages
+                }, value_repr = lambda x: "None"),
+            },
+        )
+        config_repo_template = repr(rctx.attr.name)
+    else:
+        config_repo_template = _CONFIG_REPO_TEMPLATE.format(name = rctx.attr.name)
+
     rctx.template("requirements.bzl", rctx.attr._template, substitutions = {
-        "    # %%GROUP_LIBRARY%%": """\
-    group_repo = "{name}__groups"
-    group_library(
-        name = group_repo,
-        repo_prefix = "{name}_",
-        groups = all_requirement_groups,
-    )""".format(name = rctx.attr.name) if not rctx.attr.use_hub_alias_dependencies else "",
         "%%ALL_DATA_REQUIREMENTS%%": render.list([
             macro_tmpl.format(p, "data")
             for p in bzl_packages
@@ -225,6 +244,7 @@
         }),
         "%%ANNOTATIONS%%": render.dict(dict(sorted(annotations.items()))),
         "%%CONFIG%%": render.dict(dict(sorted(config.items()))),
+        "%%CONFIG_REPO%%": config_repo_template,
         "%%EXTRA_PIP_ARGS%%": json.encode(options),
         "%%IMPORTS%%": "\n".join(imports),
         "%%MACRO_TMPL%%": macro_tmpl,
@@ -249,6 +269,9 @@
 file](https://github.com/bazel-contrib/rules_python/blob/main/examples/pip_repository_annotations/WORKSPACE).
 """,
         ),
+        _config_template = attr.label(
+            default = ":config.bzl.tmpl",
+        ),
         _template = attr.label(
             default = ":requirements.bzl.tmpl.workspace",
         ),
diff --git a/python/private/pypi/requirements.bzl.tmpl.workspace b/python/private/pypi/requirements.bzl.tmpl.workspace
index 2f4bcd6..f61a527 100644
--- a/python/private/pypi/requirements.bzl.tmpl.workspace
+++ b/python/private/pypi/requirements.bzl.tmpl.workspace
@@ -52,7 +52,8 @@
         for requirement in group_requirements
     }
 
-    # %%GROUP_LIBRARY%%
+    config_repo = %%CONFIG_REPO%%
+    config_load = "@{}//:config.bzl".format(config_repo)
 
     # Install wheels which may be participants in a group
     whl_config = dict(_config)
@@ -68,5 +69,6 @@
             group_name = group_name,
             group_deps = group_deps,
             annotation = _get_annotation(requirement),
+            config_load = config_load,
             **whl_config
         )
diff --git a/python/private/pypi/whl_config_repo.bzl b/python/private/pypi/whl_config_repo.bzl
new file mode 100644
index 0000000..b7cea5a
--- /dev/null
+++ b/python/private/pypi/whl_config_repo.bzl
@@ -0,0 +1,46 @@
+"""whl_config_library implementation for WORKSPACE setups."""
+
+load("//python/private:text_util.bzl", "render")
+load(":generate_group_library_build_bazel.bzl", "generate_group_library_build_bazel")
+
+def _whl_config_repo_impl(rctx):
+    build_file_contents = generate_group_library_build_bazel(
+        repo_prefix = rctx.attr.repo_prefix,
+        groups = rctx.attr.groups,
+    )
+    rctx.file("_groups/BUILD.bazel", build_file_contents)
+    rctx.file("BUILD.bazel", "")
+    rctx.template(
+        "config.bzl",
+        rctx.attr._config_template,
+        substitutions = {
+            "%%PACKAGES%%": render.dict(rctx.attr.whl_map or {}, value_repr = lambda x: "None"),
+        },
+    )
+
+whl_config_repo = repository_rule(
+    attrs = {
+        "groups": attr.string_list_dict(
+            doc = "A mapping of group names to requirements within that group.",
+        ),
+        "repo_prefix": attr.string(
+            doc = "Prefix used for the whl_library created components of each group",
+        ),
+        "whl_map": attr.string_dict(
+            doc = """\
+The wheel map where values are json.encoded strings of the whl_map constructed
+in the pip.parse tag class.
+""",
+        ),
+        "_config_template": attr.label(
+            default = ":config.bzl.tmpl",
+        ),
+    },
+    doc = """
+Create a package containing only wrapper py_library and whl_library rules for implementing dependency groups.
+This is an implementation detail of dependency groups and should not be used alone.
+
+PRIVATE USE ONLY, only used in WORKSPACE.
+    """,
+    implementation = _whl_config_repo_impl,
+)
diff --git a/python/private/pypi/whl_library.bzl b/python/private/pypi/whl_library.bzl
index 5cc53d8..fe3308a 100644
--- a/python/private/pypi/whl_library.bzl
+++ b/python/private/pypi/whl_library.bzl
@@ -369,7 +369,12 @@
                 timeout = rctx.attr.timeout,
             )
 
-    if rp_config.enable_pipstar:
+    # NOTE @aignas 2025-09-28: if someone has an old vendored file that does not have the
+    # dep_template set or the packages is not set either, we should still not break, best to
+    # disable pipstar for that particular case.
+    #
+    # Remove non-pipstar and config_load check when we release rules_python 2.
+    if rp_config.enable_pipstar and rctx.attr.config_load:
         pypi_repo_utils.execute_checked(
             rctx,
             op = "whl_library.ExtractWheel({}, {})".format(rctx.attr.name, whl_path),
@@ -422,7 +427,10 @@
         build_file_contents = generate_whl_library_build_bazel(
             name = whl_path.basename,
             sdist_filename = sdist_filename,
-            dep_template = rctx.attr.dep_template or "@{}{{name}}//:{{target}}".format(rctx.attr.repo_prefix),
+            dep_template = rctx.attr.dep_template or "@{}{{name}}//:{{target}}".format(
+                rctx.attr.repo_prefix,
+            ),
+            config_load = rctx.attr.config_load,
             entry_points = entry_points,
             metadata_name = metadata.name,
             metadata_version = metadata.version,
@@ -572,6 +580,9 @@
         ),
         allow_files = True,
     ),
+    "config_load": attr.string(
+        doc = "The load location for configuration for pipstar.",
+    ),
     "dep_template": attr.string(
         doc = """
 The dep template to use for referencing the dependencies. It should have `{name}`
diff --git a/python/private/pypi/whl_library_targets.bzl b/python/private/pypi/whl_library_targets.bzl
index 89c1d34..a2d77da 100644
--- a/python/private/pypi/whl_library_targets.bzl
+++ b/python/private/pypi/whl_library_targets.bzl
@@ -273,13 +273,23 @@
     # implementation.
     if group_name and "//:" in dep_template:
         # This is the legacy behaviour where the group library is outside the hub repo
+        #
+        # It is expected to disappear when we drop WORKSPACE or drop the vendoring of
+        # pip_parse `requirements.bzl` in WORKSPACE. The alternative would be to add
+        # another argument to the macro, but it is already full of arguments.
         label_tmpl = dep_template.format(
-            name = "_groups",
+            name = "_config",
             target = normalize_name(group_name) + "_{}",
+        ).replace(
+            "//:",
+            "//_groups:",
         )
         impl_vis = [dep_template.format(
-            name = "_groups",
+            name = "_config",
             target = "__pkg__",
+        ).replace(
+            "//:",
+            "//_groups:",
         )]
 
         native.alias(
diff --git a/python/private/pypi/whl_metadata.bzl b/python/private/pypi/whl_metadata.bzl
index cf2d51a..a56aac5 100644
--- a/python/private/pypi/whl_metadata.bzl
+++ b/python/private/pypi/whl_metadata.bzl
@@ -26,7 +26,11 @@
     result = parse_whl_metadata(contents)
 
     if not (result.name and result.version):
-        logger.fail("Failed to parsed the wheel METADATA file:\n{}".format(contents))
+        logger.fail("Failed to parse the wheel METADATA file:\n{}\n{}\n{}".format(
+            80 * "=",
+            contents.rstrip("\n"),
+            80 * "=",
+        ))
         return None
 
     return result
diff --git a/tests/implicit_namespace_packages/testdata/ns-sub1/ns-sub1-1.0.dist-info/METADATA b/tests/implicit_namespace_packages/testdata/ns-sub1/ns-sub1-1.0.dist-info/METADATA
index e69de29..ecec608 100644
--- a/tests/implicit_namespace_packages/testdata/ns-sub1/ns-sub1-1.0.dist-info/METADATA
+++ b/tests/implicit_namespace_packages/testdata/ns-sub1/ns-sub1-1.0.dist-info/METADATA
@@ -0,0 +1,2 @@
+Name: ns-sub1
+Version: 1.0
diff --git a/tests/implicit_namespace_packages/testdata/ns-sub2/ns_sub2-1.0.dist-info/METADATA b/tests/implicit_namespace_packages/testdata/ns-sub2/ns_sub2-1.0.dist-info/METADATA
index e69de29..92cbb8e 100644
--- a/tests/implicit_namespace_packages/testdata/ns-sub2/ns_sub2-1.0.dist-info/METADATA
+++ b/tests/implicit_namespace_packages/testdata/ns-sub2/ns_sub2-1.0.dist-info/METADATA
@@ -0,0 +1,2 @@
+Name: ns-sub2
+Version: 1.0
diff --git a/tests/pypi/extension/extension_tests.bzl b/tests/pypi/extension/extension_tests.bzl
index 0514e1d..b1e363b 100644
--- a/tests/pypi/extension/extension_tests.bzl
+++ b/tests/pypi/extension/extension_tests.bzl
@@ -168,6 +168,7 @@
     }})
     pypi.whl_libraries().contains_exactly({
         "pypi_315_simple": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
             "python_interpreter_target": "unit_test_interpreter_target",
             "requirement": "simple==0.0.1 --hash=sha256:deadbeef --hash=sha256:deadbaaf",
diff --git a/tests/pypi/generate_whl_library_build_bazel/generate_whl_library_build_bazel_tests.bzl b/tests/pypi/generate_whl_library_build_bazel/generate_whl_library_build_bazel_tests.bzl
index 225b296..39c2eb4 100644
--- a/tests/pypi/generate_whl_library_build_bazel/generate_whl_library_build_bazel_tests.bzl
+++ b/tests/pypi/generate_whl_library_build_bazel/generate_whl_library_build_bazel_tests.bzl
@@ -37,7 +37,7 @@
         "exclude_via_attr",
         "data_exclude_all",
     ],
-    dep_template = "@pypi//{name}:{target}",
+    dep_template = "@pypi_{name}//:{target}",
     dependencies = ["foo"],
     dependencies_by_platform = {
         "baz": ["bar"],
@@ -59,7 +59,7 @@
 # SOMETHING SPECIAL AT THE END
 """
     actual = generate_whl_library_build_bazel(
-        dep_template = "@pypi//{name}:{target}",
+        dep_template = "@pypi_{name}//:{target}",
         name = "foo.whl",
         dependencies = ["foo"],
         dependencies_by_platform = {"baz": ["bar"]},
@@ -83,9 +83,9 @@
 
 _tests.append(_test_all_legacy)
 
-def _test_all(env):
+def _test_all_workspace(env):
     want = """\
-load("@pypi//:config.bzl", "whl_map")
+load("@pypi//:config.bzl", "packages")
 load("@rules_python//python/private/pypi:whl_library_targets.bzl", "whl_library_targets_from_requires")
 
 package(default_visibility = ["//visibility:public"])
@@ -112,7 +112,7 @@
         "qux",
     ],
     group_name = "qux",
-    include = whl_map,
+    include = packages,
     name = "foo.whl",
     requires_dist = [
         "foo",
@@ -140,6 +140,72 @@
             srcs_exclude_glob = ["srcs_exclude_all"],
             additive_build_content = """# SOMETHING SPECIAL AT THE END""",
         ),
+        config_load = "@pypi//:config.bzl",
+        group_name = "qux",
+        group_deps = ["foo", "fox", "qux"],
+    )
+    env.expect.that_str(actual.replace("@@", "@")).equals(want)
+
+_tests.append(_test_all_workspace)
+
+def _test_all(env):
+    want = """\
+load("@pypi//:config.bzl", "packages")
+load("@rules_python//python/private/pypi:whl_library_targets.bzl", "whl_library_targets_from_requires")
+
+package(default_visibility = ["//visibility:public"])
+
+whl_library_targets_from_requires(
+    copy_executables = {
+        "exec_src": "exec_dest",
+    },
+    copy_files = {
+        "file_src": "file_dest",
+    },
+    data = ["extra_target"],
+    data_exclude = [
+        "exclude_via_attr",
+        "data_exclude_all",
+    ],
+    dep_template = "@pypi//{name}:{target}",
+    entry_points = {
+        "foo": "bar.py",
+    },
+    group_deps = [
+        "foo",
+        "fox",
+        "qux",
+    ],
+    group_name = "qux",
+    include = packages,
+    name = "foo.whl",
+    requires_dist = [
+        "foo",
+        "bar-baz",
+        "qux",
+    ],
+    srcs_exclude = ["srcs_exclude_all"],
+)
+
+# SOMETHING SPECIAL AT THE END
+"""
+    actual = generate_whl_library_build_bazel(
+        dep_template = "@pypi//{name}:{target}",
+        name = "foo.whl",
+        requires_dist = ["foo", "bar-baz", "qux"],
+        entry_points = {
+            "foo": "bar.py",
+        },
+        data_exclude = ["exclude_via_attr"],
+        annotation = struct(
+            copy_files = {"file_src": "file_dest"},
+            copy_executables = {"exec_src": "exec_dest"},
+            data = ["extra_target"],
+            data_exclude_glob = ["data_exclude_all"],
+            srcs_exclude_glob = ["srcs_exclude_all"],
+            additive_build_content = """# SOMETHING SPECIAL AT THE END""",
+        ),
+        config_load = "@pypi//:config.bzl",
         group_name = "qux",
         group_deps = ["foo", "fox", "qux"],
     )
@@ -149,7 +215,7 @@
 
 def _test_all_with_loads(env):
     want = """\
-load("@pypi//:config.bzl", "whl_map")
+load("@pypi//:config.bzl", "packages")
 load("@rules_python//python/private/pypi:whl_library_targets.bzl", "whl_library_targets_from_requires")
 
 package(default_visibility = ["//visibility:public"])
@@ -176,7 +242,7 @@
         "qux",
     ],
     group_name = "qux",
-    include = whl_map,
+    include = packages,
     name = "foo.whl",
     requires_dist = [
         "foo",
@@ -205,6 +271,7 @@
             additive_build_content = """# SOMETHING SPECIAL AT THE END""",
         ),
         group_name = "qux",
+        config_load = "@pypi//:config.bzl",
         group_deps = ["foo", "fox", "qux"],
     )
     env.expect.that_str(actual.replace("@@", "@")).equals(want)
diff --git a/tests/pypi/hub_builder/hub_builder_tests.bzl b/tests/pypi/hub_builder/hub_builder_tests.bzl
index 9f6ee67..ee6200a 100644
--- a/tests/pypi/hub_builder/hub_builder_tests.bzl
+++ b/tests/pypi/hub_builder/hub_builder_tests.bzl
@@ -40,7 +40,7 @@
 
 def hub_builder(
         env,
-        enable_pipstar = False,
+        enable_pipstar = True,
         debug = False,
         config = None,
         minor_mapping = {},
@@ -135,6 +135,7 @@
     })
     pypi.whl_libraries().contains_exactly({
         "pypi_315_simple": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
             "python_interpreter_target": "unit_test_interpreter_target",
             "requirement": "simple==0.0.1 --hash=sha256:deadbeef --hash=sha256:deadbaaf",
@@ -186,11 +187,13 @@
     })
     pypi.whl_libraries().contains_exactly({
         "pypi_315_simple_osx_aarch64": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
             "python_interpreter_target": "unit_test_interpreter_target",
             "requirement": "simple==0.0.2 --hash=sha256:deadb00f",
         },
         "pypi_315_simple_windows_aarch64": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
             "python_interpreter_target": "unit_test_interpreter_target",
             "requirement": "simple==0.0.1 --hash=sha256:deadbeef",
@@ -276,21 +279,25 @@
     })
     pypi.whl_libraries().contains_exactly({
         "pypi_315_old_package": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
             "python_interpreter_target": "unit_test_interpreter_target",
             "requirement": "old-package==0.0.1 --hash=sha256:deadbaaf",
         },
         "pypi_315_simple": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
             "python_interpreter_target": "unit_test_interpreter_target",
             "requirement": "simple==0.0.1 --hash=sha256:deadbeef",
         },
         "pypi_316_new_package": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
             "python_interpreter_target": "unit_test_interpreter_target",
             "requirement": "new-package==0.0.1 --hash=sha256:deadb00f2",
         },
         "pypi_316_simple": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
             "python_interpreter_target": "unit_test_interpreter_target",
             "requirement": "simple==0.0.2 --hash=sha256:deadb00f",
@@ -357,11 +364,13 @@
     })
     pypi.whl_libraries().contains_exactly({
         "pypi_315_torch_linux_aarch64_osx_aarch64_windows_aarch64": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
             "python_interpreter_target": "unit_test_interpreter_target",
             "requirement": "torch==2.4.1 --hash=sha256:deadbeef",
         },
         "pypi_315_torch_linux_x86_64_linux_x86_64_freethreaded": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
             "python_interpreter_target": "unit_test_interpreter_target",
             "requirement": "torch==2.4.1+cpu",
@@ -405,7 +414,7 @@
         env,
         config = struct(
             netrc = None,
-            enable_pipstar = False,
+            enable_pipstar = True,
             auth_patterns = {},
             platforms = {
                 "{}_{}".format(os, cpu): _plat(
@@ -515,8 +524,8 @@
     })
     pypi.whl_libraries().contains_exactly({
         "pypi_312_torch_cp312_cp312_linux_x86_64_8800deef": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
-            "experimental_target_platforms": ["linux_x86_64"],
             "filename": "torch-2.4.1+cpu-cp312-cp312-linux_x86_64.whl",
             "python_interpreter_target": "unit_test_interpreter_target",
             "requirement": "torch==2.4.1+cpu",
@@ -524,8 +533,8 @@
             "urls": ["https://torch.index/whl/cpu/torch-2.4.1%2Bcpu-cp312-cp312-linux_x86_64.whl"],
         },
         "pypi_312_torch_cp312_cp312_manylinux_2_17_aarch64_36109432": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
-            "experimental_target_platforms": ["linux_aarch64"],
             "filename": "torch-2.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
             "python_interpreter_target": "unit_test_interpreter_target",
             "requirement": "torch==2.4.1",
@@ -533,8 +542,8 @@
             "urls": ["https://torch.index/whl/cpu/torch-2.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl"],
         },
         "pypi_312_torch_cp312_cp312_win_amd64_3a570e5c": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
-            "experimental_target_platforms": ["windows_x86_64"],
             "filename": "torch-2.4.1+cpu-cp312-cp312-win_amd64.whl",
             "python_interpreter_target": "unit_test_interpreter_target",
             "requirement": "torch==2.4.1+cpu",
@@ -542,8 +551,8 @@
             "urls": ["https://torch.index/whl/cpu/torch-2.4.1%2Bcpu-cp312-cp312-win_amd64.whl"],
         },
         "pypi_312_torch_cp312_none_macosx_11_0_arm64_72b484d5": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
-            "experimental_target_platforms": ["osx_aarch64"],
             "filename": "torch-2.4.1-cp312-none-macosx_11_0_arm64.whl",
             "python_interpreter_target": "unit_test_interpreter_target",
             "requirement": "torch==2.4.1",
@@ -619,15 +628,15 @@
     })
     pypi.whl_libraries().contains_exactly({
         "pypi_315_extra": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
             "download_only": True,
-            # TODO @aignas 2025-04-20: ensure that this is in the hub repo
-            # "experimental_target_platforms": ["cp315_linux_x86_64"],
             "extra_pip_args": ["--platform=manylinux_2_17_x86_64", "--python-version=315", "--implementation=cp", "--abi=cp315"],
             "python_interpreter_target": "unit_test_interpreter_target",
             "requirement": "extra==0.0.1 --hash=sha256:deadb00f",
         },
         "pypi_315_simple_linux_x86_64": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
             "download_only": True,
             "extra_pip_args": ["--platform=manylinux_2_17_x86_64", "--python-version=315", "--implementation=cp", "--abi=cp315"],
@@ -635,6 +644,7 @@
             "requirement": "simple==0.0.1 --hash=sha256:deadbeef",
         },
         "pypi_315_simple_osx_aarch64": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
             "download_only": True,
             "extra_pip_args": ["--platform=macosx_10_9_arm64", "--python-version=315", "--implementation=cp", "--abi=cp315"],
@@ -820,13 +830,8 @@
     })
     pypi.whl_libraries().contains_exactly({
         "pypi_315_any_name": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
-            "experimental_target_platforms": [
-                "linux_aarch64",
-                "linux_x86_64",
-                "osx_aarch64",
-                "windows_aarch64",
-            ],
             "extra_pip_args": ["--extra-args-for-sdist-building"],
             "filename": "any-name.tar.gz",
             "python_interpreter_target": "unit_test_interpreter_target",
@@ -835,13 +840,8 @@
             "urls": ["some-archive/any-name.tar.gz"],
         },
         "pypi_315_direct_without_sha_0_0_1_py3_none_any": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
-            "experimental_target_platforms": [
-                "linux_aarch64",
-                "linux_x86_64",
-                "osx_aarch64",
-                "windows_aarch64",
-            ],
             "filename": "direct_without_sha-0.0.1-py3-none-any.whl",
             "python_interpreter_target": "unit_test_interpreter_target",
             "requirement": "direct_without_sha==0.0.1",
@@ -849,25 +849,22 @@
             "urls": ["example-direct.org/direct_without_sha-0.0.1-py3-none-any.whl"],
         },
         "pypi_315_git_dep": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
             "extra_pip_args": ["--extra-args-for-sdist-building"],
             "python_interpreter_target": "unit_test_interpreter_target",
             "requirement": "git_dep @ git+https://git.server/repo/project@deadbeefdeadbeef",
         },
         "pypi_315_pip_fallback": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
             "extra_pip_args": ["--extra-args-for-sdist-building"],
             "python_interpreter_target": "unit_test_interpreter_target",
             "requirement": "pip_fallback==0.0.1",
         },
         "pypi_315_simple_py3_none_any_deadb00f": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
-            "experimental_target_platforms": [
-                "linux_aarch64",
-                "linux_x86_64",
-                "osx_aarch64",
-                "windows_aarch64",
-            ],
             "filename": "simple-0.0.1-py3-none-any.whl",
             "python_interpreter_target": "unit_test_interpreter_target",
             "requirement": "simple==0.0.1",
@@ -875,13 +872,8 @@
             "urls": ["example2.org"],
         },
         "pypi_315_some_pkg_py3_none_any_deadbaaf": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
-            "experimental_target_platforms": [
-                "linux_aarch64",
-                "linux_x86_64",
-                "osx_aarch64",
-                "windows_aarch64",
-            ],
             "filename": "some_pkg-0.0.1-py3-none-any.whl",
             "python_interpreter_target": "unit_test_interpreter_target",
             "requirement": "some_pkg==0.0.1",
@@ -889,13 +881,8 @@
             "urls": ["example-direct.org/some_pkg-0.0.1-py3-none-any.whl"],
         },
         "pypi_315_some_py3_none_any_deadb33f": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
-            "experimental_target_platforms": [
-                "linux_aarch64",
-                "linux_x86_64",
-                "osx_aarch64",
-                "windows_aarch64",
-            ],
             "filename": "some-other-pkg-0.0.1-py3-none-any.whl",
             "python_interpreter_target": "unit_test_interpreter_target",
             "requirement": "some_other_pkg==0.0.1",
@@ -978,11 +965,13 @@
     })
     pypi.whl_libraries().contains_exactly({
         "pypi_315_optimum_linux_aarch64_linux_x86_64_linux_x86_64_freethreaded": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
             "python_interpreter_target": "unit_test_interpreter_target",
             "requirement": "optimum[onnxruntime-gpu]==1.17.1",
         },
         "pypi_315_optimum_osx_aarch64": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
             "python_interpreter_target": "unit_test_interpreter_target",
             "requirement": "optimum[onnxruntime]==1.17.1",
@@ -1059,11 +1048,13 @@
     })
     pypi.whl_libraries().contains_exactly({
         "pypi_315_optimum_mylinuxx86_64": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
             "python_interpreter_target": "unit_test_interpreter_target",
             "requirement": "optimum[onnxruntime-gpu]==1.17.1",
         },
         "pypi_315_optimum_myosxaarch64": {
+            "config_load": "@pypi//:config.bzl",
             "dep_template": "@pypi//{name}:{target}",
             "python_interpreter_target": "unit_test_interpreter_target",
             "requirement": "optimum[onnxruntime]==1.17.1",
diff --git a/tests/pypi/whl_library_targets/whl_library_targets_tests.bzl b/tests/pypi/whl_library_targets/whl_library_targets_tests.bzl
index 615358f..b7fb909 100644
--- a/tests/pypi/whl_library_targets/whl_library_targets_tests.bzl
+++ b/tests/pypi/whl_library_targets/whl_library_targets_tests.bzl
@@ -420,8 +420,8 @@
     )
 
     env.expect.that_collection(alias_calls).contains_exactly([
-        {"name": "pkg", "actual": "@pypi__groups//:qux_pkg", "visibility": ["//visibility:public"]},
-        {"name": "whl", "actual": "@pypi__groups//:qux_whl", "visibility": ["//visibility:public"]},
+        {"name": "pkg", "actual": "@pypi__config//_groups:qux_pkg", "visibility": ["//visibility:public"]},
+        {"name": "whl", "actual": "@pypi__config//_groups:qux_whl", "visibility": ["//visibility:public"]},
     ])  # buildifier: @unsorted-dict-items
 
     env.expect.that_collection(py_library_calls).has_size(1)
@@ -446,7 +446,7 @@
             "//conditions:default": [],
         }),
         "tags": [],
-        "visibility": ["@pypi__groups//:__pkg__"],
+        "visibility": ["@pypi__config//_groups:__pkg__"],
         "experimental_venvs_site_packages": Label("//python/config_settings:venvs_site_packages"),
     })  # buildifier: @unsorted-dict-items
 
diff --git a/tests/whl_with_build_files/testdata/somepkg-1.0.dist-info/METADATA b/tests/whl_with_build_files/testdata/somepkg-1.0.dist-info/METADATA
index e69de29..0829d1f 100644
--- a/tests/whl_with_build_files/testdata/somepkg-1.0.dist-info/METADATA
+++ b/tests/whl_with_build_files/testdata/somepkg-1.0.dist-info/METADATA
@@ -0,0 +1,2 @@
+Name: somepkg
+Version: 1.0