diff options
Diffstat (limited to 'sources/shiboken6/libshiboken/embed/signature_bootstrap.py')
-rw-r--r-- | sources/shiboken6/libshiboken/embed/signature_bootstrap.py | 204 |
1 files changed, 204 insertions, 0 deletions
diff --git a/sources/shiboken6/libshiboken/embed/signature_bootstrap.py b/sources/shiboken6/libshiboken/embed/signature_bootstrap.py new file mode 100644 index 000000000..37f95cd35 --- /dev/null +++ b/sources/shiboken6/libshiboken/embed/signature_bootstrap.py @@ -0,0 +1,204 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +""" +signature_bootstrap.py +---------------------- + +This file was originally directly embedded into the C source. +After it grew more and more, I now prefer to have it as Python file. + +Meanwhile, there is also no more a stub loader necessary: +Because we meanwhile have embedding support, we could also load this file +directly from a .pyc file. + +This file replaces the hard to read Python stub in 'signature.cpp', and we +could distinguish better between bootstrap related functions and loader +functions. +It is embedded into 'signature.cpp' as "embed/signature_bootstrap.inc". + +# PYSIDE-1436: Python 3.10 had a problem with EmbeddableZipImporter because the +imports were in the functions. Moved them outside into the globals. +""" + +recursion_trap = 0 + +import base64 +import importlib +import io +import os +import sys +import traceback +import zipfile + +from contextlib import contextmanager +from importlib.machinery import ModuleSpec +from pathlib import Path + + +def bootstrap(): + + global recursion_trap + if recursion_trap: + # we are probably called from outside, already + print("Recursion occurred in Bootstrap. Did you start by hand? Then it's ok.") + print("But you should trigger start by '_init_pyside_extension()', only!") + recursion_trap += 1 + + @contextmanager + def ensure_shibokensupport(target, support_path): + # Make sure that we always have the shibokensupport containing package first. + # Also remove any prior loaded module of this name, just in case. + # PYSIDE-1621: support_path can also be a finder instance. + target.insert(0, support_path) + + sbks = "shibokensupport" + if sbks in sys.modules: + del sys.modules[sbks] + prefix = sbks + "." + for key in list(key for key in sys.modules if key.startswith(prefix)): + del sys.modules[key] + try: + import shibokensupport + yield + except Exception as e: + f = sys.stderr + print("Problem importing shibokensupport:", file=f) + print(f"{e.__class__.__name__}: {e}", file=f) + traceback.print_exc() + print("sys.path:", file=f) + for p in sys.path: + print(" " + p, file=f) + f.flush() + sys.exit(-1) + target.remove(support_path) + + # Here we decide if we re-incarnate the embedded files or use embedding. + incarnated = find_incarnated_files() + if incarnated: + target, support_path = sys.path, os.fspath(incarnated) + else: + target, support_path = prepare_zipfile() + with ensure_shibokensupport(target, support_path): + from shibokensupport.signature import loader + return loader + +# Newer functionality: +# This function checks if the support directory exist and returns it. +# If does not exist, we try to create it and return it. +# Otherwise, we return None. + +def find_incarnated_files(): + import shiboken6 as root + files_dir = Path(root.__file__).resolve().parent / "files.dir" + handle_embedding_switch(files_dir) + if files_dir.exists(): + sys.path.insert(0, os.fspath(files_dir)) + # Note: To avoid recursion problems, we need to preload the loader. + # But that has the side-effect that we need to delay the feature + # initialization until all function pointers are set. + # See `post_init_func` in signature_globals.cpp . + import shibokensupport.signature.loader + del sys.path[0] + return files_dir + return None + + +def handle_embedding_switch(files_dir): + """ + This handles the optional environment variable `SBK_EMBED` + if not set : do nothing + if set to 0, false, no : de-virtualize the Python files + if set to 1, true, yes : virtualize again (delete "files.dir") + """ + env_name = "SBK_EMBED" + env_var = os.environ.get(env_name) + if not env_var: + return + if env_var.lower() in ("1", "t", "true", "y", "yes"): + import shutil + shutil.rmtree(files_dir, ignore_errors=True) + elif env_var.lower() in ("0", "f", "false", "n", "no"): + reincarnate_files(files_dir) + + +def reincarnate_files(files_dir): + target, zip = prepare_zipfile() + names = (_ for _ in zip.zfile.namelist() if _.endswith(".py")) + try: + # First check mkdir to get an error when we cannot write. + files_dir.mkdir(exist_ok=True) + except os.error as e: + print(f"SBK_EMBED=False: Warning: Cannot write into {files_dir}") + return None + try: + # Then check for a real error when unpacking the zip file. + zip.zfile.extractall(path=files_dir, members=names) + return files_dir + except Exception as e: + print(f"{e.__class__.__name__}: {e}", file=sys.stderr) + traceback.print_exc() + raise + +# New functionality: Loading from a zip archive. +# There exists the zip importer, but as it is written, only real zip files are +# supported. Before I will start an own implementation, it is easiest to use +# a temporary zip file. +# PYSIDE-1621: make zip file access totally virtual + +def prepare_zipfile(): + """ + Old approach: + + Write the zip file to a real file and return its name. + It will be implicitly opened as such when we add the name to sys.path . + + New approach (Python 3, only): + + Use EmbeddableZipImporter and pass the zipfile structure directly. + The sys.path way does not work, instead we need to use sys.meta_path . + See https://docs.python.org/3/library/sys.html#sys.meta_path + """ + + # 'zipstring_sequence' comes from signature.cpp + zipbytes = base64.b64decode(''.join(zipstring_sequence)) + vzip = zipfile.ZipFile(io.BytesIO(zipbytes)) + return sys.meta_path, EmbeddableZipImporter(vzip) + + +class EmbeddableZipImporter(object): + + def __init__(self, zip_file): + def p2m(filename): + if filename.endswith("/__init__.py"): + return filename[:-12].replace("/", ".") + if filename.endswith(".py"): + return filename[:-3].replace("/", ".") + return None + + self.zfile = zip_file + self._mod2path = {p2m(_.filename) : _.filename for _ in zip_file.filelist} + + def find_spec(self, fullname, path, target=None): + path = self._mod2path.get(fullname) + return ModuleSpec(fullname, self) if path else None + + def create_module(self, spec): + return None + + def exec_module(self, module): + fullname = module.__spec__.name + filename = self._mod2path[fullname] + with self.zfile.open(filename, "r") as f: # "rb" not for zipfile + codeob = compile(f.read(), filename, "exec") + exec(codeob, module.__dict__) + module.__file__ = filename + module.__loader__ = self + if filename.endswith("/__init__.py"): + module.__path__ = [] + module.__package__ = fullname + else: + module.__package__ = fullname.rpartition('.')[0] + sys.modules[fullname] = module + +# eof |