prepare_compiler_kit.py (6439B)
1 #!/usr/bin/env python3 2 """Compile a source-mapped worker/adapter kit with an explicitly selected compiler. 3 4 No retained flat support facade, inferred framing, fallback or generation claim. 5 The map and output IO version are caller assertions; actual generation execution 6 and independent acceptance remain separate gates. 7 """ 8 import argparse 9 import hashlib 10 import json 11 from pathlib import Path 12 import re 13 import time 14 15 from bootstrap_generations import source_map 16 from bootstrap_inputs import load_kit 17 from generation_backend import BACKENDS, DEFAULT_BACKEND 18 from generation_io import cli_version, compiler_flags 19 from process_rss import run_guarded 20 from rts_metrics import parse_rts_stats 21 22 ROOT = Path(__file__).resolve().parent.parent 23 24 25 def digest(data): 26 return hashlib.sha256(data).hexdigest() 27 28 29 def project(mapping, source_root, io_version, backend): 30 text, entries, _ = source_map(mapping, source_root, 'source_map') 31 if io_version != 2 or isinstance(io_version, bool): 32 raise ValueError('current source kit requires IO protocol 2') 33 io_alias = 'io' 34 if io_alias not in entries: 35 raise ValueError('selected map lacks adapter IO module: ' + io_alias) 36 sources = {alias: (source_root / relative).read_bytes() 37 for alias, (_, relative, _) in entries.items()} 38 entry = BACKENDS[backend][0] 39 worker = f'!import source_map as M\n!export {entry}\n{entry} = M.{entry}\n' 40 adapter = f'!import {io_alias} as IO\n!export main\nmain build source overlay store compiler mapping root = IO.io (build source overlay store compiler mapping root)\n' 41 return text, sources, worker, adapter 42 43 44 def main(): 45 parser = argparse.ArgumentParser(description=__doc__) 46 parser.add_argument('tricu', type=Path) 47 parser.add_argument('output', type=Path) 48 parser.add_argument('--compiler-bundle', type=Path, required=True) 49 parser.add_argument('--compiler-io-version', type=cli_version, default=1) 50 parser.add_argument('--io-version', type=cli_version, required=True, help='declared worker/adapter output framing') 51 parser.add_argument('--map', type=Path, required=True) 52 parser.add_argument('--source-root', type=Path, default=ROOT) 53 parser.add_argument('--backend', choices=BACKENDS, default=DEFAULT_BACKEND) 54 parser.add_argument('--memory-mib', type=int, default=8192) 55 parser.add_argument('--seconds', type=float, default=1800) 56 args = parser.parse_args() 57 if not 0 < args.memory_mib <= 65536 or not 0 < args.seconds <= 7200: 58 parser.error('limits must be within 64 GiB and two hours') 59 start = time.monotonic() 60 runtime = args.tricu.resolve() 61 # Capture all source/compiler bytes before any output is created. Compilation 62 # uses only these copies, never a later re-read of mutable selected inputs. 63 text, sources, worker, adapter = project(args.map, args.source_root, args.io_version, args.backend) 64 compiler_bytes = args.compiler_bundle.read_bytes() 65 runtime_pin = digest(runtime.read_bytes()) 66 root = args.output.resolve(); root.mkdir(parents=True, exist_ok=False) 67 projection = root/'projection'; (projection/'inputs').mkdir(parents=True) 68 (root/'source-map.v1').write_text(text) 69 workspace = ['tricu.workspace.v1'] 70 for alias, payload in sources.items(): 71 target = 'inputs/' + alias + '.tri' 72 (projection/target).write_bytes(payload) 73 workspace.append(alias + '\t' + target) 74 (projection/'tricu.workspace').write_text('\n'.join(workspace)+'\n') 75 (projection/'worker.tri').write_text(worker) 76 (projection/'adapter.tri').write_text(adapter) 77 (root/'compiler.arboricx').write_bytes(compiler_bytes) 78 records = [] 79 80 def run(label, command): 81 remaining = args.seconds - (time.monotonic()-start) 82 if remaining <= 0: 83 raise TimeoutError('kit preparation wall-time guard') 84 command = [str(runtime), 'arboricx', *map(str, command), '+RTS', '-N1', 85 '-M' + str(args.memory_mib) + 'm', '-s', '-RTS'] 86 began = time.monotonic() 87 reply, peak = run_guarded(command, memory_mib=args.memory_mib, seconds=remaining) 88 (root/(label+'.log')).write_text(reply) 89 records.append(dict(label=label, command=command, seconds=time.monotonic()-began, 90 peak_kib=peak, rts=parse_rts_stats(reply))) 91 return reply 92 93 reply = run('import-compiler', ['import', '--file', root/'compiler.arboricx', 94 '--store', root/'tools', '--indexed']) 95 compiler = re.search(r'Created module alias .* -> ([0-9a-f]{64})', reply)[1] 96 kit = root/'kit'; kit.mkdir() 97 metadata = dict(schema='tricu.bootstrap-kit.v2' if args.io_version == 2 else 'tricu.bootstrap-kit.v1', 98 backend=args.backend, io_version=args.io_version, artifacts={}) 99 for role, label, filename, export in [ 100 ('compiler', 'worker', 'rebuilt-compiler.arboricx', BACKENDS[args.backend][0]), 101 ('adapter', 'adapter', 'io-adapter.arboricx', 'main')]: 102 run('compile-'+label, ['compile', '--file', projection/(label+'.tri'), 103 '--toolchain', compiler, '--tool-store', root/'tools', 104 *compiler_flags(args.compiler_io_version), '--store', root/'store', 105 '--output', kit/filename, '--compile-timeout', str(max(1, int(args.seconds-(time.monotonic()-start))))]) 106 metadata['artifacts'][role] = dict(file=filename, export=export, 107 sha256=digest((kit/filename).read_bytes())) 108 if digest(runtime.read_bytes()) != runtime_pin: 109 raise ValueError('runtime changed during preparation') 110 (kit/'bootstrap-kit.json').write_text(json.dumps(metadata, indent=2)+'\n') 111 load_kit(kit, args.backend) 112 (root/'report.json').write_text(json.dumps(dict(complete=True, generation_built=False, 113 compiler_io_version=args.compiler_io_version, produced_io_version=args.io_version, 114 runtime_sha256=runtime_pin, compiler_sha256=digest(compiler_bytes), compiler_manifest=compiler, 115 map_sha256=digest(text.encode()), sources={a:digest(b) for a,b in sources.items()}, 116 worker_source_sha256=digest(worker.encode()), adapter_source_sha256=digest(adapter.encode()), 117 kit=metadata, records=records, elapsed_seconds=time.monotonic()-start), indent=2)+'\n') 118 print('Prepared explicit compiler kit:', kit, '(not generation acceptance)') 119 120 121 if __name__ == '__main__': 122 main()