mirror of
https://github.com/NCBM/plyngent.git
synced 2026-07-23 05:55:16 +08:00
61582ffd7d
Short WIN_PYTHON request under wine uv.exe; check/test/ci cover ruff, basedpyright, pytest.
223 lines
7.5 KiB
Python
Executable File
223 lines
7.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""CLI for an isolated Wine + Windows uv/Python + PDM environment.
|
|
|
|
Never installs into or rewrites the host project virtualenv.
|
|
|
|
Usage:
|
|
python scripts/wine_pdm.py setup
|
|
python scripts/wine_pdm.py pdm <args...>
|
|
python scripts/wine_pdm.py run <args...>
|
|
python scripts/wine_pdm.py check [paths...] # ruff + basedpyright (default: src)
|
|
python scripts/wine_pdm.py test [pytest-args...] # full suite (default: tests -q)
|
|
python scripts/wine_pdm.py ci # check + test
|
|
python scripts/wine_pdm.py pytest [args...] # alias of test
|
|
python scripts/wine_pdm.py basedpyright [paths...]
|
|
python scripts/wine_pdm.py uvx <args...>
|
|
python scripts/wine_pdm.py python <args...>
|
|
python scripts/wine_pdm.py shell
|
|
|
|
Env:
|
|
PLYNGENT_SRC, PLYNGENT_WINE_BASE, PLYNGENT_WINEPREFIX, PLYNGENT_WINEDEBUG
|
|
WIN_PYTHON (short uv request, default 3.14), WIN_UV_VERSION
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Callable
|
|
|
|
# Static: sibling wine_env as a module (scripts/ on analysis path).
|
|
# Runtime uses importlib (else branch) because scripts/ is not a package.
|
|
from wine_env import (
|
|
WineLayout,
|
|
ensure_pdm_bootstrap,
|
|
ensure_prefix,
|
|
ensure_windows_python,
|
|
ensure_windows_uv,
|
|
find_project_venv_scripts,
|
|
refresh_project_view,
|
|
require_cmds,
|
|
run,
|
|
wine_basedpyright,
|
|
wine_pdm,
|
|
)
|
|
else:
|
|
# Runtime: load sibling by file path (scripts/ is not an installable package).
|
|
import importlib.util
|
|
|
|
def _load_wine_env():
|
|
path = Path(__file__).resolve().parent / "wine_env.py"
|
|
spec = importlib.util.spec_from_file_location("plyngent_scripts_wine_env", path)
|
|
if spec is None or spec.loader is None:
|
|
msg = f"cannot load {path}"
|
|
raise RuntimeError(msg)
|
|
module = importlib.util.module_from_spec(spec)
|
|
# dataclasses needs the module registered before exec_module.
|
|
sys.modules[spec.name] = module
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
_we = _load_wine_env()
|
|
WineLayout = _we.WineLayout
|
|
ensure_pdm_bootstrap = _we.ensure_pdm_bootstrap
|
|
ensure_prefix = _we.ensure_prefix
|
|
ensure_windows_python = _we.ensure_windows_python
|
|
ensure_windows_uv = _we.ensure_windows_uv
|
|
find_project_venv_scripts = _we.find_project_venv_scripts
|
|
refresh_project_view = _we.refresh_project_view
|
|
require_cmds = _we.require_cmds
|
|
run = _we.run
|
|
wine_basedpyright = _we.wine_basedpyright
|
|
wine_pdm = _we.wine_pdm
|
|
|
|
|
|
def cmd_setup(layout: WineLayout, env: dict[str, str], _args: list[str]) -> int:
|
|
require_cmds("wine", "wineboot")
|
|
ensure_prefix(layout, env)
|
|
# Windows uv first, then short-version CPython install inside Wine.
|
|
ensure_windows_uv(layout)
|
|
ensure_windows_python(layout, env)
|
|
ensure_pdm_bootstrap(layout, env)
|
|
refresh_project_view(layout)
|
|
print("Selecting Windows interpreter for PDM project-view ...", file=sys.stderr)
|
|
_ = wine_pdm(layout, env, ["use", "-f", layout.pdm_python_z])
|
|
print("pdm sync --dev (installs pywinpty on win32) ...", file=sys.stderr)
|
|
code = wine_pdm(layout, env, ["sync", "--dev"])
|
|
if code != 0:
|
|
return code
|
|
print()
|
|
print("Ready (host project venv untouched).")
|
|
print(f" PLYNGENT_SRC={layout.src}")
|
|
print(f" PLYNGENT_WINE_BASE={layout.base}")
|
|
print(f" WINEPREFIX={layout.prefix}")
|
|
print()
|
|
print("Examples:")
|
|
print(" python scripts/wine_pdm.py ci")
|
|
print(" python scripts/wine_pdm.py check")
|
|
print(" python scripts/wine_pdm.py test")
|
|
print(' python scripts/wine_pdm.py run python -c "import sys, winpty; print(sys.platform, winpty)"')
|
|
return 0
|
|
|
|
|
|
def cmd_pdm(layout: WineLayout, env: dict[str, str], args: list[str]) -> int:
|
|
return wine_pdm(layout, env, args)
|
|
|
|
|
|
def cmd_run(layout: WineLayout, env: dict[str, str], args: list[str]) -> int:
|
|
return wine_pdm(layout, env, ["run", *args])
|
|
|
|
|
|
def cmd_ruff(layout: WineLayout, env: dict[str, str], args: list[str]) -> int:
|
|
if not args:
|
|
args = ["src", "tests", "scripts"]
|
|
return wine_pdm(layout, env, ["run", "ruff", "check", *args])
|
|
|
|
|
|
def cmd_basedpyright(layout: WineLayout, env: dict[str, str], args: list[str]) -> int:
|
|
if not args:
|
|
args = ["src"]
|
|
return wine_basedpyright(layout, env, args)
|
|
|
|
|
|
def cmd_check(layout: WineLayout, env: dict[str, str], args: list[str]) -> int:
|
|
"""Ruff + basedpyright (full static check)."""
|
|
# Optional paths apply to both tools when provided.
|
|
ruff_args = list(args) if args else ["src", "tests", "scripts"]
|
|
pyright_args = list(args) if args else ["src"]
|
|
code = cmd_ruff(layout, env, ruff_args)
|
|
if code != 0:
|
|
return code
|
|
return cmd_basedpyright(layout, env, pyright_args)
|
|
|
|
|
|
def cmd_pytest(layout: WineLayout, env: dict[str, str], args: list[str]) -> int:
|
|
if not args:
|
|
args = ["tests", "-q"]
|
|
return wine_pdm(layout, env, ["run", "pytest", *args])
|
|
|
|
|
|
def cmd_ci(layout: WineLayout, env: dict[str, str], _args: list[str]) -> int:
|
|
"""Full Windows check: ruff + basedpyright + pytest."""
|
|
code = cmd_check(layout, env, [])
|
|
if code != 0:
|
|
return code
|
|
return cmd_pytest(layout, env, [])
|
|
|
|
|
|
def cmd_uvx(layout: WineLayout, env: dict[str, str], args: list[str]) -> int:
|
|
if not layout.uvx_exe.is_file():
|
|
print("error: Windows uvx missing; run setup first", file=sys.stderr)
|
|
return 1
|
|
return run(["wine", str(layout.uvx_exe), *args], env=env)
|
|
|
|
|
|
def cmd_python(layout: WineLayout, env: dict[str, str], args: list[str]) -> int:
|
|
scripts = find_project_venv_scripts(layout)
|
|
if scripts is None:
|
|
print("error: project venv missing; run setup first", file=sys.stderr)
|
|
return 1
|
|
return run(["wine", str(scripts / "python.exe"), *args], env=env)
|
|
|
|
|
|
def cmd_shell(layout: WineLayout, _env: dict[str, str], _args: list[str]) -> int:
|
|
print(f"PLYNGENT_SRC={layout.src}")
|
|
print(f"PLYNGENT_WINE_BASE={layout.base}")
|
|
print(f"WINEPREFIX={layout.prefix}")
|
|
print(f"WINE_VIEW={layout.view}")
|
|
print(f"WIN_PYTHON={layout.win_python_host}")
|
|
print(f"WIN_UV={layout.uv_exe}")
|
|
print(f"WIN_PDM={layout.pdm_exe}")
|
|
print()
|
|
print("# Typical flow:")
|
|
print("# python scripts/wine_pdm.py setup")
|
|
print("# python scripts/wine_pdm.py ci")
|
|
print('# python scripts/wine_pdm.py run python -c "import winpty; print(winpty)"')
|
|
return 0
|
|
|
|
|
|
_COMMANDS: dict[str, Callable[[WineLayout, dict[str, str], list[str]], int]] = {
|
|
"setup": cmd_setup,
|
|
"pdm": cmd_pdm,
|
|
"run": cmd_run,
|
|
"check": cmd_check,
|
|
"ruff": cmd_ruff,
|
|
"test": cmd_pytest,
|
|
"pytest": cmd_pytest,
|
|
"ci": cmd_ci,
|
|
"basedpyright": cmd_basedpyright,
|
|
"pyright": cmd_basedpyright,
|
|
"uvx": cmd_uvx,
|
|
"python": cmd_python,
|
|
"shell": cmd_shell,
|
|
"env": cmd_shell,
|
|
}
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
# Manual dispatch so flags like ``uvx --from`` are not eaten by argparse.
|
|
raw = list(sys.argv[1:] if argv is None else argv)
|
|
if not raw or raw[0] in {"-h", "--help"}:
|
|
print(__doc__ or "")
|
|
return 0
|
|
cmd = raw[0]
|
|
rest = raw[1:]
|
|
handler = _COMMANDS.get(cmd)
|
|
if handler is None:
|
|
print(f"error: unknown command {cmd!r}", file=sys.stderr)
|
|
return 2
|
|
layout = WineLayout.from_env()
|
|
env = layout.export_env()
|
|
try:
|
|
return handler(layout, env, rest)
|
|
except RuntimeError as exc:
|
|
print(f"error: {exc}", file=sys.stderr)
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|