Import pycross_wheel_library (#1403)

This patch imports a few files from jvolkman/rules_pycross at
757033ff8afeb5f7090b1320759f6f03d9c4615c.

I would like to re-use this rule for the `pypi_install` repo rule that
I'm working on. This rule extracts a downloaded wheel and generates an
appropriate `PyInfo` provider for it.

All the .pyfiles are taken as-is without modification. I had to run
buildifier
on all the bazel-related files. As per bazelbuild/rules_python#1360,
that
meant that I had to add copyright headers.

A followup patch will make tweaks so that the code can be used from
within rules_python.

References: #1360
diff --git a/third_party/rules_pycross/LICENSE b/third_party/rules_pycross/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/third_party/rules_pycross/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
diff --git a/third_party/rules_pycross/pycross/private/providers.bzl b/third_party/rules_pycross/pycross/private/providers.bzl
new file mode 100644
index 0000000..f55e986
--- /dev/null
+++ b/third_party/rules_pycross/pycross/private/providers.bzl
@@ -0,0 +1,32 @@
+# Copyright 2023 Jeremy Volkman. All rights reserved.
+# 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.
+
+"""Pycross providers."""
+
+PycrossWheelInfo = provider(
+    doc = "Information about a Python wheel.",
+    fields = {
+        "name_file": "File: A file containing the canonical name of the wheel.",
+        "wheel_file": "File: The wheel file itself.",
+    },
+)
+
+PycrossTargetEnvironmentInfo = provider(
+    doc = "A target environment description.",
+    fields = {
+        "file": "The JSON file containing target environment information.",
+        "python_compatible_with": "A list of constraints used to select this platform.",
+    },
+)
diff --git a/third_party/rules_pycross/pycross/private/tools/BUILD.bazel b/third_party/rules_pycross/pycross/private/tools/BUILD.bazel
new file mode 100644
index 0000000..867b771
--- /dev/null
+++ b/third_party/rules_pycross/pycross/private/tools/BUILD.bazel
@@ -0,0 +1,53 @@
+# Copyright 2023 Jeremy Volkman. All rights reserved.
+# 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("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test")
+
+package(default_visibility = ["//visibility:private"])
+
+py_library(
+    name = "namespace_pkgs",
+    srcs = [
+        "namespace_pkgs.py",
+    ],
+)
+
+py_test(
+    name = "namespace_pkgs_test",
+    size = "small",
+    srcs = [
+        "namespace_pkgs_test.py",
+    ],
+    tags = [
+        "unit",
+        # TODO(philsc): Make this work.
+        "manual",
+    ],
+    deps = [
+        ":namespace_pkgs",
+    ],
+)
+
+py_binary(
+    name = "wheel_installer",
+    srcs = ["wheel_installer.py"],
+    visibility = ["//visibility:public"],
+    deps = [
+        ":namespace_pkgs",
+        # TODO(philsc): Make this work with what's available in rules_python.
+        #"@rules_pycross_pypi_deps_absl_py//:pkg",
+        #"@rules_pycross_pypi_deps_installer//:pkg",
+    ],
+)
diff --git a/third_party/rules_pycross/pycross/private/tools/namespace_pkgs.py b/third_party/rules_pycross/pycross/private/tools/namespace_pkgs.py
new file mode 100644
index 0000000..59300ff
--- /dev/null
+++ b/third_party/rules_pycross/pycross/private/tools/namespace_pkgs.py
@@ -0,0 +1,109 @@
+"""Utility functions to discover python package types"""
+import os
+import textwrap
+from pathlib import Path  # supported in >= 3.4
+from typing import List
+from typing import Optional
+from typing import Set
+
+
+def implicit_namespace_packages(
+    directory: str, ignored_dirnames: Optional[List[str]] = None
+) -> Set[Path]:
+    """Discovers namespace packages implemented using the 'native namespace packages' method.
+
+    AKA 'implicit namespace packages', which has been supported since Python 3.3.
+    See: https://packaging.python.org/guides/packaging-namespace-packages/#native-namespace-packages
+
+    Args:
+        directory: The root directory to recursively find packages in.
+        ignored_dirnames: A list of directories to exclude from the search
+
+    Returns:
+        The set of directories found under root to be packages using the native namespace method.
+    """
+    namespace_pkg_dirs: Set[Path] = set()
+    standard_pkg_dirs: Set[Path] = set()
+    directory_path = Path(directory)
+    ignored_dirname_paths: List[Path] = [Path(p) for p in ignored_dirnames or ()]
+    # Traverse bottom-up because a directory can be a namespace pkg because its child contains module files.
+    for dirpath, dirnames, filenames in map(
+        lambda t: (Path(t[0]), *t[1:]), os.walk(directory_path, topdown=False)
+    ):
+        if "__init__.py" in filenames:
+            standard_pkg_dirs.add(dirpath)
+            continue
+        elif ignored_dirname_paths:
+            is_ignored_dir = dirpath in ignored_dirname_paths
+            child_of_ignored_dir = any(
+                d in dirpath.parents for d in ignored_dirname_paths
+            )
+            if is_ignored_dir or child_of_ignored_dir:
+                continue
+
+        dir_includes_py_modules = _includes_python_modules(filenames)
+        parent_of_namespace_pkg = any(
+            Path(dirpath, d) in namespace_pkg_dirs for d in dirnames
+        )
+        parent_of_standard_pkg = any(
+            Path(dirpath, d) in standard_pkg_dirs for d in dirnames
+        )
+        parent_of_pkg = parent_of_namespace_pkg or parent_of_standard_pkg
+        if (
+            (dir_includes_py_modules or parent_of_pkg)
+            and
+            # The root of the directory should never be an implicit namespace
+            dirpath != directory_path
+        ):
+            namespace_pkg_dirs.add(dirpath)
+    return namespace_pkg_dirs
+
+
+def add_pkgutil_style_namespace_pkg_init(dir_path: Path) -> None:
+    """Adds 'pkgutil-style namespace packages' init file to the given directory
+
+    See: https://packaging.python.org/guides/packaging-namespace-packages/#pkgutil-style-namespace-packages
+
+    Args:
+        dir_path: The directory to create an __init__.py for.
+
+    Raises:
+        ValueError: If the directory already contains an __init__.py file
+    """
+    ns_pkg_init_filepath = os.path.join(dir_path, "__init__.py")
+
+    if os.path.isfile(ns_pkg_init_filepath):
+        raise ValueError("%s already contains an __init__.py file." % dir_path)
+
+    with open(ns_pkg_init_filepath, "w") as ns_pkg_init_f:
+        # See https://packaging.python.org/guides/packaging-namespace-packages/#pkgutil-style-namespace-packages
+        ns_pkg_init_f.write(
+            textwrap.dedent(
+                """\
+                # __path__ manipulation added by bazelbuild/rules_python to support namespace pkgs.
+                __path__ = __import__('pkgutil').extend_path(__path__, __name__)
+                """
+            )
+        )
+
+
+def _includes_python_modules(files: List[str]) -> bool:
+    """
+    In order to only transform directories that Python actually considers namespace pkgs
+    we need to detect if a directory includes Python modules.
+
+    Which files are loadable as modules is extension based, and the particular set of extensions
+    varies by platform.
+
+    See:
+    1. https://github.com/python/cpython/blob/7d9d25dbedfffce61fc76bc7ccbfa9ae901bf56f/Lib/importlib/machinery.py#L19
+    2. PEP 420 -- Implicit Namespace Packages, Specification - https://www.python.org/dev/peps/pep-0420/#specification
+    3. dynload_shlib.c and dynload_win.c in python/cpython.
+    """
+    module_suffixes = {
+        ".py",  # Source modules
+        ".pyc",  # Compiled bytecode modules
+        ".so",  # Unix extension modules
+        ".pyd",  # https://docs.python.org/3/faq/windows.html#is-a-pyd-file-the-same-as-a-dll
+    }
+    return any(Path(f).suffix in module_suffixes for f in files)
diff --git a/third_party/rules_pycross/pycross/private/tools/namespace_pkgs_test.py b/third_party/rules_pycross/pycross/private/tools/namespace_pkgs_test.py
new file mode 100644
index 0000000..49945f9
--- /dev/null
+++ b/third_party/rules_pycross/pycross/private/tools/namespace_pkgs_test.py
@@ -0,0 +1,179 @@
+import os
+import pathlib
+import shutil
+import tempfile
+import unittest
+from typing import Optional
+from typing import Set
+
+from pycross.private.tools 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/third_party/rules_pycross/pycross/private/tools/wheel_installer.py b/third_party/rules_pycross/pycross/private/tools/wheel_installer.py
new file mode 100644
index 0000000..6d36736
--- /dev/null
+++ b/third_party/rules_pycross/pycross/private/tools/wheel_installer.py
@@ -0,0 +1,122 @@
+"""
+A tool that invokes pypa/build to build the given sdist tarball.
+"""
+
+import os
+import shutil
+import tempfile
+from pathlib import Path
+from typing import Any
+
+from absl import app
+from absl.flags import argparse_flags
+from installer import install
+from installer.destinations import SchemeDictionaryDestination
+from installer.sources import WheelFile
+from pycross.private.tools import namespace_pkgs
+
+
+def setup_namespace_pkg_compatibility(wheel_dir: Path) -> 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(
+        str(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 main(args: Any) -> None:
+    dest_dir = args.directory
+    lib_dir = dest_dir / "site-packages"
+    destination = SchemeDictionaryDestination(
+        scheme_dict={
+            "platlib": str(lib_dir),
+            "purelib": str(lib_dir),
+            "headers": str(dest_dir / "include"),
+            "scripts": str(dest_dir / "bin"),
+            "data": str(dest_dir / "data"),
+        },
+        interpreter="/usr/bin/env python3",  # Generic; it's not feasible to run these scripts directly.
+        script_kind="posix",
+        bytecode_optimization_levels=[0, 1],
+    )
+
+    link_dir = Path(tempfile.mkdtemp())
+    if args.wheel_name_file:
+        with open(args.wheel_name_file, "r") as f:
+            wheel_name = f.read().strip()
+    else:
+        wheel_name = os.path.basename(args.wheel)
+
+    link_path = link_dir / wheel_name
+    os.symlink(os.path.join(os.getcwd(), args.wheel), link_path)
+
+    try:
+        with WheelFile.open(link_path) as source:
+            install(
+                source=source,
+                destination=destination,
+                # Additional metadata that is generated by the installation tool.
+                additional_metadata={
+                    "INSTALLER": b"https://github.com/jvolkman/rules_pycross",
+                },
+            )
+    finally:
+        shutil.rmtree(link_dir, ignore_errors=True)
+
+    setup_namespace_pkg_compatibility(lib_dir)
+
+
+def parse_flags(argv) -> Any:
+    parser = argparse_flags.ArgumentParser(description="Extract a Python wheel.")
+
+    parser.add_argument(
+        "--wheel",
+        type=Path,
+        required=True,
+        help="The wheel file path.",
+    )
+
+    parser.add_argument(
+        "--wheel-name-file",
+        type=Path,
+        required=False,
+        help="A file containing the canonical name of the wheel.",
+    )
+
+    parser.add_argument(
+        "--enable-implicit-namespace-pkgs",
+        action="store_true",
+        help="If true, disables conversion of implicit namespace packages and will unzip as-is.",
+    )
+
+    parser.add_argument(
+        "--directory",
+        type=Path,
+        help="The output path.",
+    )
+
+    return parser.parse_args(argv[1:])
+
+
+if __name__ == "__main__":
+    # When under `bazel run`, change to the actual working dir.
+    if "BUILD_WORKING_DIRECTORY" in os.environ:
+        os.chdir(os.environ["BUILD_WORKING_DIRECTORY"])
+
+    app.run(main, flags_parser=parse_flags)
diff --git a/third_party/rules_pycross/pycross/private/wheel_library.bzl b/third_party/rules_pycross/pycross/private/wheel_library.bzl
new file mode 100644
index 0000000..25a2497
--- /dev/null
+++ b/third_party/rules_pycross/pycross/private/wheel_library.bzl
@@ -0,0 +1,137 @@
+# Copyright 2023 Jeremy Volkman. All rights reserved.
+# 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.
+
+"""Implementation of the pycross_wheel_library rule."""
+
+load("@bazel_skylib//lib:paths.bzl", "paths")
+load("@rules_python//python:defs.bzl", "PyInfo")
+load(":providers.bzl", "PycrossWheelInfo")
+
+def _pycross_wheel_library_impl(ctx):
+    out = ctx.actions.declare_directory(ctx.attr.name)
+
+    wheel_target = ctx.attr.wheel
+    if PycrossWheelInfo in wheel_target:
+        wheel_file = wheel_target[PycrossWheelInfo].wheel_file
+        name_file = wheel_target[PycrossWheelInfo].name_file
+    else:
+        wheel_file = ctx.file.wheel
+        name_file = None
+
+    args = ctx.actions.args().use_param_file("--flagfile=%s")
+    args.add("--wheel", wheel_file)
+    args.add("--directory", out.path)
+
+    inputs = [wheel_file]
+    if name_file:
+        inputs.append(name_file)
+        args.add("--wheel-name-file", name_file)
+
+    if ctx.attr.enable_implicit_namespace_pkgs:
+        args.add("--enable-implicit-namespace-pkgs")
+
+    ctx.actions.run(
+        inputs = inputs,
+        outputs = [out],
+        executable = ctx.executable._tool,
+        arguments = [args],
+        # Set environment variables to make generated .pyc files reproducible.
+        env = {
+            "PYTHONHASHSEED": "0",
+            "SOURCE_DATE_EPOCH": "315532800",
+        },
+        mnemonic = "WheelInstall",
+        progress_message = "Installing %s" % ctx.file.wheel.basename,
+    )
+
+    has_py2_only_sources = ctx.attr.python_version == "PY2"
+    has_py3_only_sources = ctx.attr.python_version == "PY3"
+    if not has_py2_only_sources:
+        for d in ctx.attr.deps:
+            if d[PyInfo].has_py2_only_sources:
+                has_py2_only_sources = True
+                break
+    if not has_py3_only_sources:
+        for d in ctx.attr.deps:
+            if d[PyInfo].has_py3_only_sources:
+                has_py3_only_sources = True
+                break
+
+    # TODO: Is there a more correct way to get this runfiles-relative import path?
+    imp = paths.join(
+        ctx.label.workspace_name or ctx.workspace_name,  # Default to the local workspace.
+        ctx.label.package,
+        ctx.label.name,
+        "site-packages",  # we put lib files in this subdirectory.
+    )
+
+    imports = depset(
+        direct = [imp],
+        transitive = [d[PyInfo].imports for d in ctx.attr.deps],
+    )
+    transitive_sources = depset(
+        direct = [out],
+        transitive = [dep[PyInfo].transitive_sources for dep in ctx.attr.deps if PyInfo in dep],
+    )
+    runfiles = ctx.runfiles(files = [out])
+    for d in ctx.attr.deps:
+        runfiles = runfiles.merge(d[DefaultInfo].default_runfiles)
+
+    return [
+        DefaultInfo(
+            files = depset(direct = [out]),
+            runfiles = runfiles,
+        ),
+        PyInfo(
+            has_py2_only_sources = has_py2_only_sources,
+            has_py3_only_sources = has_py3_only_sources,
+            imports = imports,
+            transitive_sources = transitive_sources,
+            uses_shared_libraries = True,  # Docs say this is unused
+        ),
+    ]
+
+pycross_wheel_library = rule(
+    implementation = _pycross_wheel_library_impl,
+    attrs = {
+        "deps": attr.label_list(
+            doc = "A list of this wheel's Python library dependencies.",
+            providers = [DefaultInfo, PyInfo],
+        ),
+        "enable_implicit_namespace_pkgs": attr.bool(
+            default = True,
+            doc = """
+If true, disables conversion of native namespace packages into pkg-util style namespace packages. When set all py_binary
+and py_test targets must specify either `legacy_create_init=False` or the global Bazel option
+`--incompatible_default_to_explicit_init_py` to prevent `__init__.py` being automatically generated in every directory.
+This option is required to support some packages which cannot handle the conversion to pkg-util style.
+            """,
+        ),
+        "python_version": attr.string(
+            doc = "The python version required for this wheel ('PY2' or 'PY3')",
+            values = ["PY2", "PY3", ""],
+        ),
+        "wheel": attr.label(
+            doc = "The wheel file.",
+            allow_single_file = [".whl"],
+            mandatory = True,
+        ),
+        "_tool": attr.label(
+            default = Label("//pycross/private/tools:wheel_installer"),
+            cfg = "exec",
+            executable = True,
+        ),
+    },
+)