Adds an experimental power-cap mode using the undocumented RM ioctl interface (based on panchovix's LACT PR #1205) to set power limits below the VBIOS minimum (down to 30 W). - hal/rm_power.py: RM ioctl power-cap read/write/reset + runtime probe - limits.py: power_cap_mode (nvml/ioctl) with support detection - config.py: persist power_cap_mode per GPU - profiles: record/apply power_cap_mode - server.py: POST /api/limits validates ioctl support (409 on failure) - cli.py: profile save falls back to persisted mode - client.py: power_cap_mode in Limits - frontend: toggle + warning with panchovix attribution (LACT #1205) - tests: test_rm_power.py (unit) + integration coverage - Makefile: add test_rm_power.py to make test Also includes automated linter reformatting (prettier, ruff, shellcheck, isort, markdownlint) that the linter would apply anyway.
75 lines
2.5 KiB
Python
75 lines
2.5 KiB
Python
"""Custom hatchling build hook: build the React frontend if it is missing or stale.
|
|
|
|
This makes NVCurve installable with a single command, e.g.::
|
|
|
|
uv tool install "git+https://gitea.zephyre.one/Pakobbix/nvcurve.git"
|
|
|
|
The hook runs inside the isolated build environment right before the wheel
|
|
(or sdist) is assembled. If ``frontend/dist`` does not exist yet — or is older
|
|
than the frontend sources — it compiles the frontend using the host's ``npm``
|
|
(PATH is inherited from the environment).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
|
|
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
|
|
|
|
# Frontend inputs that must be newer than dist/index.html to trigger a rebuild.
|
|
_FRONTEND_INPUTS = (
|
|
"src",
|
|
"index.html",
|
|
"vite.config.ts",
|
|
"package.json",
|
|
"tsconfig.json",
|
|
)
|
|
|
|
|
|
class FrontendBuildHook(BuildHookInterface):
|
|
"""Build ``frontend/dist`` with npm when it is missing or stale."""
|
|
|
|
PLUGIN_NAME = "custom"
|
|
|
|
def initialize(self, version: str, build_data: dict) -> None:
|
|
frontend = os.path.join(self.root, "frontend")
|
|
dist_index = os.path.join(frontend, "dist", "index.html")
|
|
|
|
if not self._needs_build(frontend, dist_index):
|
|
return
|
|
|
|
npm = shutil.which("npm")
|
|
if npm is None:
|
|
raise RuntimeError(
|
|
"npm not found on PATH. Node.js 18+ and npm are required to build "
|
|
"the NVCurve frontend. Install them and retry, or use install.sh "
|
|
"which checks prerequisites for you."
|
|
)
|
|
|
|
print(
|
|
"[nvcurve] frontend/dist missing or stale — building frontend with npm ...",
|
|
file=sys.stderr,
|
|
)
|
|
subprocess.run([npm, "ci", "--no-audit", "--no-fund"], cwd=frontend, check=True)
|
|
subprocess.run([npm, "run", "build"], cwd=frontend, check=True)
|
|
|
|
@staticmethod
|
|
def _needs_build(frontend: str, dist_index: str) -> bool:
|
|
if not os.path.isfile(dist_index):
|
|
return True
|
|
dist_mtime = os.path.getmtime(dist_index)
|
|
for name in _FRONTEND_INPUTS:
|
|
path = os.path.join(frontend, name)
|
|
if os.path.isfile(path):
|
|
if os.path.getmtime(path) > dist_mtime:
|
|
return True
|
|
elif os.path.isdir(path):
|
|
for root, _dirs, files in os.walk(path):
|
|
for file in files:
|
|
if os.path.getmtime(os.path.join(root, file)) > dist_mtime:
|
|
return True
|
|
return False
|