refactor: move the remaining PyPI related functions to private/pypi (#2006)

A continuation of #2003, this time we finish moving the `pip_compile`
stuff.

Summary:
- move multi_pip_parse to private/pypi and re-export
- remove unused files leftover from #2003
- move repositories.bzl to private/pypi/deps.bzl
- move pip_compile to private/pypi
- move the pip_install tools to private
diff --git a/python/BUILD.bazel b/python/BUILD.bazel
index 29b495b..96b2282 100644
--- a/python/BUILD.bazel
+++ b/python/BUILD.bazel
@@ -39,6 +39,7 @@
         "//python/constraints:distribution",
         "//python/entry_points:distribution",
         "//python/extensions:distribution",
+        "//python/pip_install:distribution",
         "//python/private:distribution",
         "//python/runfiles:distribution",
     ],
@@ -96,12 +97,12 @@
     name = "pip_bzl",
     srcs = ["pip.bzl"],
     deps = [
-        "//python/pip_install:pip_repository_bzl",
-        "//python/pip_install:repositories_bzl",
-        "//python/pip_install:requirements_bzl",
-        "//python/private:bzlmod_enabled_bzl",
-        "//python/private:full_version_bzl",
-        "//python/private/pypi:render_pkg_aliases_bzl",
+        "//python/private:normalize_name_bzl",
+        "//python/private/pypi:multi_pip_parse_bzl",
+        "//python/private/pypi:package_annotation_bzl",
+        "//python/private/pypi:pip_compile_bzl",
+        "//python/private/pypi:pip_repository_bzl",
+        "//python/private/pypi:whl_library_alias_bzl",
         "//python/private/whl_filegroup:whl_filegroup_bzl",
     ],
 )
@@ -210,7 +211,6 @@
     srcs = ["repositories.bzl"],
     deps = [
         ":versions_bzl",
-        "//python/pip_install:repositories_bzl",
         "//python/private:auth_bzl",
         "//python/private:bazel_tools_bzl",
         "//python/private:bzlmod_enabled_bzl",
@@ -219,6 +219,7 @@
         "//python/private:internal_config_repo_bzl",
         "//python/private:repo_utils_bzl",
         "//python/private:toolchains_repo_bzl",
+        "//python/private/pypi:deps_bzl",
     ],
 )
 
