fix installer

This commit is contained in:
2026-04-01 14:36:36 -07:00
parent 746096f5d1
commit 01f5e35b61
2 changed files with 128 additions and 43 deletions

View File

@@ -1619,14 +1619,14 @@ def main(argv: Optional[list[str]] = None) -> int:
break break
script_dir = Path(__file__).resolve().parent script_dir = Path(__file__).resolve().parent
ensure_run_client_helper(dest, script_dir) installed_helper = ensure_run_client_helper(dest, script_dir)
run_client_script = None run_client_script = None
if client_found: if client_found:
# Prefer run_client helper located in the cloned repo; if missing, fall back to top-level scripts folder helper. # Prefer the helper installed directly into the Hydrus repository.
helper_candidates = [dest / "run_client.py", script_dir / "run_client.py"] helper_candidates = [installed_helper, dest / "run_client.py", script_dir / "run_client.py"]
for cand in helper_candidates: for cand in helper_candidates:
if cand.exists(): if cand and cand.exists():
run_client_script = cand run_client_script = cand
break break
if getattr(args, if getattr(args,
@@ -1830,20 +1830,22 @@ def main(argv: Optional[list[str]] = None) -> int:
# Helpful hint: show the new run_client helper and direct run example # Helpful hint: show the new run_client helper and direct run example
try: try:
helper_to_show = (
run_client_script if
(run_client_script and run_client_script.exists()) else
(script_dir / "run_client.py")
)
if venv_py: if venv_py:
logging.info( if run_client_script and run_client_script.exists() and run_client_script.resolve().parent == dest.resolve():
"To run the Hydrus client using the repo venv (no activation needed):\n %s %s [args]\nOr use the helper: %s --help\nHelper examples:\n %s --install-deps --verify\n %s --headless --detached", logging.info(
venv_py, "To run the Hydrus client using the repo venv (no activation needed):\n %s %s [args]\nOr use the helper: %s --help\nHelper examples:\n %s --install-deps --verify\n %s --no-update --headless --detached",
dest / "hydrus_client.py", venv_py,
helper_to_show, dest / "hydrus_client.py",
helper_to_show, run_client_script,
helper_to_show, run_client_script,
) run_client_script,
)
else:
logging.info(
"To run the Hydrus client using the repo venv (no activation needed):\n %s %s [args]",
venv_py,
dest / "hydrus_client.py",
)
else: else:
logging.info( logging.info(
"To run the Hydrus client: python %s [args]", "To run the Hydrus client: python %s [args]",
@@ -1875,14 +1877,11 @@ def ensure_run_client_helper(dest: Path, script_dir: Path) -> Optional[Path]:
return None return None
helper_dest = dest / "run_client.py" helper_dest = dest / "run_client.py"
if helper_dest.exists():
return helper_dest
try: try:
shutil.copy2(helper_src, helper_dest) shutil.copy2(helper_src, helper_dest)
if os.name != "nt": if os.name != "nt":
helper_dest.chmod(helper_dest.stat().st_mode | 0o111) helper_dest.chmod(helper_dest.stat().st_mode | 0o111)
logging.debug("Copied run_client helper to %s", helper_dest) logging.debug("Installed run_client helper to %s", helper_dest)
return helper_dest return helper_dest
except Exception as exc: except Exception as exc:
logging.debug("Failed to copy run_client helper: %s", exc) logging.debug("Failed to copy run_client helper: %s", exc)

View File

@@ -22,7 +22,7 @@ from __future__ import annotations
import argparse import argparse
import os import os
import pwd import re
import shlex import shlex
import shutil import shutil
import subprocess import subprocess
@@ -30,6 +30,11 @@ import sys
from pathlib import Path from pathlib import Path
from typing import List, Optional from typing import List, Optional
try:
import pwd
except ImportError:
pwd = None
def get_python_in_venv(venv_dir: Path) -> Optional[Path]: def get_python_in_venv(venv_dir: Path) -> Optional[Path]:
try: try:
@@ -72,6 +77,7 @@ def find_requirements(root: Path) -> Optional[Path]:
def install_requirements( def install_requirements(
venv_py: Path, venv_py: Path,
repo_root: Path,
req_path: Path, req_path: Path,
reinstall: bool = False, reinstall: bool = False,
upgrade: bool = False upgrade: bool = False
@@ -93,20 +99,13 @@ def install_requirements(
check=True, check=True,
**kwargs **kwargs
) )
install_cmd = [str(venv_py), "-m", "pip", "install", "-r", str(req_path)] python_version = get_python_version_info(venv_py)
install_cmd = [str(venv_py), "-m", "pip", "install", "--prefer-binary"]
if upgrade: if upgrade:
install_cmd = [str(venv_py), "-m", "pip", "install", "--upgrade", "-r", str(req_path)] install_cmd.append("--upgrade")
if reinstall: if reinstall:
install_cmd = [ install_cmd.extend(["--upgrade", "--force-reinstall"])
str(venv_py), install_cmd.extend(build_hydrus_install_targets(req_path, repo_root, python_version))
"-m",
"pip",
"install",
"--upgrade",
"--force-reinstall",
"-r",
str(req_path),
]
subprocess.run(install_cmd, check=True, **kwargs) subprocess.run(install_cmd, check=True, **kwargs)
return True return True
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
@@ -140,6 +139,69 @@ def parse_requirements_file(req_path: Path) -> List[str]:
return names return names
def get_python_version_info(python_exe: Path) -> Optional[tuple[int, int, int]]:
try:
result = subprocess.run(
[
str(python_exe),
"-c",
"import sys; print('.'.join(str(part) for part in sys.version_info[:3]))",
],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
check=True,
timeout=10,
)
version_text = (result.stdout or "").strip()
major, minor, micro = version_text.split(".", 2)
return int(major), int(minor), int(micro)
except Exception:
return None
def is_hydrus_repo(repo_root: Path) -> bool:
return (repo_root / "setup_venv.py").exists() and (repo_root / "hydrus_client.py").exists()
def build_hydrus_install_targets(
req_path: Path,
repo_root: Path,
python_version: Optional[tuple[int, int, int]],
) -> List[str]:
if python_version is None or python_version < (3, 13) or not is_hydrus_repo(repo_root):
return ["-r", str(req_path)]
overrides = {
"pyside6": "PySide6==6.10.1",
"qtpy": "QtPy==2.4.3",
"opencv-python-headless": "opencv-python-headless==4.13.0.90",
"numpy": "numpy==2.4.1",
}
targets: List[str] = []
seen_packages = set()
for raw_line in req_path.read_text(encoding="utf-8").splitlines():
line = raw_line.strip()
if not line or line.startswith("#"):
continue
package_name = re.split(r"[<>=!~\[]", line, maxsplit=1)[0].strip().lower()
if package_name in overrides:
line = overrides[package_name]
targets.append(line)
seen_packages.add(package_name)
if os.name == "nt" and "pywin32" not in seen_packages:
targets.append("pywin32")
print(
f"Using Hydrus compatibility dependency set for Python {python_version[0]}.{python_version[1]}.{python_version[2]}"
)
return targets
def verify_imports(venv_py: Path, packages: List[str]) -> bool: def verify_imports(venv_py: Path, packages: List[str]) -> bool:
# Skip mpv check as it is problematic to install and causes slow startups # Skip mpv check as it is problematic to install and causes slow startups
packages = [p for p in packages if p.lower() != "mpv"] packages = [p for p in packages if p.lower() != "mpv"]
@@ -228,6 +290,8 @@ def is_first_run(repo_root: Path) -> bool:
def _user_exists(username: str) -> bool: def _user_exists(username: str) -> bool:
if pwd is None:
return False
try: try:
pwd.getpwnam(username) pwd.getpwnam(username)
return True return True
@@ -914,7 +978,12 @@ def main(argv: Optional[List[str]] = None) -> int:
p.add_argument( p.add_argument(
"--pull", "--pull",
action="store_true", action="store_true",
help="Run 'git pull' before starting the client", help="Force a repository update before starting the client (legacy alias; startup update is enabled by default)",
)
p.add_argument(
"--no-update",
action="store_true",
help="Skip the default repository git pull before startup",
) )
p.add_argument( p.add_argument(
"--update-deps", "--update-deps",
@@ -984,19 +1053,36 @@ def main(argv: Optional[List[str]] = None) -> int:
else: else:
repo_root = workspace_root repo_root = workspace_root
# Handle git pull update if requested repo_updated = False
# Skip execution during service install/uninstall; it will run when the service starts should_pull_repo = not args.no_update and not (args.install_service or args.uninstall_service)
if args.pull and not (args.install_service or args.uninstall_service): if should_pull_repo:
if shutil.which("git"): if shutil.which("git"):
if (repo_root / ".git").exists(): if (repo_root / ".git").exists():
if not args.quiet: if not args.quiet:
print(f"Updating repository via 'git pull' in {repo_root}...") print(f"Updating repository via 'git pull --ff-only' in {repo_root}...")
try: try:
# Use creationflags to hide the window on Windows
k = {} k = {}
if os.name == "nt": if os.name == "nt":
k["creationflags"] = 0x08000000 k["creationflags"] = 0x08000000
subprocess.run(["git", "pull"], cwd=str(repo_root), check=False, **k) result = subprocess.run(
["git", "pull", "--ff-only"],
cwd=str(repo_root),
check=False,
capture_output=True,
text=True,
**k,
)
pull_output = "\n".join(
part.strip()
for part in [result.stdout or "", result.stderr or ""]
if part.strip()
)
if result.returncode != 0:
print(f"Warning: git pull failed: {pull_output or result.returncode}")
else:
repo_updated = "already up to date" not in pull_output.lower()
if pull_output and not args.quiet:
print(pull_output)
except Exception as e: except Exception as e:
print(f"Warning: git pull failed: {e}") print(f"Warning: git pull failed: {e}")
else: else:
@@ -1127,7 +1213,7 @@ def main(argv: Optional[List[str]] = None) -> int:
except Exception: except Exception:
pass pass
if not should_update and args.pull and not (args.install_service or args.uninstall_service): if not should_update and (args.pull or repo_updated) and not (args.install_service or args.uninstall_service):
should_update = True should_update = True
if args.install_deps or args.reinstall or should_update: if args.install_deps or args.reinstall or should_update:
@@ -1135,7 +1221,7 @@ def main(argv: Optional[List[str]] = None) -> int:
if not req: if not req:
print("No requirements.txt found; skipping install") print("No requirements.txt found; skipping install")
else: else:
ok = install_requirements(venv_py, req, reinstall=args.reinstall, upgrade=should_update) ok = install_requirements(venv_py, repo_root, req, reinstall=args.reinstall, upgrade=should_update)
if not ok: if not ok:
print("Dependency installation failed; aborting") print("Dependency installation failed; aborting")
return 4 return 4