mirror of
https://github.com/Quad4-Software/MeshChatX.git
synced 2026-08-18 09:49:09 -04:00
154 lines
4.9 KiB
Python
Executable file
154 lines
4.9 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
# SPDX-License-Identifier: 0BSD
|
|
|
|
"""Bundle libcodec2.so into Chaquopy pycodec2 wheels (Android vendor dir)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import base64
|
|
import hashlib
|
|
import re
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
import zipfile
|
|
from pathlib import Path
|
|
|
|
|
|
def _abi_tag_from_wheel_name(name: str) -> str | None:
|
|
match = re.search(r"android_\d+_(arm64_v8a|armeabi_v7a|x86_64)", name)
|
|
return match.group(1) if match else None
|
|
|
|
|
|
def _extract_libcodec2(lib_wheel: Path, dest_dir: Path) -> Path | None:
|
|
with zipfile.ZipFile(lib_wheel) as zf:
|
|
for name in zf.namelist():
|
|
if name.endswith("chaquopy/lib/libcodec2.so"):
|
|
zf.extract(name, dest_dir)
|
|
return dest_dir / name
|
|
return None
|
|
|
|
|
|
def _find_libcodec2(vendor_dir: Path, abi_tag: str, dest_dir: Path) -> Path | None:
|
|
matches = sorted(vendor_dir.glob(f"chaquopy_libcodec2-*-android_*_{abi_tag}.whl"))
|
|
if not matches:
|
|
return None
|
|
return _extract_libcodec2(matches[-1], dest_dir)
|
|
|
|
|
|
def _urlsafe_sha256_digest(data: bytes) -> str:
|
|
return (
|
|
base64.urlsafe_b64encode(hashlib.sha256(data).digest())
|
|
.decode("ascii")
|
|
.rstrip("=")
|
|
)
|
|
|
|
|
|
def _rewrite_wheel_record(root: Path) -> None:
|
|
"""Regenerate dist-info/RECORD after wheel contents change.
|
|
|
|
Chaquopy's pip post-processor parses RECORD sizes with ``int()`` and rejects
|
|
directory placeholder lines (``path,,``). Rebuilding RECORD from file bytes
|
|
keeps repacked pycodec2 wheels installable.
|
|
"""
|
|
dist_infos = sorted(root.glob("*.dist-info"))
|
|
if not dist_infos:
|
|
return
|
|
|
|
record_path = dist_infos[0] / "RECORD"
|
|
if not record_path.is_file():
|
|
return
|
|
|
|
record_rel = record_path.relative_to(root).as_posix()
|
|
file_entries: list[tuple[str, bytes]] = []
|
|
for path in sorted(root.rglob("*")):
|
|
if not path.is_file():
|
|
continue
|
|
rel = path.relative_to(root).as_posix()
|
|
if rel == record_rel:
|
|
continue
|
|
file_entries.append((rel, path.read_bytes()))
|
|
|
|
rows = []
|
|
for rel, data in file_entries:
|
|
digest = _urlsafe_sha256_digest(data)
|
|
rows.append(f"{rel},sha256={digest},{len(data)}")
|
|
|
|
record_body = "\n".join(rows) + "\n"
|
|
record_digest = _urlsafe_sha256_digest(record_body.encode())
|
|
record_body += f"{record_rel},sha256={record_digest},{len(record_body.encode())}\n"
|
|
record_path.write_bytes(record_body.encode())
|
|
|
|
|
|
def repack_pycodec2_wheel(pycodec2_wheel: Path, libcodec2_so: Path) -> bool:
|
|
if not libcodec2_so.is_file():
|
|
print(f"Missing libcodec2.so for {pycodec2_wheel.name}", file=sys.stderr)
|
|
return False
|
|
|
|
with tempfile.TemporaryDirectory(prefix="repack-pycodec2-") as tmp_dir:
|
|
root = Path(tmp_dir)
|
|
with zipfile.ZipFile(pycodec2_wheel) as zin:
|
|
zin.extractall(root)
|
|
|
|
target = root / "pycodec2" / "libcodec2.so"
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copy2(libcodec2_so, target)
|
|
_rewrite_wheel_record(root)
|
|
|
|
tmp_wheel = pycodec2_wheel.with_suffix(".repack.whl")
|
|
with zipfile.ZipFile(
|
|
tmp_wheel,
|
|
"w",
|
|
compression=zipfile.ZIP_DEFLATED,
|
|
) as zout:
|
|
for path in sorted(root.rglob("*")):
|
|
if path.is_file():
|
|
zout.write(path, path.relative_to(root).as_posix())
|
|
|
|
tmp_wheel.replace(pycodec2_wheel)
|
|
return True
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument(
|
|
"--vendor-dir",
|
|
type=Path,
|
|
default=Path("android/vendor"),
|
|
help="Directory containing pycodec2 and chaquopy_libcodec2 wheels",
|
|
)
|
|
args = parser.parse_args()
|
|
vendor_dir = args.vendor_dir.resolve()
|
|
if not vendor_dir.is_dir():
|
|
print(f"Vendor directory not found: {vendor_dir}", file=sys.stderr)
|
|
return 1
|
|
|
|
wheels = sorted(vendor_dir.glob("pycodec2-*-android_*.whl"))
|
|
if not wheels:
|
|
print(f"No pycodec2 Android wheels under {vendor_dir}", file=sys.stderr)
|
|
return 1
|
|
|
|
ok = True
|
|
for wheel in wheels:
|
|
abi_tag = _abi_tag_from_wheel_name(wheel.name)
|
|
if not abi_tag:
|
|
print(f"Skipping unrecognized wheel name: {wheel.name}", file=sys.stderr)
|
|
ok = False
|
|
continue
|
|
with tempfile.TemporaryDirectory(prefix="libcodec2-src-") as lib_tmp:
|
|
libcodec2 = _find_libcodec2(vendor_dir, abi_tag, Path(lib_tmp))
|
|
if libcodec2 is None:
|
|
print(f"No chaquopy_libcodec2 wheel for ABI {abi_tag}", file=sys.stderr)
|
|
ok = False
|
|
continue
|
|
if not repack_pycodec2_wheel(wheel, libcodec2):
|
|
ok = False
|
|
continue
|
|
print(f"Repacked {wheel.name} with pycodec2/libcodec2.so")
|
|
|
|
return 0 if ok else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|