mirror of
https://github.com/mirkobrombin/raven.git
synced 2026-10-26 21:17:40 +00:00
97 lines
4.2 KiB
Python
97 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
import hashlib
|
|
import io
|
|
import json
|
|
from pathlib import Path
|
|
import subprocess
|
|
import tarfile
|
|
import urllib.request
|
|
|
|
|
|
def digest(path):
|
|
with path.open("rb") as source:
|
|
return hashlib.file_digest(source, "sha256").hexdigest()
|
|
|
|
|
|
def provision(destination, manifest):
|
|
destination.mkdir(parents=True, exist_ok=True)
|
|
installed = []
|
|
for component, package in manifest.items():
|
|
archive = destination / package["archive"]
|
|
if not archive.exists():
|
|
partial = archive.with_suffix(archive.suffix + ".partial")
|
|
created = False
|
|
try:
|
|
with partial.open("xb") as output:
|
|
created = True
|
|
received = 0
|
|
with urllib.request.urlopen(package["url"], timeout=45) as response:
|
|
while chunk := response.read(1024 * 1024):
|
|
received += len(chunk)
|
|
if received > package["size"]:
|
|
raise RuntimeError(f"archive exceeds pinned size: {component}")
|
|
output.write(chunk)
|
|
if digest(partial) != package["sha256"]:
|
|
raise RuntimeError(f"archive checksum mismatch: {component}")
|
|
partial.rename(archive)
|
|
finally:
|
|
if created:
|
|
partial.unlink(missing_ok=True)
|
|
if digest(archive) != package["sha256"]:
|
|
raise RuntimeError(f"cached archive checksum mismatch: {component}")
|
|
|
|
expected = {
|
|
f'{package["archive_root"]}/{folder}/{dll}': (architecture, dll)
|
|
for architecture, folder in package["architectures"].items()
|
|
for dll in package["dlls"]
|
|
}
|
|
if archive.suffix == ".zst":
|
|
decoded = subprocess.run(["zstd", "-dc", str(archive)], check=True, capture_output=True)
|
|
source = tarfile.open(fileobj=io.BytesIO(decoded.stdout), mode="r:")
|
|
else:
|
|
source = tarfile.open(archive, mode="r:gz")
|
|
with source:
|
|
found = set()
|
|
for member in source:
|
|
if member.name not in expected:
|
|
continue
|
|
if not member.isfile() or member.name in found:
|
|
raise RuntimeError(f"invalid DLL archive entry: {member.name}")
|
|
architecture, dll = expected[member.name]
|
|
with source.extractfile(member) as data:
|
|
content = data.read()
|
|
actual = hashlib.sha256(content).hexdigest()
|
|
target = destination / component / architecture / dll
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
if target.exists():
|
|
if digest(target) != actual:
|
|
raise RuntimeError(f"cached DLL differs: {target}")
|
|
else:
|
|
with target.open("xb") as output:
|
|
output.write(content)
|
|
installed.append({"component": component, "version": package["version"],
|
|
"architecture": architecture, "path": str(target.relative_to(destination)),
|
|
"sha256": actual})
|
|
found.add(member.name)
|
|
if found != expected.keys():
|
|
raise RuntimeError(f"missing DLLs in {component}: {sorted(expected.keys() - found)}")
|
|
print(f'{component} {package["version"]}: {len(found)} DLLs verified')
|
|
(destination / "installed.json").write_text(json.dumps(installed, indent=2) + "\n")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Provision pinned Raven graphics DLLs without installing into a prefix")
|
|
parser.add_argument("--destination", type=Path, required=True)
|
|
arguments = parser.parse_args()
|
|
manifest_path = Path(__file__).resolve().parent.parent / "native/graphics/backends.json"
|
|
try:
|
|
provision(arguments.destination.resolve(), json.loads(manifest_path.read_text()))
|
|
except (OSError, RuntimeError, subprocess.CalledProcessError, tarfile.TarError) as error:
|
|
parser.exit(1, f"graphics provisioning failed: {error}\n")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|