initial commit
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check recorded Arch patches on selected pristine release sources, without building."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path, PurePosixPath
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def sha(path):
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser(description=__doc__)
|
||||
p.add_argument('--output', type=Path, required=True)
|
||||
a = p.parse_args(); output = a.output.resolve()
|
||||
if not output.is_relative_to(ROOT):
|
||||
p.error('Use an output directory inside the workspace')
|
||||
inspection = ROOT / 'tests/results/source-archives/qt-inspection'
|
||||
record = json.loads((inspection / 'report.json').read_text())
|
||||
if not record['success'] or sha(inspection / 'files.jsonl') != record['inventorySha256']:
|
||||
raise SystemExit('A verified source inventory is required')
|
||||
inventory = {row['path']: row for line in (inspection / 'files.jsonl').open() if (row := json.loads(line))}
|
||||
source = ROOT / record['sourceTree']
|
||||
evidence = ROOT / 'tests/results/source-correspondence/arch'
|
||||
patches = [
|
||||
('qtbase', evidence / 'packages/qt6-base/qt6-base-cflags.patch'),
|
||||
('qtbase', evidence / 'packages/qt6-base/qt6-base-nostrip.patch'),
|
||||
('qtbase', evidence / 'upstream-metadata/qtbase-e80e3f0.patch'),
|
||||
('qtdeclarative', evidence / 'upstream-metadata/qtdeclarative-2efb7c6.patch')]
|
||||
output.mkdir(parents=True, exist_ok=False)
|
||||
inputs, steps = {}, []
|
||||
for module, patch in patches:
|
||||
for line in patch.read_text().splitlines():
|
||||
if not line.startswith('+++ b/'):
|
||||
continue
|
||||
name = line[len('+++ b/'):].split('\t', 1)[0]
|
||||
path = PurePosixPath(name)
|
||||
if path.is_absolute() or '..' in path.parts:
|
||||
raise ValueError('Unsafe patch target')
|
||||
relative = module + '/' + name
|
||||
target = output / 'tree' / relative
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
if relative in inputs:
|
||||
continue
|
||||
before = None
|
||||
if (source / relative).exists():
|
||||
before = sha(source / relative)
|
||||
if before != inventory[relative]['sha256']:
|
||||
raise ValueError('Selected release file changed')
|
||||
shutil.copyfile(source / relative, target)
|
||||
inputs[relative] = before
|
||||
success = True
|
||||
for module, patch in patches:
|
||||
# The two local Arch patches use GNU patch's default fuzz tolerance.
|
||||
# Keep exact context for the separately recorded upstream changes.
|
||||
fuzz = 2 if patch.name.startswith('qt6-base-') else 0
|
||||
step = {'module': module, 'patch': str(patch.relative_to(ROOT)), 'patchSha256': sha(patch), 'fuzzLimit': fuzz}
|
||||
for label, extra in [('dryRun', ['--dry-run']), ('apply', [])]:
|
||||
command = ['patch', '--batch', '--forward', '--fuzz=' + str(fuzz), '-p1', '-d', str(output / 'tree' / module), '-i', str(patch), *extra]
|
||||
run = subprocess.run(command, capture_output=True, text=True, timeout=20)
|
||||
step[label] = {'exitCode': run.returncode, 'stdout': run.stdout, 'stderr': run.stderr}
|
||||
if run.returncode:
|
||||
success = False; break
|
||||
steps.append(step)
|
||||
if not success:
|
||||
break
|
||||
after = {name: sha(output / 'tree' / name) if (output / 'tree' / name).is_file() else None for name in inputs}
|
||||
report = {'success': success, 'sourceArchiveSha256': record['archiveSha256'],
|
||||
'beforeSha256': inputs, 'afterSha256': after, 'steps': steps,
|
||||
'pristineFilesUnchanged': all(sha(source / name) == digest for name, digest in inputs.items() if digest),
|
||||
'scope': 'Recorded Arch patches checked on a private selected-file copy with explicit tolerances; no upstream build scripts run and no resulting binaries rebuilt'}
|
||||
report['installedMkspecComparisons'] = {
|
||||
name: sha(output / 'tree/qtbase/mkspecs/common' / name) == sha(Path('/usr/lib/qt6/mkspecs/common') / name)
|
||||
for name in ['g++-unix.conf', 'gcc-base.conf']}
|
||||
(output / 'report.json').write_text(json.dumps(report, indent=2) + '\n')
|
||||
print(json.dumps({'success': success, 'patches': len(steps), 'targetFiles': len(inputs), 'pristineFilesUnchanged': report['pristineFilesUnchanged']}))
|
||||
return 0 if success else 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user