diff --git a/python/pip.bzl b/python/pip.bzl
index f1c74dd..a1a6720 100644
--- a/python/pip.bzl
+++ b/python/pip.bzl
@@ -19,250 +19,27 @@
 for internal use only.
 """
 
-load("//python/pip_install:requirements.bzl", _compile_pip_requirements = "compile_pip_requirements")
-load("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED")
-load("//python/private:full_version.bzl", "full_version")
 load("//python/private:normalize_name.bzl", "normalize_name")
+load("//python/private/pypi:multi_pip_parse.bzl", _multi_pip_parse = "multi_pip_parse")
 load("//python/private/pypi:package_annotation.bzl", _package_annotation = "package_annotation")
+load("//python/private/pypi:pip_compile.bzl", "pip_compile")
 load("//python/private/pypi:pip_repository.bzl", "pip_repository")
-load("//python/private/pypi:render_pkg_aliases.bzl", "NO_MATCH_ERROR_MESSAGE_TEMPLATE")
+load("//python/private/pypi:whl_library_alias.bzl", _whl_library_alias = "whl_library_alias")
 load("//python/private/whl_filegroup:whl_filegroup.bzl", _whl_filegroup = "whl_filegroup")
 
-compile_pip_requirements = _compile_pip_requirements
+compile_pip_requirements = pip_compile
 package_annotation = _package_annotation
 pip_parse = pip_repository
 whl_filegroup = _whl_filegroup
 
-def _multi_pip_parse_impl(rctx):
-    rules_python = rctx.attr._rules_python_workspace.workspace_name
-    load_statements = []
-    install_deps_calls = []
-    process_requirements_calls = []
-    for python_version, pypi_repository in rctx.attr.pip_parses.items():
-        sanitized_python_version = python_version.replace(".", "_")
-        load_statement = """\
-load(
-    "@{pypi_repository}//:requirements.bzl",
-    _{sanitized_python_version}_install_deps = "install_deps",
-    _{sanitized_python_version}_all_requirements = "all_requirements",
-)""".format(
-            pypi_repository = pypi_repository,
-            sanitized_python_version = sanitized_python_version,
-        )
-        load_statements.append(load_statement)
-        process_requirements_call = """\
-_process_requirements(
-    pkg_labels = _{sanitized_python_version}_all_requirements,
-    python_version = "{python_version}",
-    repo_prefix = "{pypi_repository}_",
-)""".format(
-            pypi_repository = pypi_repository,
-            python_version = python_version,
-            sanitized_python_version = sanitized_python_version,
-        )
-        process_requirements_calls.append(process_requirements_call)
-        install_deps_call = """    _{sanitized_python_version}_install_deps(**whl_library_kwargs)""".format(
-            sanitized_python_version = sanitized_python_version,
-        )
-        install_deps_calls.append(install_deps_call)
-
-    # NOTE @aignas 2023-10-31: I am not sure it is possible to render aliases
-    # for all of the packages using the `render_pkg_aliases` function because
-    # we need to know what the list of packages for each version is and then
-    # we would be creating directories for each.
-    macro_tmpl = "@%s_{}//:{}" % rctx.attr.name
-
-    requirements_bzl = """\
-# Generated by python/pip.bzl
-
-load("@{rules_python}//python:pip.bzl", "whl_library_alias", "pip_utils")
-{load_statements}
-
-_wheel_names = []
-_version_map = dict()
-def _process_requirements(pkg_labels, python_version, repo_prefix):
-    for pkg_label in pkg_labels:
-        wheel_name = Label(pkg_label).package
-        if not wheel_name:
-            # We are dealing with the cases where we don't have aliases.
-            workspace_name = Label(pkg_label).workspace_name
-            wheel_name = workspace_name[len(repo_prefix):]
-
-        _wheel_names.append(wheel_name)
-        if not wheel_name in _version_map:
-            _version_map[wheel_name] = dict()
-        _version_map[wheel_name][python_version] = repo_prefix
-
-{process_requirements_calls}
-
-def requirement(name):
-    return "{macro_tmpl}".format(pip_utils.normalize_name(name), "pkg")
-
-def whl_requirement(name):
-    return "{macro_tmpl}".format(pip_utils.normalize_name(name), "whl")
-
-def data_requirement(name):
-    return "{macro_tmpl}".format(pip_utils.normalize_name(name), "data")
-
-def dist_info_requirement(name):
-    return "{macro_tmpl}".format(pip_utils.normalize_name(name), "dist_info")
-
-def install_deps(**whl_library_kwargs):
-{install_deps_calls}
-    for wheel_name in _wheel_names:
-        whl_library_alias(
-            name = "{name}_" + wheel_name,
-            wheel_name = wheel_name,
-            default_version = "{default_version}",
-            version_map = _version_map[wheel_name],
-        )
-""".format(
-        name = rctx.attr.name,
-        install_deps_calls = "\n".join(install_deps_calls),
-        load_statements = "\n".join(load_statements),
-        macro_tmpl = macro_tmpl,
-        process_requirements_calls = "\n".join(process_requirements_calls),
-        rules_python = rules_python,
-        default_version = rctx.attr.default_version,
-    )
-    rctx.file("requirements.bzl", requirements_bzl)
-    rctx.file("BUILD.bazel", "exports_files(['requirements.bzl'])")
-
-_multi_pip_parse = repository_rule(
-    _multi_pip_parse_impl,
-    attrs = {
-        "default_version": attr.string(),
-        "pip_parses": attr.string_dict(),
-        "_rules_python_workspace": attr.label(default = Label("//:WORKSPACE")),
-    },
-)
-
-def _whl_library_alias_impl(rctx):
-    rules_python = rctx.attr._rules_python_workspace.workspace_name
-    if rctx.attr.default_version:
-        default_repo_prefix = rctx.attr.version_map[rctx.attr.default_version]
-    else:
-        default_repo_prefix = None
-    version_map = rctx.attr.version_map.items()
-    build_content = ["# Generated by python/pip.bzl"]
-    for alias_name in ["pkg", "whl", "data", "dist_info"]:
-        build_content.append(_whl_library_render_alias_target(
-            alias_name = alias_name,
-            default_repo_prefix = default_repo_prefix,
-            rules_python = rules_python,
-            version_map = version_map,
-            wheel_name = rctx.attr.wheel_name,
-        ))
-    rctx.file("BUILD.bazel", "\n".join(build_content))
-
-def _whl_library_render_alias_target(
-        alias_name,
-        default_repo_prefix,
-        rules_python,
-        version_map,
-        wheel_name):
-    # The template below adds one @, but under bzlmod, the name
-    # is canonical, so we have to add a second @.
-    if BZLMOD_ENABLED:
-        rules_python = "@" + rules_python
-
-    alias = ["""\
-alias(
-    name = "{alias_name}",
-    actual = select({{""".format(alias_name = alias_name)]
-    for [python_version, repo_prefix] in version_map:
-        alias.append("""\
-        "@{rules_python}//python/config_settings:is_python_{full_python_version}": "{actual}",""".format(
-            full_python_version = full_version(python_version),
-            actual = "@{repo_prefix}{wheel_name}//:{alias_name}".format(
-                repo_prefix = repo_prefix,
-                wheel_name = wheel_name,
-                alias_name = alias_name,
-            ),
-            rules_python = rules_python,
-        ))
-    if default_repo_prefix:
-        default_actual = "@{repo_prefix}{wheel_name}//:{alias_name}".format(
-            repo_prefix = default_repo_prefix,
-            wheel_name = wheel_name,
-            alias_name = alias_name,
-        )
-        alias.append('        "//conditions:default": "{default_actual}",'.format(
-            default_actual = default_actual,
-        ))
-
-    alias.append("    },")  # Close select expression condition dict
-    if not default_repo_prefix:
-        supported_versions = sorted([python_version for python_version, _ in version_map])
-        alias.append('    no_match_error="""{}""",'.format(
-            NO_MATCH_ERROR_MESSAGE_TEMPLATE.format(
-                supported_versions = ", ".join(supported_versions),
-                rules_python = rules_python,
-            ),
-        ))
-    alias.append("    ),")  # Close the select expression
-    alias.append('    visibility = ["//visibility:public"],')
-    alias.append(")")  # Close the alias() expression
-    return "\n".join(alias)
-
-whl_library_alias = repository_rule(
-    _whl_library_alias_impl,
-    attrs = {
-        "default_version": attr.string(
-            mandatory = False,
-            doc = "Optional Python version in major.minor format, e.g. '3.10'." +
-                  "The Python version of the wheel to use when the versions " +
-                  "from `version_map` don't match. This allows the default " +
-                  "(version unaware) rules to match and select a wheel. If " +
-                  "not specified, then the default rules won't be able to " +
-                  "resolve a wheel and an error will occur.",
-        ),
-        "version_map": attr.string_dict(mandatory = True),
-        "wheel_name": attr.string(mandatory = True),
-        "_rules_python_workspace": attr.label(default = Label("//:WORKSPACE")),
-    },
-)
-
-def multi_pip_parse(name, default_version, python_versions, python_interpreter_target, requirements_lock, **kwargs):
-    """NOT INTENDED FOR DIRECT USE!
-
-    This is intended to be used by the multi_pip_parse implementation in the template of the
-    multi_toolchain_aliases repository rule.
-
-    Args:
-        name: the name of the multi_pip_parse repository.
-        default_version: the default Python version.
-        python_versions: all Python toolchain versions currently registered.
-        python_interpreter_target: a dictionary which keys are Python versions and values are resolved host interpreters.
-        requirements_lock: a dictionary which keys are Python versions and values are locked requirements files.
-        **kwargs: extra arguments passed to all wrapped pip_parse.
-
-    Returns:
-        The internal implementation of multi_pip_parse repository rule.
-    """
-    pip_parses = {}
-    for python_version in python_versions:
-        if not python_version in python_interpreter_target:
-            fail("Missing python_interpreter_target for Python version %s in '%s'" % (python_version, name))
-        if not python_version in requirements_lock:
-            fail("Missing requirements_lock for Python version %s in '%s'" % (python_version, name))
-
-        pip_parse_name = name + "_" + python_version.replace(".", "_")
-        pip_parse(
-            name = pip_parse_name,
-            python_interpreter_target = python_interpreter_target[python_version],
-            requirements_lock = requirements_lock[python_version],
-            **kwargs
-        )
-        pip_parses[python_version] = pip_parse_name
-
-    return _multi_pip_parse(
-        name = name,
-        default_version = default_version,
-        pip_parses = pip_parses,
-    )
-
 # Extra utilities visible to rules_python users.
 pip_utils = struct(
     normalize_name = normalize_name,
 )
+
+# The following are only exported here because they are used from
+# multi_toolchain_aliases repository_rule, not intended for public use.
+#
+# See ./private/toolchains_repo.bzl
+multi_pip_parse = _multi_pip_parse
+whl_library_alias = _whl_library_alias
diff --git a/python/pip_install/BUILD.bazel b/python/pip_install/BUILD.bazel
index 1894c4d..683199f 100644
--- a/python/pip_install/BUILD.bazel
+++ b/python/pip_install/BUILD.bazel
@@ -32,43 +32,21 @@
 bzl_library(
     name = "requirements_bzl",
     srcs = ["requirements.bzl"],
-    deps = [
-        ":repositories_bzl",
-        "//python:defs_bzl",
-    ],
+    deps = ["//python/private/pypi:pip_compile_bzl"],
 )
 
 bzl_library(
     name = "repositories_bzl",
     srcs = ["repositories.bzl"],
     deps = [
-        "//:version_bzl",
-        "//python/private:bazel_tools_bzl",
-        "@bazel_skylib//lib:versions",
+        "//python/private/pypi:deps_bzl",
     ],
 )
 
 filegroup(
     name = "distribution",
-    srcs = glob(["*.bzl"]) + [
-        "BUILD.bazel",
-        "pip_repository_requirements.bzl.tmpl",
-        "//python/pip_install/tools/dependency_resolver:distribution",
-        "//python/pip_install/tools/wheel_installer:distribution",
-    ],
-    visibility = ["//:__pkg__"],
-)
-
-filegroup(
-    name = "repositories",
-    srcs = ["repositories.bzl"],
-    visibility = ["//tools/private/update_deps:__pkg__"],
-)
-
-filegroup(
-    name = "requirements_txt",
-    srcs = ["tools/requirements.txt"],
-    visibility = ["//tools/private/update_deps:__pkg__"],
+    srcs = glob(["**"]),
+    visibility = ["//python:__pkg__"],
 )
 
 filegroup(
diff --git a/python/pip_install/pip_repository_requirements.bzl.tmpl b/python/pip_install/pip_repository_requirements.bzl.tmpl
deleted file mode 100644
index 2f4bcd6..0000000
--- a/python/pip_install/pip_repository_requirements.bzl.tmpl
+++ /dev/null
@@ -1,72 +0,0 @@
-"""Starlark representation of locked requirements.
-
-@generated by rules_python pip_parse repository rule.
-"""
-
-%%IMPORTS%%
-
-all_requirements = %%ALL_REQUIREMENTS%%
-
-all_whl_requirements_by_package = %%ALL_WHL_REQUIREMENTS_BY_PACKAGE%%
-
-all_whl_requirements = all_whl_requirements_by_package.values()
-
-all_data_requirements = %%ALL_DATA_REQUIREMENTS%%
-
-_packages = %%PACKAGES%%
-_config = %%CONFIG%%
-_annotations = %%ANNOTATIONS%%
-
-def requirement(name):
-    return "%%MACRO_TMPL%%".format(pip_utils.normalize_name(name), "pkg")
-
-def whl_requirement(name):
-    return "%%MACRO_TMPL%%".format(pip_utils.normalize_name(name), "whl")
-
-def data_requirement(name):
-    return "%%MACRO_TMPL%%".format(pip_utils.normalize_name(name), "data")
-
-def dist_info_requirement(name):
-    return "%%MACRO_TMPL%%".format(pip_utils.normalize_name(name), "dist_info")
-
-def _get_annotation(requirement):
-    # This expects to parse `setuptools==58.2.0     --hash=sha256:2551203ae6955b9876741a26ab3e767bb3242dafe86a32a749ea0d78b6792f11`
-    # down to `setuptools`.
-    name = requirement.split(" ")[0].split("=")[0].split("[")[0]
-    return _annotations.get(name)
-
-def install_deps(**whl_library_kwargs):
-    """Repository rule macro. Install dependencies from `pip_parse`.
-
-    Args:
-       **whl_library_kwargs: Additional arguments which will flow to underlying
-         `whl_library` calls. See pip_repository.bzl for details.
-    """
-
-    # Set up the requirement groups
-    all_requirement_groups = %%ALL_REQUIREMENT_GROUPS%%
-
-    requirement_group_mapping = {
-        requirement: group_name
-        for group_name, group_requirements in all_requirement_groups.items()
-        for requirement in group_requirements
-    }
-
-    # %%GROUP_LIBRARY%%
-
-    # Install wheels which may be participants in a group
-    whl_config = dict(_config)
-    whl_config.update(whl_library_kwargs)
-
-    for name, requirement in _packages:
-        group_name = requirement_group_mapping.get(name.replace("%%NAME%%_", ""))
-        group_deps = all_requirement_groups.get(group_name, [])
-
-        whl_library(
-            name = name,
-            requirement = requirement,
-            group_name = group_name,
-            group_deps = group_deps,
-            annotation = _get_annotation(requirement),
-            **whl_config
-        )
diff --git a/python/pip_install/repositories.bzl b/python/pip_install/repositories.bzl
index 3f91860..5231d1f 100644
--- a/python/pip_install/repositories.bzl
+++ b/python/pip_install/repositories.bzl
@@ -14,131 +14,6 @@
 
 ""
 
-load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
-load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
+load("//python/private/pypi:deps.bzl", "pypi_deps")
 
-_RULE_DEPS = [
-    # START: maintained by 'bazel run //tools/private/update_deps:update_pip_deps'
-    (
-        "pypi__build",
-        "https://files.pythonhosted.org/packages/e2/03/f3c8ba0a6b6e30d7d18c40faab90807c9bb5e9a1e3b2fe2008af624a9c97/build-1.2.1-py3-none-any.whl",
-        "75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4",
-    ),
-    (
-        "pypi__click",
-        "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl",
-        "ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28",
-    ),
-    (
-        "pypi__colorama",
-        "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl",
-        "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6",
-    ),
-    (
-        "pypi__importlib_metadata",
-        "https://files.pythonhosted.org/packages/2d/0a/679461c511447ffaf176567d5c496d1de27cbe34a87df6677d7171b2fbd4/importlib_metadata-7.1.0-py3-none-any.whl",
-        "30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570",
-    ),
-    (
-        "pypi__installer",
-        "https://files.pythonhosted.org/packages/e5/ca/1172b6638d52f2d6caa2dd262ec4c811ba59eee96d54a7701930726bce18/installer-0.7.0-py3-none-any.whl",
-        "05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53",
-    ),
-    (
-        "pypi__more_itertools",
-        "https://files.pythonhosted.org/packages/50/e2/8e10e465ee3987bb7c9ab69efb91d867d93959095f4807db102d07995d94/more_itertools-10.2.0-py3-none-any.whl",
-        "686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684",
-    ),
-    (
-        "pypi__packaging",
-        "https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl",
-        "2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5",
-    ),
-    (
-        "pypi__pep517",
-        "https://files.pythonhosted.org/packages/25/6e/ca4a5434eb0e502210f591b97537d322546e4833dcb4d470a48c375c5540/pep517-0.13.1-py3-none-any.whl",
-        "31b206f67165b3536dd577c5c3f1518e8fbaf38cbc57efff8369a392feff1721",
-    ),
-    (
-        "pypi__pip",
-        "https://files.pythonhosted.org/packages/8a/6a/19e9fe04fca059ccf770861c7d5721ab4c2aebc539889e97c7977528a53b/pip-24.0-py3-none-any.whl",
-        "ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc",
-    ),
-    (
-        "pypi__pip_tools",
-        "https://files.pythonhosted.org/packages/0d/dc/38f4ce065e92c66f058ea7a368a9c5de4e702272b479c0992059f7693941/pip_tools-7.4.1-py3-none-any.whl",
-        "4c690e5fbae2f21e87843e89c26191f0d9454f362d8acdbd695716493ec8b3a9",
-    ),
-    (
-        "pypi__pyproject_hooks",
-        "https://files.pythonhosted.org/packages/ae/f3/431b9d5fe7d14af7a32340792ef43b8a714e7726f1d7b69cc4e8e7a3f1d7/pyproject_hooks-1.1.0-py3-none-any.whl",
-        "7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2",
-    ),
-    (
-        "pypi__setuptools",
-        "https://files.pythonhosted.org/packages/de/88/70c5767a0e43eb4451c2200f07d042a4bcd7639276003a9c54a68cfcc1f8/setuptools-70.0.0-py3-none-any.whl",
-        "54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4",
-    ),
-    (
-        "pypi__tomli",
-        "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl",
-        "939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
-    ),
-    (
-        "pypi__wheel",
-        "https://files.pythonhosted.org/packages/7d/cd/d7460c9a869b16c3dd4e1e403cce337df165368c71d6af229a74699622ce/wheel-0.43.0-py3-none-any.whl",
-        "55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81",
-    ),
-    (
-        "pypi__zipp",
-        "https://files.pythonhosted.org/packages/da/55/a03fd7240714916507e1fcf7ae355bd9d9ed2e6db492595f1a67f61681be/zipp-3.18.2-py3-none-any.whl",
-        "dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e",
-    ),
-    # END: maintained by 'bazel run //tools/private/update_deps:update_pip_deps'
-]
-
-_GENERIC_WHEEL = """\
-package(default_visibility = ["//visibility:public"])
-
-load("@rules_python//python:defs.bzl", "py_library")
-
-py_library(
-    name = "lib",
-    srcs = glob(["**/*.py"]),
-    data = glob(["**/*"], exclude=[
-        # These entries include those put into user-installed dependencies by
-        # data_exclude in /python/pip_install/tools/bazel.py
-        # to avoid non-determinism following pip install's behavior.
-        "**/*.py",
-        "**/*.pyc",
-        "**/*.pyc.*",  # During pyc creation, temp files named *.pyc.NNN are created
-        "**/* *",
-        "**/*.dist-info/RECORD",
-        "BUILD",
-        "WORKSPACE",
-    ]),
-    # This makes this directory a top-level in the python import
-    # search path for anything that depends on this.
-    imports = ["."],
-)
-"""
-
-# Collate all the repository names so they can be easily consumed
-all_requirements = [name for (name, _, _) in _RULE_DEPS]
-
-def requirement(pkg):
-    return Label("@pypi__" + pkg + "//:lib")
-
-def pip_install_dependencies():
-    """
-    Fetch dependencies these rules depend on. Workspaces that use the pip_parse rule can call this.
-    """
-    for (name, url, sha256) in _RULE_DEPS:
-        maybe(
-            http_archive,
-            name,
-            url = url,
-            sha256 = sha256,
-            type = "zip",
-            build_file_content = _GENERIC_WHEEL,
-        )
+pip_install_dependencies = pypi_deps
diff --git a/python/pip_install/requirements.bzl b/python/pip_install/requirements.bzl
index 5caf762..6ae3f8f 100644
--- a/python/pip_install/requirements.bzl
+++ b/python/pip_install/requirements.bzl
@@ -14,149 +14,6 @@
 
 """Rules to verify and update pip-compile locked requirements.txt"""
 
-load("//python:defs.bzl", _py_binary = "py_binary", _py_test = "py_test")
-load("//python/pip_install:repositories.bzl", "requirement")
+load("//python/private/pypi:pip_compile.bzl", "pip_compile")
 
-def compile_pip_requirements(
-        name,
-        src = None,
-        extra_args = [],
-        extra_deps = [],
-        generate_hashes = True,
-        py_binary = _py_binary,
-        py_test = _py_test,
-        requirements_in = None,
-        requirements_txt = None,
-        requirements_darwin = None,
-        requirements_linux = None,
-        requirements_windows = None,
-        visibility = ["//visibility:private"],
-        tags = None,
-        **kwargs):
-    """Generates targets for managing pip dependencies with pip-compile.
-
-    By default this rules generates a filegroup named "[name]" which can be included in the data
-    of some other compile_pip_requirements rule that references these requirements
-    (e.g. with `-r ../other/requirements.txt`).
-
-    It also generates two targets for running pip-compile:
-
-    - validate with `bazel test [name]_test`
-    - update with   `bazel run [name].update`
-
-    If you are using a version control system, the requirements.txt generated by this rule should
-    be checked into it to ensure that all developers/users have the same dependency versions.
-
-    Args:
-        name: base name for generated targets, typically "requirements".
-        src: file containing inputs to dependency resolution. If not specified,
-            defaults to `pyproject.toml`. Supported formats are:
-            * a requirements text file, usually named `requirements.in`
-            * A `.toml` file, where the `project.dependencies` list is used as per
-              [PEP621](https://peps.python.org/pep-0621/).
-        extra_args: passed to pip-compile.
-        extra_deps: extra dependencies passed to pip-compile.
-        generate_hashes: whether to put hashes in the requirements_txt file.
-        py_binary: the py_binary rule to be used.
-        py_test: the py_test rule to be used.
-        requirements_in: file expressing desired dependencies. Deprecated, use src instead.
-        requirements_txt: result of "compiling" the requirements.in file.
-        requirements_linux: File of linux specific resolve output to check validate if requirement.in has changes.
-        requirements_darwin: File of darwin specific resolve output to check validate if requirement.in has changes.
-        requirements_windows: File of windows specific resolve output to check validate if requirement.in has changes.
-        tags: tagging attribute common to all build rules, passed to both the _test and .update rules.
-        visibility: passed to both the _test and .update rules.
-        **kwargs: other bazel attributes passed to the "_test" rule.
-    """
-    if requirements_in and src:
-        fail("Only one of 'src' and 'requirements_in' attributes can be used")
-    else:
-        src = requirements_in or src or "pyproject.toml"
-
-    requirements_txt = name + ".txt" if requirements_txt == None else requirements_txt
-
-    # "Default" target produced by this macro
-    # Allow a compile_pip_requirements rule to include another one in the data
-    # for a requirements file that does `-r ../other/requirements.txt`
-    native.filegroup(
-        name = name,
-        srcs = kwargs.pop("data", []) + [requirements_txt],
-        visibility = visibility,
-    )
-
-    data = [name, requirements_txt, src] + [f for f in (requirements_linux, requirements_darwin, requirements_windows) if f != None]
-
-    # Use the Label constructor so this is expanded in the context of the file
-    # where it appears, which is to say, in @rules_python
-    pip_compile = Label("//python/pip_install/tools/dependency_resolver:dependency_resolver.py")
-
-    loc = "$(rlocationpath {})"
-
-    args = [
-        loc.format(src),
-        loc.format(requirements_txt),
-        "//%s:%s.update" % (native.package_name(), name),
-        "--resolver=backtracking",
-        "--allow-unsafe",
-    ]
-    if generate_hashes:
-        args.append("--generate-hashes")
-    if requirements_linux:
-        args.append("--requirements-linux={}".format(loc.format(requirements_linux)))
-    if requirements_darwin:
-        args.append("--requirements-darwin={}".format(loc.format(requirements_darwin)))
-    if requirements_windows:
-        args.append("--requirements-windows={}".format(loc.format(requirements_windows)))
-    args.extend(extra_args)
-
-    deps = [
-        requirement("build"),
-        requirement("click"),
-        requirement("colorama"),
-        requirement("importlib_metadata"),
-        requirement("more_itertools"),
-        requirement("packaging"),
-        requirement("pep517"),
-        requirement("pip"),
-        requirement("pip_tools"),
-        requirement("pyproject_hooks"),
-        requirement("setuptools"),
-        requirement("tomli"),
-        requirement("zipp"),
-        Label("//python/runfiles:runfiles"),
-    ] + extra_deps
-
-    tags = tags or []
-    tags.append("requires-network")
-    tags.append("no-remote-exec")
-    tags.append("no-sandbox")
-    attrs = {
-        "args": args,
-        "data": data,
-        "deps": deps,
-        "main": pip_compile,
-        "srcs": [pip_compile],
-        "tags": tags,
-        "visibility": visibility,
-    }
-
-    # cheap way to detect the bazel version
-    _bazel_version_4_or_greater = "propeller_optimize" in dir(native)
-
-    # Bazel 4.0 added the "env" attribute to py_test/py_binary
-    if _bazel_version_4_or_greater:
-        attrs["env"] = kwargs.pop("env", {})
-
-    py_binary(
-        name = name + ".update",
-        **attrs
-    )
-
-    timeout = kwargs.pop("timeout", "short")
-
-    py_test(
-        name = name + "_test",
-        timeout = timeout,
-        # kwargs could contain test-specific attributes like size or timeout
-        **dict(attrs, **kwargs)
-    )
+compile_pip_requirements = pip_compile
diff --git a/python/pip_install/tools/dependency_resolver/BUILD.bazel b/python/pip_install/tools/dependency_resolver/BUILD.bazel
deleted file mode 100644
index 467b009..0000000
--- a/python/pip_install/tools/dependency_resolver/BUILD.bazel
+++ /dev/null
@@ -1,19 +0,0 @@
-exports_files(["dependency_resolver.py"])
-
-filegroup(
-    name = "distribution",
-    srcs = glob(
-        ["*"],
-        exclude = ["*_test.py"],
-    ),
-    visibility = ["//python/pip_install:__subpackages__"],
-)
-
-filegroup(
-    name = "py_srcs",
-    srcs = glob(
-        include = ["**/*.py"],
-        exclude = ["**/*_test.py"],
-    ),
-    visibility = ["//:__subpackages__"],
-)
diff --git a/python/pip_install/tools/wheel_installer/BUILD.bazel b/python/pip_install/tools/wheel_installer/BUILD.bazel
deleted file mode 100644
index 0c24d5a..0000000
--- a/python/pip_install/tools/wheel_installer/BUILD.bazel
+++ /dev/null
@@ -1,91 +0,0 @@
-load("//python:defs.bzl", "py_binary", "py_library", "py_test")
-load("//python/pip_install:repositories.bzl", "requirement")
-
-py_library(
-    name = "lib",
-    srcs = [
-        "arguments.py",
-        "namespace_pkgs.py",
-        "wheel.py",
-        "wheel_installer.py",
-    ],
-    visibility = ["//third_party/rules_pycross/pycross/private:__subpackages__"],
-    deps = [
-        requirement("installer"),
-        requirement("pip"),
-        requirement("packaging"),
-        requirement("setuptools"),
-    ],
-)
-
-py_binary(
-    name = "wheel_installer",
-    srcs = [
-        "wheel_installer.py",
-    ],
-    deps = [":lib"],
-)
-
-py_test(
-    name = "arguments_test",
-    size = "small",
-    srcs = [
-        "arguments_test.py",
-    ],
-    deps = [
-        ":lib",
-    ],
-)
-
-py_test(
-    name = "namespace_pkgs_test",
-    size = "small",
-    srcs = [
-        "namespace_pkgs_test.py",
-    ],
-    deps = [
-        ":lib",
-    ],
-)
-
-py_test(
-    name = "wheel_test",
-    size = "small",
-    srcs = [
-        "wheel_test.py",
-    ],
-    data = ["//examples/wheel:minimal_with_py_package"],
-    deps = [
-        ":lib",
-    ],
-)
-
-py_test(
-    name = "wheel_installer_test",
-    size = "small",
-    srcs = [
-        "wheel_installer_test.py",
-    ],
-    data = ["//examples/wheel:minimal_with_py_package"],
-    deps = [
-        ":lib",
-    ],
-)
-
-filegroup(
-    name = "distribution",
-    srcs = glob(
-        ["*"],
-        exclude = ["*_test.py"],
-    ),
-    visibility = ["//python/pip_install:__subpackages__"],
-)
-
-filegroup(
-    name = "py_srcs",
-    srcs = glob(
-        include = ["**/*.py"],
-        exclude = ["**/*_test.py"],
-    ),
-    visibility = ["//:__subpackages__"],
-)
diff --git a/python/pip_install/tools/wheel_installer/arguments_test.py b/python/pip_install/tools/wheel_installer/arguments_test.py
deleted file mode 100644
index fa018da..0000000
--- a/python/pip_install/tools/wheel_installer/arguments_test.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# Copyright 2023 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.
-
-import argparse
-import json
-import unittest
-
-from python.pip_install.tools.wheel_installer import arguments, wheel
-
-
-class ArgumentsTestCase(unittest.TestCase):
-    def test_arguments(self) -> None:
-        parser = arguments.parser()
-        repo_name = "foo"
-        repo_prefix = "pypi_"
-        index_url = "--index_url=pypi.org/simple"
-        extra_pip_args = [index_url]
-        requirement = "foo==1.0.0 --hash=sha256:deadbeef"
-        args_dict = vars(
-            parser.parse_args(
-                args=[
-                    f'--requirement="{requirement}"',
-                    f"--extra_pip_args={json.dumps({'arg': extra_pip_args})}",
-                ]
-            )
-        )
-        args_dict = arguments.deserialize_structured_args(args_dict)
-        self.assertIn("requirement", args_dict)
-        self.assertIn("extra_pip_args", args_dict)
-        self.assertEqual(args_dict["pip_data_exclude"], [])
-        self.assertEqual(args_dict["enable_implicit_namespace_pkgs"], False)
-        self.assertEqual(args_dict["extra_pip_args"], extra_pip_args)
-
-    def test_deserialize_structured_args(self) -> None:
-        serialized_args = {
-            "pip_data_exclude": json.dumps({"arg": ["**.foo"]}),
-            "environment": json.dumps({"arg": {"PIP_DO_SOMETHING": "True"}}),
-        }
-        args = arguments.deserialize_structured_args(serialized_args)
-        self.assertEqual(args["pip_data_exclude"], ["**.foo"])
-        self.assertEqual(args["environment"], {"PIP_DO_SOMETHING": "True"})
-        self.assertEqual(args["extra_pip_args"], [])
-
-    def test_platform_aggregation(self) -> None:
-        parser = arguments.parser()
-        args = parser.parse_args(
-            args=[
-                "--platform=linux_*",
-                "--platform=osx_*",
-                "--platform=windows_*",
-                "--requirement=foo",
-            ]
-        )
-        self.assertEqual(set(wheel.Platform.all()), arguments.get_platforms(args))
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/python/pip_install/tools/wheel_installer/namespace_pkgs_test.py b/python/pip_install/tools/wheel_installer/namespace_pkgs_test.py
deleted file mode 100644
index 4aa0fea..0000000
--- a/python/pip_install/tools/wheel_installer/namespace_pkgs_test.py
+++ /dev/null
@@ -1,192 +0,0 @@
-# Copyright 2023 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.
-
-import os
-import pathlib
-import shutil
-import tempfile
-import unittest
-from typing import Optional, Set
-
-from python.pip_install.tools.wheel_installer import namespace_pkgs
-
-
-class TempDir:
-    def __init__(self) -> None:
-        self.dir = tempfile.mkdtemp()
-
-    def root(self) -> str:
-        return self.dir
-
-    def add_dir(self, rel_path: str) -> None:
-        d = pathlib.Path(self.dir, rel_path)
-        d.mkdir(parents=True)
-
-    def add_file(self, rel_path: str, contents: Optional[str] = None) -> None:
-        f = pathlib.Path(self.dir, rel_path)
-        f.parent.mkdir(parents=True, exist_ok=True)
-        if contents:
-            with open(str(f), "w") as writeable_f:
-                writeable_f.write(contents)
-        else:
-            f.touch()
-
-    def remove(self) -> None:
-        shutil.rmtree(self.dir)
-
-
-class TestImplicitNamespacePackages(unittest.TestCase):
-    def assertPathsEqual(self, actual: Set[pathlib.Path], expected: Set[str]) -> None:
-        self.assertEqual(actual, {pathlib.Path(p) for p in expected})
-
-    def test_in_current_directory(self) -> None:
-        directory = TempDir()
-        directory.add_file("foo/bar/biz.py")
-        directory.add_file("foo/bee/boo.py")
-        directory.add_file("foo/buu/__init__.py")
-        directory.add_file("foo/buu/bii.py")
-        cwd = os.getcwd()
-        os.chdir(directory.root())
-        expected = {
-            "foo",
-            "foo/bar",
-            "foo/bee",
-        }
-        try:
-            actual = namespace_pkgs.implicit_namespace_packages(".")
-            self.assertPathsEqual(actual, expected)
-        finally:
-            os.chdir(cwd)
-            directory.remove()
-
-    def test_finds_correct_namespace_packages(self) -> None:
-        directory = TempDir()
-        directory.add_file("foo/bar/biz.py")
-        directory.add_file("foo/bee/boo.py")
-        directory.add_file("foo/buu/__init__.py")
-        directory.add_file("foo/buu/bii.py")
-
-        expected = {
-            directory.root() + "/foo",
-            directory.root() + "/foo/bar",
-            directory.root() + "/foo/bee",
-        }
-        actual = namespace_pkgs.implicit_namespace_packages(directory.root())
-        self.assertPathsEqual(actual, expected)
-
-    def test_ignores_empty_directories(self) -> None:
-        directory = TempDir()
-        directory.add_file("foo/bar/biz.py")
-        directory.add_dir("foo/cat")
-
-        expected = {
-            directory.root() + "/foo",
-            directory.root() + "/foo/bar",
-        }
-        actual = namespace_pkgs.implicit_namespace_packages(directory.root())
-        self.assertPathsEqual(actual, expected)
-
-    def test_empty_case(self) -> None:
-        directory = TempDir()
-        directory.add_file("foo/__init__.py")
-        directory.add_file("foo/bar/__init__.py")
-        directory.add_file("foo/bar/biz.py")
-
-        actual = namespace_pkgs.implicit_namespace_packages(directory.root())
-        self.assertEqual(actual, set())
-
-    def test_ignores_non_module_files_in_directories(self) -> None:
-        directory = TempDir()
-        directory.add_file("foo/__init__.pyi")
-        directory.add_file("foo/py.typed")
-
-        actual = namespace_pkgs.implicit_namespace_packages(directory.root())
-        self.assertEqual(actual, set())
-
-    def test_parent_child_relationship_of_namespace_pkgs(self):
-        directory = TempDir()
-        directory.add_file("foo/bar/biff/my_module.py")
-        directory.add_file("foo/bar/biff/another_module.py")
-
-        expected = {
-            directory.root() + "/foo",
-            directory.root() + "/foo/bar",
-            directory.root() + "/foo/bar/biff",
-        }
-        actual = namespace_pkgs.implicit_namespace_packages(directory.root())
-        self.assertPathsEqual(actual, expected)
-
-    def test_parent_child_relationship_of_namespace_and_standard_pkgs(self):
-        directory = TempDir()
-        directory.add_file("foo/bar/biff/__init__.py")
-        directory.add_file("foo/bar/biff/another_module.py")
-
-        expected = {
-            directory.root() + "/foo",
-            directory.root() + "/foo/bar",
-        }
-        actual = namespace_pkgs.implicit_namespace_packages(directory.root())
-        self.assertPathsEqual(actual, expected)
-
-    def test_parent_child_relationship_of_namespace_and_nested_standard_pkgs(self):
-        directory = TempDir()
-        directory.add_file("foo/bar/__init__.py")
-        directory.add_file("foo/bar/biff/another_module.py")
-        directory.add_file("foo/bar/biff/__init__.py")
-        directory.add_file("foo/bar/boof/big_module.py")
-        directory.add_file("foo/bar/boof/__init__.py")
-        directory.add_file("fim/in_a_ns_pkg.py")
-
-        expected = {
-            directory.root() + "/foo",
-            directory.root() + "/fim",
-        }
-        actual = namespace_pkgs.implicit_namespace_packages(directory.root())
-        self.assertPathsEqual(actual, expected)
-
-    def test_recognized_all_nonstandard_module_types(self):
-        directory = TempDir()
-        directory.add_file("ayy/my_module.pyc")
-        directory.add_file("bee/ccc/dee/eee.so")
-        directory.add_file("eff/jee/aych.pyd")
-
-        expected = {
-            directory.root() + "/ayy",
-            directory.root() + "/bee",
-            directory.root() + "/bee/ccc",
-            directory.root() + "/bee/ccc/dee",
-            directory.root() + "/eff",
-            directory.root() + "/eff/jee",
-        }
-        actual = namespace_pkgs.implicit_namespace_packages(directory.root())
-        self.assertPathsEqual(actual, expected)
-
-    def test_skips_ignored_directories(self):
-        directory = TempDir()
-        directory.add_file("foo/boo/my_module.py")
-        directory.add_file("foo/bar/another_module.py")
-
-        expected = {
-            directory.root() + "/foo",
-            directory.root() + "/foo/bar",
-        }
-        actual = namespace_pkgs.implicit_namespace_packages(
-            directory.root(),
-            ignored_dirnames=[directory.root() + "/foo/boo"],
-        )
-        self.assertPathsEqual(actual, expected)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/python/pip_install/tools/wheel_installer/wheel_installer_test.py b/python/pip_install/tools/wheel_installer/wheel_installer_test.py
deleted file mode 100644
index 74b9c30..0000000
--- a/python/pip_install/tools/wheel_installer/wheel_installer_test.py
+++ /dev/null
@@ -1,105 +0,0 @@
-# Copyright 2023 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.
-
-import json
-import os
-import shutil
-import tempfile
-import unittest
-from pathlib import Path
-
-from python.pip_install.tools.wheel_installer import wheel, wheel_installer
-
-
-class TestRequirementExtrasParsing(unittest.TestCase):
-    def test_parses_requirement_for_extra(self) -> None:
-        cases = [
-            ("name[foo]", ("name", frozenset(["foo"]))),
-            ("name[ Foo123 ]", ("name", frozenset(["Foo123"]))),
-            (" name1[ foo ] ", ("name1", frozenset(["foo"]))),
-            ("Name[foo]", ("name", frozenset(["foo"]))),
-            ("name_foo[bar]", ("name-foo", frozenset(["bar"]))),
-            (
-                "name [fred,bar] @ http://foo.com ; python_version=='2.7'",
-                ("name", frozenset(["fred", "bar"])),
-            ),
-            (
-                "name[quux, strange];python_version<'2.7' and platform_version=='2'",
-                ("name", frozenset(["quux", "strange"])),
-            ),
-            (
-                "name; (os_name=='a' or os_name=='b') and os_name=='c'",
-                (None, None),
-            ),
-            (
-                "name@http://foo.com",
-                (None, None),
-            ),
-        ]
-
-        for case, expected in cases:
-            with self.subTest():
-                self.assertTupleEqual(
-                    wheel_installer._parse_requirement_for_extra(case), expected
-                )
-
-
-class TestWhlFilegroup(unittest.TestCase):
-    def setUp(self) -> None:
-        self.wheel_name = "example_minimal_package-0.0.1-py3-none-any.whl"
-        self.wheel_dir = tempfile.mkdtemp()
-        self.wheel_path = os.path.join(self.wheel_dir, self.wheel_name)
-        shutil.copy(os.path.join("examples", "wheel", self.wheel_name), self.wheel_dir)
-
-    def tearDown(self):
-        shutil.rmtree(self.wheel_dir)
-
-    def test_wheel_exists(self) -> None:
-        wheel_installer._extract_wheel(
-            Path(self.wheel_path),
-            installation_dir=Path(self.wheel_dir),
-            extras={},
-            enable_implicit_namespace_pkgs=False,
-            platforms=[],
-        )
-
-        want_files = [
-            "metadata.json",
-            "site-packages",
-            self.wheel_name,
-        ]
-        self.assertEqual(
-            sorted(want_files),
-            sorted(
-                [
-                    str(p.relative_to(self.wheel_dir))
-                    for p in Path(self.wheel_dir).glob("*")
-                ]
-            ),
-        )
-        with open("{}/metadata.json".format(self.wheel_dir)) as metadata_file:
-            metadata_file_content = json.load(metadata_file)
-
-        want = dict(
-            version="0.0.1",
-            name="example-minimal-package",
-            deps=[],
-            deps_by_platform={},
-            entry_points=[],
-        )
-        self.assertEqual(want, metadata_file_content)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/python/pip_install/tools/wheel_installer/wheel_test.py b/python/pip_install/tools/wheel_installer/wheel_test.py
deleted file mode 100644
index 3ddfaf7..0000000
--- a/python/pip_install/tools/wheel_installer/wheel_test.py
+++ /dev/null
@@ -1,492 +0,0 @@
-import unittest
-from random import shuffle
-from unittest import mock
-
-from python.pip_install.tools.wheel_installer import wheel
-
-
-class DepsTest(unittest.TestCase):
-    def test_simple(self):
-        deps = wheel.Deps("foo", requires_dist=["bar"])
-
-        got = deps.build()
-
-        self.assertIsInstance(got, wheel.FrozenDeps)
-        self.assertEqual(["bar"], got.deps)
-        self.assertEqual({}, got.deps_select)
-
-    def test_can_add_os_specific_deps(self):
-        deps = wheel.Deps(
-            "foo",
-            requires_dist=[
-                "bar",
-                "an_osx_dep; sys_platform=='darwin'",
-                "posix_dep; os_name=='posix'",
-                "win_dep; os_name=='nt'",
-            ],
-            platforms={
-                wheel.Platform(os=wheel.OS.linux, arch=wheel.Arch.x86_64),
-                wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.x86_64),
-                wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.aarch64),
-                wheel.Platform(os=wheel.OS.windows, arch=wheel.Arch.x86_64),
-            },
-        )
-
-        got = deps.build()
-
-        self.assertEqual(["bar"], got.deps)
-        self.assertEqual(
-            {
-                "@platforms//os:linux": ["posix_dep"],
-                "@platforms//os:osx": ["an_osx_dep", "posix_dep"],
-                "@platforms//os:windows": ["win_dep"],
-            },
-            got.deps_select,
-        )
-
-    def test_can_add_os_specific_deps_with_specific_python_version(self):
-        deps = wheel.Deps(
-            "foo",
-            requires_dist=[
-                "bar",
-                "an_osx_dep; sys_platform=='darwin'",
-                "posix_dep; os_name=='posix'",
-                "win_dep; os_name=='nt'",
-            ],
-            platforms={
-                wheel.Platform(
-                    os=wheel.OS.linux, arch=wheel.Arch.x86_64, minor_version=8
-                ),
-                wheel.Platform(
-                    os=wheel.OS.osx, arch=wheel.Arch.x86_64, minor_version=8
-                ),
-                wheel.Platform(
-                    os=wheel.OS.osx, arch=wheel.Arch.aarch64, minor_version=8
-                ),
-                wheel.Platform(
-                    os=wheel.OS.windows, arch=wheel.Arch.x86_64, minor_version=8
-                ),
-            },
-        )
-
-        got = deps.build()
-
-        self.assertEqual(["bar"], got.deps)
-        self.assertEqual(
-            {
-                "@platforms//os:linux": ["posix_dep"],
-                "@platforms//os:osx": ["an_osx_dep", "posix_dep"],
-                "@platforms//os:windows": ["win_dep"],
-            },
-            got.deps_select,
-        )
-
-    def test_deps_are_added_to_more_specialized_platforms(self):
-        got = wheel.Deps(
-            "foo",
-            requires_dist=[
-                "m1_dep; sys_platform=='darwin' and platform_machine=='arm64'",
-                "mac_dep; sys_platform=='darwin'",
-            ],
-            platforms={
-                wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.x86_64),
-                wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.aarch64),
-            },
-        ).build()
-
-        self.assertEqual(
-            wheel.FrozenDeps(
-                deps=[],
-                deps_select={
-                    "osx_aarch64": ["m1_dep", "mac_dep"],
-                    "@platforms//os:osx": ["mac_dep"],
-                },
-            ),
-            got,
-        )
-
-    def test_deps_from_more_specialized_platforms_are_propagated(self):
-        got = wheel.Deps(
-            "foo",
-            requires_dist=[
-                "a_mac_dep; sys_platform=='darwin'",
-                "m1_dep; sys_platform=='darwin' and platform_machine=='arm64'",
-            ],
-            platforms={
-                wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.x86_64),
-                wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.aarch64),
-            },
-        ).build()
-
-        self.assertEqual([], got.deps)
-        self.assertEqual(
-            {
-                "osx_aarch64": ["a_mac_dep", "m1_dep"],
-                "@platforms//os:osx": ["a_mac_dep"],
-            },
-            got.deps_select,
-        )
-
-    def test_non_platform_markers_are_added_to_common_deps(self):
-        got = wheel.Deps(
-            "foo",
-            requires_dist=[
-                "bar",
-                "baz; implementation_name=='cpython'",
-                "m1_dep; sys_platform=='darwin' and platform_machine=='arm64'",
-            ],
-            platforms={
-                wheel.Platform(os=wheel.OS.linux, arch=wheel.Arch.x86_64),
-                wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.x86_64),
-                wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.aarch64),
-                wheel.Platform(os=wheel.OS.windows, arch=wheel.Arch.x86_64),
-            },
-        ).build()
-
-        self.assertEqual(["bar", "baz"], got.deps)
-        self.assertEqual(
-            {
-                "osx_aarch64": ["m1_dep"],
-            },
-            got.deps_select,
-        )
-
-    def test_self_is_ignored(self):
-        deps = wheel.Deps(
-            "foo",
-            requires_dist=[
-                "bar",
-                "req_dep; extra == 'requests'",
-                "foo[requests]; extra == 'ssl'",
-                "ssl_lib; extra == 'ssl'",
-            ],
-            extras={"ssl"},
-        )
-
-        got = deps.build()
-
-        self.assertEqual(["bar", "req_dep", "ssl_lib"], got.deps)
-        self.assertEqual({}, got.deps_select)
-
-    def test_self_dependencies_can_come_in_any_order(self):
-        deps = wheel.Deps(
-            "foo",
-            requires_dist=[
-                "bar",
-                "baz; extra == 'feat'",
-                "foo[feat2]; extra == 'all'",
-                "foo[feat]; extra == 'feat2'",
-                "zdep; extra == 'all'",
-            ],
-            extras={"all"},
-        )
-
-        got = deps.build()
-
-        self.assertEqual(["bar", "baz", "zdep"], got.deps)
-        self.assertEqual({}, got.deps_select)
-
-    def test_can_get_deps_based_on_specific_python_version(self):
-        requires_dist = [
-            "bar",
-            "baz; python_version < '3.8'",
-            "posix_dep; os_name=='posix' and python_version >= '3.8'",
-        ]
-
-        py38_deps = wheel.Deps(
-            "foo",
-            requires_dist=requires_dist,
-            platforms=[
-                wheel.Platform(
-                    os=wheel.OS.linux, arch=wheel.Arch.x86_64, minor_version=8
-                ),
-            ],
-        ).build()
-        py37_deps = wheel.Deps(
-            "foo",
-            requires_dist=requires_dist,
-            platforms=[
-                wheel.Platform(
-                    os=wheel.OS.linux, arch=wheel.Arch.x86_64, minor_version=7
-                ),
-            ],
-        ).build()
-
-        self.assertEqual(["bar", "baz"], py37_deps.deps)
-        self.assertEqual({}, py37_deps.deps_select)
-        self.assertEqual(["bar"], py38_deps.deps)
-        self.assertEqual({"@platforms//os:linux": ["posix_dep"]}, py38_deps.deps_select)
-
-    @mock.patch(
-        "python.pip_install.tools.wheel_installer.wheel.host_interpreter_minor_version"
-    )
-    def test_can_get_version_select(self, mock_host_interpreter_version):
-        requires_dist = [
-            "bar",
-            "baz; python_version < '3.8'",
-            "baz_new; python_version >= '3.8'",
-            "posix_dep; os_name=='posix'",
-            "posix_dep_with_version; os_name=='posix' and python_version >= '3.8'",
-        ]
-        mock_host_interpreter_version.return_value = 7
-
-        self.maxDiff = None
-
-        deps = wheel.Deps(
-            "foo",
-            requires_dist=requires_dist,
-            platforms=[
-                wheel.Platform(os=os, arch=wheel.Arch.x86_64, minor_version=minor)
-                for minor in [7, 8, 9]
-                for os in [wheel.OS.linux, wheel.OS.windows]
-            ],
-        )
-        got = deps.build()
-
-        self.assertEqual(["bar"], got.deps)
-        self.assertEqual(
-            {
-                "//conditions:default": ["baz"],
-                "@//python/config_settings:is_python_3.7": ["baz"],
-                "@//python/config_settings:is_python_3.8": ["baz_new"],
-                "@//python/config_settings:is_python_3.9": ["baz_new"],
-                "@platforms//os:linux": ["baz", "posix_dep"],
-                "cp37_linux_anyarch": ["baz", "posix_dep"],
-                "cp38_linux_anyarch": [
-                    "baz_new",
-                    "posix_dep",
-                    "posix_dep_with_version",
-                ],
-                "cp39_linux_anyarch": [
-                    "baz_new",
-                    "posix_dep",
-                    "posix_dep_with_version",
-                ],
-            },
-            got.deps_select,
-        )
-
-    @mock.patch(
-        "python.pip_install.tools.wheel_installer.wheel.host_interpreter_minor_version"
-    )
-    def test_deps_spanning_all_target_py_versions_are_added_to_common(
-        self, mock_host_version
-    ):
-        requires_dist = [
-            "bar",
-            "baz (<2,>=1.11) ; python_version < '3.8'",
-            "baz (<2,>=1.14) ; python_version >= '3.8'",
-        ]
-        mock_host_version.return_value = 8
-
-        deps = wheel.Deps(
-            "foo",
-            requires_dist=requires_dist,
-            platforms=wheel.Platform.from_string(["cp37_*", "cp38_*", "cp39_*"]),
-        )
-        got = deps.build()
-
-        self.assertEqual(["bar", "baz"], got.deps)
-        self.assertEqual({}, got.deps_select)
-
-    @mock.patch(
-        "python.pip_install.tools.wheel_installer.wheel.host_interpreter_minor_version"
-    )
-    def test_deps_are_not_duplicated(self, mock_host_version):
-        mock_host_version.return_value = 7
-
-        # See an example in
-        # https://files.pythonhosted.org/packages/76/9e/db1c2d56c04b97981c06663384f45f28950a73d9acf840c4006d60d0a1ff/opencv_python-4.9.0.80-cp37-abi3-win32.whl.metadata
-        requires_dist = [
-            "bar >=0.1.0 ; python_version < '3.7'",
-            "bar >=0.2.0 ; python_version >= '3.7'",
-            "bar >=0.4.0 ; python_version >= '3.6' and platform_system == 'Linux' and platform_machine == 'aarch64'",
-            "bar >=0.4.0 ; python_version >= '3.9'",
-            "bar >=0.5.0 ; python_version <= '3.9' and platform_system == 'Darwin' and platform_machine == 'arm64'",
-            "bar >=0.5.0 ; python_version >= '3.10' and platform_system == 'Darwin'",
-            "bar >=0.5.0 ; python_version >= '3.10'",
-            "bar >=0.6.0 ; python_version >= '3.11'",
-        ]
-
-        deps = wheel.Deps(
-            "foo",
-            requires_dist=requires_dist,
-            platforms=wheel.Platform.from_string(["cp37_*", "cp310_*"]),
-        )
-        got = deps.build()
-
-        self.assertEqual(["bar"], got.deps)
-        self.assertEqual({}, got.deps_select)
-
-    @mock.patch(
-        "python.pip_install.tools.wheel_installer.wheel.host_interpreter_minor_version"
-    )
-    def test_deps_are_not_duplicated_when_encountering_platform_dep_first(
-        self, mock_host_version
-    ):
-        mock_host_version.return_value = 7
-
-        # Note, that we are sorting the incoming `requires_dist` and we need to ensure that we are not getting any
-        # issues even if the platform-specific line comes first.
-        requires_dist = [
-            "bar >=0.4.0 ; python_version >= '3.6' and platform_system == 'Linux' and platform_machine == 'aarch64'",
-            "bar >=0.5.0 ; python_version >= '3.9'",
-        ]
-
-        deps = wheel.Deps(
-            "foo",
-            requires_dist=requires_dist,
-            platforms=wheel.Platform.from_string(["cp37_*", "cp310_*"]),
-        )
-        got = deps.build()
-
-        self.assertEqual(["bar"], got.deps)
-        self.assertEqual({}, got.deps_select)
-
-
-class MinorVersionTest(unittest.TestCase):
-    def test_host(self):
-        host = wheel.host_interpreter_minor_version()
-        self.assertIsNotNone(host)
-
-
-class PlatformTest(unittest.TestCase):
-    def test_can_get_host(self):
-        host = wheel.Platform.host()
-        self.assertIsNotNone(host)
-        self.assertEqual(1, len(wheel.Platform.from_string("host")))
-        self.assertEqual(host, wheel.Platform.from_string("host"))
-
-    def test_can_get_linux_x86_64_without_py_version(self):
-        got = wheel.Platform.from_string("linux_x86_64")
-        want = wheel.Platform(os=wheel.OS.linux, arch=wheel.Arch.x86_64)
-        self.assertEqual(want, got[0])
-
-    def test_can_get_specific_from_string(self):
-        got = wheel.Platform.from_string("cp33_linux_x86_64")
-        want = wheel.Platform(
-            os=wheel.OS.linux, arch=wheel.Arch.x86_64, minor_version=3
-        )
-        self.assertEqual(want, got[0])
-
-    def test_can_get_all_for_py_version(self):
-        cp39 = wheel.Platform.all(minor_version=9)
-        self.assertEqual(18, len(cp39), f"Got {cp39}")
-        self.assertEqual(cp39, wheel.Platform.from_string("cp39_*"))
-
-    def test_can_get_all_for_os(self):
-        linuxes = wheel.Platform.all(wheel.OS.linux, minor_version=9)
-        self.assertEqual(6, len(linuxes))
-        self.assertEqual(linuxes, wheel.Platform.from_string("cp39_linux_*"))
-
-    def test_can_get_all_for_os_for_host_python(self):
-        linuxes = wheel.Platform.all(wheel.OS.linux)
-        self.assertEqual(6, len(linuxes))
-        self.assertEqual(linuxes, wheel.Platform.from_string("linux_*"))
-
-    def test_specific_version_specializations(self):
-        any_py33 = wheel.Platform(minor_version=3)
-
-        # When
-        all_specializations = list(any_py33.all_specializations())
-
-        want = (
-            [any_py33]
-            + [
-                wheel.Platform(arch=arch, minor_version=any_py33.minor_version)
-                for arch in wheel.Arch
-            ]
-            + [
-                wheel.Platform(os=os, minor_version=any_py33.minor_version)
-                for os in wheel.OS
-            ]
-            + wheel.Platform.all(minor_version=any_py33.minor_version)
-        )
-        self.assertEqual(want, all_specializations)
-
-    def test_aarch64_specializations(self):
-        any_aarch64 = wheel.Platform(arch=wheel.Arch.aarch64)
-        all_specializations = list(any_aarch64.all_specializations())
-        want = [
-            wheel.Platform(os=None, arch=wheel.Arch.aarch64),
-            wheel.Platform(os=wheel.OS.linux, arch=wheel.Arch.aarch64),
-            wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.aarch64),
-            wheel.Platform(os=wheel.OS.windows, arch=wheel.Arch.aarch64),
-        ]
-        self.assertEqual(want, all_specializations)
-
-    def test_linux_specializations(self):
-        any_linux = wheel.Platform(os=wheel.OS.linux)
-        all_specializations = list(any_linux.all_specializations())
-        want = [
-            wheel.Platform(os=wheel.OS.linux, arch=None),
-            wheel.Platform(os=wheel.OS.linux, arch=wheel.Arch.x86_64),
-            wheel.Platform(os=wheel.OS.linux, arch=wheel.Arch.x86_32),
-            wheel.Platform(os=wheel.OS.linux, arch=wheel.Arch.aarch64),
-            wheel.Platform(os=wheel.OS.linux, arch=wheel.Arch.ppc),
-            wheel.Platform(os=wheel.OS.linux, arch=wheel.Arch.s390x),
-            wheel.Platform(os=wheel.OS.linux, arch=wheel.Arch.arm),
-        ]
-        self.assertEqual(want, all_specializations)
-
-    def test_osx_specializations(self):
-        any_osx = wheel.Platform(os=wheel.OS.osx)
-        all_specializations = list(any_osx.all_specializations())
-        # NOTE @aignas 2024-01-14: even though in practice we would only have
-        # Python on osx aarch64 and osx x86_64, we return all arch posibilities
-        # to make the code simpler.
-        want = [
-            wheel.Platform(os=wheel.OS.osx, arch=None),
-            wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.x86_64),
-            wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.x86_32),
-            wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.aarch64),
-            wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.ppc),
-            wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.s390x),
-            wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.arm),
-        ]
-        self.assertEqual(want, all_specializations)
-
-    def test_platform_sort(self):
-        platforms = [
-            wheel.Platform(os=wheel.OS.linux, arch=None),
-            wheel.Platform(os=wheel.OS.linux, arch=wheel.Arch.x86_64),
-            wheel.Platform(os=wheel.OS.osx, arch=None),
-            wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.x86_64),
-            wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.aarch64),
-        ]
-        shuffle(platforms)
-        platforms.sort()
-        want = [
-            wheel.Platform(os=wheel.OS.linux, arch=None),
-            wheel.Platform(os=wheel.OS.linux, arch=wheel.Arch.x86_64),
-            wheel.Platform(os=wheel.OS.osx, arch=None),
-            wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.x86_64),
-            wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.aarch64),
-        ]
-
-        self.assertEqual(want, platforms)
-
-    def test_wheel_os_alias(self):
-        self.assertEqual("osx", str(wheel.OS.osx))
-        self.assertEqual(str(wheel.OS.darwin), str(wheel.OS.osx))
-
-    def test_wheel_arch_alias(self):
-        self.assertEqual("x86_64", str(wheel.Arch.x86_64))
-        self.assertEqual(str(wheel.Arch.amd64), str(wheel.Arch.x86_64))
-
-    def test_wheel_platform_alias(self):
-        give = wheel.Platform(
-            os=wheel.OS.darwin,
-            arch=wheel.Arch.amd64,
-        )
-        alias = wheel.Platform(
-            os=wheel.OS.osx,
-            arch=wheel.Arch.x86_64,
-        )
-
-        self.assertEqual("osx_x86_64", str(give))
-        self.assertEqual(str(alias), str(give))
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/python/private/bzlmod/internal_deps.bzl b/python/private/bzlmod/internal_deps.bzl
index 62ca71f..e0eca9e 100644
--- a/python/private/bzlmod/internal_deps.bzl
+++ b/python/private/bzlmod/internal_deps.bzl
@@ -9,12 +9,12 @@
 "Python toolchain module extension for internal rule use"
 
 load("@bazel_skylib//lib:modules.bzl", "modules")
-load("//python/pip_install:repositories.bzl", "pip_install_dependencies")
 load("//python/private:internal_config_repo.bzl", "internal_config_repo")
+load("//python/private/pypi:deps.bzl", "pypi_deps")
 
 def _internal_deps():
     internal_config_repo(name = "rules_python_internal")
-    pip_install_dependencies()
+    pypi_deps()
 
 internal_deps = modules.as_extension(
     _internal_deps,
diff --git a/python/private/normalize_name.bzl b/python/private/normalize_name.bzl
index aaeca80..7898222 100644
--- a/python/private/normalize_name.bzl
+++ b/python/private/normalize_name.bzl
@@ -38,7 +38,6 @@
 https://packaging.python.org/en/latest/specifications/name-normalization/
 """
 
-# Keep in sync with ../pip_install/tools/lib/bazel.py
 def normalize_name(name):
     """normalize a PyPI package name and return a valid bazel label.
 
diff --git a/python/private/pypi/BUILD.bazel b/python/private/pypi/BUILD.bazel
index 1530837..e7ae735 100644
--- a/python/private/pypi/BUILD.bazel
+++ b/python/private/pypi/BUILD.bazel
@@ -21,7 +21,13 @@
 
 filegroup(
     name = "distribution",
-    srcs = glob(["**"]),
+    srcs = glob(
+        ["**"],
+        exclude = ["requirements.txt"],
+    ) + [
+        "//python/private/pypi/dependency_resolver:distribution",
+        "//python/private/pypi/whl_installer:distribution",
+    ],
     visibility = ["//python/private:__pkg__"],
 )
 
@@ -29,7 +35,16 @@
 filegroup(
     name = "bzl",
     srcs = glob(["**/*.bzl"]),
-    visibility = ["//python/private:__pkg__"],
+    visibility = [
+        "//python/private:__pkg__",
+        "//tools/private:__pkg__",
+    ],
+)
+
+filegroup(
+    name = "requirements_txt",
+    srcs = ["requirements.txt"],
+    visibility = ["//tools/private/update_deps:__pkg__"],
 )
 
 # Keep sorted by library name and keep the files named by the main symbol they export
@@ -67,6 +82,14 @@
 )
 
 bzl_library(
+    name = "deps_bzl",
+    srcs = ["deps.bzl"],
+    deps = [
+        "//python/private:bazel_tools_bzl",
+    ],
+)
+
+bzl_library(
     name = "flags_bzl",
     srcs = ["flags.bzl"],
     deps = ["//python/private:enum_bzl"],
@@ -119,6 +142,12 @@
 )
 
 bzl_library(
+    name = "multi_pip_parse_bzl",
+    srcs = ["multi_pip_parse.bzl"],
+    deps = ["pip_repository_bzl"],
+)
+
+bzl_library(
     name = "package_annotation_bzl",
     srcs = ["package_annotation.bzl"],
 )
@@ -159,6 +188,15 @@
 )
 
 bzl_library(
+    name = "pip_compile_bzl",
+    srcs = ["pip_compile.bzl"],
+    deps = [
+        ":deps_bzl",
+        "//python:defs_bzl",
+    ],
+)
+
+bzl_library(
     name = "pip_repository_bzl",
     srcs = ["pip_repository.bzl"],
     deps = [
@@ -204,17 +242,26 @@
 )
 
 bzl_library(
+    name = "whl_library_alias_bzl",
+    srcs = ["whl_library_alias.bzl"],
+    deps = [
+        ":render_pkg_aliases_bzl",
+        "//python/private:full_version_bzl",
+    ],
+)
+
+bzl_library(
     name = "whl_library_bzl",
     srcs = ["whl_library.bzl"],
     deps = [
         ":attrs_bzl",
+        ":deps_bzl",
         ":generate_whl_library_build_bazel_bzl",
         ":parse_whl_name_bzl",
         ":patch_whl_bzl",
         ":whl_target_platforms_bzl",
         "//python:repositories_bzl",
         "//python:versions_bzl",
-        "//python/pip_install:repositories_bzl",
         "//python/private:auth_bzl",
         "//python/private:envsubst_bzl",
         "//python/private:repo_utils_bzl",
diff --git a/python/private/pypi/dependency_resolver/BUILD.bazel b/python/private/pypi/dependency_resolver/BUILD.bazel
new file mode 100644
index 0000000..9531b55
--- /dev/null
+++ b/python/private/pypi/dependency_resolver/BUILD.bazel
@@ -0,0 +1,7 @@
+exports_files(["dependency_resolver.py"])
+
+filegroup(
+    name = "distribution",
+    srcs = glob(["**"]),
+    visibility = ["//python/private/pypi:__subpackages__"],
+)
diff --git a/python/pip_install/tools/dependency_resolver/__init__.py b/python/private/pypi/dependency_resolver/__init__.py
similarity index 100%
rename from python/pip_install/tools/dependency_resolver/__init__.py
rename to python/private/pypi/dependency_resolver/__init__.py
diff --git a/python/pip_install/tools/dependency_resolver/dependency_resolver.py b/python/private/pypi/dependency_resolver/dependency_resolver.py
similarity index 100%
rename from python/pip_install/tools/dependency_resolver/dependency_resolver.py
rename to python/private/pypi/dependency_resolver/dependency_resolver.py
diff --git a/python/private/pypi/deps.bzl b/python/private/pypi/deps.bzl
new file mode 100644
index 0000000..81bef7a
--- /dev/null
+++ b/python/private/pypi/deps.bzl
@@ -0,0 +1,143 @@
+# Copyright 2023 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.
+
+""
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
+
+_RULE_DEPS = [
+    # START: maintained by 'bazel run //tools/private/update_deps:update_pip_deps'
+    (
+        "pypi__build",
+        "https://files.pythonhosted.org/packages/e2/03/f3c8ba0a6b6e30d7d18c40faab90807c9bb5e9a1e3b2fe2008af624a9c97/build-1.2.1-py3-none-any.whl",
+        "75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4",
+    ),
+    (
+        "pypi__click",
+        "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl",
+        "ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28",
+    ),
+    (
+        "pypi__colorama",
+        "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl",
+        "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6",
+    ),
+    (
+        "pypi__importlib_metadata",
+        "https://files.pythonhosted.org/packages/2d/0a/679461c511447ffaf176567d5c496d1de27cbe34a87df6677d7171b2fbd4/importlib_metadata-7.1.0-py3-none-any.whl",
+        "30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570",
+    ),
+    (
+        "pypi__installer",
+        "https://files.pythonhosted.org/packages/e5/ca/1172b6638d52f2d6caa2dd262ec4c811ba59eee96d54a7701930726bce18/installer-0.7.0-py3-none-any.whl",
+        "05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53",
+    ),
+    (
+        "pypi__more_itertools",
+        "https://files.pythonhosted.org/packages/50/e2/8e10e465ee3987bb7c9ab69efb91d867d93959095f4807db102d07995d94/more_itertools-10.2.0-py3-none-any.whl",
+        "686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684",
+    ),
+    (
+        "pypi__packaging",
+        "https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl",
+        "2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5",
+    ),
+    (
+        "pypi__pep517",
+        "https://files.pythonhosted.org/packages/25/6e/ca4a5434eb0e502210f591b97537d322546e4833dcb4d470a48c375c5540/pep517-0.13.1-py3-none-any.whl",
+        "31b206f67165b3536dd577c5c3f1518e8fbaf38cbc57efff8369a392feff1721",
+    ),
+    (
+        "pypi__pip",
+        "https://files.pythonhosted.org/packages/8a/6a/19e9fe04fca059ccf770861c7d5721ab4c2aebc539889e97c7977528a53b/pip-24.0-py3-none-any.whl",
+        "ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc",
+    ),
+    (
+        "pypi__pip_tools",
+        "https://files.pythonhosted.org/packages/0d/dc/38f4ce065e92c66f058ea7a368a9c5de4e702272b479c0992059f7693941/pip_tools-7.4.1-py3-none-any.whl",
+        "4c690e5fbae2f21e87843e89c26191f0d9454f362d8acdbd695716493ec8b3a9",
+    ),
+    (
+        "pypi__pyproject_hooks",
+        "https://files.pythonhosted.org/packages/ae/f3/431b9d5fe7d14af7a32340792ef43b8a714e7726f1d7b69cc4e8e7a3f1d7/pyproject_hooks-1.1.0-py3-none-any.whl",
+        "7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2",
+    ),
+    (
+        "pypi__setuptools",
+        "https://files.pythonhosted.org/packages/de/88/70c5767a0e43eb4451c2200f07d042a4bcd7639276003a9c54a68cfcc1f8/setuptools-70.0.0-py3-none-any.whl",
+        "54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4",
+    ),
+    (
+        "pypi__tomli",
+        "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl",
+        "939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
+    ),
+    (
+        "pypi__wheel",
+        "https://files.pythonhosted.org/packages/7d/cd/d7460c9a869b16c3dd4e1e403cce337df165368c71d6af229a74699622ce/wheel-0.43.0-py3-none-any.whl",
+        "55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81",
+    ),
+    (
+        "pypi__zipp",
+        "https://files.pythonhosted.org/packages/da/55/a03fd7240714916507e1fcf7ae355bd9d9ed2e6db492595f1a67f61681be/zipp-3.18.2-py3-none-any.whl",
+        "dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e",
+    ),
+    # END: maintained by 'bazel run //tools/private/update_deps:update_pip_deps'
+]
+
+_GENERIC_WHEEL = """\
+package(default_visibility = ["//visibility:public"])
+
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+    name = "lib",
+    srcs = glob(["**/*.py"]),
+    data = glob(["**/*"], exclude=[
+        # These entries include those put into user-installed dependencies by
+        # data_exclude to avoid non-determinism.
+        "**/*.py",
+        "**/*.pyc",
+        "**/*.pyc.*",  # During pyc creation, temp files named *.pyc.NNN are created
+        "**/* *",
+        "**/*.dist-info/RECORD",
+        "BUILD",
+        "WORKSPACE",
+    ]),
+    # This makes this directory a top-level in the python import
+    # search path for anything that depends on this.
+    imports = ["."],
+)
+"""
+
+# Collate all the repository names so they can be easily consumed
+all_requirements = [name for (name, _, _) in _RULE_DEPS]
+
+def requirement(pkg):
+    return Label("@pypi__" + pkg + "//:lib")
+
+def pypi_deps():
+    """
+    Fetch dependencies these rules depend on. Workspaces that use the pip_parse rule can call this.
+    """
+    for (name, url, sha256) in _RULE_DEPS:
+        maybe(
+            http_archive,
+            name,
+            url = url,
+            sha256 = sha256,
+            type = "zip",
+            build_file_content = _GENERIC_WHEEL,
+        )
diff --git a/python/private/pypi/multi_pip_parse.bzl b/python/private/pypi/multi_pip_parse.bzl
new file mode 100644
index 0000000..fe9e2db
--- /dev/null
+++ b/python/private/pypi/multi_pip_parse.bzl
@@ -0,0 +1,160 @@
+# 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.
+
+"""A pip_parse implementation for version aware toolchains in WORKSPACE."""
+
+load(":pip_repository.bzl", pip_parse = "pip_repository")
+
+def _multi_pip_parse_impl(rctx):
+    rules_python = rctx.attr._rules_python_workspace.workspace_name
+    load_statements = []
+    install_deps_calls = []
+    process_requirements_calls = []
+    for python_version, pypi_repository in rctx.attr.pip_parses.items():
+        sanitized_python_version = python_version.replace(".", "_")
+        load_statement = """\
+load(
+    "@{pypi_repository}//:requirements.bzl",
+    _{sanitized_python_version}_install_deps = "install_deps",
+    _{sanitized_python_version}_all_requirements = "all_requirements",
+)""".format(
+            pypi_repository = pypi_repository,
+            sanitized_python_version = sanitized_python_version,
+        )
+        load_statements.append(load_statement)
+        process_requirements_call = """\
+_process_requirements(
+    pkg_labels = _{sanitized_python_version}_all_requirements,
+    python_version = "{python_version}",
+    repo_prefix = "{pypi_repository}_",
+)""".format(
+            pypi_repository = pypi_repository,
+            python_version = python_version,
+            sanitized_python_version = sanitized_python_version,
+        )
+        process_requirements_calls.append(process_requirements_call)
+        install_deps_call = """    _{sanitized_python_version}_install_deps(**whl_library_kwargs)""".format(
+            sanitized_python_version = sanitized_python_version,
+        )
+        install_deps_calls.append(install_deps_call)
+
+    # NOTE @aignas 2023-10-31: I am not sure it is possible to render aliases
+    # for all of the packages using the `render_pkg_aliases` function because
+    # we need to know what the list of packages for each version is and then
+    # we would be creating directories for each.
+    macro_tmpl = "@%s_{}//:{}" % rctx.attr.name
+
+    requirements_bzl = """\
+# Generated by python/pip.bzl
+
+load("@{rules_python}//python:pip.bzl", "whl_library_alias", "pip_utils")
+{load_statements}
+
+_wheel_names = []
+_version_map = dict()
+def _process_requirements(pkg_labels, python_version, repo_prefix):
+    for pkg_label in pkg_labels:
+        wheel_name = Label(pkg_label).package
+        if not wheel_name:
+            # We are dealing with the cases where we don't have aliases.
+            workspace_name = Label(pkg_label).workspace_name
+            wheel_name = workspace_name[len(repo_prefix):]
+
+        _wheel_names.append(wheel_name)
+        if not wheel_name in _version_map:
+            _version_map[wheel_name] = dict()
+        _version_map[wheel_name][python_version] = repo_prefix
+
+{process_requirements_calls}
+
+def requirement(name):
+    return "{macro_tmpl}".format(pip_utils.normalize_name(name), "pkg")
+
+def whl_requirement(name):
+    return "{macro_tmpl}".format(pip_utils.normalize_name(name), "whl")
+
+def data_requirement(name):
+    return "{macro_tmpl}".format(pip_utils.normalize_name(name), "data")
+
+def dist_info_requirement(name):
+    return "{macro_tmpl}".format(pip_utils.normalize_name(name), "dist_info")
+
+def install_deps(**whl_library_kwargs):
+{install_deps_calls}
+    for wheel_name in _wheel_names:
+        whl_library_alias(
+            name = "{name}_" + wheel_name,
+            wheel_name = wheel_name,
+            default_version = "{default_version}",
+            version_map = _version_map[wheel_name],
+        )
+""".format(
+        name = rctx.attr.name,
+        install_deps_calls = "\n".join(install_deps_calls),
+        load_statements = "\n".join(load_statements),
+        macro_tmpl = macro_tmpl,
+        process_requirements_calls = "\n".join(process_requirements_calls),
+        rules_python = rules_python,
+        default_version = rctx.attr.default_version,
+    )
+    rctx.file("requirements.bzl", requirements_bzl)
+    rctx.file("BUILD.bazel", "exports_files(['requirements.bzl'])")
+
+_multi_pip_parse = repository_rule(
+    _multi_pip_parse_impl,
+    attrs = {
+        "default_version": attr.string(),
+        "pip_parses": attr.string_dict(),
+        "_rules_python_workspace": attr.label(default = Label("//:WORKSPACE")),
+    },
+)
+
+def multi_pip_parse(name, default_version, python_versions, python_interpreter_target, requirements_lock, **kwargs):
+    """NOT INTENDED FOR DIRECT USE!
+
+    This is intended to be used by the multi_pip_parse implementation in the template of the
+    multi_toolchain_aliases repository rule.
+
+    Args:
+        name: the name of the multi_pip_parse repository.
+        default_version: the default Python version.
+        python_versions: all Python toolchain versions currently registered.
+        python_interpreter_target: a dictionary which keys are Python versions and values are resolved host interpreters.
+        requirements_lock: a dictionary which keys are Python versions and values are locked requirements files.
+        **kwargs: extra arguments passed to all wrapped pip_parse.
+
+    Returns:
+        The internal implementation of multi_pip_parse repository rule.
+    """
+    pip_parses = {}
+    for python_version in python_versions:
+        if not python_version in python_interpreter_target:
+            fail("Missing python_interpreter_target for Python version %s in '%s'" % (python_version, name))
+        if not python_version in requirements_lock:
+            fail("Missing requirements_lock for Python version %s in '%s'" % (python_version, name))
+
+        pip_parse_name = name + "_" + python_version.replace(".", "_")
+        pip_parse(
+            name = pip_parse_name,
+            python_interpreter_target = python_interpreter_target[python_version],
+            requirements_lock = requirements_lock[python_version],
+            **kwargs
+        )
+        pip_parses[python_version] = pip_parse_name
+
+    return _multi_pip_parse(
+        name = name,
+        default_version = default_version,
+        pip_parses = pip_parses,
+    )
diff --git a/python/private/pypi/pip_compile.bzl b/python/private/pypi/pip_compile.bzl
new file mode 100644
index 0000000..7389e72
--- /dev/null
+++ b/python/private/pypi/pip_compile.bzl
@@ -0,0 +1,167 @@
+# Copyright 2023 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.
+
+"""
+Rules to verify and update pip-compile locked requirements.txt.
+
+NOTE @aignas 2024-06-23: We are using the implementation specific name here to
+make it possible to have multiple tools inside the `pypi` directory
+"""
+
+load("//python:defs.bzl", _py_binary = "py_binary", _py_test = "py_test")
+load(":deps.bzl", "requirement")
+
+def pip_compile(
+        name,
+        src = None,
+        extra_args = [],
+        extra_deps = [],
+        generate_hashes = True,
+        py_binary = _py_binary,
+        py_test = _py_test,
+        requirements_in = None,
+        requirements_txt = None,
+        requirements_darwin = None,
+        requirements_linux = None,
+        requirements_windows = None,
+        visibility = ["//visibility:private"],
+        tags = None,
+        **kwargs):
+    """Generates targets for managing pip dependencies with pip-compile.
+
+    By default this rules generates a filegroup named "[name]" which can be included in the data
+    of some other compile_pip_requirements rule that references these requirements
+    (e.g. with `-r ../other/requirements.txt`).
+
+    It also generates two targets for running pip-compile:
+
+    - validate with `bazel test [name]_test`
+    - update with   `bazel run [name].update`
+
+    If you are using a version control system, the requirements.txt generated by this rule should
+    be checked into it to ensure that all developers/users have the same dependency versions.
+
+    Args:
+        name: base name for generated targets, typically "requirements".
+        src: file containing inputs to dependency resolution. If not specified,
+            defaults to `pyproject.toml`. Supported formats are:
+            * a requirements text file, usually named `requirements.in`
+            * A `.toml` file, where the `project.dependencies` list is used as per
+              [PEP621](https://peps.python.org/pep-0621/).
+        extra_args: passed to pip-compile.
+        extra_deps: extra dependencies passed to pip-compile.
+        generate_hashes: whether to put hashes in the requirements_txt file.
+        py_binary: the py_binary rule to be used.
+        py_test: the py_test rule to be used.
+        requirements_in: file expressing desired dependencies. Deprecated, use src instead.
+        requirements_txt: result of "compiling" the requirements.in file.
+        requirements_linux: File of linux specific resolve output to check validate if requirement.in has changes.
+        requirements_darwin: File of darwin specific resolve output to check validate if requirement.in has changes.
+        requirements_windows: File of windows specific resolve output to check validate if requirement.in has changes.
+        tags: tagging attribute common to all build rules, passed to both the _test and .update rules.
+        visibility: passed to both the _test and .update rules.
+        **kwargs: other bazel attributes passed to the "_test" rule.
+    """
+    if requirements_in and src:
+        fail("Only one of 'src' and 'requirements_in' attributes can be used")
+    else:
+        src = requirements_in or src or "pyproject.toml"
+
+    requirements_txt = name + ".txt" if requirements_txt == None else requirements_txt
+
+    # "Default" target produced by this macro
+    # Allow a compile_pip_requirements rule to include another one in the data
+    # for a requirements file that does `-r ../other/requirements.txt`
+    native.filegroup(
+        name = name,
+        srcs = kwargs.pop("data", []) + [requirements_txt],
+        visibility = visibility,
+    )
+
+    data = [name, requirements_txt, src] + [f for f in (requirements_linux, requirements_darwin, requirements_windows) if f != None]
+
+    # Use the Label constructor so this is expanded in the context of the file
+    # where it appears, which is to say, in @rules_python
+    pip_compile = Label("//python/private/pypi/dependency_resolver:dependency_resolver.py")
+
+    loc = "$(rlocationpath {})"
+
+    args = [
+        loc.format(src),
+        loc.format(requirements_txt),
+        "//%s:%s.update" % (native.package_name(), name),
+        "--resolver=backtracking",
+        "--allow-unsafe",
+    ]
+    if generate_hashes:
+        args.append("--generate-hashes")
+    if requirements_linux:
+        args.append("--requirements-linux={}".format(loc.format(requirements_linux)))
+    if requirements_darwin:
+        args.append("--requirements-darwin={}".format(loc.format(requirements_darwin)))
+    if requirements_windows:
+        args.append("--requirements-windows={}".format(loc.format(requirements_windows)))
+    args.extend(extra_args)
+
+    deps = [
+        requirement("build"),
+        requirement("click"),
+        requirement("colorama"),
+        requirement("importlib_metadata"),
+        requirement("more_itertools"),
+        requirement("packaging"),
+        requirement("pep517"),
+        requirement("pip"),
+        requirement("pip_tools"),
+        requirement("pyproject_hooks"),
+        requirement("setuptools"),
+        requirement("tomli"),
+        requirement("zipp"),
+        Label("//python/runfiles:runfiles"),
+    ] + extra_deps
+
+    tags = tags or []
+    tags.append("requires-network")
+    tags.append("no-remote-exec")
+    tags.append("no-sandbox")
+    attrs = {
+        "args": args,
+        "data": data,
+        "deps": deps,
+        "main": pip_compile,
+        "srcs": [pip_compile],
+        "tags": tags,
+        "visibility": visibility,
+    }
+
+    # cheap way to detect the bazel version
+    _bazel_version_4_or_greater = "propeller_optimize" in dir(native)
+
+    # Bazel 4.0 added the "env" attribute to py_test/py_binary
+    if _bazel_version_4_or_greater:
+        attrs["env"] = kwargs.pop("env", {})
+
+    py_binary(
+        name = name + ".update",
+        **attrs
+    )
+
+    timeout = kwargs.pop("timeout", "short")
+
+    py_test(
+        name = name + "_test",
+        timeout = timeout,
+        # kwargs could contain test-specific attributes like size or timeout
+        **dict(attrs, **kwargs)
+    )
diff --git a/python/pip_install/tools/requirements.txt b/python/private/pypi/requirements.txt
similarity index 100%
rename from python/pip_install/tools/requirements.txt
rename to python/private/pypi/requirements.txt
diff --git a/python/private/pypi/whl_installer/BUILD.bazel b/python/private/pypi/whl_installer/BUILD.bazel
new file mode 100644
index 0000000..58231ce
--- /dev/null
+++ b/python/private/pypi/whl_installer/BUILD.bazel
@@ -0,0 +1,36 @@
+load("//python:defs.bzl", "py_binary", "py_library")
+load("//python/private/pypi:deps.bzl", "requirement")
+
+py_library(
+    name = "lib",
+    srcs = [
+        "arguments.py",
+        "namespace_pkgs.py",
+        "wheel.py",
+        "wheel_installer.py",
+    ],
+    visibility = [
+        "//tests:__subpackages__",
+        "//third_party/rules_pycross/pycross/private:__subpackages__",
+    ],
+    deps = [
+        requirement("installer"),
+        requirement("pip"),
+        requirement("packaging"),
+        requirement("setuptools"),
+    ],
+)
+
+py_binary(
+    name = "wheel_installer",
+    srcs = [
+        "wheel_installer.py",
+    ],
+    deps = [":lib"],
+)
+
+filegroup(
+    name = "distribution",
+    srcs = glob(["*"]),
+    visibility = ["//python/private/pypi:__subpackages__"],
+)
diff --git a/python/pip_install/tools/wheel_installer/arguments.py b/python/private/pypi/whl_installer/arguments.py
similarity index 97%
rename from python/pip_install/tools/wheel_installer/arguments.py
rename to python/private/pypi/whl_installer/arguments.py
index 71133c2..173d3a3 100644
--- a/python/pip_install/tools/wheel_installer/arguments.py
+++ b/python/private/pypi/whl_installer/arguments.py
@@ -17,7 +17,7 @@
 import pathlib
 from typing import Any, Dict, Set
 
-from python.pip_install.tools.wheel_installer import wheel
+from python.private.pypi.whl_installer import wheel
 
 
 def parser(**kwargs: Any) -> argparse.ArgumentParser:
diff --git a/python/pip_install/tools/wheel_installer/namespace_pkgs.py b/python/private/pypi/whl_installer/namespace_pkgs.py
similarity index 100%
rename from python/pip_install/tools/wheel_installer/namespace_pkgs.py
rename to python/private/pypi/whl_installer/namespace_pkgs.py
diff --git a/python/pip_install/tools/wheel_installer/wheel.py b/python/private/pypi/whl_installer/wheel.py
similarity index 100%
rename from python/pip_install/tools/wheel_installer/wheel.py
rename to python/private/pypi/whl_installer/wheel.py
diff --git a/python/pip_install/tools/wheel_installer/wheel_installer.py b/python/private/pypi/whl_installer/wheel_installer.py
similarity index 98%
rename from python/pip_install/tools/wheel_installer/wheel_installer.py
rename to python/private/pypi/whl_installer/wheel_installer.py
index 801ef95..ef8181c 100644
--- a/python/pip_install/tools/wheel_installer/wheel_installer.py
+++ b/python/private/pypi/whl_installer/wheel_installer.py
@@ -27,7 +27,7 @@
 
 from pip._vendor.packaging.utils import canonicalize_name
 
-from python.pip_install.tools.wheel_installer import arguments, namespace_pkgs, wheel
+from python.private.pypi.whl_installer import arguments, namespace_pkgs, wheel
 
 
 def _configure_reproducible_wheels() -> None:
diff --git a/python/private/pypi/whl_library.bzl b/python/private/pypi/whl_library.bzl
index cae0db3..77cbd4e 100644
--- a/python/private/pypi/whl_library.bzl
+++ b/python/private/pypi/whl_library.bzl
@@ -16,12 +16,12 @@
 
 load("//python:repositories.bzl", "is_standalone_interpreter")
 load("//python:versions.bzl", "WINDOWS_NAME")
-load("//python/pip_install:repositories.bzl", "all_requirements")
 load("//python/private:auth.bzl", "AUTH_ATTRS", "get_auth")
 load("//python/private:envsubst.bzl", "envsubst")
 load("//python/private:repo_utils.bzl", "REPO_DEBUG_ENV_VAR", "repo_utils")
 load("//python/private:toolchains_repo.bzl", "get_host_os_arch")
 load(":attrs.bzl", "ATTRS", "use_isolated")
+load(":deps.bzl", "all_requirements")
 load(":generate_whl_library_build_bazel.bzl", "generate_whl_library_build_bazel")
 load(":parse_whl_name.bzl", "parse_whl_name")
 load(":patch_whl.bzl", "patch_whl")
@@ -241,7 +241,7 @@
     args = [
         python_interpreter,
         "-m",
-        "python.pip_install.tools.wheel_installer.wheel_installer",
+        "python.private.pypi.whl_installer.wheel_installer",
         "--requirement",
         rctx.attr.requirement,
     ]
diff --git a/python/private/pypi/whl_library_alias.bzl b/python/private/pypi/whl_library_alias.bzl
new file mode 100644
index 0000000..263d7ec
--- /dev/null
+++ b/python/private/pypi/whl_library_alias.bzl
@@ -0,0 +1,99 @@
+# 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.
+
+"""whl_library aliases for multi_pip_parse."""
+
+load("//python/private:full_version.bzl", "full_version")
+load(":render_pkg_aliases.bzl", "NO_MATCH_ERROR_MESSAGE_TEMPLATE")
+
+def _whl_library_alias_impl(rctx):
+    rules_python = rctx.attr._rules_python_workspace.workspace_name
+    if rctx.attr.default_version:
+        default_repo_prefix = rctx.attr.version_map[rctx.attr.default_version]
+    else:
+        default_repo_prefix = None
+    version_map = rctx.attr.version_map.items()
+    build_content = ["# Generated by python/pip.bzl"]
+    for alias_name in ["pkg", "whl", "data", "dist_info"]:
+        build_content.append(_whl_library_render_alias_target(
+            alias_name = alias_name,
+            default_repo_prefix = default_repo_prefix,
+            rules_python = rules_python,
+            version_map = version_map,
+            wheel_name = rctx.attr.wheel_name,
+        ))
+    rctx.file("BUILD.bazel", "\n".join(build_content))
+
+def _whl_library_render_alias_target(
+        alias_name,
+        default_repo_prefix,
+        rules_python,
+        version_map,
+        wheel_name):
+    alias = ["""\
+alias(
+    name = "{alias_name}",
+    actual = select({{""".format(alias_name = alias_name)]
+    for [python_version, repo_prefix] in version_map:
+        alias.append("""\
+        "@{rules_python}//python/config_settings:is_python_{full_python_version}": "{actual}",""".format(
+            full_python_version = full_version(python_version),
+            actual = "@{repo_prefix}{wheel_name}//:{alias_name}".format(
+                repo_prefix = repo_prefix,
+                wheel_name = wheel_name,
+                alias_name = alias_name,
+            ),
+            rules_python = rules_python,
+        ))
+    if default_repo_prefix:
+        default_actual = "@{repo_prefix}{wheel_name}//:{alias_name}".format(
+            repo_prefix = default_repo_prefix,
+            wheel_name = wheel_name,
+            alias_name = alias_name,
+        )
+        alias.append('        "//conditions:default": "{default_actual}",'.format(
+            default_actual = default_actual,
+        ))
+
+    alias.append("    },")  # Close select expression condition dict
+    if not default_repo_prefix:
+        supported_versions = sorted([python_version for python_version, _ in version_map])
+        alias.append('    no_match_error="""{}""",'.format(
+            NO_MATCH_ERROR_MESSAGE_TEMPLATE.format(
+                supported_versions = ", ".join(supported_versions),
+                rules_python = rules_python,
+            ),
+        ))
+    alias.append("    ),")  # Close the select expression
+    alias.append('    visibility = ["//visibility:public"],')
+    alias.append(")")  # Close the alias() expression
+    return "\n".join(alias)
+
+whl_library_alias = repository_rule(
+    _whl_library_alias_impl,
+    attrs = {
+        "default_version": attr.string(
+            mandatory = False,
+            doc = "Optional Python version in major.minor format, e.g. '3.10'." +
+                  "The Python version of the wheel to use when the versions " +
+                  "from `version_map` don't match. This allows the default " +
+                  "(version unaware) rules to match and select a wheel. If " +
+                  "not specified, then the default rules won't be able to " +
+                  "resolve a wheel and an error will occur.",
+        ),
+        "version_map": attr.string_dict(mandatory = True),
+        "wheel_name": attr.string(mandatory = True),
+        "_rules_python_workspace": attr.label(default = Label("//:WORKSPACE")),
+    },
+)
diff --git a/python/repositories.bzl b/python/repositories.bzl
index 245aae2..d58feef 100644
--- a/python/repositories.bzl
+++ b/python/repositories.bzl
@@ -19,7 +19,6 @@
 
 load("@bazel_tools//tools/build_defs/repo:http.bzl", _http_archive = "http_archive")
 load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
-load("//python/pip_install:repositories.bzl", "pip_install_dependencies")
 load("//python/private:auth.bzl", "get_auth")
 load("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED")
 load("//python/private:coverage_deps.bzl", "coverage_dep")
@@ -33,6 +32,7 @@
     "toolchain_aliases",
     "toolchains_repo",
 )
+load("//python/private/pypi:deps.bzl", "pypi_deps")
 load(
     ":versions.bzl",
     "DEFAULT_RELEASE_BASE_URL",
@@ -68,7 +68,7 @@
         sha256 = "2037875b9a4456dce4a79d112a8ae885bbc4aad968e6587dca6e64f3a0900cdf",
         strip_prefix = "rules_cc-0.0.9",
     )
-    pip_install_dependencies()
+    pypi_deps()
 
 ########
 # Remaining content of the file is only used to support toolchains.