blob: 6d3673669b90f920859c32fa9664b4af1315c345 [file] [log] [blame]
"""
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)