Files
MaxsTechReview c755d05473 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.
2026-11-08 09:07:31 -05:00

146 lines
7.1 KiB
Python

#!/usr/bin/env python3
import io
import json
from pathlib import Path
import subprocess
import sys
import tarfile
import tempfile
import hashlib
import http.client
import time
import urllib.error
import urllib.request
import zipfile
ROOT = Path(__file__).resolve().parents[2]
OUTPUT = ROOT / 'app/build/prepared-assets/graphics_driver/adrenotools-turnip25.1.0-quest1.tzst'
A6XX_TAG = 'v26.3.0-20261012-r2'
A6XX_SHA256 = 'bb1b2c51e6446cf428d70255d037be169ac68108c00af33aa1ba7ece711f8336'
A6XX_URL = f'https://github.com/The412Banner/Banners-Turnip/releases/download/{A6XX_TAG}/Turnip-{A6XX_TAG}.zip'
def pack(entries, output):
stream = io.BytesIO()
with tarfile.open(fileobj=stream, mode='w', format=tarfile.USTAR_FORMAT) as archive:
for name, content in entries:
entry = tarfile.TarInfo(name)
entry.size = len(content)
entry.mode = 0o644
archive.addfile(entry, io.BytesIO(content))
compressed = subprocess.run(['zstd', '-q', '-19', '-c'], input=stream.getvalue(),
capture_output=True, check=True).stdout
output.parent.mkdir(parents=True, exist_ok=True)
output.write_bytes(compressed)
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 = download(A6XX_URL)
if hashlib.sha256(data).hexdigest() != A6XX_SHA256:
raise ValueError('A6xx archive checksum mismatch')
cached.write_bytes(data)
data = cached.read_bytes()
if hashlib.sha256(data).hexdigest() != A6XX_SHA256:
raise ValueError('A6xx archive checksum mismatch')
with zipfile.ZipFile(io.BytesIO(data)) as archive:
driver = archive.read('libvulkan_freedreno.so')
original_meta = json.loads(archive.read('meta.json'))
if original_meta.get('libraryName') != 'libvulkan_freedreno.so':
raise ValueError('Unexpected A6xx driver library')
hosts = list((Path(ndk) / 'toolchains/llvm/prebuilt').glob('*'))
if len(hosts) != 1:
raise ValueError('Expected one NDK host toolchain')
llvm = hosts[0] / 'bin'
provenance = (f'Banners-Turnip {A6XX_TAG}\n{A6XX_URL}\nSHA256 {A6XX_SHA256}\n'
'Mesa source: a0ff50acf0e1d152f54949490fa41558d2e0f7a4\n'
'Quest adapter: DroidDeck tools/quest (GPL-3.0-or-later)\n').encode()
for quest in (False, True):
meta = dict(original_meta)
meta['name'] = 'Turnip Adreno 6xx' + (' - Quest 2 GPUOBJ experimental' if quest else ' - bundled fallback')
meta['description'] += ' Bundled for Adreno 6xx; Quest 2 hardware validation pending.'
entries = [('libvulkan_freedreno.so', driver), ('SOURCE.txt', provenance)]
if quest:
with tempfile.TemporaryDirectory(prefix='quest-a6xx-') as folder:
work = Path(folder)
original = work / 'libvulkan_freedreno.so'
original.write_bytes(driver)
wrapper = work / 'libquest_turnip.so'
subprocess.run([
str(llvm / 'aarch64-linux-android28-clang'), '-shared', '-fPIC', '-O2',
'-Wall', '-Wextra', '-Werror', '-fvisibility=hidden',
'-Wl,-z,max-page-size=16384', '-Wl,-soname,libquest_turnip.so',
'-Wl,--no-as-needed', str(original), '-Wl,--as-needed',
str(ROOT / 'tools/quest/android_wrapper.c'), str(ROOT / 'tools/quest/kgsl_compat.c'),
'-ldl', '-llog', '-o', str(wrapper),
], check=True)
subprocess.run([str(llvm / 'llvm-strip'), '--strip-unneeded', str(wrapper)], check=True)
entries.append(('libquest_turnip.so', wrapper.read_bytes()))
meta['libraryName'] = 'libquest_turnip.so'
entries.append(('meta.json', (json.dumps(meta, indent=2) + '\n').encode()))
name = 'turnip26.3.0-quest2' if quest else 'turnip26.3.0-a6xx'
pack(entries, output_dir / f'adrenotools-{name}.tzst')
def build(ndk, output=OUTPUT):
hosts = list((Path(ndk) / 'toolchains/llvm/prebuilt').glob('*'))
if len(hosts) != 1:
raise ValueError('Expected one NDK host toolchain')
llvm = hosts[0] / 'bin'
original = ROOT / 'app/src/main/assets/graphics_driver/adrenotools-turnip25.1.0.tzst'
data = subprocess.run(['zstd', '-dc', str(original)], capture_output=True, check=True).stdout
with tempfile.TemporaryDirectory(prefix='quest-driver-') as folder:
work = Path(folder)
with tarfile.open(fileobj=io.BytesIO(data)) as archive:
driver = archive.extractfile('vulkan.ad07xx.so').read()
(work / 'vulkan.ad07xx.so').write_bytes(driver)
wrapper = work / 'libquest_turnip.so'
subprocess.run([
str(llvm / 'aarch64-linux-android26-clang'), '-shared', '-fPIC', '-O2',
'-Wall', '-Wextra', '-Werror', '-fvisibility=hidden',
'-Wl,-z,max-page-size=16384', '-Wl,-soname,libquest_turnip.so',
'-Wl,--no-as-needed', str(work / 'vulkan.ad07xx.so'), '-Wl,--as-needed',
str(ROOT / 'tools/quest/android_wrapper.c'), str(ROOT / 'tools/quest/kgsl_compat.c'),
'-ldl', '-llog', '-o', str(wrapper),
], check=True)
subprocess.run([str(llvm / 'llvm-strip'), '--strip-unneeded', str(wrapper)], check=True)
metadata = json.dumps({
'libraryName': 'libquest_turnip.so',
'name': 'Turnip Adreno 7xx - Quest GPUOBJ experimental',
'description': 'Bundled A7xx driver with the experimental Quest 3/3S allocation adapter.',
'driverVersion': 'Bundled Mesa + Quest GPUOBJ v1',
}, indent=2).encode() + b'\n'
stream = io.BytesIO()
with tarfile.open(fileobj=stream, mode='w', format=tarfile.USTAR_FORMAT) as archive:
for name, content in [('meta.json', metadata), ('libquest_turnip.so', wrapper.read_bytes()),
('vulkan.ad07xx.so', driver)]:
entry = tarfile.TarInfo(name)
entry.size = len(content)
entry.mode = 0o644
archive.addfile(entry, io.BytesIO(content))
compressed = subprocess.run(['zstd', '-q', '-19', '-c'], input=stream.getvalue(),
capture_output=True, check=True).stdout
output.parent.mkdir(parents=True, exist_ok=True)
output.write_bytes(compressed)
print(output)
if __name__ == '__main__':
build(sys.argv[1])
build_a6xx(sys.argv[1])