ci: cache the pinned Turnip archive and retry its download (#2276)

* ci: cache the pinned Turnip archive and retry its download

"Prepare graphics driver variants" fetched the same checksum-pinned Banners-Turnip
zip from GitHub on every run with a single 120 s attempt, so a 502 or a read
timeout failed whole PR builds three times today. The archive is now cached
between runs (it is still checked against its SHA-256 after a restore), and a
cold download is retried on timeouts, 429 and 5xx with a growing pause.

* ci: keep PowerVR's verified downloads between runs

tools/powervr/fetch.py already keeps checksum-verified downloads in
~/.cache/droiddeck-build, but CI never saved that folder, so every run fetched
the PowerVR components again and a read timeout failed "Build the add-on
packs". The folder joins the existing pack-inputs cache, keyed on
release.json too.
This commit is contained in:
MaxsTechReview authored and GitHub committed 2026-11-08 09:07:31 -05:00
1 parent aa883830ca
commit c755d05473
3 files changed
+82 -4

No files matched your search

+10 -3
View File
@@ -313,6 +313,11 @@ jobs:
- name: Build the DiRT 3 / DiRT Showdown ws2_32 stand-in
run: python3 tools/components/prepare.py dirt-ws2
- name: Cache graphics driver inputs
uses: actions/cache@v4
with:
path: app/build/quest-inputs
key: quest-inputs-${{ hashFiles('tools/quest/build-driver.py') }}
- name: Prepare graphics driver variants
run: python3 tools/quest/build-driver.py "$ANDROID_HOME/ndk/$NDK_VERSION"
- name: Record and pack prepared components
@@ -558,9 +563,11 @@ jobs:
- name: Cache checksum-pinned pack inputs
uses: actions/cache@v4
with:
path: ~/.cache/droiddeck-pack-inputs
key: pack-inputs-v1-${{ hashFiles('tools/packs/build.sh', 'tools/packs/fetch-verified.sh', 'tools/packs/tracking/release.env') }}
restore-keys: pack-inputs-v1-
path: |
~/.cache/droiddeck-pack-inputs
~/.cache/droiddeck-build
key: pack-inputs-v2-${{ hashFiles('tools/packs/build.sh', 'tools/packs/fetch-verified.sh', 'tools/packs/tracking/release.env', 'tools/powervr/release.json') }}
restore-keys: pack-inputs-v2-
- name: Build the add-on packs
run: |
python3 tools/powervr/fetch.py
+16 -1
View File
@@ -7,6 +7,9 @@ import sys
import tarfile
import tempfile
import hashlib
import http.client
import time
import urllib.error
import urllib.request
import zipfile
@@ -32,11 +35,23 @@ def pack(entries, output):
print(output)
def download(url, attempts=4):
for attempt in range(1, attempts + 1):
try:
with urllib.request.urlopen(url, timeout=120) as response:
return response.read()
except (OSError, http.client.HTTPException) as error:
if attempt == attempts or isinstance(error, urllib.error.HTTPError) and error.code < 500 and error.code != 429:
raise
print(f'Download failed ({error}); retrying in {attempt * 15} s')
time.sleep(attempt * 15)
def build_a6xx(ndk, output_dir=OUTPUT.parent, archive_path=None):
cached = archive_path or ROOT / 'app/build/quest-inputs' / f'Turnip-{A6XX_TAG}.zip'
if not cached.exists():
cached.parent.mkdir(parents=True, exist_ok=True)
data = urllib.request.urlopen(A6XX_URL, timeout=120).read()
data = download(A6XX_URL)
if hashlib.sha256(data).hexdigest() != A6XX_SHA256:
raise ValueError('A6xx archive checksum mismatch')
cached.write_bytes(data)
+56
View File
@@ -0,0 +1,56 @@
import importlib.util
import io
import unittest
import urllib.error
from pathlib import Path
from unittest import mock
SCRIPT = Path(__file__).resolve().parents[1] / "quest/build-driver.py"
spec = importlib.util.spec_from_file_location("build_driver", SCRIPT)
driver = importlib.util.module_from_spec(spec)
spec.loader.exec_module(driver)
def http_error(code):
return urllib.error.HTTPError("https://example.invalid/a.zip", code, "error", {}, io.BytesIO())
class Response(io.BytesIO):
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
class DriverDownloadTest(unittest.TestCase):
def fetch(self, *outcomes):
calls = []
def urlopen(url, timeout):
calls.append(url)
outcome = outcomes[len(calls) - 1]
if isinstance(outcome, Exception):
raise outcome
return Response(outcome)
with mock.patch.object(driver.urllib.request, "urlopen", urlopen), mock.patch.object(driver.time, "sleep") as sleep:
try:
return driver.download("https://example.invalid/a.zip"), len(calls), sleep.call_count
except Exception as error:
return error, len(calls), sleep.call_count
def test_a_timeout_or_server_error_is_retried(self):
self.assertEqual((b"zip", 3, 2), self.fetch(TimeoutError("read timed out"), http_error(502), b"zip"))
def test_a_missing_file_fails_at_once(self):
error, calls, sleeps = self.fetch(http_error(404))
self.assertEqual((404, 1, 0), (error.code, calls, sleeps))
def test_it_gives_up_after_the_last_attempt(self):
error, calls, sleeps = self.fetch(*[http_error(504)] * 4)
self.assertEqual((504, 4, 3), (error.code, calls, sleeps))
if __name__ == "__main__":
unittest.main()