"""Build DroidDeck's ARM64 VR components in Docker and attest their inputs.""" import argparse import hashlib import json import os from pathlib import Path import shutil import struct import subprocess import tempfile ROOT = Path(__file__).resolve().parents[2] IMAGE = 'droiddeck-local-cross:24.04-v3' MANIFEST = 'vr-build.json' SOURCE_TREES = ('tools/vr/src', 'tools/vr/tests', 'tools/vr/third_party', 'tools/vr/driver_droiddeck', 'tools/vr/layer') SOURCE_FILES = ('tools/vr/build.sh', 'tools/vr/build_windows.py', 'tools/vr/make_hand_models.cpp', 'tools/local-cross.Dockerfile', 'app/src/main/cpp/waylandcomp/src/headpose_shm.h', 'app/src/main/cpp/waylandcomp/src/spatialpose_shm.h', 'app/src/main/cpp/waylandcomp/generated/xdg-shell-protocol.c', 'app/src/main/cpp/waylandcomp/generated/xdg-shell-client-protocol.h') LIBRARIES = {'droiddeck/bin/linuxarm64/driver_droiddeck.so', 'layer/libVkLayer_droiddeck_vr.so', 'layer/libdroiddeck_vr_scheduling.so'} CONFIGS = { 'droiddeck/driver.vrdrivermanifest': 'tools/vr/driver_droiddeck/driver.vrdrivermanifest', 'droiddeck/resources/settings/default.vrsettings': 'tools/vr/driver_droiddeck/resources/settings/default.vrsettings', 'layer/VK_LAYER_DROIDDECK_vr.json': 'tools/vr/layer/VK_LAYER_DROIDDECK_vr.json', 'layer/VK_LAYER_DROIDDECK_vr_implicit.json': 'tools/vr/layer/VK_LAYER_DROIDDECK_vr_implicit.json', } EXECUTABLES = {'droiddeck/bin/linuxarm64/xr-display-mode'} HAND_MODELS = { f'droiddeck/resources/rendermodels/droiddeck_hand_{side}/{name}' for side in ('left', 'right') for name in (f'droiddeck_hand_{side}.json', 'hand.mtl', 'hand.png', 'hand_open.obj', 'hand_fist.obj', 'thumb_index_open.obj', 'thumb_index_pinch.obj') } OUTPUTS = LIBRARIES | CONFIGS.keys() | EXECUTABLES | HAND_MODELS def is_source(path): return path in SOURCE_FILES or any(path.startswith(tree + '/') for tree in SOURCE_TREES) def sha(data): return hashlib.sha256(data).hexdigest() def commit(root): return subprocess.check_output(['git', '-C', str(root), 'rev-parse', 'HEAD']).decode().strip() def sources(root): paths = {root / name for name in SOURCE_FILES} for tree in SOURCE_TREES: paths.update(path for path in (root / tree).rglob('*') if path.is_file()) result = {} for path in sorted(paths): if path.is_symlink() or not path.resolve().is_relative_to(root.resolve()): raise ValueError('VR source escapes the checkout: ' + str(path)) result[path.relative_to(root).as_posix()] = path.read_bytes().replace(b'\r\n', b'\n') return result def headers(include): for name in ('vulkan/vulkan_core.h', 'vulkan/vk_layer.h'): if not (include / name).is_file(): raise ValueError('Missing Vulkan header: ' + str(include / name)) return {path.relative_to(include).as_posix(): path.read_bytes().replace(b'\r\n', b'\n') for tree in ('vulkan', 'vk_video') for path in sorted((include / tree).rglob('*')) if path.is_file()} def hashes(files): return {name: sha(data) for name, data in files.items()} def read_outputs(folder, root): found = {p.relative_to(folder).as_posix() for p in folder.rglob('*') if p.is_file()} if found - {MANIFEST} != OUTPUTS: raise ValueError('VR build has missing or unexpected output files') files = {} for name in sorted(OUTPUTS): path = folder / name if path.is_symlink() or not path.resolve().is_relative_to(folder.resolve()): raise ValueError('VR output escapes its build directory: ' + name) data = path.read_bytes() if name in LIBRARIES or name in EXECUTABLES: if (len(data) < 64 or data[:6] != b'\x7fELF\x02\x01' or struct.unpack_from('