Refactor wheel_installer (#937)

* .

* .

* .

* .

* .

* .
diff --git a/examples/wheel/wheel_test.py b/examples/wheel/wheel_test.py
index cbca092..67aaac5 100644
--- a/examples/wheel/wheel_test.py
+++ b/examples/wheel/wheel_test.py
@@ -109,7 +109,8 @@
 examples/wheel/lib/module_with_data.py,sha256=8s0Khhcqz3yVsBKv2IB5u4l4TMKh7-c_V6p65WVHPms,637
 examples/wheel/lib/simple_module.py,sha256=z2hwciab_XPNIBNH8B1Q5fYgnJvQTeYf0ZQJpY8yLLY,637
 examples/wheel/main.py,sha256=sgg5iWN_9inYBjm6_Zw27hYdmo-l24fA-2rfphT-IlY,909
-""")
+""",
+            )
             self.assertEqual(
                 wheel_contents,
                 b"""\
@@ -134,7 +135,8 @@
 Requires-Dist: pytest
 
 This is a sample description of a wheel.
-""")
+""",
+            )
             self.assertEqual(
                 entry_point_contents,
                 b"""\
diff --git a/python/pip_install/extract_wheels/BUILD b/python/pip_install/extract_wheels/BUILD
index 1420f4b..1cf7e2e 100644
--- a/python/pip_install/extract_wheels/BUILD
+++ b/python/pip_install/extract_wheels/BUILD
@@ -8,11 +8,10 @@
         "annotation.py",
         "arguments.py",
         "bazel.py",
-        "extract_single_wheel.py",
         "namespace_pkgs.py",
         "parse_requirements_to_bzl.py",
-        "requirements.py",
         "wheel.py",
+        "wheel_installer.py",
     ],
     deps = [
         requirement("installer"),
@@ -21,9 +20,9 @@
 )
 
 py_binary(
-    name = "extract_single_wheel",
+    name = "wheel_installer",
     srcs = [
-        "extract_single_wheel.py",
+        "wheel_installer.py",
     ],
     deps = [":lib"],
 )
