#! /usr/bin/env python3 import sys import re import shutil from pathlib import Path from subprocess import check_output, check_call def parse_ldd_line(line: str): line = line.strip() line = re.sub(r" *\(0x[0-9a-fA-F]*\)", "", line) parts = line.split("=>") name = Path(parts[0].strip()).name if name not in exlude_list and len(parts) == 2: return name, Path(parts[1].strip()).resolve() def collect_dependencies(target): target = Path(target) def collect(): for line in check_output(["ldd", target]).decode("utf-8").splitlines(): dep = parse_ldd_line(line) if dep is not None: yield dep return list(collect()) def make_python_portable(interpreter): interpreter = Path(interpreter) bin_dir = interpreter.parent root_dir = bin_dir.parent lib_dir = root_dir / "lib" # Patch interpreter to find all libraries check_call(["patchelf", "--set-rpath", "$ORIGIN/../lib", interpreter]) # Install all dependencies targets = [ interpreter, *list(Path(lib_dir / (interpreter.resolve().name)).glob("**/*.so")) ] deps = [] for target in targets: deps += collect_dependencies(target) for name, path in deps: if not root_dir in path.parents: shutil.copy2(str(path), str(lib_dir / name)) # Patch targets and dependencies to find each other for target in targets[1:]: num_parents = len(target.relative_to(lib_dir).parts) - 1 rpath = Path("$ORIGIN") for _ in range(num_parents): rpath /= ".." check_call(["patchelf", "--set-rpath", str(rpath), target]) for name, path in deps: check_call(["patchelf", "--set-rpath", "$ORIGIN", str(lib_dir / name)]) # Retrieved: 2021-11-18 appimage_exclude_list = [ "ld-linux.so.2", "ld-linux-x86-64.so.2", "libanl.so.1", "libBrokenLocale.so.1", "libcidn.so.1", "libc.so.6", "libdl.so.2", "libm.so.6", "libmvec.so.1", "libnss_compat.so.2", "libnss_dns.so.2", "libnss_files.so.2", "libnss_hesiod.so.2", "libnss_nisplus.so.2", "libnss_nis.so.2", "libpthread.so.0", "libresolv.so.2", "librt.so.1", "libthread_db.so.1", "libutil.so.1", "libstdc++.so.6", "libGL.so.1", "libEGL.so.1", "libGLdispatch.so.0", "libGLX.so.0", "libOpenGL.so.0", "libdrm.so.2", "libglapi.so.0", "libgbm.so.1", "libxcb.so.1", "libX11.so.6", "libgio-2.0.so.0", "libasound.so.2", "libfontconfig.so.1", "libthai.so.0", "libfreetype.so.6", "libharfbuzz.so.0", "libcom_err.so.2", "libexpat.so.1", "libgcc_s.so.1", "libglib-2.0.so.0", "libgpg-error.so.0", "libICE.so.6", "libp11-kit.so.0", "libSM.so.6", "libusb-1.0.so.0", "libuuid.so.1", "libz.so.1", "libgobject-2.0.so.0", "libpangoft2-1.0.so.0", "libpangocairo-1.0.so.0", "libpango-1.0.so.0", "libgpg-error.so.0", "libjack.so.0", "libxcb-dri3.so.0", "libxcb-dri2.so.0", "libfribidi.so.0", "libgmp.so.10", ] exlude_list = [ *appimage_exclude_list, "linux-vdso.so.1", ] if __name__ == "__main__": if len(sys.argv) >= 2: make_python_portable(sys.argv[1]) else: raise SystemExit(1)