mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 02:51:10 +00:00
* 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.
146 lines
7.1 KiB
Python
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])
|