diff --git a/util/make_python_lib_zip.py b/util/make_python_lib_zip.py new file mode 100644 index 000000000..483bc955b --- /dev/null +++ b/util/make_python_lib_zip.py @@ -0,0 +1,100 @@ +from pathlib import Path +import sys +import os +import tempfile +import zipfile +import py_compile + +################################################# +# Script to generate a compatible pythonXY.zip for a python version as +# RenderDoc expects to distribute the standard library. +# +# Normally the 'embeddable package' includes this, but not every version +# of python has that - some versions are provided as source only if they +# are security-only releases. +# +# These are not generated bitwise identically to the python.org zips for +# unknown reasons, more than just a timestamp/checksum. +# +# If you have an installed version of Python in PythonXY/ then +# running this like: +# +# PythonXY/python make_python_lib_zip.py +# +# Will set up everything RenderDoc expects for building against that +# version of python, expecting to write a pythonXY.zip next to that +# executable (this will likely not work on linux, but this script is +# generally only relevant on windows as linux distributions ship the +# plain python scripts standard library) + +def usage(): + print(f"Usage: {sys.argv[0]} [PythonXY/Lib/] [pythonXY.zip]") + print(" If zip is omitted, will write next to python binary.") + print(" If Lib/ is omitted, will use first sys.path folder.") + sys.exit(1) + +if len(sys.argv) > 3: + usage() + +if len(sys.argv) >= 3: + outpath = os.path.abspath(sys.argv[2]) +else: + ver = sys.version_info + outpath = os.path.join(os.path.dirname(sys.executable), f"python{ver.major}{ver.minor}.zip") + +if len(sys.argv) >= 2: + inpath = os.path.abspath(sys.argv[1]) +else: + dirpaths = [x for x in sys.path if os.path.isdir(x) and os.path.exists(os.path.join(x, 'os.py'))] + + if len(dirpaths) == 0: + print(f"Couldn't determine standard library location from {sys.path}") + usage() + + inpath = dirpaths[0] + +if not os.path.exists(os.path.join(inpath, 'abc.py')): + print(f"Input path '{inpath}' must be a python Lib/ folder with standard .py files like os.py") + usage() + +if not outpath.endswith('.zip'): + print(f"Output path '{outpath}' must be a zip") + usage() + +print(f"Creating {outpath} from compiling library in {inpath}") + +# ensure we can write output zip +os.makedirs(os.path.dirname(outpath), exist_ok=True) + +def include_path(path): + # exclude based on directories (that may have false positives) + if any([x in path.split(os.sep) for x in ['test', 'tests', 'distutils', 'site-packages', '__pycache__']]): + return False + # exclude general patterns + if any([x in path for x in ['turtle', 'venv', 'tkinter', 'idlelib', 'lib2to3', 'ensurepip']]): + return False + + # don't include any .pyc files that aren't in __pycache__ + if path.endswith('.pyc'): + return False + + return True + +with zipfile.ZipFile(outpath, mode='w', compression=zipfile.ZIP_DEFLATED) as z: + os.chdir(inpath) + + for path in Path('.').rglob('*'): + relpath = str(path) + + if not include_path(relpath): + continue + + if str(path).endswith('.py'): + fd, tmpname = tempfile.mkstemp(suffix=".pyc") + os.close(fd) + + py_compile.compile(relpath, cfile=tmpname, dfile=relpath, optimize=2) + z.write(tmpname, arcname=relpath + "c") # .py => .pyc + os.unlink(tmpname) + else: + z.write(relpath)