aboutsummaryrefslogtreecommitdiffstats
path: root/sources/shiboken6/libshiboken/embed
diff options
context:
space:
mode:
Diffstat (limited to 'sources/shiboken6/libshiboken/embed')
-rw-r--r--sources/shiboken6/libshiboken/embed/embedding_generator.py221
-rw-r--r--sources/shiboken6/libshiboken/embed/module_collector.py72
-rw-r--r--sources/shiboken6/libshiboken/embed/qt_python_license.txt48
-rw-r--r--sources/shiboken6/libshiboken/embed/signature_bootstrap.py205
4 files changed, 546 insertions, 0 deletions
diff --git a/sources/shiboken6/libshiboken/embed/embedding_generator.py b/sources/shiboken6/libshiboken/embed/embedding_generator.py
new file mode 100644
index 000000000..51c46ce54
--- /dev/null
+++ b/sources/shiboken6/libshiboken/embed/embedding_generator.py
@@ -0,0 +1,221 @@
+# 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
+from __future__ import annotations
+
+"""
+embedding_generator.py
+
+This file takes the content of the two supported directories and inserts
+it into a zip file. The zip file is then converted into a C++ source
+file that can easily be unpacked again with Python (see signature.cpp,
+constant 'PySide_PythonCode').
+
+Note that this _is_ a zipfile, but since it is embedded into the shiboken
+binary, we cannot use the zipimport module from Python.
+But a similar solution is possible that allows for normal imports.
+
+See signature_bootstrap.py for details.
+"""
+
+import sys
+import os
+import subprocess
+import textwrap
+import tempfile
+import argparse
+import marshal
+import traceback
+from pathlib import Path
+
+# work_dir is set to the source for testing, only.
+# It can be overridden in the command line.
+work_dir = Path(__file__).parent.resolve()
+embed_dir = work_dir
+cur_dir = Path.cwd()
+source_dir = work_dir.parents[2]
+assert source_dir.name == "sources"
+build_script_dir = work_dir.parents[3]
+assert (build_script_dir / "build_scripts").exists()
+
+sys.path.insert(0, os.fspath(build_script_dir))
+
+from build_scripts import utils
+
+
+def runpy(cmd, **kw):
+ subprocess.call([sys.executable, '-E'] + cmd.split(), **kw)
+
+
+def create_zipfile(use_pyc, quiet):
+ """
+ Collect all Python files, compile them, create a zip file
+ and make a chunked base64 encoded file from it.
+ """
+ zip_name = "signature.zip"
+ inc_name = "signature_inc.h"
+ flag = '-b'
+ os.chdir(work_dir)
+
+ # Remove all left-over py[co] and other files first, in case we use '--reuse-build'.
+ # Note that we could improve that with the PyZipfile function to use .pyc files
+ # in different folders, but that makes only sense when COIN allows us to have
+ # multiple Python versions in parallel.
+ for root, dirs, files in os.walk(work_dir):
+ for name in files:
+ fpath = Path(root) / name
+ ew = name.endswith
+ if ew(".pyc") or ew(".pyo") or ew(".zip") or ew(".inc"):
+ os.remove(fpath)
+ # We copy every Python file into this dir, but only for the right version.
+ # For testing in the source dir, we need to filter.
+ ignore = []
+ utils.copydir(source_dir / "shiboken6" / "shibokenmodule" / "files.dir" / "shibokensupport",
+ work_dir / "shibokensupport",
+ ignore=ignore, file_filter_function=lambda name, n2: name.endswith(".py"))
+ if embed_dir != work_dir:
+ utils.copyfile(embed_dir / "signature_bootstrap.py", work_dir)
+
+ if not use_pyc:
+ pass # We cannot compile, unless we have folders per Python version
+ else:
+ files = ' '.join(fn for fn in os.listdir('.'))
+ runpy(f'-m compileall -q {flag} {files}')
+ files = ' '.join(fn for fn in os.listdir('.') if not fn == zip_name)
+ runpy(f'-m zipfile -c {zip_name} {files}')
+ tmp = tempfile.TemporaryFile(mode="w+")
+ runpy(f'-m base64 {zip_name}', stdout=tmp)
+ # now generate the include file
+ tmp.seek(0)
+ with open(inc_name, "w") as inc:
+ _embed_file(tmp, inc)
+ tmp.close()
+
+ # also generate a simple embeddable .pyc file for signature_bootstrap.pyc
+ boot_name = "signature_bootstrap.py" if not use_pyc else "signature_bootstrap.pyc"
+ with open(boot_name, "rb") as ldr, open("signature_bootstrap_inc.h", "w") as inc:
+ _embed_bytefile(ldr, inc, not use_pyc)
+ os.chdir(cur_dir)
+ if quiet:
+ return
+
+ # have a look at our populated folder unless quiet option
+ def tree(directory):
+ print(f'+ {directory}')
+ for path in sorted(directory.rglob('*')):
+ depth = len(path.relative_to(directory).parts)
+ spacer = ' ' * depth
+ print(f'{spacer}+ {path.name}')
+
+ print("++++ Current contents of")
+ tree(work_dir)
+ print("++++")
+
+
+def _embed_file(fin, fout):
+ """
+ Format a text file for embedding in a C++ source file.
+ """
+ # MSVC has a 64k string limitation. In C, it would be easy to create an
+ # array of 64 byte strings and use them as one big array. In C++ this does
+ # not work, since C++ insists in having the terminating nullbyte.
+ # Therefore, we split the string after an arbitrary number of lines
+ # (chunked file).
+ limit = 50
+ text = fin.readlines()
+ print(textwrap.dedent("""
+ // This is a ZIP archive of all Python files in the directory
+ // "shiboken6/shibokenmodule/files.dir/shibokensupport/signature"
+ // There is also a toplevel file "signature_bootstrap.py[c]" that will be
+ // directly executed from C++ as a bootstrap loader.
+ """).strip(), file=fout)
+ block, blocks = 0, len(text) // limit + 1
+ for idx, line in enumerate(text):
+ if idx % limit == 0:
+ if block:
+ fout.write(')"\n')
+ comma = "," if block else ""
+ block += 1
+ fout.write(f'\n{comma} // Block {block} of {blocks}\nR"(')
+ else:
+ fout.write('\n')
+ fout.write(line.strip())
+ fout.write(')"\n\n/* Sentinel */, ""\n')
+
+
+def _embed_bytefile(fin, fout, is_text):
+ """
+ Format a binary file for embedding in a C++ source file.
+ This version works directly with a single .pyc file.
+ """
+ fname = fin.name
+ remark = ("No .pyc file because '--LIMITED-API=yes'" if is_text else
+ "The .pyc header is stripped away")
+ print(textwrap.dedent(f"""
+ /*
+ * This is the file "{fname}" as a simple byte array.
+ * It can be directly embedded without any further processing.
+ * {remark}.
+ */
+ """), file=fout)
+ headsize = ( 0 if is_text else
+ 16 if sys.version_info >= (3, 7) else 12 if sys.version_info >= (3, 3) else 8)
+ binstr = fin.read()[headsize:]
+ if is_text:
+ try:
+ compile(binstr, fin.name, "exec")
+ except SyntaxError as e:
+ print(e)
+ traceback.print_exc(file=sys.stdout)
+ print(textwrap.dedent(f"""
+ *************************************************************************
+ ***
+ *** Could not compile the boot loader '{fname}'!
+ ***
+ *************************************************************************
+ """))
+ raise SystemError
+ else:
+ try:
+ marshal.loads(binstr)
+ except ValueError as e:
+ print(e)
+ traceback.print_exc(file=sys.stdout)
+ version = sys.version_info[:3]
+ print(textwrap.dedent(f"""
+ *************************************************************************
+ ***
+ *** This Python version {version} seems to have a new .pyc header size.
+ *** Please correct the 'headsize' constant ({headsize}).
+ ***
+ *************************************************************************
+ """))
+ raise SystemError
+
+ print(file=fout)
+ use_ord = sys.version_info[0] == 2
+ for i in range(0, len(binstr), 16):
+ for c in bytes(binstr[i : i + 16]):
+ ord_c = ord(c) if use_ord else c
+ print(f"{ord_c:#4},", file=fout, end="")
+ print(file=fout)
+ print("/* End Of File */", file=fout)
+
+
+def str2bool(v):
+ if v.lower() in ('yes', 'true', 't', 'y', '1'):
+ return True
+ elif v.lower() in ('no', 'false', 'f', 'n', '0'):
+ return False
+ else:
+ raise argparse.ArgumentTypeError('Boolean value expected.')
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--cmake-dir', nargs="?")
+ parser.add_argument('--use-pyc', type=str2bool)
+ parser.add_argument('--quiet', action='store_true')
+ args = parser.parse_args()
+ if args.cmake_dir:
+ work_dir = Path(args.cmake_dir).resolve()
+ create_zipfile(args.use_pyc, args.quiet)
diff --git a/sources/shiboken6/libshiboken/embed/module_collector.py b/sources/shiboken6/libshiboken/embed/module_collector.py
new file mode 100644
index 000000000..8f7be6437
--- /dev/null
+++ b/sources/shiboken6/libshiboken/embed/module_collector.py
@@ -0,0 +1,72 @@
+# 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
+from __future__ import annotations
+
+"""
+module_collector.py
+
+Collect a number of modules listed on the command line.
+
+The purpose of this script is to generate the scripts needed for
+a complete isolation of the signature extension.
+
+Usage:
+
+Run this script in one of the used python versions.
+It will create an executable archive of the files on the command line.
+"""
+
+import sys
+import os
+import argparse
+import pickle
+from textwrap import dedent
+from pathlib import path
+
+
+def source_archive(module, modname):
+ fname = Path(module.__file__).stem + ".py"
+ with open(fname) as source:
+ text = source.read()
+ encoded = text.replace("'''", "(triple_single)")
+ # modname = module.__name__
+ # Do not use: Some modules rename themselves!
+ version = ".".join(map(str, sys.version_info[:3]))
+ shortname = fname.stem
+ preamble = dedent(fr"""
+ # BEGIN SOURCE ARCHIVE Python {version} module {modname}
+
+ sources = {{}} if "sources" not in globals() else sources
+ sources["{modname}"] = '''\
+ {encoded}'''.replace("(triple_single)", "'''")
+
+ # END SOURCE ARCHIVE Python {version} module {modname}
+ """)
+ return preamble
+
+
+def read_all(modules):
+ collected = ""
+ for modname in modules:
+ mod = __import__(modname)
+ collected += source_archive(mod, modname)
+ return collected
+
+
+def license_header():
+ license = Path(__file__).parent / "qt_python_license.txt"
+ with license.open() as f:
+ return f.read()
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser()
+ parser.add_argument('modules', nargs="+")
+ args = parser.parse_args()
+ print("modules:", args.modules)
+ ret = license_header() + read_all(args.modules)
+ ma_mi = "_".join(map(str, sys.version_info[:2]))
+ outpath = Path(__file__).parents[2] / Path("shibokenmodule",
+ "files.dir", "shibokensupport", f"python_minilib_{ma_mi}.py")
+ with outpath.open("w") as f:
+ f.write(ret)
diff --git a/sources/shiboken6/libshiboken/embed/qt_python_license.txt b/sources/shiboken6/libshiboken/embed/qt_python_license.txt
new file mode 100644
index 000000000..e5fdfdf4d
--- /dev/null
+++ b/sources/shiboken6/libshiboken/embed/qt_python_license.txt
@@ -0,0 +1,48 @@
+# 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
+
+##
+## PSF LICENSE AGREEMENT FOR PYTHON 3.7.0
+##
+## 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and
+## the Individual or Organization ("Licensee") accessing and otherwise using Python
+## 3.7.0 software in source or binary form and its associated documentation.
+##
+## 2. Subject to the terms and conditions of this License Agreement, PSF hereby
+## grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
+## analyze, test, perform and/or display publicly, prepare derivative works,
+## distribute, and otherwise use Python 3.7.0 alone or in any derivative
+## version, provided, however, that PSF's License Agreement and PSF's notice of
+## copyright, i.e., "Copyright © 2001-2018 Python Software Foundation; All Rights
+## Reserved" are retained in Python 3.7.0 alone or in any derivative version
+## prepared by Licensee.
+##
+## 3. In the event Licensee prepares a derivative work that is based on or
+## incorporates Python 3.7.0 or any part thereof, and wants to make the
+## derivative work available to others as provided herein, then Licensee hereby
+## agrees to include in any such work a brief summary of the changes made to Python
+## 3.7.0.
+##
+## 4. PSF is making Python 3.7.0 available to Licensee on an "AS IS" basis.
+## PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF
+## EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR
+## WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE
+## USE OF PYTHON 3.7.0 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS.
+##
+## 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 3.7.0
+## FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF
+## MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 3.7.0, OR ANY DERIVATIVE
+## THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+##
+## 6. This License Agreement will automatically terminate upon a material breach of
+## its terms and conditions.
+##
+## 7. Nothing in this License Agreement shall be deemed to create any relationship
+## of agency, partnership, or joint venture between PSF and Licensee. This License
+## Agreement does not grant permission to use PSF trademarks or trade name in a
+## trademark sense to endorse or promote products or services of Licensee, or any
+## third party.
+##
+## 8. By copying, installing or otherwise using Python 3.7.0, Licensee agrees
+## to be bound by the terms and conditions of this License Agreement.
+##
diff --git a/sources/shiboken6/libshiboken/embed/signature_bootstrap.py b/sources/shiboken6/libshiboken/embed/signature_bootstrap.py
new file mode 100644
index 000000000..b0ba77107
--- /dev/null
+++ b/sources/shiboken6/libshiboken/embed/signature_bootstrap.py
@@ -0,0 +1,205 @@
+# 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
+from __future__ import annotations
+
+"""
+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