@@ -79,18 +78,6 @@
 )
 
 py_test(
-    name = "bazel_test",
-    size = "small",
-    srcs = [
-        "bazel_test.py",
-    ],
-    tags = ["unit"],
-    deps = [
-        ":lib",
-    ],
-)
-
-py_test(
     name = "namespace_pkgs_test",
     size = "small",
     srcs = [
@@ -103,11 +90,12 @@
 )
 
 py_test(
-    name = "requirements_test",
+    name = "wheel_installer_test",
     size = "small",
     srcs = [
-        "requirements_test.py",
+        "wheel_installer_test.py",
     ],
+    data = ["//examples/wheel:minimal_with_py_package"],
     tags = ["unit"],
     deps = [
         ":lib",
@@ -127,16 +115,6 @@
 )
 
 py_test(
-    name = "whl_filegroup_test",
-    size = "small",
-    srcs = ["whl_filegroup_test.py"],
-    data = ["//examples/wheel:minimal_with_py_package"],
-    main = "whl_filegroup_test.py",
-    tags = ["unit"],
-    deps = [":lib"],
-)
-
-py_test(
     name = "parse_requirements_to_bzl_test",
     size = "small",
     srcs = [
diff --git a/python/pip_install/extract_wheels/bazel.py b/python/pip_install/extract_wheels/bazel.py
index 28a2292..c0a4aec 100644
--- a/python/pip_install/extract_wheels/bazel.py
+++ b/python/pip_install/extract_wheels/bazel.py
@@ -1,13 +1,3 @@
-"""Utility functions to manipulate Bazel files"""
-import json
-import os
-import shutil
-import textwrap
-from pathlib import Path
-from typing import Dict, Iterable, List, Optional, Set
-
-from python.pip_install.extract_wheels import annotation, namespace_pkgs, wheel
-
 WHEEL_FILE_LABEL = "whl"
 PY_LIBRARY_LABEL = "pkg"
 DATA_LABEL = "data"
@@ -15,192 +5,6 @@
 WHEEL_ENTRY_POINT_PREFIX = "rules_python_wheel_entry_point"
 
 
-def generate_entry_point_contents(
-    module: str, attribute: str, shebang: str = "#!/usr/bin/env python3"
-) -> str:
-    """Generate the contents of an entry point script.
-
-    Args:
-        module (str): The name of the module to use.
-        attribute (str): The name of the attribute to call.
-        shebang (str, optional): The shebang to use for the entry point python
-            file.
-
-    Returns:
-        str: A string of python code.
-    """
-    return textwrap.dedent(
-        """\
-        {shebang}
-        import sys
-        from {module} import {attribute}
-        if __name__ == "__main__":
-            sys.exit({attribute}())
-        """.format(
-            shebang=shebang, module=module, attribute=attribute
-        )
-    )
-
-
-def generate_entry_point_rule(name: str, script: str, pkg: str) -> str:
-    """Generate a Bazel `py_binary` rule for an entry point script.
-
-    Note that the script is used to determine the name of the target. The name of
-    entry point targets should be uniuqe to avoid conflicts with existing sources or
-    directories within a wheel.
-
-    Args:
-        name (str): The name of the generated py_binary.
-        script (str): The path to the entry point's python file.
-        pkg (str): The package owning the entry point. This is expected to
-            match up with the `py_library` defined for each repository.
-
-
-    Returns:
-        str: A `py_binary` instantiation.
-    """
-    return textwrap.dedent(
-        """\
-        py_binary(
-            name = "{name}",
-            srcs = ["{src}"],
-            # This makes this directory a top-level in the python import
-            # search path for anything that depends on this.
-            imports = ["."],
-            deps = ["{pkg}"],
-        )
-        """.format(
-            name=name, src=str(script).replace("\\", "/"), pkg=pkg
-        )
-    )
-
-
-def generate_copy_commands(src, dest, is_executable=False) -> str:
-    """Generate a [@bazel_skylib//rules:copy_file.bzl%copy_file][cf] target
-
-    [cf]: https://github.com/bazelbuild/bazel-skylib/blob/1.1.1/docs/copy_file_doc.md
-
-    Args:
-        src (str): The label for the `src` attribute of [copy_file][cf]
-        dest (str): The label for the `out` attribute of [copy_file][cf]
-        is_executable (bool, optional): Whether or not the file being copied is executable.
-            sets `is_executable` for [copy_file][cf]
-
-    Returns:
-        str: A `copy_file` instantiation.
-    """
-    return textwrap.dedent(
-        """\
-        copy_file(
-            name = "{dest}.copy",
-            src = "{src}",
-            out = "{dest}",
-            is_executable = {is_executable},
-        )
-    """.format(
-            src=src,
-            dest=dest,
-            is_executable=is_executable,
-        )
-    )
-
-
-def generate_build_file_contents(
-    name: str,
-    dependencies: List[str],
-    whl_file_deps: List[str],
-    data_exclude: List[str],
-    tags: List[str],
-    srcs_exclude: List[str] = [],
-    data: List[str] = [],
-    additional_content: List[str] = [],
-) -> str:
-    """Generate a BUILD file for an unzipped Wheel
-
-    Args:
-        name: the target name of the py_library
-        dependencies: a list of Bazel labels pointing to dependencies of the library
-        whl_file_deps: a list of Bazel labels pointing to wheel file dependencies of this wheel.
-        data_exclude: more patterns to exclude from the data attribute of generated py_library rules.
-        tags: list of tags to apply to generated py_library rules.
-        additional_content: A list of additional content to append to the BUILD file.
-
-    Returns:
-        A complete BUILD file as a string
-
-    We allow for empty Python sources as for Wheels containing only compiled C code
-    there may be no Python sources whatsoever (e.g. packages written in Cython: like `pymssql`).
-    """
-
-    data_exclude = list(
-        set(
-            [
-                "**/* *",
-                "**/*.py",
-                "**/*.pyc",
-                # RECORD is known to contain sha256 checksums of files which might include the checksums
-                # of generated files produced when wheels are installed. The file is ignored to avoid
-                # Bazel caching issues.
-                "**/*.dist-info/RECORD",
-            ]
-            + data_exclude
-        )
-    )
-
-    return "\n".join(
-        [
-            textwrap.dedent(
-                """\
-        load("@rules_python//python:defs.bzl", "py_library", "py_binary")
-        load("@rules_python//third_party/github.com/bazelbuild/bazel-skylib/rules:copy_file.bzl", "copy_file")
-
-        package(default_visibility = ["//visibility:public"])
-
-        filegroup(
-            name = "{dist_info_label}",
-            srcs = glob(["site-packages/*.dist-info/**"], allow_empty = True),
-        )
-
-        filegroup(
-            name = "{data_label}",
-            srcs = glob(["data/**"], allow_empty = True),
-        )
-
-        filegroup(
-            name = "{whl_file_label}",
-            srcs = glob(["*.whl"], allow_empty = True),
-            data = [{whl_file_deps}],
-        )
-
-        py_library(
-            name = "{name}",
-            srcs = glob(["site-packages/**/*.py"], exclude={srcs_exclude}, allow_empty = True),
-            data = {data} + glob(["site-packages/**/*"], exclude={data_exclude}),
-            # This makes this directory a top-level in the python import
-            # search path for anything that depends on this.
-            imports = ["site-packages"],
-            deps = [{dependencies}],
-            tags = [{tags}],
-        )
-        """.format(
-                    name=name,
-                    dependencies=",".join(sorted(dependencies)),
-                    data_exclude=json.dumps(sorted(data_exclude)),
-                    whl_file_label=WHEEL_FILE_LABEL,
-                    whl_file_deps=",".join(sorted(whl_file_deps)),
-                    tags=",".join(sorted(['"%s"' % t for t in tags])),
-                    data_label=DATA_LABEL,
-                    dist_info_label=DIST_INFO_LABEL,
-                    entry_point_prefix=WHEEL_ENTRY_POINT_PREFIX,
-                    srcs_exclude=json.dumps(sorted(srcs_exclude)),
-                    data=json.dumps(sorted(data)),
-                )
-            )
-        ]
-        + additional_content
-    )
-
-
 def sanitise_name(name: str, prefix: str) -> str:
     """Sanitises the name to be compatible with Bazel labels.
 
@@ -221,38 +25,6 @@
     return prefix + name.replace("-", "_").replace(".", "_").lower()
 
 
-def setup_namespace_pkg_compatibility(wheel_dir: str) -> None:
-    """Converts native namespace packages to pkgutil-style packages
-
-    Namespace packages can be created in one of three ways. They are detailed here:
-    https://packaging.python.org/guides/packaging-namespace-packages/#creating-a-namespace-package
-
-    'pkgutil-style namespace packages' (2) and 'pkg_resources-style namespace packages' (3) works in Bazel, but
-    'native namespace packages' (1) do not.
-
-    We ensure compatibility with Bazel of method 1 by converting them into method 2.
-
-    Args:
-        wheel_dir: the directory of the wheel to convert
-    """
-
-    namespace_pkg_dirs = namespace_pkgs.implicit_namespace_packages(
-        wheel_dir,
-        ignored_dirnames=["%s/bin" % wheel_dir],
-    )
-
-    for ns_pkg_dir in namespace_pkg_dirs:
-        namespace_pkgs.add_pkgutil_style_namespace_pkg_init(ns_pkg_dir)
-
-
-def sanitised_library_label(whl_name: str, prefix: str) -> str:
-    return '"//%s"' % sanitise_name(whl_name, prefix)
-
-
-def sanitised_file_label(whl_name: str, prefix: str) -> str:
-    return '"//%s:%s"' % (sanitise_name(whl_name, prefix), WHEEL_FILE_LABEL)
-
-
 def _whl_name_to_repo_root(whl_name: str, repo_prefix: str) -> str:
     return "@{}//".format(sanitise_name(whl_name, prefix=repo_prefix))
 
@@ -267,127 +39,3 @@
     return '"{}:{}"'.format(
         _whl_name_to_repo_root(whl_name, repo_prefix), WHEEL_FILE_LABEL
     )
-
-
-def extract_wheel(
-    wheel_file: str,
-    extras: Dict[str, Set[str]],
-    pip_data_exclude: List[str],
-    enable_implicit_namespace_pkgs: bool,
-    repo_prefix: str,
-    incremental: bool = False,
-    incremental_dir: Path = Path("."),
-    annotation: Optional[annotation.Annotation] = None,
-) -> Optional[str]:
-    """Extracts wheel into given directory and creates py_library and filegroup targets.
-
-    Args:
-        wheel_file: the filepath of the .whl
-        extras: a list of extras to add as dependencies for the installed wheel
-        pip_data_exclude: list of file patterns to exclude from the generated data section of the py_library
-        enable_implicit_namespace_pkgs: if true, disables conversion of implicit namespace packages and will unzip as-is
-        incremental: If true the extract the wheel in a format suitable for an external repository. This
-            effects the names of libraries and their dependencies, which point to other external repositories.
-        incremental_dir: An optional override for the working directory of incremental builds.
-        annotation: An optional set of annotations to apply to the BUILD contents of the wheel.
-
-    Returns:
-        The Bazel label for the extracted wheel, in the form '//path/to/wheel'.
-    """
-
-    whl = wheel.Wheel(wheel_file)
-    if incremental:
-        directory = incremental_dir
-    else:
-        directory = sanitise_name(whl.name, prefix=repo_prefix)
-
-        os.mkdir(directory)
-        # copy the original wheel
-        shutil.copy(whl.path, directory)
-    whl.unzip(directory)
-
-    if not enable_implicit_namespace_pkgs:
-        setup_namespace_pkg_compatibility(directory)
-
-    extras_requested = extras[whl.name] if whl.name in extras else set()
-    # Packages may create dependency cycles when specifying optional-dependencies / 'extras'.
-    # Example: github.com/google/etils/blob/a0b71032095db14acf6b33516bca6d885fe09e35/pyproject.toml#L32.
-    self_edge_dep = set([whl.name])
-    whl_deps = sorted(whl.dependencies(extras_requested) - self_edge_dep)
-
-    if incremental:
-        sanitised_dependencies = [
-            sanitised_repo_library_label(d, repo_prefix=repo_prefix) for d in whl_deps
-        ]
-        sanitised_wheel_file_dependencies = [
-            sanitised_repo_file_label(d, repo_prefix=repo_prefix) for d in whl_deps
-        ]
-    else:
-        sanitised_dependencies = [
-            sanitised_library_label(d, prefix=repo_prefix) for d in whl_deps
-        ]
-        sanitised_wheel_file_dependencies = [
-            sanitised_file_label(d, prefix=repo_prefix) for d in whl_deps
-        ]
-
-    library_name = (
-        PY_LIBRARY_LABEL if incremental else sanitise_name(whl.name, repo_prefix)
-    )
-
-    directory_path = Path(directory)
-    entry_points = []
-    for name, (module, attribute) in sorted(whl.entry_points().items()):
-        # There is an extreme edge-case with entry_points that end with `.py`
-        # See: https://github.com/bazelbuild/bazel/blob/09c621e4cf5b968f4c6cdf905ab142d5961f9ddc/src/test/java/com/google/devtools/build/lib/rules/python/PyBinaryConfiguredTargetTest.java#L174
-        entry_point_without_py = f"{name[:-3]}_py" if name.endswith(".py") else name
-        entry_point_target_name = f"{WHEEL_ENTRY_POINT_PREFIX}_{entry_point_without_py}"
-        entry_point_script_name = f"{entry_point_target_name}.py"
-        (directory_path / entry_point_script_name).write_text(
-            generate_entry_point_contents(module, attribute)
-        )
-        entry_points.append(
-            generate_entry_point_rule(
-                entry_point_target_name,
-                entry_point_script_name,
-                library_name,
-            )
-        )
-
-    with open(os.path.join(directory, "BUILD.bazel"), "w") as build_file:
-        additional_content = entry_points
-        data = []
-        data_exclude = pip_data_exclude
-        srcs_exclude = []
-        if annotation:
-            for src, dest in annotation.copy_files.items():
-                data.append(dest)
-                additional_content.append(generate_copy_commands(src, dest))
-            for src, dest in annotation.copy_executables.items():
-                data.append(dest)
-                additional_content.append(
-                    generate_copy_commands(src, dest, is_executable=True)
-                )
-            data.extend(annotation.data)
-            data_exclude.extend(annotation.data_exclude_glob)
-            srcs_exclude.extend(annotation.srcs_exclude_glob)
-            if annotation.additive_build_content:
-                additional_content.append(annotation.additive_build_content)
-
-        contents = generate_build_file_contents(
-            name=PY_LIBRARY_LABEL
-            if incremental
-            else sanitise_name(whl.name, repo_prefix),
-            dependencies=sanitised_dependencies,
-            whl_file_deps=sanitised_wheel_file_dependencies,
-            data_exclude=data_exclude,
-            data=data,
-            srcs_exclude=srcs_exclude,
-            tags=["pypi_name=" + whl.name, "pypi_version=" + whl.version],
-            additional_content=additional_content,
-        )
-        build_file.write(contents)
-
-    if not incremental:
-        os.remove(whl.path)
-        return f"//{directory}"
-    return None
diff --git a/python/pip_install/extract_wheels/bazel_test.py b/python/pip_install/extract_wheels/bazel_test.py
deleted file mode 100644
index 7ecf422..0000000
--- a/python/pip_install/extract_wheels/bazel_test.py
+++ /dev/null
@@ -1,26 +0,0 @@
-import unittest
-
-from python.pip_install.extract_wheels.bazel import generate_entry_point_contents
-
-
-class BazelTestCase(unittest.TestCase):
-    def test_generate_entry_point_contents(self):
-        got = generate_entry_point_contents("sphinx.cmd.build:main")
-        want = """#!/usr/bin/env python3
-import sys
-from sphinx.cmd.build import main
-if __name__ == "__main__":
-    sys.exit(main())
-"""
-        self.assertEqual(got, want)
-
-    def test_generate_entry_point_contents_with_shebang(self):
-        got = generate_entry_point_contents(
-            "sphinx.cmd.build:main", shebang="#!/usr/bin/python"
-        )
-        want = """#!/usr/bin/python
-import sys
-from sphinx.cmd.build import main
-sys.exit(main())
-"""
-        self.assertEqual(got, want)
diff --git a/python/pip_install/extract_wheels/extract_single_wheel.py b/python/pip_install/extract_wheels/extract_single_wheel.py
deleted file mode 100644
index ff64291..0000000
--- a/python/pip_install/extract_wheels/extract_single_wheel.py
+++ /dev/null
@@ -1,105 +0,0 @@
-import argparse
-import errno
-import glob
-import os
-import subprocess
-import sys
-from tempfile import NamedTemporaryFile
-
-from python.pip_install.extract_wheels import arguments, bazel, requirements
-from python.pip_install.extract_wheels.annotation import annotation_from_str_path
-
-
-def configure_reproducible_wheels() -> None:
-    """Modifies the environment to make wheel building reproducible.
-    Wheels created from sdists are not reproducible by default. We can however workaround this by
-    patching in some configuration with environment variables.
-    """
-
-    # wheel, by default, enables debug symbols in GCC. This incidentally captures the build path in the .so file
-    # We can override this behavior by disabling debug symbols entirely.
-    # https://github.com/pypa/pip/issues/6505
-    if "CFLAGS" in os.environ:
-        os.environ["CFLAGS"] += " -g0"
-    else:
-        os.environ["CFLAGS"] = "-g0"
-
-    # set SOURCE_DATE_EPOCH to 1980 so that we can use python wheels
-    # https://github.com/NixOS/nixpkgs/blob/master/doc/languages-frameworks/python.section.md#python-setuppy-bdist_wheel-cannot-create-whl
-    if "SOURCE_DATE_EPOCH" not in os.environ:
-        os.environ["SOURCE_DATE_EPOCH"] = "315532800"
-
-    # Python wheel metadata files can be unstable.
-    # See https://bitbucket.org/pypa/wheel/pull-requests/74/make-the-output-of-metadata-files/diff
-    if "PYTHONHASHSEED" not in os.environ:
-        os.environ["PYTHONHASHSEED"] = "0"
-
-
-def main() -> None:
-    parser = argparse.ArgumentParser(
-        description="Build and/or fetch a single wheel based on the requirement passed in"
-    )
-    parser.add_argument(
-        "--requirement",
-        action="store",
-        required=True,
-        help="A single PEP508 requirement specifier string.",
-    )
-    parser.add_argument(
-        "--annotation",
-        type=annotation_from_str_path,
-        help="A json encoded file containing annotations for rendered packages.",
-    )
-    arguments.parse_common_args(parser)
-    args = parser.parse_args()
-    deserialized_args = dict(vars(args))
-    arguments.deserialize_structured_args(deserialized_args)
-
-    configure_reproducible_wheels()
-
-    pip_args = (
-        [sys.executable, "-m", "pip"]
-        + (["--isolated"] if args.isolated else [])
-        + ["download" if args.download_only else "wheel", "--no-deps"]
-        + deserialized_args["extra_pip_args"]
-    )
-
-    requirement_file = NamedTemporaryFile(mode="wb", delete=False)
-    try:
-        requirement_file.write(args.requirement.encode("utf-8"))
-        requirement_file.flush()
-        # Close the file so pip is allowed to read it when running on Windows.
-        # For more information, see: https://bugs.python.org/issue14243
-        requirement_file.close()
-        # Requirement specific args like --hash can only be passed in a requirements file,
-        # so write our single requirement into a temp file in case it has any of those flags.
-        pip_args.extend(["-r", requirement_file.name])
-
-        env = os.environ.copy()
-        env.update(deserialized_args["environment"])
-        # Assumes any errors are logged by pip so do nothing. This command will fail if pip fails
-        subprocess.run(pip_args, check=True, env=env)
-    finally:
-        try:
-            os.unlink(requirement_file.name)
-        except OSError as e:
-            if e.errno != errno.ENOENT:
-                raise
-
-    name, extras_for_pkg = requirements._parse_requirement_for_extra(args.requirement)
-    extras = {name: extras_for_pkg} if extras_for_pkg and name else dict()
-
-    whl = next(iter(glob.glob("*.whl")))
-    bazel.extract_wheel(
-        wheel_file=whl,
-        extras=extras,
-        pip_data_exclude=deserialized_args["pip_data_exclude"],
-        enable_implicit_namespace_pkgs=args.enable_implicit_namespace_pkgs,
-        incremental=True,
-        repo_prefix=args.repo_prefix,
-        annotation=args.annotation,
-    )
-
-
-if __name__ == "__main__":
-    main()
diff --git a/python/pip_install/extract_wheels/requirements.py b/python/pip_install/extract_wheels/requirements.py
deleted file mode 100644
index caf20d0..0000000
--- a/python/pip_install/extract_wheels/requirements.py
+++ /dev/null
@@ -1,47 +0,0 @@
-import re
-from typing import Dict, Optional, Set, Tuple
-
-from pip._vendor.packaging.utils import canonicalize_name
-
-
-def parse_extras(requirements_path: str) -> Dict[str, Set[str]]:
-    """Parse over the requirements.txt file to find extras requested.
-
-    Args:
-        requirements_path: The filepath for the requirements.txt file to parse.
-
-    Returns:
-         A dictionary mapping the requirement name to a set of extras requested.
-    """
-
-    extras_requested = {}
-    with open(requirements_path, "r") as requirements:
-        # Merge all backslash line continuations so we parse each requirement as a single line.
-        for line in requirements.read().replace("\\\n", "").split("\n"):
-            requirement, extras = _parse_requirement_for_extra(line)
-            if requirement and extras:
-                extras_requested[requirement] = extras
-
-    return extras_requested
-
-
-def _parse_requirement_for_extra(
-    requirement: str,
-) -> Tuple[Optional[str], Optional[Set[str]]]:
-    """Given a requirement string, returns the requirement name and set of extras, if extras specified.
-    Else, returns (None, None)
-    """
-
-    # https://www.python.org/dev/peps/pep-0508/#grammar
-    extras_pattern = re.compile(
-        r"^\s*([0-9A-Za-z][0-9A-Za-z_.\-]*)\s*\[\s*([0-9A-Za-z][0-9A-Za-z_.\-]*(?:\s*,\s*[0-9A-Za-z][0-9A-Za-z_.\-]*)*)\s*\]"
-    )
-
-    matches = extras_pattern.match(requirement)
-    if matches:
-        return (
-            canonicalize_name(matches.group(1)),
-            {extra.strip() for extra in matches.group(2).split(",")},
-        )
-
-    return None, None
diff --git a/python/pip_install/extract_wheels/requirements_test.py b/python/pip_install/extract_wheels/requirements_test.py
deleted file mode 100644
index 297cd91..0000000
--- a/python/pip_install/extract_wheels/requirements_test.py
+++ /dev/null
@@ -1,40 +0,0 @@
-import unittest
-
-from python.pip_install.extract_wheels import requirements
-
-
-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(
-                    requirements._parse_requirement_for_extra(case), expected
-                )
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/python/pip_install/extract_wheels/wheel_installer.py b/python/pip_install/extract_wheels/wheel_installer.py
new file mode 100644
index 0000000..fe00b5c
--- /dev/null
+++ b/python/pip_install/extract_wheels/wheel_installer.py
@@ -0,0 +1,488 @@
+import argparse
+import errno
+import glob
+import json
+import os
+import re
+import shutil
+import subprocess
+import sys
+import textwrap
+from pathlib import Path
+from tempfile import NamedTemporaryFile
+from typing import Dict, Iterable, List, Optional, Set, Tuple
+
+from pip._vendor.packaging.utils import canonicalize_name
+
+from python.pip_install.extract_wheels import (
+    annotation,
+    arguments,
+    bazel,
+    namespace_pkgs,
+    wheel,
+)
+
+
+def _configure_reproducible_wheels() -> None:
+    """Modifies the environment to make wheel building reproducible.
+    Wheels created from sdists are not reproducible by default. We can however workaround this by
+    patching in some configuration with environment variables.
+    """
+
+    # wheel, by default, enables debug symbols in GCC. This incidentally captures the build path in the .so file
+    # We can override this behavior by disabling debug symbols entirely.
+    # https://github.com/pypa/pip/issues/6505
+    if "CFLAGS" in os.environ:
+        os.environ["CFLAGS"] += " -g0"
+    else:
+        os.environ["CFLAGS"] = "-g0"
+
+    # set SOURCE_DATE_EPOCH to 1980 so that we can use python wheels
+    # https://github.com/NixOS/nixpkgs/blob/master/doc/languages-frameworks/python.section.md#python-setuppy-bdist_wheel-cannot-create-whl
+    if "SOURCE_DATE_EPOCH" not in os.environ:
+        os.environ["SOURCE_DATE_EPOCH"] = "315532800"
+
+    # Python wheel metadata files can be unstable.
+    # See https://bitbucket.org/pypa/wheel/pull-requests/74/make-the-output-of-metadata-files/diff
+    if "PYTHONHASHSEED" not in os.environ:
+        os.environ["PYTHONHASHSEED"] = "0"
+
+
+def _parse_requirement_for_extra(
+    requirement: str,
+) -> Tuple[Optional[str], Optional[Set[str]]]:
+    """Given a requirement string, returns the requirement name and set of extras, if extras specified.
+    Else, returns (None, None)
+    """
+
+    # https://www.python.org/dev/peps/pep-0508/#grammar
+    extras_pattern = re.compile(
+        r"^\s*([0-9A-Za-z][0-9A-Za-z_.\-]*)\s*\[\s*([0-9A-Za-z][0-9A-Za-z_.\-]*(?:\s*,\s*[0-9A-Za-z][0-9A-Za-z_.\-]*)*)\s*\]"
+    )
+
+    matches = extras_pattern.match(requirement)
+    if matches:
+        return (
+            canonicalize_name(matches.group(1)),
+            {extra.strip() for extra in matches.group(2).split(",")},
+        )
+
+    return None, None
+
+
+def _setup_namespace_pkg_compatibility(wheel_dir: str) -> None:
+    """Converts native namespace packages to pkgutil-style packages
+
+    Namespace packages can be created in one of three ways. They are detailed here:
+    https://packaging.python.org/guides/packaging-namespace-packages/#creating-a-namespace-package
+
+    'pkgutil-style namespace packages' (2) and 'pkg_resources-style namespace packages' (3) works in Bazel, but
+    'native namespace packages' (1) do not.
+
+    We ensure compatibility with Bazel of method 1 by converting them into method 2.
+
+    Args:
+        wheel_dir: the directory of the wheel to convert
+    """
+
+    namespace_pkg_dirs = namespace_pkgs.implicit_namespace_packages(
+        wheel_dir,
+        ignored_dirnames=["%s/bin" % wheel_dir],
+    )
+
+    for ns_pkg_dir in namespace_pkg_dirs:
+        namespace_pkgs.add_pkgutil_style_namespace_pkg_init(ns_pkg_dir)
+
+
+def _generate_entry_point_contents(
+    module: str, attribute: str, shebang: str = "#!/usr/bin/env python3"
+) -> str:
+    """Generate the contents of an entry point script.
+
+    Args:
+        module (str): The name of the module to use.
+        attribute (str): The name of the attribute to call.
+        shebang (str, optional): The shebang to use for the entry point python
+            file.
+
+    Returns:
+        str: A string of python code.
+    """
+    return textwrap.dedent(
+        """\
+        {shebang}
+        import sys
+        from {module} import {attribute}
+        if __name__ == "__main__":
+            sys.exit({attribute}())
+        """.format(
+            shebang=shebang, module=module, attribute=attribute
+        )
+    )
+
+
+def _generate_entry_point_rule(name: str, script: str, pkg: str) -> str:
+    """Generate a Bazel `py_binary` rule for an entry point script.
+
+    Note that the script is used to determine the name of the target. The name of
+    entry point targets should be uniuqe to avoid conflicts with existing sources or
+    directories within a wheel.
+
+    Args:
+        name (str): The name of the generated py_binary.
+        script (str): The path to the entry point's python file.
+        pkg (str): The package owning the entry point. This is expected to
+            match up with the `py_library` defined for each repository.
+
+
+    Returns:
+        str: A `py_binary` instantiation.
+    """
+    return textwrap.dedent(
+        """\
+        py_binary(
+            name = "{name}",
+            srcs = ["{src}"],
+            # This makes this directory a top-level in the python import
+            # search path for anything that depends on this.
+            imports = ["."],
+            deps = ["{pkg}"],
+        )
+        """.format(
+            name=name, src=str(script).replace("\\", "/"), pkg=pkg
+        )
+    )
+
+
+def _generate_copy_commands(src, dest, is_executable=False) -> str:
+    """Generate a [@bazel_skylib//rules:copy_file.bzl%copy_file][cf] target
+
+    [cf]: https://github.com/bazelbuild/bazel-skylib/blob/1.1.1/docs/copy_file_doc.md
+
+    Args:
+        src (str): The label for the `src` attribute of [copy_file][cf]
+        dest (str): The label for the `out` attribute of [copy_file][cf]
+        is_executable (bool, optional): Whether or not the file being copied is executable.
+            sets `is_executable` for [copy_file][cf]
+
+    Returns:
+        str: A `copy_file` instantiation.
+    """
+    return textwrap.dedent(
+        """\
+        copy_file(
+            name = "{dest}.copy",
+            src = "{src}",
+            out = "{dest}",
+            is_executable = {is_executable},
+        )
+    """.format(
+            src=src,
+            dest=dest,
+            is_executable=is_executable,
+        )
+    )
+
+
+def _generate_build_file_contents(
+    name: str,
+    dependencies: List[str],
+    whl_file_deps: List[str],
+    data_exclude: List[str],
+    tags: List[str],
+    srcs_exclude: List[str] = [],
+    data: List[str] = [],
+    additional_content: List[str] = [],
+) -> str:
+    """Generate a BUILD file for an unzipped Wheel
+
+    Args:
+        name: the target name of the py_library
+        dependencies: a list of Bazel labels pointing to dependencies of the library
+        whl_file_deps: a list of Bazel labels pointing to wheel file dependencies of this wheel.
+        data_exclude: more patterns to exclude from the data attribute of generated py_library rules.
+        tags: list of tags to apply to generated py_library rules.
+        additional_content: A list of additional content to append to the BUILD file.
+
+    Returns:
+        A complete BUILD file as a string
+
+    We allow for empty Python sources as for Wheels containing only compiled C code
+    there may be no Python sources whatsoever (e.g. packages written in Cython: like `pymssql`).
+    """
+
+    data_exclude = list(
+        set(
+            [
+                "**/* *",
+                "**/*.py",
+                "**/*.pyc",
+                # RECORD is known to contain sha256 checksums of files which might include the checksums
+                # of generated files produced when wheels are installed. The file is ignored to avoid
+                # Bazel caching issues.
+                "**/*.dist-info/RECORD",
+            ]
+            + data_exclude
+        )
+    )
+
+    return "\n".join(
+        [
+            textwrap.dedent(
+                """\
+        load("@rules_python//python:defs.bzl", "py_library", "py_binary")
+        load("@rules_python//third_party/github.com/bazelbuild/bazel-skylib/rules:copy_file.bzl", "copy_file")
+
+        package(default_visibility = ["//visibility:public"])
+
+        filegroup(
+            name = "{dist_info_label}",
+            srcs = glob(["site-packages/*.dist-info/**"], allow_empty = True),
+        )
+
+        filegroup(
+            name = "{data_label}",
+            srcs = glob(["data/**"], allow_empty = True),
+        )
+
+        filegroup(
+            name = "{whl_file_label}",
+            srcs = glob(["*.whl"], allow_empty = True),
+            data = [{whl_file_deps}],
+        )
+
+        py_library(
+            name = "{name}",
+            srcs = glob(["site-packages/**/*.py"], exclude={srcs_exclude}, allow_empty = True),
+            data = {data} + glob(["site-packages/**/*"], exclude={data_exclude}),
+            # This makes this directory a top-level in the python import
+            # search path for anything that depends on this.
+            imports = ["site-packages"],
+            deps = [{dependencies}],
+            tags = [{tags}],
+        )
+        """.format(
+                    name=name,
+                    dependencies=",".join(sorted(dependencies)),
+                    data_exclude=json.dumps(sorted(data_exclude)),
+                    whl_file_label=bazel.WHEEL_FILE_LABEL,
+                    whl_file_deps=",".join(sorted(whl_file_deps)),
+                    tags=",".join(sorted(['"%s"' % t for t in tags])),
+                    data_label=bazel.DATA_LABEL,
+                    dist_info_label=bazel.DIST_INFO_LABEL,
+                    entry_point_prefix=bazel.WHEEL_ENTRY_POINT_PREFIX,
+                    srcs_exclude=json.dumps(sorted(srcs_exclude)),
+                    data=json.dumps(sorted(data)),
+                )
+            )
+        ]
+        + additional_content
+    )
+
+
+def _sanitised_library_label(whl_name: str, prefix: str) -> str:
+    return '"//%s"' % bazel.sanitise_name(whl_name, prefix)
+
+
+def _sanitised_file_label(whl_name: str, prefix: str) -> str:
+    return '"//%s:%s"' % (bazel.sanitise_name(whl_name, prefix), bazel.WHEEL_FILE_LABEL)
+
+
+def _extract_wheel(
+    wheel_file: str,
+    extras: Dict[str, Set[str]],
+    pip_data_exclude: List[str],
+    enable_implicit_namespace_pkgs: bool,
+    repo_prefix: str,
+    incremental: bool = False,
+    incremental_dir: Path = Path("."),
+    annotation: Optional[annotation.Annotation] = None,
+) -> Optional[str]:
+    """Extracts wheel into given directory and creates py_library and filegroup targets.
+
+    Args:
+        wheel_file: the filepath of the .whl
+        extras: a list of extras to add as dependencies for the installed wheel
+        pip_data_exclude: list of file patterns to exclude from the generated data section of the py_library
+        enable_implicit_namespace_pkgs: if true, disables conversion of implicit namespace packages and will unzip as-is
+        incremental: If true the extract the wheel in a format suitable for an external repository. This
+            effects the names of libraries and their dependencies, which point to other external repositories.
+        incremental_dir: An optional override for the working directory of incremental builds.
+        annotation: An optional set of annotations to apply to the BUILD contents of the wheel.
+
+    Returns:
+        The Bazel label for the extracted wheel, in the form '//path/to/wheel'.
+    """
+
+    whl = wheel.Wheel(wheel_file)
+    if incremental:
+        directory = incremental_dir
+    else:
+        directory = bazel.sanitise_name(whl.name, prefix=repo_prefix)
+
+        os.mkdir(directory)
+        # copy the original wheel
+        shutil.copy(whl.path, directory)
+    whl.unzip(directory)
+
+    if not enable_implicit_namespace_pkgs:
+        _setup_namespace_pkg_compatibility(directory)
+
+    extras_requested = extras[whl.name] if whl.name in extras else set()
+    # Packages may create dependency cycles when specifying optional-dependencies / 'extras'.
+    # Example: github.com/google/etils/blob/a0b71032095db14acf6b33516bca6d885fe09e35/pyproject.toml#L32.
+    self_edge_dep = set([whl.name])
+    whl_deps = sorted(whl.dependencies(extras_requested) - self_edge_dep)
+
+    if incremental:
+        sanitised_dependencies = [
+            bazel.sanitised_repo_library_label(d, repo_prefix=repo_prefix)
+            for d in whl_deps
+        ]
+        sanitised_wheel_file_dependencies = [
+            bazel.sanitised_repo_file_label(d, repo_prefix=repo_prefix)
+            for d in whl_deps
+        ]
+    else:
+        sanitised_dependencies = [
+            _sanitised_library_label(d, prefix=repo_prefix) for d in whl_deps
+        ]
+        sanitised_wheel_file_dependencies = [
+            _sanitised_file_label(d, prefix=repo_prefix) for d in whl_deps
+        ]
+
+    library_name = (
+        bazel.PY_LIBRARY_LABEL
+        if incremental
+        else bazel.sanitise_name(whl.name, repo_prefix)
+    )
+
+    directory_path = Path(directory)
+    entry_points = []
+    for name, (module, attribute) in sorted(whl.entry_points().items()):
+        # There is an extreme edge-case with entry_points that end with `.py`
+        # See: https://github.com/bazelbuild/bazel/blob/09c621e4cf5b968f4c6cdf905ab142d5961f9ddc/src/test/java/com/google/devtools/build/lib/rules/python/PyBinaryConfiguredTargetTest.java#L174
+        entry_point_without_py = f"{name[:-3]}_py" if name.endswith(".py") else name
+        entry_point_target_name = (
+            f"{bazel.WHEEL_ENTRY_POINT_PREFIX}_{entry_point_without_py}"
+        )
+        entry_point_script_name = f"{entry_point_target_name}.py"
+        (directory_path / entry_point_script_name).write_text(
+            _generate_entry_point_contents(module, attribute)
+        )
+        entry_points.append(
+            _generate_entry_point_rule(
+                entry_point_target_name,
+                entry_point_script_name,
+                library_name,
+            )
+        )
+
+    with open(os.path.join(directory, "BUILD.bazel"), "w") as build_file:
+        additional_content = entry_points
+        data = []
+        data_exclude = pip_data_exclude
+        srcs_exclude = []
+        if annotation:
+            for src, dest in annotation.copy_files.items():
+                data.append(dest)
+                additional_content.append(_generate_copy_commands(src, dest))
+            for src, dest in annotation.copy_executables.items():
+                data.append(dest)
+                additional_content.append(
+                    _generate_copy_commands(src, dest, is_executable=True)
+                )
+            data.extend(annotation.data)
+            data_exclude.extend(annotation.data_exclude_glob)
+            srcs_exclude.extend(annotation.srcs_exclude_glob)
+            if annotation.additive_build_content:
+                additional_content.append(annotation.additive_build_content)
+
+        contents = _generate_build_file_contents(
+            name=bazel.PY_LIBRARY_LABEL
+            if incremental
+            else bazel.sanitise_name(whl.name, repo_prefix),
+            dependencies=sanitised_dependencies,
+            whl_file_deps=sanitised_wheel_file_dependencies,
+            data_exclude=data_exclude,
+            data=data,
+            srcs_exclude=srcs_exclude,
+            tags=["pypi_name=" + whl.name, "pypi_version=" + whl.version],
+            additional_content=additional_content,
+        )
+        build_file.write(contents)
+
+    if not incremental:
+        os.remove(whl.path)
+        return f"//{directory}"
+    return None
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser(
+        description="Build and/or fetch a single wheel based on the requirement passed in"
+    )
+    parser.add_argument(
+        "--requirement",
+        action="store",
+        required=True,
+        help="A single PEP508 requirement specifier string.",
+    )
+    parser.add_argument(
+        "--annotation",
+        type=annotation.annotation_from_str_path,
+        help="A json encoded file containing annotations for rendered packages.",
+    )
+    arguments.parse_common_args(parser)
+    args = parser.parse_args()
+    deserialized_args = dict(vars(args))
+    arguments.deserialize_structured_args(deserialized_args)
+
+    _configure_reproducible_wheels()
+
+    pip_args = (
+        [sys.executable, "-m", "pip"]
+        + (["--isolated"] if args.isolated else [])
+        + ["download" if args.download_only else "wheel", "--no-deps"]
+        + deserialized_args["extra_pip_args"]
+    )
+
+    requirement_file = NamedTemporaryFile(mode="wb", delete=False)
+    try:
+        requirement_file.write(args.requirement.encode("utf-8"))
+        requirement_file.flush()
+        # Close the file so pip is allowed to read it when running on Windows.
+        # For more information, see: https://bugs.python.org/issue14243
+        requirement_file.close()
+        # Requirement specific args like --hash can only be passed in a requirements file,
+        # so write our single requirement into a temp file in case it has any of those flags.
+        pip_args.extend(["-r", requirement_file.name])
+
+        env = os.environ.copy()
+        env.update(deserialized_args["environment"])
+        # Assumes any errors are logged by pip so do nothing. This command will fail if pip fails
+        subprocess.run(pip_args, check=True, env=env)
+    finally:
+        try:
+            os.unlink(requirement_file.name)
+        except OSError as e:
+            if e.errno != errno.ENOENT:
+                raise
+
+    name, extras_for_pkg = _parse_requirement_for_extra(args.requirement)
+    extras = {name: extras_for_pkg} if extras_for_pkg and name else dict()
+
+    whl = next(iter(glob.glob("*.whl")))
+    _extract_wheel(
+        wheel_file=whl,
+        extras=extras,
+        pip_data_exclude=deserialized_args["pip_data_exclude"],
+        enable_implicit_namespace_pkgs=args.enable_implicit_namespace_pkgs,
+        incremental=True,
+        repo_prefix=args.repo_prefix,
+        annotation=args.annotation,
+    )
+
+
+if __name__ == "__main__":
+    main()
diff --git a/python/pip_install/extract_wheels/wheel_installer_test.py b/python/pip_install/extract_wheels/wheel_installer_test.py
new file mode 100644
index 0000000..59a5ed1
--- /dev/null
+++ b/python/pip_install/extract_wheels/wheel_installer_test.py
@@ -0,0 +1,110 @@
+import os
+import shutil
+import tempfile
+import unittest
+from pathlib import Path
+
+from python.pip_install.extract_wheels import 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 BazelTestCase(unittest.TestCase):
+    def test_generate_entry_point_contents(self):
+        got = wheel_installer._generate_entry_point_contents("sphinx.cmd.build", "main")
+        want = """#!/usr/bin/env python3
+import sys
+from sphinx.cmd.build import main
+if __name__ == "__main__":
+    sys.exit(main())
+"""
+        self.assertEqual(got, want)
+
+    def test_generate_entry_point_contents_with_shebang(self):
+        got = wheel_installer._generate_entry_point_contents(
+            "sphinx.cmd.build", "main", shebang="#!/usr/bin/python"
+        )
+        want = """#!/usr/bin/python
+import sys
+from sphinx.cmd.build import main
+if __name__ == "__main__":
+    sys.exit(main())
+"""
+        self.assertEqual(got, want)
+
+
+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 _run(
+        self,
+        repo_prefix: str,
+        incremental: bool = False,
+    ) -> None:
+        generated_bazel_dir = wheel_installer._extract_wheel(
+            self.wheel_path,
+            extras={},
+            pip_data_exclude=[],
+            enable_implicit_namespace_pkgs=False,
+            incremental=incremental,
+            repo_prefix=repo_prefix,
+            incremental_dir=Path(self.wheel_dir),
+        )
+        # Take off the leading // from the returned label.
+        # Assert that the raw wheel ends up in the package.
+        generated_bazel_dir = (
+            generated_bazel_dir[2:] if not incremental else self.wheel_dir
+        )
+
+        self.assertIn(self.wheel_name, os.listdir(generated_bazel_dir))
+        with open("{}/BUILD.bazel".format(generated_bazel_dir)) as build_file:
+            build_file_content = build_file.read()
+            self.assertIn("filegroup", build_file_content)
+
+    def test_nonincremental(self) -> None:
+        self._run(repo_prefix="prefix_")
+
+    def test_incremental(self) -> None:
+        self._run(incremental=True, repo_prefix="prefix_")
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/python/pip_install/extract_wheels/whl_filegroup_test.py b/python/pip_install/extract_wheels/whl_filegroup_test.py
deleted file mode 100644
index 2a7ade3..0000000
--- a/python/pip_install/extract_wheels/whl_filegroup_test.py
+++ /dev/null
@@ -1,53 +0,0 @@
-import os
-import shutil
-import tempfile
-import unittest
-from pathlib import Path
-
-from python.pip_install.extract_wheels import bazel
-
-
-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 _run(
-        self,
-        repo_prefix: str,
-        incremental: bool = False,
-    ) -> None:
-        generated_bazel_dir = bazel.extract_wheel(
-            self.wheel_path,
-            extras={},
-            pip_data_exclude=[],
-            enable_implicit_namespace_pkgs=False,
-            incremental=incremental,
-            repo_prefix=repo_prefix,
-            incremental_dir=Path(self.wheel_dir),
-        )
-        # Take off the leading // from the returned label.
-        # Assert that the raw wheel ends up in the package.
-        generated_bazel_dir = (
-            generated_bazel_dir[2:] if not incremental else self.wheel_dir
-        )
-
-        self.assertIn(self.wheel_name, os.listdir(generated_bazel_dir))
-        with open("{}/BUILD.bazel".format(generated_bazel_dir)) as build_file:
-            build_file_content = build_file.read()
-            self.assertIn("filegroup", build_file_content)
-
-    def test_nonincremental(self) -> None:
-        self._run(repo_prefix="prefix_")
-
-    def test_incremental(self) -> None:
-        self._run(incremental=True, repo_prefix="prefix_")
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/python/pip_install/pip_repository.bzl b/python/pip_install/pip_repository.bzl
index 7fbf503..101ec6a 100644
--- a/python/pip_install/pip_repository.bzl
+++ b/python/pip_install/pip_repository.bzl
@@ -522,7 +522,7 @@
     args = [
         python_interpreter,
         "-m",
-        "python.pip_install.extract_wheels.extract_single_wheel",
+        "python.pip_install.extract_wheels.wheel_installer",
         "--requirement",
         rctx.attr.requirement,
         "--repo",
diff --git a/python/pip_install/private/srcs.bzl b/python/pip_install/private/srcs.bzl
index e42bb8e..89bd55d 100644
--- a/python/pip_install/private/srcs.bzl
+++ b/python/pip_install/private/srcs.bzl
@@ -11,9 +11,8 @@
     "@rules_python//python/pip_install/extract_wheels:annotation.py",
     "@rules_python//python/pip_install/extract_wheels:arguments.py",
     "@rules_python//python/pip_install/extract_wheels:bazel.py",
-    "@rules_python//python/pip_install/extract_wheels:extract_single_wheel.py",
     "@rules_python//python/pip_install/extract_wheels:namespace_pkgs.py",
     "@rules_python//python/pip_install/extract_wheels:parse_requirements_to_bzl.py",
-    "@rules_python//python/pip_install/extract_wheels:requirements.py",
     "@rules_python//python/pip_install/extract_wheels:wheel.py",
+    "@rules_python//python/pip_install/extract_wheels:wheel_installer.py",
 ]
diff --git a/tools/bazel_integration_test/test_runner.py b/tools/bazel_integration_test/test_runner.py
index 31bb627..fbc27e4 100644
--- a/tools/bazel_integration_test/test_runner.py
+++ b/tools/bazel_integration_test/test_runner.py
@@ -56,7 +56,7 @@
                 # TODO: --override_module isn't supported in the current BAZEL_VERSION (5.2.0)
                 # This condition and attribute can be removed when bazel is updated for
                 # the rest of rules_python.
-                if (config["bzlmod"]):
+                if config["bzlmod"]:
                     bazel_args.append(
                         "--override_module=rules_python=%s/rules_python"
                         % os.environ["TEST_SRCDIR"]
diff --git a/tools/wheelmaker.py b/tools/wheelmaker.py
index d517900..7d65706 100644
--- a/tools/wheelmaker.py
+++ b/tools/wheelmaker.py
@@ -275,7 +275,7 @@
         action="append",
         default=[],
         help="Path prefix to be stripped from input package files' path. "
-             "Can be supplied multiple times. Evaluated in order.",
+        "Can be supplied multiple times. Evaluated in order.",
     )
 
     wheel_group = parser.add_argument_group("Wheel metadata")
@@ -283,7 +283,7 @@
         "--metadata_file",
         type=Path,
         help="Contents of the METADATA file (before appending contents of "
-             "--description_file)",
+        "--description_file)",
     )
     wheel_group.add_argument(
         "--description_file", help="Path to the file with package description"
@@ -381,12 +381,12 @@
         description = None
         if arguments.description_file:
             if sys.version_info[0] == 2:
-                with open(arguments.description_file,
-                          "rt") as description_file:
+                with open(arguments.description_file, "rt") as description_file:
                     description = description_file.read()
             else:
-                with open(arguments.description_file, "rt",
-                          encoding="utf-8") as description_file:
+                with open(
+                    arguments.description_file, "rt", encoding="utf-8"
+                ) as description_file:
                     description = description_file.read()
 
         metadata = None
@@ -394,8 +394,7 @@
             with open(arguments.metadata_file, "rt") as metadata_file:
                 metadata = metadata_file.read()
         else:
-            with open(arguments.metadata_file, "rt",
-                      encoding="utf-8") as metadata_file:
+            with open(arguments.metadata_file, "rt", encoding="utf-8") as metadata_file:
                 metadata = metadata_file.read()
 
         maker.add_metadata(metadata=metadata, description=description)