# based on https://github.com/ali5h/rules_pip/blob/master/src/whl.py # MIT """downloads and parses info of a pkg and generates a BUILD file for it""" import argparse import glob import logging import os import shutil import sys import re import subprocess from pip._internal.commands import create_command from pip._vendor import pkg_resources import pkginfo def _create_nspkg_init(dirpath): """Creates an init file to enable namespacing""" if not os.path.exists(dirpath): # Handle missing namespace packages by ignoring them return nspkg_init = os.path.join(dirpath, "__init__.py") with open(nspkg_init, "w") as nspkg: nspkg.write("__path__ = __import__('pkgutil').extend_path(__path__, __name__)") def install_package(pkg, directory, pip_args): """Downloads wheel for a package. Assumes python binary provided has pip and wheel package installed. Args: pkg: package name directory: destination directory to download the wheel file in python: python binary path used to run pip command pip_args: extra pip args sent to pip Returns: str: path to the wheel file """ pip_args = [ "--isolated", "--disable-pip-version-check", "--target", directory, "--no-deps", "--ignore-requires-python", pkg, ] + pip_args cmd = create_command("install") cmd.main(pip_args) # need dist-info directory for pkg_resources to be able to find the packages dist_info = glob.glob(os.path.join(directory, "*.dist-info"))[0] # fix namespace packages by adding proper __init__.py files namespace_packages = os.path.join(dist_info, "namespace_packages.txt") if os.path.exists(namespace_packages): with open(namespace_packages) as nspkg: for line in nspkg.readlines(): namespace = line.strip().replace(".", os.sep) if namespace: _create_nspkg_init(os.path.join(directory, namespace)) # PEP 420 -- Implicit Namespace Packages if (sys.version_info[0], sys.version_info[1]) >= (3, 3): for dirpath, dirnames, filenames in os.walk(directory): # we are only interested in dirs with no init file if "__init__.py" in filenames: dirnames[:] = [] continue # remove bin and dist-info dirs for ignored in ("bin", os.path.basename(dist_info)): if ignored in dirnames: dirnames.remove(ignored) _create_nspkg_init(dirpath) return pkginfo.Wheel(dist_info) def _cleanup(directory, pattern): for p in glob.glob(os.path.join(directory, pattern)): shutil.rmtree(p) fix_none = re.compile(r"(\s*None) =") def copy_and_fix_pyi(source, dest): "Fix broken PyQt types." with open(source) as input_file: with open(dest, "w") as output_file: for line in input_file.readlines(): # assigning to None is a syntax error line = fix_none.sub(r"\1_ =", line) # inheriting from the missing sip.sipwrapper definition # causes missing attributes not to be detected, as it's treating # the class as inheriting from Any line = line.replace("sip.simplewrapper", "object") line = line.replace("sip.wrapper", "object") # remove blanket getattr in QObject which also causes missing # attributes not to be detected if "def __getattr__(self, name: str) -> typing.Any" in line: continue output_file.write(line) def merge_files(root, source): for dirpath, _dirnames, filenames in os.walk(source): target_dir = os.path.join(root, os.path.relpath(dirpath, source)) if not os.path.exists(target_dir): os.mkdir(target_dir) for fname in filenames: source_path = os.path.join(dirpath, fname) target_path = os.path.join(target_dir, fname) if not os.path.exists(target_path): if fname.endswith(".pyi"): copy_and_fix_pyi(source_path, target_path) else: shutil.copy2(source_path, target_path) def main(): base = sys.argv[1] local_site_packages = os.environ.get("PYTHON_SITE_PACKAGES") if local_site_packages: subprocess.run( [ "rsync", "-ai", "--include=PyQt**", "--exclude=*", local_site_packages, base + "/", ], check=True, ) with open(os.path.join(base, "__init__.py"), "w") as file: pass else: packages = [ ("pyqt5", "pyqt5==5.15.2"), ("pyqtwebengine", "pyqtwebengine==5.15.2"), ("pyqt5-sip", "pyqt5_sip==12.8.1"), ] for (name, with_version) in packages: # install package in subfolder folder = os.path.join(base, "temp") _pkg = install_package(with_version, folder, []) # merge into parent merge_files(base, folder) shutil.rmtree(folder) # add missing py.typed file with open(os.path.join(base, "py.typed"), "w") as file: pass result = """ load("@rules_python//python:defs.bzl", "py_library") package(default_visibility = ["//visibility:public"]) py_library( name = "pkg", srcs = glob(["**/*.py"]), data = glob(["**/*"], exclude = [ "**/*.py", "**/*.pyc", "**/* *", "BUILD", "WORKSPACE", "bin/*", "__pycache__", # these make building slower "Qt/qml/**", "**/*.sip", "**/*.png", ]), # This makes this directory a top-level in the python import # search path for anything that depends on this. imports = ["."], ) """ # clean up _cleanup(base, "__pycache__") with open(os.path.join(base, "BUILD"), "w") as f: f.write(result) if __name__ == "__main__": main()