tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

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()