SXXXXXXX_PyUCC/tools/bundle_pygount.py

163 lines
4.6 KiB
Python

#!/usr/bin/env python3
"""Bundle pygount wheel into the project and update pyucc.spec.
Usage: run inside your project's virtualenv/checkout root:
python tools/bundle_pygount.py
What it does:
- Locates a local `pygount-*.whl` in the current Python environment (site-packages)
- Copies it to `pyucc/_internal/`
- Updates `pyucc.spec`, adding the wheel as a `datas` entry so PyInstaller bundles it
This enables the runtime fallback added in `pyucc.core.countings_impl` to import pygount
from a bundled wheel when running on an offline target.
"""
from __future__ import annotations
import os
import sys
import shutil
import glob
from pathlib import Path
import sysconfig
import site
import re
REPO_ROOT = Path(__file__).resolve().parents[1]
SPEC_PATH = REPO_ROOT / "pyucc.spec"
DEST_DIR = REPO_ROOT / "pyucc" / "_internal"
def find_local_pygount_wheel() -> Path | None:
"""Search common site-packages locations for pygount-*.whl and return first match."""
candidates = []
# Preferred: current environment's site-packages (sysconfig)
try:
purelib = sysconfig.get_paths().get("purelib")
if purelib:
candidates.append(Path(purelib))
except Exception:
pass
# site.getsitepackages() may return multiple dirs
try:
for p in site.getsitepackages():
candidates.append(Path(p))
except Exception:
pass
# Fallback: sys.path entries
for p in sys.path:
try:
candidates.append(Path(p))
except Exception:
continue
# Also check repository-local wheel folder and current working dir
try:
candidates.append(REPO_ROOT / "wheels")
except Exception:
pass
try:
candidates.append(Path.cwd())
except Exception:
pass
try:
candidates.append(DEST_DIR)
except Exception:
pass
seen = set()
for base in candidates:
if not base:
continue
base = base.resolve()
if str(base) in seen:
continue
seen.add(str(base))
pattern = str(base / "pygount-*.whl")
for f in glob.glob(pattern):
if f:
return Path(f).resolve()
return None
def copy_wheel_to_internal(wheel_path: Path) -> Path:
DEST_DIR.mkdir(parents=True, exist_ok=True)
dest = DEST_DIR / wheel_path.name
shutil.copy2(wheel_path, dest)
return dest
def patch_spec_add_datas(spec_path: Path, wheel_rel_path: str) -> bool:
"""Insert a datas tuple ('<wheel_rel_path>', '_internal') into all datas=[...] lists in the spec.
Returns True if file modified.
"""
text = spec_path.read_text(encoding="utf-8")
tuple_entry = f"('{wheel_rel_path}', '_internal'),"
if tuple_entry in text:
print("Spec already contains wheel datas entry; nothing to do.")
return False
# Find all occurrences of 'datas=[' and insert the tuple after the opening bracket
new_text = text
inserts = 0
idx = 0
while True:
m = re.search(r"\bdatas\s*=\s*\[", new_text[idx:])
if not m:
break
# m.start() relative to new_text[idx:]
start = idx + m.end()
# Insert after start
new_text = new_text[:start] + tuple_entry + new_text[start:]
inserts += 1
idx = start + len(tuple_entry)
if inserts:
backup = spec_path.with_suffix(spec_path.suffix + ".bak")
spec_path.replace(backup)
spec_path.write_text(new_text, encoding="utf-8")
print(f"Patched {spec_path} - inserted datas entry in {inserts} places (backup saved to {backup}).")
return True
print(r"No datas=\[...] occurrences found in spec; no changes made.")
return False
def main():
print(f"Repo root: {REPO_ROOT}")
wheel = find_local_pygount_wheel()
if not wheel:
print("Could not locate a local pygount wheel in this environment.")
print("Please download a pygount wheel (pygount-<ver>-py3-none-any.whl) into your environment or site-packages.")
sys.exit(2)
print(f"Found pygount wheel: {wheel}")
dest = copy_wheel_to_internal(wheel)
print(f"Copied wheel to: {dest}")
# wheel_rel_path relative to project root (use forward slashes)
wheel_rel = f"pyucc/_internal/{dest.name}"
if not SPEC_PATH.exists():
print(f"Spec file not found at {SPEC_PATH}; please run this script from project root.")
sys.exit(3)
changed = patch_spec_add_datas(SPEC_PATH, wheel_rel)
if changed:
print("Specification updated. Rebuild using: pyinstaller pyucc.spec")
else:
print("Specification unchanged.")
if __name__ == "__main__":
main()