prepare_formatter.py (6458B)
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 (workspace / 'tricu.workspace').write_text('tricu.workspace.v1\n') 29 tools, store = root / 'tools', root / 'store' 30 report = dict(complete=False, compiler_sha256=metadata['sha256'], sources={}, commands=[], cases=[]) 31 started = time.monotonic() 32 33 def run(arguments, seconds=15, success=True): 34 remaining = 1200 - (time.monotonic() - started) 35 if remaining <= 0: 36 raise TimeoutError('formatter preparation whole-job guard exceeded') 37 command = [str(tricu), *map(str, arguments), '+RTS', '-N1', '-M8192m', '-RTS'] 38 reply = subprocess.run(command, cwd=ROOT, capture_output=True, 39 timeout=min(seconds, remaining)) 40 stdout, stderr = reply.stdout.decode('utf-8'), reply.stderr.decode('utf-8') 41 report['commands'].append(dict(argv=command[1:], code=reply.returncode, 42 stdout=stdout, stderr=stderr)) 43 (root / 'report.json').write_text(json.dumps(report, indent=2)+'\n') 44 assert (reply.returncode == 0) == success, report['commands'][-1] 45 return stdout 46 47 sources = {row['alias']: row['path'] for row in metadata['sources']} 48 sources.update(recursion='lib/recursion/list.tri', formatter='lib/tools/format-tri.tri') 49 for alias, path in sources.items(): 50 data = (ROOT / path).read_bytes() 51 (workspace / (alias + '.tri')).write_bytes(data) 52 report['sources'][path] = hashlib.sha256(data).hexdigest() 53 (workspace / 'recursion_support.tri').write_text('!import base as B\n!export pair\npair = B.pair\n') 54 (workspace / 'format_support.tri').write_text(''.join( 55 '!export * from ' + name + '\n' for name in ['prelude', 'parser', 'source_lexer', 'source_equations', 'source_ast'])) 56 output = run(['arboricx', 'import', '--indexed', '--file', ROOT / metadata['bundle'], '--store', tools]) 57 compiler = re.search(r'Created module alias .* -> ([0-9a-f]{64})', output)[1] 58 bundle = root / 'formatter.arboricx' 59 run(['arboricx', 'compile', '--file', workspace / 'formatter.tri', '--toolchain', compiler, 60 '--tool-store', tools, '--store', store, '--name', 'formatTri', '--output', bundle, 61 '--compile-timeout', '900'], seconds=930) 62 output = run(['arboricx', 'import', '--indexed', '--file', bundle, '--store', store]) 63 formatter = re.search(r'Created module alias .* -> ([0-9a-f]{64})', output)[1] 64 report['formatter_manifest'] = formatter 65 report['formatter_sha256'] = hashlib.sha256(bundle.read_bytes()).hexdigest() 66 deadline_source = workspace / 'deadline_probe.tri' 67 deadline_source.write_text('!export formatTri\nspin x = spin x\nformatTri x = spin x\n') 68 deadline_bundle = root / 'deadline.arboricx' 69 run(['arboricx', 'compile', '--file', deadline_source, '--toolchain', compiler, 70 '--tool-store', tools, '--store', store, '--name', 'formatTri', '--output', deadline_bundle, 71 '--compile-timeout', '60'], seconds=90) 72 output = run(['arboricx', 'import', '--indexed', '--file', deadline_bundle, '--store', store]) 73 diverging = re.search(r'Created module alias .* -> ([0-9a-f]{64})', output)[1] 74 report['deadline_manifest'] = diverging 75 report['workspace_sha256'] = {p.name: hashlib.sha256(p.read_bytes()).hexdigest() 76 for p in workspace.glob('*.tri')} 77 report['cases'] = check_formatter(root, formatter, store, run, diverging) 78 report['complete'] = True 79 report['elapsed_seconds'] = time.monotonic() - started 80 (root / 'report.json').write_text(json.dumps(report, indent=2)+'\n') 81 print('Formatter manifest:', formatter) 82 print('Tool store:', store) 83 84 85 def check_formatter(root, formatter, store, run, diverging): 86 labels = [] 87 source = root / 'input.tri' 88 for label, before, after in json.loads((ROOT / 'test/formatter_cases.json').read_text()): 89 source.write_text(before) 90 assert run(['format', source, '--formatter', formatter, '--tool-store', store]) == after, label 91 assert source.read_bytes() == before.encode('utf-8') 92 source.write_text(after) 93 assert run(['format', source, '--formatter', formatter, '--tool-store', store]) == after, label + ' idempotence' 94 labels.append(label) 95 before, after = json.loads((ROOT / 'test/formatter_cases.json').read_text())[0][1:] 96 source.write_text(before) 97 options = ['format', source, '--formatter', formatter, '--tool-store', store] 98 run([*options, '--check'], success=False) 99 assert source.read_bytes() == before.encode('utf-8') 100 source.chmod(0o640) 101 run([*options, '--write']) 102 assert source.read_bytes() == after.encode('utf-8') 103 assert source.stat().st_mode & 0o777 == 0o640 104 run([*options, '--check']) 105 run([*options, '--write', '--check'], success=False) 106 link = root / 'link.tri' 107 if not link.is_symlink(): 108 link.symlink_to(source) 109 run(['format', link, '--formatter', formatter, '--tool-store', store, '--write'], success=False) 110 assert link.is_symlink() and source.read_bytes() == after.encode('utf-8') 111 source.write_bytes(b'\xff') 112 run([*options, '--write'], success=False) 113 assert source.read_bytes() == b'\xff' 114 source.write_text(before) 115 run(['format', source, '--formatter', '0' * 64, '--tool-store', store, '--write'], success=False) 116 assert source.read_bytes() == before.encode('utf-8') 117 run(['format', source, '--formatter', diverging, '--tool-store', store, 118 '--write', '--seconds', '1'], success=False) 119 assert source.read_bytes() == before.encode('utf-8') 120 return labels 121 122 123 if __name__ == '__main__': 124 main()