diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 5cab5cd588f..04540d395fd 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -6587,6 +6587,11 @@ def _install_psutil_android_compat( We patch only the extracted build tree used for this install attempt; nothing is persisted in the repository. + + Stopgap: remove this once https://github.com/giampaolo/psutil/pull/2762 + merges and ships in a release. ``scripts/install_psutil_android.py`` + contains the same logic for ``scripts/install.sh`` (fresh installs). + Both copies should be removed together. """ import tarfile import tempfile diff --git a/scripts/install.sh b/scripts/install.sh index d452a26490b..bc391eee43c 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -985,6 +985,19 @@ install_deps() { "$PIP_PYTHON" -m pip install --upgrade pip setuptools wheel >/dev/null + # On Android, psutil's setup.py rejects sys.platform == 'android' before + # it ever invokes the C build, so the next pip install would fail at + # "platform android is not supported". Prebuild psutil from the official + # sdist with a one-line marker patch (Linux source path is fine on + # Android). Stopgap until psutil#2762 ships upstream. + if "$PIP_PYTHON" -c 'import sys; raise SystemExit(0 if sys.platform == "android" else 1)' 2>/dev/null; then + log_info "Android Python detected: prebuilding psutil compatibility shim..." + if ! "$PIP_PYTHON" "$INSTALL_DIR/scripts/install_psutil_android.py" --pip "$PIP_PYTHON -m pip"; then + log_warn "psutil Android prebuild failed — package install will likely fail next." + log_info "Workaround: manually rerun 'python scripts/install_psutil_android.py' once your toolchain is set up." + fi + fi + # Try the broad Termux profile first (best-effort "install all" for Android), # then fall back to the conservative Termux baseline, then base package. if ! "$PIP_PYTHON" -m pip install -e '.[termux-all]' -c constraints-termux.txt; then diff --git a/scripts/install_psutil_android.py b/scripts/install_psutil_android.py new file mode 100755 index 00000000000..4e2c49805a6 --- /dev/null +++ b/scripts/install_psutil_android.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +"""Install psutil on Termux/Android by patching upstream platform detection. + +psutil's setup currently gates Linux sources behind +``sys.platform.startswith('linux')``. On Termux, Python reports +``sys.platform == 'android'``, so ``pip install psutil`` aborts with +"platform android is not supported" — even though psutil compiles fine +when the Linux source path is reused. + +This script downloads the official psutil sdist, applies a one-line +patch (``LINUX = sys.platform.startswith(("linux", "android"))``), and +installs the patched tree with ``pip install --no-build-isolation``. + +Usage: + python scripts/install_psutil_android.py [--pip "/path/to/pip"] [--uv] + +When neither flag is given, the script auto-detects ``uv`` on PATH and +falls back to `` -m pip``. + +This is a stopgap. Remove once psutil upstream merges +https://github.com/giampaolo/psutil/pull/2762 and ships a release. +""" + +from __future__ import annotations + +import argparse +import shutil +import subprocess +import sys +import tarfile +import tempfile +import urllib.request +from pathlib import Path + +# Pin a version we know patches cleanly. Update when a newer psutil +# changes the marker line shape and we need to follow upstream. +PSUTIL_URL = ( + "https://files.pythonhosted.org/packages/aa/c6/" + "d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/" + "psutil-7.2.2.tar.gz" +) + +MARKER = 'LINUX = sys.platform.startswith("linux")' +REPLACEMENT = 'LINUX = sys.platform.startswith(("linux", "android"))' + + +def _resolve_install_cmd(pip_arg: str | None, prefer_uv: bool) -> list[str]: + if pip_arg: + return pip_arg.split() + if prefer_uv: + uv = shutil.which("uv") + if not uv: + sys.exit("--uv requested but no uv on PATH") + return [uv, "pip"] + auto_uv = shutil.which("uv") + if auto_uv: + return [auto_uv, "pip"] + return [sys.executable, "-m", "pip"] + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--pip", + help="Explicit installer command (e.g. '/usr/bin/uv pip' or 'python -m pip')", + ) + parser.add_argument( + "--uv", + action="store_true", + help="Force using uv (errors out if uv is not on PATH)", + ) + args = parser.parse_args() + + install_cmd_prefix = _resolve_install_cmd(args.pip, args.uv) + + print( + "→ Termux/Android: prebuilding psutil with Linux source path " + "compatibility shim (see psutil#2762)..." + ) + + with tempfile.TemporaryDirectory() as tmp: + tmp_path = Path(tmp) + archive = tmp_path / "psutil.tar.gz" + urllib.request.urlretrieve(PSUTIL_URL, archive) + with tarfile.open(archive) as tar: + tar.extractall(tmp_path) + + try: + src_root = next( + p for p in tmp_path.iterdir() + if p.is_dir() and p.name.startswith("psutil-") + ) + except StopIteration: + sys.exit("psutil sdist did not contain a psutil-* directory") + + common_py = src_root / "psutil" / "_common.py" + content = common_py.read_text(encoding="utf-8") + if MARKER not in content: + sys.exit( + "psutil Android compatibility patch marker not found — " + "upstream may have changed the LINUX detection line. " + "Update MARKER/REPLACEMENT in this script." + ) + common_py.write_text(content.replace(MARKER, REPLACEMENT), encoding="utf-8") + + cmd = install_cmd_prefix + ["install", "--no-build-isolation", str(src_root)] + print(f" $ {' '.join(cmd)}") + result = subprocess.run(cmd) + if result.returncode != 0: + return result.returncode + + print("✓ psutil installed via Android compatibility shim") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())