mirror of
https://github.com/NCBM/plyngent.git
synced 2026-07-23 05:55:16 +08:00
scripts: install Windows Python via Wine uv and full ci check
Short WIN_PYTHON request under wine uv.exe; check/test/ci cover ruff, basedpyright, pytest.
This commit is contained in:
+52
-24
@@ -17,11 +17,12 @@ from pathlib import Path
|
||||
from urllib.request import urlretrieve
|
||||
|
||||
# Defaults are version tags only — not machine paths.
|
||||
DEFAULT_WIN_CPYTHON = "3.14"
|
||||
# Short uv request (e.g. ``3.14``); Windows uv.exe installs the matching windows build.
|
||||
DEFAULT_WIN_PYTHON = "3.14"
|
||||
DEFAULT_WIN_UV_VERSION = "0.11.28"
|
||||
# typings/ is required for Wine basedpyright: pywinpty's shipped types are incomplete
|
||||
# (e.g. kill(sig) required) and stubPath=typings is set in pyproject.toml.
|
||||
VIEW_LINKS = ("src", "tests", "typings", "README.md", "LICENSE", "CLAUDE.md", "doc")
|
||||
# (e.g. kill(sig) required); pyright default stubPath is ``typings/``.
|
||||
VIEW_LINKS = ("src", "tests", "scripts", "typings", "README.md", "LICENSE", "CLAUDE.md", "doc")
|
||||
VIEW_COPIES = ("pyproject.toml", "pdm.lock")
|
||||
|
||||
|
||||
@@ -79,7 +80,7 @@ class WineLayout:
|
||||
uv_cache: Path
|
||||
uv_tools: Path
|
||||
pdm_bootstrap: Path
|
||||
win_cpython_tag: str
|
||||
win_python: str
|
||||
win_uv_version: str
|
||||
|
||||
@classmethod
|
||||
@@ -98,13 +99,33 @@ class WineLayout:
|
||||
uv_cache=base / "uv-cache",
|
||||
uv_tools=base / "uv-tools",
|
||||
pdm_bootstrap=base / "venv-pdm",
|
||||
win_cpython_tag=os.environ.get("WIN_CPYTHON_TAG", DEFAULT_WIN_CPYTHON),
|
||||
win_python=os.environ.get("WIN_PYTHON", DEFAULT_WIN_PYTHON),
|
||||
win_uv_version=os.environ.get("WIN_UV_VERSION", DEFAULT_WIN_UV_VERSION),
|
||||
)
|
||||
|
||||
def find_win_python_host(self) -> Path | None:
|
||||
"""Locate a Windows ``python.exe`` under the uv install dir."""
|
||||
if not self.uv_python.is_dir():
|
||||
return None
|
||||
# Prefer newest full windows install (cpython-*-windows-*/python.exe).
|
||||
candidates = sorted(
|
||||
self.uv_python.glob("cpython-*-windows-*/python.exe"),
|
||||
key=lambda p: p.stat().st_mtime,
|
||||
)
|
||||
if candidates:
|
||||
return candidates[-1]
|
||||
# Fallback: any python.exe one level down.
|
||||
for path in sorted(self.uv_python.glob("*/python.exe")):
|
||||
return path
|
||||
return None
|
||||
|
||||
@property
|
||||
def win_python_host(self) -> Path:
|
||||
return self.uv_python / self.win_cpython_tag / "python.exe"
|
||||
found = self.find_win_python_host()
|
||||
if found is not None:
|
||||
return found
|
||||
# Placeholder path used in error messages before install.
|
||||
return self.uv_python / f"cpython-{self.win_python}-windows-x86_64-none" / "python.exe"
|
||||
|
||||
@property
|
||||
def win_python_z(self) -> str:
|
||||
@@ -161,23 +182,6 @@ def ensure_prefix(layout: WineLayout, env: dict[str, str]) -> None:
|
||||
_ = run(["wineboot", "-i"], env=env)
|
||||
|
||||
|
||||
def ensure_windows_python(layout: WineLayout, env: dict[str, str]) -> None:
|
||||
if layout.win_python_host.is_file():
|
||||
return
|
||||
print(f"Installing Windows CPython ({layout.win_cpython_tag}) ...", file=sys.stderr)
|
||||
host_env = env.copy()
|
||||
# Host uv installs into host paths, not Wine Z: paths.
|
||||
host_env["UV_PYTHON_INSTALL_DIR"] = str(layout.uv_python)
|
||||
host_env["UV_CACHE_DIR"] = str(layout.uv_cache)
|
||||
code = run(["uv", "python", "install", layout.win_cpython_tag], env=host_env)
|
||||
if code != 0:
|
||||
msg = f"uv python install failed with exit {code}"
|
||||
raise RuntimeError(msg)
|
||||
if not layout.win_python_host.is_file():
|
||||
msg = f"Windows python.exe missing after install: {layout.win_python_host}"
|
||||
raise RuntimeError(msg)
|
||||
|
||||
|
||||
def ensure_windows_uv(layout: WineLayout) -> None:
|
||||
if layout.uv_exe.is_file():
|
||||
return
|
||||
@@ -196,6 +200,30 @@ def ensure_windows_uv(layout: WineLayout) -> None:
|
||||
raise RuntimeError(msg)
|
||||
|
||||
|
||||
def ensure_windows_python(layout: WineLayout, env: dict[str, str]) -> None:
|
||||
"""Install Windows CPython via Wine ``uv.exe`` using a short version request."""
|
||||
if layout.find_win_python_host() is not None:
|
||||
return
|
||||
if not layout.uv_exe.is_file():
|
||||
msg = "Windows uv.exe missing; call ensure_windows_uv first"
|
||||
raise RuntimeError(msg)
|
||||
print(
|
||||
f"Installing Windows CPython via Wine uv ({layout.win_python}) ...",
|
||||
file=sys.stderr,
|
||||
)
|
||||
code = run(
|
||||
["wine", str(layout.uv_exe), "python", "install", layout.win_python],
|
||||
env=env,
|
||||
)
|
||||
if code != 0:
|
||||
msg = f"wine uv python install failed with exit {code}"
|
||||
raise RuntimeError(msg)
|
||||
found = layout.find_win_python_host()
|
||||
if found is None or not found.is_file():
|
||||
msg = f"Windows python.exe missing after install under {layout.uv_python}"
|
||||
raise RuntimeError(msg)
|
||||
|
||||
|
||||
def ensure_pdm_bootstrap(layout: WineLayout, env: dict[str, str]) -> None:
|
||||
if layout.pdm_exe.is_file():
|
||||
return
|
||||
@@ -283,7 +311,7 @@ def wine_basedpyright(layout: WineLayout, env: dict[str, str], paths: list[str])
|
||||
out = layout.base / "basedpyright-out.txt"
|
||||
out_z = to_wine_z(out)
|
||||
# Node under Wine often hits EBADF on stderr; redirect via cmd.
|
||||
args = " ".join(paths) if paths else "src/plyngent/tools/process"
|
||||
args = " ".join(paths) if paths else "src"
|
||||
cmdline = f"{scripts_win}\\basedpyright.exe {args} > {out_z} 2>&1"
|
||||
code = run(["wine", "cmd", "/c", cmdline], env=env, cwd=layout.view)
|
||||
if out.is_file():
|
||||
|
||||
+46
-12
@@ -7,7 +7,10 @@ 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 pytest [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...>
|
||||
@@ -15,7 +18,7 @@ Usage:
|
||||
|
||||
Env:
|
||||
PLYNGENT_SRC, PLYNGENT_WINE_BASE, PLYNGENT_WINEPREFIX, PLYNGENT_WINEDEBUG
|
||||
WIN_CPYTHON_TAG, WIN_UV_VERSION
|
||||
WIN_PYTHON (short uv request, default 3.14), WIN_UV_VERSION
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -73,10 +76,11 @@ else:
|
||||
|
||||
|
||||
def cmd_setup(layout: WineLayout, env: dict[str, str], _args: list[str]) -> int:
|
||||
require_cmds("wine", "uv", "wineboot")
|
||||
require_cmds("wine", "wineboot")
|
||||
ensure_prefix(layout, env)
|
||||
ensure_windows_python(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)
|
||||
@@ -92,10 +96,10 @@ def cmd_setup(layout: WineLayout, env: dict[str, str], _args: list[str]) -> int:
|
||||
print(f" WINEPREFIX={layout.prefix}")
|
||||
print()
|
||||
print("Examples:")
|
||||
print(" python scripts/wine_pdm.py pytest")
|
||||
print(" python scripts/wine_pdm.py basedpyright src/plyngent/tools/process")
|
||||
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)"')
|
||||
print(" python scripts/wine_pdm.py uvx --from pdm pdm --version")
|
||||
return 0
|
||||
|
||||
|
||||
@@ -107,16 +111,43 @@ def cmd_run(layout: WineLayout, env: dict[str, str], args: list[str]) -> int:
|
||||
return wine_pdm(layout, env, ["run", *args])
|
||||
|
||||
|
||||
def cmd_pytest(layout: WineLayout, env: dict[str, str], args: list[str]) -> int:
|
||||
def cmd_ruff(layout: WineLayout, env: dict[str, str], args: list[str]) -> int:
|
||||
if not args:
|
||||
args = ["tests/test_tools/test_process.py", "-q"]
|
||||
return wine_pdm(layout, env, ["run", "pytest", *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)
|
||||
@@ -143,9 +174,8 @@ def cmd_shell(layout: WineLayout, _env: dict[str, str], _args: list[str]) -> int
|
||||
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)"')
|
||||
print("# python scripts/wine_pdm.py basedpyright src/plyngent/tools/process")
|
||||
print("# python scripts/wine_pdm.py pytest")
|
||||
return 0
|
||||
|
||||
|
||||
@@ -153,7 +183,11 @@ _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,
|
||||
|
||||
Reference in New Issue
Block a user