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
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
if client_found:
# Prefer run_client helper located in the cloned repo; if missing, fall back to top-level scripts folder helper.
helper_candidates = [dest / "run_client.py", script_dir / "run_client.py"]
# Prefer the helper installed directly into the Hydrus repository.
helper_candidates = [installed_helper, dest / "run_client.py", script_dir / "run_client.py"]
for cand in helper_candidates:
if cand.exists():
if cand and cand.exists():
run_client_script = cand
break
if getattr(args,
@@ -1830,19 +1830,21 @@ def main(argv: Optional[list[str]] = None) -> int:
# Helpful hint: show the new run_client helper and direct run example
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 run_client_script and run_client_script.exists() and run_client_script.resolve().parent == dest.resolve():
logging.info(
"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",
"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",
venv_py,
dest / "hydrus_client.py",
run_client_script,
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",
helper_to_show,
helper_to_show,
helper_to_show,
)
else:
logging.info(
@@ -1875,14 +1877,11 @@ def ensure_run_client_helper(dest: Path, script_dir: Path) -> Optional[Path]:
return None
helper_dest = dest / "run_client.py"
if helper_dest.exists():
return helper_dest
try:
shutil.copy2(helper_src, helper_dest)
if os.name != "nt":
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
except Exception as exc:
logging.debug("Failed to copy run_client helper: %s", exc)

View File

@@ -22,7 +22,7 @@ from __future__ import annotations
import argparse
import os
import pwd
import re
import shlex
import shutil
import subprocess
@@ -30,6 +30,11 @@ import sys
from pathlib import Path
from typing import List, Optional
try:
import pwd
except ImportError:
pwd = None
def get_python_in_venv(venv_dir: Path) -> Optional[Path]:
try:
@@ -72,6 +77,7 @@ def find_requirements(root: Path) -> Optional[Path]:
def install_requirements(
venv_py: Path,
repo_root: Path,
req_path: Path,
reinstall: bool = False,
upgrade: bool = False
@@ -93,20 +99,13 @@ def install_requirements(
check=True,
**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:
install_cmd = [str(venv_py), "-m", "pip", "install", "--upgrade", "-r", str(req_path)]
install_cmd.append("--upgrade")
if reinstall:
install_cmd = [
str(venv_py),
"-m",
"pip",
"install",
"--upgrade",
"--force-reinstall",
"-r",
str(req_path),
]
install_cmd.extend(["--upgrade", "--force-reinstall"])
install_cmd.extend(build_hydrus_install_targets(req_path, repo_root, python_version))
subprocess.run(install_cmd, check=True, **kwargs)
return True
except subprocess.CalledProcessError as e:
@@ -140,6 +139,69 @@ def parse_requirements_file(req_path: Path) -> List[str]:
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:
# Skip mpv check as it is problematic to install and causes slow startups
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:
if pwd is None:
return False
try:
pwd.getpwnam(username)
return True
@@ -914,7 +978,12 @@ def main(argv: Optional[List[str]] = None) -> int:
p.add_argument(
"--pull",
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(
"--update-deps",
@@ -984,19 +1053,36 @@ def main(argv: Optional[List[str]] = None) -> int:
else:
repo_root = workspace_root
# Handle git pull update if requested
# Skip execution during service install/uninstall; it will run when the service starts
if args.pull and not (args.install_service or args.uninstall_service):
repo_updated = False
should_pull_repo = not args.no_update and not (args.install_service or args.uninstall_service)
if should_pull_repo:
if shutil.which("git"):
if (repo_root / ".git").exists():
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:
# Use creationflags to hide the window on Windows
k = {}
if os.name == "nt":
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:
print(f"Warning: git pull failed: {e}")
else:
@@ -1127,7 +1213,7 @@ def main(argv: Optional[List[str]] = None) -> int:
except Exception:
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
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:
print("No requirements.txt found; skipping install")
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:
print("Dependency installation failed; aborting")
return 4