#!/usr/bin/env python """ Python property extractor for Jaunch. Outputs configuration values that Jaunch uses to reason about the Python installation, including the Python shared library path and other metadata. """ import platform import sys import sysconfig from pathlib import Path def guess_libpython_path(): """Tries to discern the path to the Python shared library.""" if hasattr(sys, "dllhandle"): # Windows: use sys.dllhandle + GetModuleFileName. import ctypes from ctypes.wintypes import HMODULE, LPWSTR, DWORD GetModuleFileNameW = ctypes.windll.kernel32.GetModuleFileNameW GetModuleFileNameW.argtypes = [HMODULE, LPWSTR, DWORD] GetModuleFileNameW.restype = DWORD buf = ctypes.create_unicode_buffer(260) GetModuleFileNameW(sys.dllhandle, buf, 260) return str(buf.value) else: # POSIX: use sysconfig metadata. libdir = sysconfig.get_config_var("LIBDIR") ldlibrary = sysconfig.get_config_var("LDLIBRARY") if "Python.framework" in str(ldlibrary): # Homebrew/framework: use PYTHONFRAMEWORKPREFIX. framework_prefix = sysconfig.get_config_var("PYTHONFRAMEWORKPREFIX") if framework_prefix: return str(Path(framework_prefix, ldlibrary)) # Fallback: try to construct from prefix. prefix = sysconfig.get_config_var("prefix") if prefix and "Frameworks/Python.framework" in prefix: base = prefix.split("/Frameworks/Python.framework")[0] return str(Path(f"{base}/Frameworks", ldlibrary)) elif libdir and ldlibrary: p = Path(libdir, ldlibrary) if p.suffix == ".a": # HACK: Work around Conda envs reporting # the static lib over the shared lib. for ext in [".dylib", ".so"]: candidate = p.with_suffix(ext) if candidate.exists(): return str(candidate) if not p.exists(): # HACK: Check for variant library name on Linux. p1 = p.with_suffix(".so.1") if p1.exists(): p = p1 return str(p) return None def discern_packages(): """Returns a dict mapping package names to versions.""" try: from importlib.metadata import distributions return { dist.metadata['Name']: dist.version for dist in distributions() if dist.version is not None } except Exception: return {} props = { "jaunch.libpython_path": guess_libpython_path(), "platform.machine": platform.machine(), "platform.system": platform.system(), "sys.executable": sys.executable, "sys.version": sys.version, "sys.prefix": sys.prefix, } props.update({f"paths.{k}": v for k, v in sysconfig.get_paths().items()}) props.update({f"cvars.{k}": v for k, v in sysconfig.get_config_vars().items()}) props.update({f"packages.{k}": v for k, v in discern_packages().items() if k}) if len(sys.argv) > 1: # Generalize machine-specific paths. prefix = sys.prefix home = str(Path().home()) version = props["cvars.py_version"] vshort = props["cvars.py_version_short"] vnodot = props["cvars.py_version_nodot"] for k, v in props.items(): if not isinstance(v, str): continue v = v.replace(prefix, "${PREFIX}") v = v.replace(home, "${HOME}") if not k.startswith("cvars.py_version"): if version: v = v.replace(version, "${VERSION}") if vshort: v = v.replace(vshort, "${SPEC}") if vnodot: v = v.replace(vnodot, "${NODOT}") props[k] = v for k, v in props.items(): print(f"{k}={v}")