swayfx-mosaic/tooling/probes/step20c-scenefx-abi-package/prepare.py

134 lines
7.3 KiB
Python

#!/usr/bin/env python3
"""One-shot private source/build/stage reconstruction; never runs a compositor.
CONTRACT: empty external LAB, explicit local Git object stores at fixed commits.
INVARIANT: every command is logged; no install except stock DESTDIR staging.
WHY: a successful experimental link must never stand in for stock packaging.
"""
import argparse
import hashlib
import json
import os
from pathlib import Path
import shutil
import signal
import subprocess
import tarfile
p = argparse.ArgumentParser(description=__doc__)
p.add_argument('lab', type=Path)
p.add_argument('--scenefx-git', type=Path, required=True)
p.add_argument('--wlroots-git', type=Path, required=True)
p.add_argument('--pristine-binary', type=Path)
p.add_argument('--static', action='store_true')
a = p.parse_args()
lab = a.lab.resolve()
repo = Path(__file__).resolve().parents[3]
assert lab.is_dir() and lab.is_relative_to(Path.home()) and lab != Path.home()
assert not lab.is_relative_to(repo) and not (lab/'stock').exists()
assert not (lab/'prepare-started.json').exists(), 'budget consumed; inspect logs, never restart'
(lab/'prepare-started.json').write_text(json.dumps(vars(a), default=str, indent=2)+'\n')
raw = lab/'raw'; raw.mkdir(exist_ok=True)
results = lab/'results'; results.mkdir(exist_ok=True)
env = {k: v for k, v in os.environ.items() if k not in (
'SWAYSOCK', 'I3SOCK', 'WAYLAND_DISPLAY', 'DISPLAY', 'XDG_RUNTIME_DIR',
'LD_LIBRARY_PATH', 'LD_PRELOAD', 'PKG_CONFIG_PATH', 'OPT_TRACE')}
env['PYTHONDONTWRITEBYTECODE'] = '1'
commands = []
def run(args, cwd=None, extra=None):
i = len(commands)
entry = {'args': list(map(str, args)), 'cwd': str(cwd or repo), 'env_override': extra or {}}
commands.append(entry)
with (raw/f'build-{i:03}.log').open('wb') as out:
proc = subprocess.Popen(entry['args'], cwd=cwd or repo, env={**env, **(extra or {})},
stdout=out, stderr=subprocess.STDOUT, start_new_session=True)
try:
proc.wait(timeout=600)
except subprocess.TimeoutExpired:
# Own only this command's group; compiler children must not outlive
# a failed reproduction. No process-name or desktop-wide cleanup.
os.killpg(proc.pid, signal.SIGTERM)
try:
proc.wait(timeout=5)
except subprocess.TimeoutExpired:
os.killpg(proc.pid, signal.SIGKILL)
proc.wait()
try:
os.killpg(proc.pid, signal.SIGKILL)
except ProcessLookupError:
pass
entry['timed_out'] = True
entry['returncode'] = proc.returncode
(results/'build-commands.json').write_text(json.dumps(commands, indent=2)+'\n')
if proc.returncode:
raise RuntimeError(f'command {i} failed; inspect {raw}/build-{i:03}.log')
def archive(git, rev, name):
target = lab/name
target.mkdir()
arc = lab/(name+'.tar')
with arc.open('wb') as out:
subprocess.run(['git', '-C', str(git), 'archive', rev], stdout=out, check=True, env=env)
with tarfile.open(arc) as tf:
tf.extractall(target, filter='data')
return target
pins = {'SwayFX': 'fd71a6bdc061bd633b488ae7b83e8a6981d22f86',
'Mosaic': 'cd550382b311efdbfe3ed892f4e1d281d852e754',
'SceneFX': '37ccd723bef49e6891156ffafce8f549f01446cc',
'wlroots': 'd783533489e1f75d6886c2ab5c5960090ef268f8'}
stock = archive(repo, pins['SwayFX'], 'stock')
archive(a.scenefx_git, pins['SceneFX'], 'scene-pristine')
archive(a.scenefx_git, pins['SceneFX'], 'scene-private')
archive(a.wlroots_git, pins['wlroots'], 'wlroots-source')
archive(repo, pins['Mosaic'], 'mosaic-private')
patchdir = lab/'patches'; patchdir.mkdir()
patch = patchdir/'production.patch'; shutil.copyfile(repo/'patches/0001-mosaic-layout-viewport.patch', patch)
run(['git', 'apply', '--check', patch], stock); run(['git', 'apply', patch], stock)
for rel in ('include', 'sway', 'meson.build', 'meson_options.txt', 'tests/mosaic-model.c'):
run(['git', 'diff', '--no-index', '--exit-code', stock/rel, repo/rel])
chains = {'scene-private': ['step20c-valid-grab/scenefx-popup-plus-base.patch',
'step20c-temporal-damage/scenefx-timing.patch',
'step20c-optimized-blur-cache/scenefx-cache-trace.patch'],
'mosaic-private': ['step20c-valid-grab/mosaic-popup-plus-base.patch',
'step20c-valid-grab/hidden-guard.patch',
'step20c-temporal-damage/mosaic-output-timing.patch',
'step20c-presentation-identity/presentation-events.patch',
'step20c-optimized-blur-cache/sway-source-trace.patch']}
for tree, chain in chains.items():
for rel in chain:
dst = patchdir/Path(rel).name; shutil.copyfile(repo/'tooling/probes'/rel, dst)
run(['git', 'apply', '--check', dst], lab/tree); run(['git', 'apply', dst], lab/tree)
flags = ['-Dwerror=false', '-Dsd-bus-provider=libsystemd', '-Dmosaic-package=true',
'-Dswaybar=false', '-Dswaynag=false', '-Dman-pages=disabled', '-Ddefault-wallpaper=false']
run(['meson', 'setup', lab/'stock-build', stock, '--prefix=/usr', '--bindir=bin', *flags])
run(['meson', 'compile', '-C', lab/'stock-build', '-j', '8'])
run(['meson', 'test', '-C', lab/'stock-build', 'mosaic-model', '--print-errorlogs'])
run(['meson', 'install', '-C', lab/'stock-build', '--destdir', lab/'stage-current', '--no-rebuild'])
for name in ('pristine', 'private'):
run(['meson', 'setup', lab/('scene-'+name+'-build'), lab/('scene-'+name), '-Dexamples=false', '-Dwerror=false'])
run(['meson', 'compile', '-C', lab/('scene-'+name+'-build'), '-j', '8'])
private_env = {'PKG_CONFIG_PATH': str(lab/'scene-private-build/meson-uninstalled')}
run(['meson', 'setup', lab/'private-build', lab/'mosaic-private', *flags], extra=private_env)
run(['meson', 'compile', '-C', lab/'private-build', '-j', '8'], extra=private_env)
run(['meson', 'test', '-C', lab/'private-build', 'mosaic-model', '--print-errorlogs'])
if a.static:
# Source inspection establishes a native Meson library() target honoring this
# option. Only SceneFX is embedded; wlroots and other dependencies stay shared.
run(['meson', 'setup', lab/'scene-static-build', lab/'scene-private',
'-Ddefault_library=static', '-Dexamples=false', '-Dwerror=false'])
run(['meson', 'compile', '-C', lab/'scene-static-build', '-j', '8'])
static_env = {'PKG_CONFIG_PATH': str(lab/'scene-static-build/meson-uninstalled')}
run(['meson', 'setup', lab/'static-build', lab/'mosaic-private', *flags], extra=static_env)
run(['meson', 'compile', '-C', lab/'static-build', '-j', '8'], extra=static_env)
run(['meson', 'test', '-C', lab/'static-build', 'mosaic-model', '--print-errorlogs'])
if a.pristine_binary:
# Retained pristine binary is tied to the sealed A campaign identity.
assert hashlib.sha256(a.pristine_binary.read_bytes()).hexdigest() == '8ac865481bc0487344210cd0e16d8e6c7655b29db887cd58ddc11b0ca22fe494'
# Copy/hash only, never launch it.
shutil.copyfile(a.pristine_binary, lab/'retained-pristine-sway')
pins['pristine_binary_origin'] = str(a.pristine_binary.resolve())
pins['pristine_binary_sha256'] = hashlib.sha256(a.pristine_binary.read_bytes()).hexdigest()
(results/'source-pins.json').write_text(json.dumps(pins, indent=2)+'\n')
print('PASS private builds/model tests and single stock staging; zero compositor launches')