compiler_candidate.py (4725B)
1 """Shared opt-in candidate workspace, with explicitly authenticated checkpoint reuse. 2 3 No recovery compilation, generation promotion, or writes through source hardlinks. 4 Only immutable CAS objects are linked; mutable checkpoint files are copied. 5 """ 6 import hashlib 7 import json 8 import os 9 from pathlib import Path 10 import shutil 11 import time 12 from bootstrap_generations import source_map 13 from collections_batch import ProbeRunner, audit_workspace 14 from native_library_migration import digest 15 ROOT=Path(__file__).resolve().parent.parent 16 17 def sha(raw):return hashlib.sha256(raw).hexdigest() 18 19 class Candidate: 20 def __init__(self,args,closure): 21 if not 0 < args.memory_mib <= 65536: raise ValueError('memory budget must be within 64 GiB') 22 self.root=args.directory.resolve();self.root.mkdir(parents=True,exist_ok=args.resume) 23 self.workspace=self.root/'workspace';self.workspace.mkdir(exist_ok=args.resume) 24 self.runner=ProbeRunner(args.tricu,self.root,1500,memory_mib=args.memory_mib) 25 self.report=dict(complete=False,diagnostic_reuse=args.resume,compiler_sha256=sha(args.compiler.read_bytes()),runtime_sha256=sha(args.tricu.read_bytes()),limits=dict(memory_mib=args.memory_mib,wall_seconds=1500,compile_seconds=900)) 26 self.tool=self.runner.imported(args.compiler,'tool');raw=(self.runner.store/'objects'/self.tool[:3]/self.tool).read_bytes() 27 assert digest('arboricx.module-manifest.v1',raw)==self.tool 28 self.producer=next(row.split('\t')[3] for row in raw.decode().splitlines()[1:] if row.split('\t')[1]=='sourceBuildGraph') 29 if args.seed and not args.resume: 30 seed=args.seed.resolve();previous=json.loads((seed/'report.json').read_text()) 31 assert previous['complete'] and previous['compiler_sha256']==self.report['compiler_sha256'] 32 assert audit_workspace(seed/'workspace',seed/'store',self.producer)==previous['modules'] 33 for source in (seed/'store/objects').glob('*/*'): 34 target=self.runner.store/'objects'/source.parent.name/source.name 35 target.parent.mkdir(parents=True,exist_ok=True) 36 if not target.exists():os.link(source,target) 37 shutil.copytree(seed/'store/cache',self.runner.store/'cache',dirs_exist_ok=True) 38 self.report['seed_report_sha256']=sha((seed/'report.json').read_bytes()) 39 (self.root/'seed.json').write_text(json.dumps(dict(report_sha256=self.report['seed_report_sha256'],compiler_sha256=previous['compiler_sha256']))+'\n') 40 elif (self.root/'seed.json').exists():self.report['seed']=json.loads((self.root/'seed.json').read_text()) 41 _,self.entries,self.needed=source_map(ROOT/'bootstrap/compiler-source-map.v1',ROOT,closure) 42 for alias in self.needed:(self.workspace/(alias+'.tri')).write_bytes((ROOT/self.entries[alias][1]).read_bytes()) 43 def build(self,alias,names=()): 44 output=self.root/(alias+'.arboricx');argv=['compile','--file',self.workspace/(alias+'.tri'),'--toolchain',self.tool,'--tool-store',self.runner.store,'--store',self.runner.store,'--output',output,'--compile-timeout','900'] 45 for name in names:argv+=['--name',name] 46 self.runner.run('compile-'+alias,argv,930);return output 47 def audit(self): 48 self.report['modules']=audit_workspace(self.workspace,self.runner.store,self.producer) 49 for alias in self.needed:assert (self.workspace/(alias+'.tri')).read_bytes()==(ROOT/self.entries[alias][1]).read_bytes() 50 return self.report['modules'] 51 def export(self,alias,names): 52 output=self.root/(alias+'-selected.arboricx');argv=['export','--store',self.runner.store,'--output',output] 53 for name in names:argv+=['--target',self.report['modules'][alias]['export_hashes'][name],'--name',name] 54 self.runner.run('export-'+alias,argv,120);return output 55 def invoke(self,pin,name,arguments,label=None,prefix=()): 56 argv=list(prefix) 57 for i,argument in enumerate(arguments): 58 path=self.root/f'argument-{i}.tree';path.write_bytes(argument);argv+=['--arg-tree-file',path] 59 return self.runner.invoke(label or name,pin,name,argv) 60 def save(self): 61 self.report['seconds']=time.monotonic()-self.runner.started 62 (self.root/'report.json').write_text(json.dumps(self.report,indent=2)+'\n') 63 64 def options(description): 65 import argparse 66 p=argparse.ArgumentParser(description=description);p.add_argument('tricu',type=Path) 67 p.add_argument('--compiler',type=Path,required=True);p.add_argument('--directory',type=Path,required=True) 68 p.add_argument('--memory-mib',type=int,default=8192) 69 p.add_argument('--seed',type=Path);p.add_argument('--resume',action='store_true');return p