prepare_formatter.py (6233B)
1 #!/usr/bin/env python3 2 """Build/check the current formatter using the shipped accepted stable compiler. 3 4 One explicit application workspace; no Git history, differential baseline, 5 compiler generation, fixture publication or recovery frontend. 6 """ 7 import argparse 8 import hashlib 9 import json 10 from pathlib import Path 11 import re 12 import subprocess 13 import time 14 from shipped_compiler import verify, ROOT 15 16 17 def main(): 18 parser = argparse.ArgumentParser(description=__doc__) 19 parser.add_argument('tricu', type=Path) 20 parser.add_argument('--directory', required=True, type=Path, help='New output directory') 21 args = parser.parse_args() 22 metadata = verify() 23 tricu = args.tricu.resolve() 24 root = args.directory.resolve() 25 root.mkdir(parents=True, exist_ok=False) 26 workspace = root / 'workspace' 27 workspace.mkdir() 28 tools, store = root / 'tools', root / 'store' 29 report = dict(complete=False, compiler_sha256=metadata['sha256'], sources={}, commands=[], cases=[]) 30 started = time.monotonic() 31 32 def run(arguments, seconds=15, success=True): 33 remaining = 1200 - (time.monotonic() - started) 34 if remaining <= 0: 35 raise TimeoutError('formatter preparation whole-job guard exceeded') 36 command = [str(tricu), *map(str, arguments), '+RTS', '-N1', '-M8192m', '-RTS'] 37 reply = subprocess.run(command, cwd=ROOT, capture_output=True, 38 timeout=min(seconds, remaining)) 39 stdout, stderr = reply.stdout.decode('utf-8'), reply.stderr.decode('utf-8') 40 report['commands'].append(dict(argv=command[1:], code=reply.returncode, 41 stdout=stdout, stderr=stderr)) 42 (root / 'report.json').write_text(json.dumps(report, indent=2)+'\n') 43 assert (reply.returncode == 0) == success, report['commands'][-1] 44 return stdout 45 46 sources = {row['alias']: row['path'] for row in metadata['sources']} 47 sources.update(recursion='lib/recursion/list.tri', formatter='lib/tools/format-tri.tri') 48 for alias, path in sources.items(): 49 data = (ROOT / path).read_bytes() 50 (workspace / (alias + '.tri')).write_bytes(data) 51 report['sources'][path] = hashlib.sha256(data).hexdigest() 52 (workspace / 'recursion_support.tri').write_text('!import base as B\n!export pair\npair = B.pair\n') 53 (workspace / 'format_support.tri').write_text(''.join( 54 '!export * from ' + name + '\n' for name in ['prelude', 'parser', 'source_lexer', 'source_equations', 'source_ast'])) 55 output = run(['arboricx', 'import', '--indexed', '--file', ROOT / metadata['bundle'], '--store', tools]) 56 compiler = re.search(r'Created module alias .* -> ([0-9a-f]{64})', output)[1] 57 bundle = root / 'formatter.arboricx' 58 run(['arboricx', 'compile', '--file', workspace / 'formatter.tri', '--toolchain', compiler, 59 '--tool-store', tools, '--store', store, '--name', 'formatTri', '--output', bundle, 60 '--compile-timeout', '900'], seconds=930) 61 output = run(['arboricx', 'import', '--indexed', '--file', bundle, '--store', store]) 62 formatter = re.search(r'Created module alias .* -> ([0-9a-f]{64})', output)[1] 63 report['formatter_manifest'] = formatter 64 report['formatter_sha256'] = hashlib.sha256(bundle.read_bytes()).hexdigest() 65 deadline_source = workspace / 'deadline_probe.tri' 66 deadline_source.write_text('!export formatTri\nspin x = spin x\nformatTri x = spin x\n') 67 deadline_bundle = root / 'deadline.arboricx' 68 run(['arboricx', 'compile', '--file', deadline_source, '--toolchain', compiler, 69 '--tool-store', tools, '--store', store, '--name', 'formatTri', '--output', deadline_bundle, 70 '--compile-timeout', '60'], seconds=90) 71 output = run(['arboricx', 'import', '--indexed', '--file', deadline_bundle, '--store', store]) 72 diverging = re.search(r'Created module alias .* -> ([0-9a-f]{64})', output)[1] 73 report['deadline_manifest'] = diverging 74 report['workspace_sha256'] = {p.name: hashlib.sha256(p.read_bytes()).hexdigest() 75 for p in workspace.glob('*.tri')} 76 report['cases'] = check_formatter(root, formatter, store, run, diverging) 77 report['complete'] = True 78 report['elapsed_seconds'] = time.monotonic() - started 79 (root / 'report.json').write_text(json.dumps(report, indent=2)+'\n') 80 print('Formatter manifest:', formatter) 81 print('Tool store:', store) 82 83 84 def check_formatter(root, formatter, store, run, diverging): 85 labels = [] 86 source = root / 'input.tri' 87 for label, before, after in json.loads((ROOT / 'test/formatter_cases.json').read_text()): 88 source.write_text(before) 89 assert run(['format', source, '--formatter', formatter, '--tool-store', store]) == after, label 90 assert source.read_bytes() == before.encode('utf-8') 91 labels.append(label) 92 before, after = json.loads((ROOT / 'test/formatter_cases.json').read_text())[0][1:] 93 source.write_text(before) 94 options = ['format', source, '--formatter', formatter, '--tool-store', store] 95 run([*options, '--check'], success=False) 96 assert source.read_bytes() == before.encode('utf-8') 97 source.chmod(0o640) 98 run([*options, '--write']) 99 assert source.read_bytes() == after.encode('utf-8') 100 assert source.stat().st_mode & 0o777 == 0o640 101 run([*options, '--check']) 102 run([*options, '--write', '--check'], success=False) 103 link = root / 'link.tri' 104 if not link.is_symlink(): 105 link.symlink_to(source) 106 run(['format', link, '--formatter', formatter, '--tool-store', store, '--write'], success=False) 107 assert link.is_symlink() and source.read_bytes() == after.encode('utf-8') 108 source.write_bytes(b'\xff') 109 run([*options, '--write'], success=False) 110 assert source.read_bytes() == b'\xff' 111 source.write_text(before) 112 run(['format', source, '--formatter', '0' * 64, '--tool-store', store, '--write'], success=False) 113 assert source.read_bytes() == before.encode('utf-8') 114 run(['format', source, '--formatter', diverging, '--tool-store', store, 115 '--write', '--seconds', '1'], success=False) 116 assert source.read_bytes() == before.encode('utf-8') 117 return labels 118 119 120 if __name__ == '__main__': 121 main()