#!/usr/bin/env python3 """Build/package the pinned v2 candidate only in the dedicated Ubuntu guest. Input transfer is recorded separately. Existing source/build/install/provider prefixes and earlier evidence are preserved. This does not stop the VM. """ import argparse import hashlib import json import os from pathlib import Path import re import shutil import subprocess import sys import time ROOT = Path(__file__).resolve().parents[2] HOME = Path('/home/docview') PREFIX = HOME / 'validation/pdfium-context-prefix' BUILD = HOME / 'docview-context-build' INSTALL = HOME / 'docview-context-install' RESULTS = HOME / 'validation/pdfium-context-package' VERSION = '0.1.0~validation4' def sha(path): with path.open('rb') as stream: return hashlib.file_digest(stream, 'sha256').hexdigest() def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--phase', choices=('build', 'package', 'smoke'), required=True) parser.add_argument('--run-name', required=True) args = parser.parse_args() assert os.getuid() != 0 and Path.home() == HOME and ROOT == HOME / 'docview-context-source' assert re.fullmatch('[a-z0-9-]{1,64}', args.run_name) output = RESULTS / args.run_name; output.mkdir(parents=True, exist_ok=False) env = {**os.environ, 'PATH': '/opt/docview-deps/bin:/opt/docview-qt/6.11.2/gcc_64/bin:' + os.environ['PATH'], 'PKG_CONFIG_PATH': '/opt/docview-deps/lib/pkgconfig', 'LD_LIBRARY_PATH': '/opt/docview-deps/lib:/opt/docview-qt/6.11.2/gcc_64/lib', 'QT_QPA_PLATFORM': 'xcb', 'QT_QUICK_BACKEND': 'software', 'QTWEBENGINE_CHROMIUM_FLAGS': '--disable-gpu', 'QT_SCALE_FACTOR': '1', 'QT_AUTO_SCREEN_SCALE_FACTOR': '0', 'XDG_SESSION_TYPE': 'x11'} for key in ('LD_PRELOAD', 'LD_AUDIT', 'QTWEBENGINE_DISABLE_SANDBOX', 'WAYLAND_DISPLAY'): assert not env.get(key), key preserved = [HOME / 'docview-source/.deps/pdfium/lib/libpdfium.so', HOME / 'docview-build/docview', HOME / 'docview-build/docview-pdf-worker', HOME / 'docview-install/bin/docview'] before = {str(path): sha(path) for path in preserved} report = {'success': False, 'phase': args.phase, 'commands': [], 'runnerSha256': sha(Path(__file__)), 'providerAndOriginalBuildBefore': before, 'scope': 'Local Ubuntu candidate integration only; no public release or complete Chromium notice claim'} def run(label, command, timeout=1800, environment=env): start = time.monotonic(); log = output / (label + '.log') print('Starting', label, flush=True) with log.open('w') as stream: result = subprocess.run(command, cwd=ROOT, env=environment, stdout=stream, stderr=subprocess.STDOUT, timeout=timeout) report['commands'].append({'name': label, 'command': list(map(str, command)), 'exitCode': result.returncode, 'seconds': time.monotonic() - start, 'logSha256': sha(log)}) print('Finished', label, result.returncode, flush=True) assert result.returncode == 0, label + ' failed; inspect ' + str(log) try: sys.path.insert(0, str(ROOT / 'tools')) from verify_pdfium_candidate import verify_prefix, verify_installed report['candidatePrefix'], _ = verify_prefix(PREFIX, PREFIX / 'lib/libpdfium.so', PREFIX / 'include') if args.phase == 'build': run('configure', ['cmake', '-S', str(ROOT), '-B', str(BUILD), '-G', 'Ninja', '-DCMAKE_BUILD_TYPE=Release', '-DCMAKE_PREFIX_PATH=/opt/docview-qt/6.11.2/gcc_64;/opt/docview-deps', '-DDOCVIEW_PDFIUM_ROOT=' + str(PREFIX)]) run('build', ['cmake', '--build', str(BUILD), '--parallel', '4']) run('candidate-integration-tests', [sys.executable, str(ROOT / 'tests/test_pdfium_candidate_integration.py'), '-v']) run('ctest', ['dbus-run-session', '--', 'xvfb-run', '-a', '-s', '-screen 0 1920x1080x24', 'ctest', '--test-dir', str(BUILD), '--output-on-failure', '--output-junit', str(output / 'tests.xml')]) shutil.copyfile(BUILD / 'Testing/Temporary/LastTest.log', output / 'LastTest.log') run('install', ['cmake', '--install', str(BUILD), '--prefix', str(INSTALL)]) report['installedCandidate'] = verify_installed(INSTALL) report['binaries'] = {name: sha(BUILD / name) for name in ('docview', 'docview-pdf-worker', 'docview-archive-worker')} elif args.phase == 'package': package_command = [sys.executable, str(ROOT / 'tools/package_ubuntu.py'), '--prefix', str(INSTALL), '--qtpaths', '/opt/docview-qt/6.11.2/gcc_64/bin/qtpaths', '--dependency-prefix', '/opt/docview-deps', '--source-root', str(HOME / 'dependency-sources/qpdf-12.4.1'), '--source-root', str(HOME / 'dependency-sources/libzip-1.11.4'), '--input-manifest', str(HOME / 'incoming/sdk-downloads.json'), '--sdk-notices', str(ROOT / 'tests/results/source-archives/qt-sdk-notices-final'), '--sdk-supplement', str(ROOT / 'tests/results/qt-notice-supplement/collection'), '--qt-licenses', str(ROOT / 'tests/results/ubuntu-package/inputs/qt-licenses'), '--version', VERSION] for name in ('package', 'repeat'): destination = output / name run(name, [*package_command, '--output', str(destination)]) run(name + '-verify', [sys.executable, str(ROOT / 'tools/verify_ubuntu_package.py'), '--archive', str(destination / ('docview_' + VERSION + '_amd64.deb')), '--output', str(destination / 'verification.json')]) archive = 'docview_' + VERSION + '_amd64.deb' report['archiveSha256'] = sha(output / 'package' / archive) report['repeatSha256'] = sha(output / 'repeat' / archive) assert report['archiveSha256'] == report['repeatSha256'] report['archive'] = str(output / 'package' / archive) else: package = json.loads((RESULTS / 'package/report.json').read_text()) assert package['success'] and sha(Path(package['archive'])) == package['archiveSha256'] run('apt-install', ['sudo', 'env', 'DEBIAN_FRONTEND=noninteractive', 'apt-get', '-y', '--no-install-recommends', 'install', package['archive']]) report['installedCandidate'] = verify_installed(Path('/opt/docview')) run('installed-smoke', [sys.executable, str(ROOT / 'tests/ubuntu_vm/package_smoke.py'), '--output', str(output / 'installed-smoke'), '--hide-prefix', str(BUILD), '--hide-prefix', str(INSTALL), '--hide-prefix', str(PREFIX), '--hide-prefix', str(HOME / 'validation/pdfium-intent-context')], environment={key: value for key, value in os.environ.items() if key not in ( 'LD_LIBRARY_PATH', 'QT_PLUGIN_PATH', 'QT_QPA_PLATFORM_PLUGIN_PATH', 'QML_IMPORT_PATH', 'QML2_IMPORT_PATH', 'QTWEBENGINEPROCESS_PATH', 'QTWEBENGINE_DISABLE_SANDBOX')}) run('lifecycle', [sys.executable, str(ROOT / 'tests/ubuntu_vm/package_lifecycle.py'), '--smoke', str(output / 'installed-smoke'), '--output', str(output / 'lifecycle')]) report['packagePurged'] = not Path('/opt/docview').exists() and not Path('/etc/apparmor.d/docview').exists() assert report['packagePurged'] after = {str(path): sha(path) for path in preserved} report['providerAndOriginalBuildAfter'] = after assert before == after report['originalsUnchanged'] = report['success'] = True finally: report['evidenceSha256'] = {str(path.relative_to(output)): sha(path) for path in sorted(output.rglob('*')) if path.is_file() and path.suffix != '.deb'} (output / 'report.json').write_text(json.dumps(report, indent=2) + '\n') print(json.dumps({'phase': args.phase, 'success': report['success']})) if __name__ == '__main__': main()