aboutsummaryrefslogtreecommitdiff
path: root/third_party/rules_pycross/pycross/private/tools/wheel_installer.py
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rules_pycross/pycross/private/tools/wheel_installer.py')
-rw-r--r--third_party/rules_pycross/pycross/private/tools/wheel_installer.py196
1 files changed, 196 insertions, 0 deletions
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..0c352cf
--- /dev/null
+++ b/third_party/rules_pycross/pycross/private/tools/wheel_installer.py
@@ -0,0 +1,196 @@
+# 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.
+
+"""
+A tool that invokes pypa/build to build the given sdist tarball.
+"""
+
+import argparse
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+from pathlib import Path
+from typing import Any
+
+from installer import install
+from installer.destinations import SchemeDictionaryDestination
+from installer.sources import WheelFile
+
+from python.pip_install.tools.wheel_installer 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/bazelbuild/rules_python/tree/main/third_party/rules_pycross",
+ },
+ )
+ finally:
+ shutil.rmtree(link_dir, ignore_errors=True)
+
+ setup_namespace_pkg_compatibility(lib_dir)
+
+ if args.patch:
+ if not args.patch_tool and not args.patch_tool_target:
+ raise ValueError("Specify one of 'patch_tool' or 'patch_tool_target'.")
+
+ patch_args = [
+ args.patch_tool or Path.cwd() / args.patch_tool_target
+ ] + args.patch_arg
+ for patch in args.patch:
+ with patch.open("r") as stdin:
+ try:
+ subprocess.run(
+ patch_args,
+ stdin=stdin,
+ check=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ cwd=args.directory,
+ )
+ except subprocess.CalledProcessError as error:
+ print(f"Patch {patch} failed to apply:")
+ print(error.stdout.decode("utf-8"))
+ raise
+
+
+def parse_flags(argv) -> Any:
+ parser = argparse.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.",
+ )
+
+ parser.add_argument(
+ "--patch",
+ type=Path,
+ default=[],
+ action="append",
+ help="A patch file to apply.",
+ )
+
+ parser.add_argument(
+ "--patch-arg",
+ type=str,
+ default=[],
+ action="append",
+ help="An argument for the patch tool when applying the patches.",
+ )
+
+ parser.add_argument(
+ "--patch-tool",
+ type=str,
+ help=(
+ "The tool from PATH to invoke when applying patches. "
+ "If set, --patch-tool-target is ignored."
+ ),
+ )
+
+ parser.add_argument(
+ "--patch-tool-target",
+ type=Path,
+ help=(
+ "The path to the tool to invoke when applying patches. "
+ "Ignored when --patch-tool is set."
+ ),
+ )
+
+ 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"])
+
+ main(parse_flags(sys.argv))