From 7cb2acaff12f935253e8d11bcca4846f6f0f6198 Mon Sep 17 00:00:00 2001 From: Benjamin Leggett Date: Mon, 5 Oct 2026 12:09:02 -0400 Subject: [PATCH] fix(ci): Nvidia updated their driver page, so our scraper broke. Fix it --- hack/build/refresh-nvidia-versions.py | 72 ++++++++++++++------------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/hack/build/refresh-nvidia-versions.py b/hack/build/refresh-nvidia-versions.py index 9aa7ee31..ce5ea5bf 100755 --- a/hack/build/refresh-nvidia-versions.py +++ b/hack/build/refresh-nvidia-versions.py @@ -7,6 +7,7 @@ Currently only supports amd64 drivers. A human must review the PR opened by the GH Action that runs this. """ +import json import re import sys import urllib.request @@ -15,50 +16,51 @@ NVIDIA_URL = "https://www.nvidia.com/en-us/drivers/unix/" CONFIG_PATH = Path("config.yaml") -# The three NVIDIA-page labels we care about, mapped to the literal text used -# in the trailing comment of each local_tags line in config.yaml. The script -# matches lines by the comment label, so the order in config.yaml is free. -LABELS = [ - "Latest Production Branch Version", - "Latest New Feature Branch Version", - "Latest Beta Version", -] - -# Only match Linux x86_64 paragraph for now. -LINUX_X86_64_BLOCK = re.compile( - r"Linux x86_64/AMD64/EM64T(?P.*?)

", - re.DOTALL, +# NVIDIA_URL fills its version table client-side from this lookup service, so +# query it directly. Keep the per-label params in sync with the drvrLkupInputs +# entries for osCode "linux64" in that page's inline JS. +LOOKUP_URL = ( + "https://gfwsl.geforce.com/services_toolkit/services/com/nvidia/services/" + "AjaxDriverService.php?func=DriverManualLookup" + "&psid=133&pfid=1075&osID=12&languageCode=1033&isWHQL=0&dltype=-1&dch=0" + "&upCRD=null&ctk=null&numberOfResults=1&" ) +# The three NVIDIA-page labels we care about, mapped to the lookup params for +# each. The labels are the literal text used in the trailing comment of each +# local_tags line in config.yaml. The script matches lines by the comment +# label, so the order in config.yaml is free. +LABELS = { + "Latest Production Branch Version": "beta=0&qnf=0&sort1=", + "Latest New Feature Branch Version": "beta=null&qnf=1&sort1=1", + "Latest Beta Version": "beta=1&qnf=0&sort1=1", +} -def fetch_latest_versions(url: str = NVIDIA_URL) -> dict[str, str]: - req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}) - with urllib.request.urlopen(req) as resp: - html = resp.read().decode("utf-8", errors="replace") +VERSION_RE = re.compile(r"[0-9][0-9.]*[0-9]") - block_match = LINUX_X86_64_BLOCK.search(html) - if not block_match: - raise RuntimeError( - "Could not locate the Linux x86_64 block on %s — page layout may have changed." - % url - ) - body = block_match.group("body") +def fetch_latest_versions() -> dict[str, str]: versions = {} - for label in LABELS: - # The page uses `LABEL: VERSION` - # (yes, "calss" — NVIDIA's typo). Match liberally on the label and the - # next ... so the parser survives small markup tweaks. - pat = re.compile( - re.escape(label) + r":\s*\s*]*>([0-9][0-9.]*[0-9])", - re.IGNORECASE, + for label, params in LABELS.items(): + req = urllib.request.Request( + LOOKUP_URL + params, headers={"User-Agent": "Mozilla/5.0"} ) - m = pat.search(body) - if not m: + with urllib.request.urlopen(req) as resp: + data = json.load(resp) + try: + assert data["Success"] == "1" + info = data["IDS"][0]["downloadInfo"] + assert info["OsCode"] == "linux64" + # Version is a mangled form (595.1040 for 595.104.02); the page + # itself prefers DisplayVersion. + version = info["DisplayVersion"] + except (AssertionError, KeyError, IndexError, TypeError): raise RuntimeError( - "Could not find version for %r in Linux x86_64 block." % label + "Unexpected NVIDIA lookup response for %r: %s" % (label, data) ) - versions[label] = m.group(1) + if not VERSION_RE.fullmatch(version): + raise RuntimeError("Bad version %r for %r" % (version, label)) + versions[label] = version return versions