Files
ARCADIA-Studio/harness/experiments/traceability-view-17f1f89-20260923/native/supplement-source-bundle-v2/build_converter.py
T

84 lines
6.9 KiB
Python

"""Build supplemental bundle evaluator only; never launches product or converter."""
from pathlib import Path
import argparse,datetime,hashlib,json,os,shutil,subprocess
HERE=Path(__file__).resolve().parent
WORKSPACE=HERE.parents[2]
REPO=WORKSPACE/'.local/traceability-view-20260923'
PIN='17f1f89fef4a6bf520f4b09a2116548b967d030e'
ENV=WORKSPACE/'.local/toolchain/env.ps1'
RUNTIME=WORKSPACE/'.local/toolchain/llvm-mingw-20260908-ucrt-x86_64/bin/libunwind.dll'
TARGET=WORKSPACE/'target'
def rec(p):
p=Path(p).resolve();h=hashlib.sha256()
with p.open('rb') as f:
for b in iter(lambda:f.read(1024*1024),b''):h.update(b)
return {'path':str(p),'bytes':p.stat().st_size,'sha256':h.hexdigest()}
def save(p,v):
with p.open('x',encoding='utf-8') as f:json.dump(v,f,indent=2);f.write('\n')
def git(*a):return subprocess.check_output(['git','-c','core.longpaths=true','-C',str(REPO),*a])
def clean():
if git('rev-parse','HEAD').decode().strip()!=PIN or git('status','--porcelain'):raise RuntimeError('Exact clean source required')
def inventory():return [rec(REPO/x.decode()) for x in sorted(git('ls-files','--cached','--others','--exclude-standard','-z').split(b'\0')) if x and (REPO/x.decode()).is_file()]
def configs():
result=[];cargo=Path(os.environ['CARGO_HOME'])
for d in dict.fromkeys([REPO,*REPO.parents,cargo]):
for name in ('config','config.toml'):
p=d/name if d==cargo else d/'.cargo'/name;result.append({'path':str(p),'present':p.exists(),'record':rec(p) if p.is_file() else None})
return result
def main():
ap=argparse.ArgumentParser();ap.add_argument('--attempt',required=True);a=ap.parse_args()
if not __import__('re').fullmatch(r'build-[0-9]{2}',a.attempt):raise RuntimeError('Fresh build-NN required')
clean()
if Path(os.environ.get('CARGO_TARGET_DIR','')).resolve()!=TARGET.resolve() or Path(os.environ.get('CARGO_HOME','')).resolve()!=(WORKSPACE/'.local/toolchain/cargo').resolve() or os.environ.get('RUSTUP_TOOLCHAIN')!='stable-x86_64-pc-windows-gnullvm':raise RuntimeError('Source admitted env.ps1 and set root target')
if rec(RUNTIME)['sha256']!='aa282fa47bb2c4e210cea5e470cdbdca58568f0be0bc73e404d86ab9cd95ef6a':raise RuntimeError('Runtime mismatch')
out=HERE/a.attempt;out.mkdir(exist_ok=False);(out/'package').mkdir()
before=inventory();save(out/'inputs-before.json',before)
cfg=configs();bound=[rec(ENV),rec(Path(__file__)),rec(HERE/'bundle_converter.rs'),rec(HERE/'capture_bundle.py'),rec(HERE/'README.md'),rec(RUNTIME)]
save(out/'environment.json',{'environment':{k:os.environ.get(k) for k in ['PATH','CARGO_HOME','RUSTUP_HOME','RUSTUP_TOOLCHAIN','CARGO_TARGET_DIR','CARGO_BUILD_JOBS','CARGO_INCREMENTAL','RUSTFLAGS','CARGO_ENCODED_RUSTFLAGS','CARGO_TARGET_X86_64_PC_WINDOWS_GNULLVM_LINKER','RC']},'configs':cfg,'bound':bound})
steps=[];failure=None;outputs=[];libraries=[];after=None
def run(name,argv):
with (out/(name+'.stdout.log')).open('xb') as stdout,(out/(name+'.stderr.log')).open('xb') as stderr:
start=datetime.datetime.now(datetime.timezone.utc).isoformat();p=subprocess.run(argv,cwd=REPO,stdout=stdout,stderr=stderr)
r={'argv':argv,'cwd':str(REPO),'started_utc':start,'exit_code':p.returncode,'stdout':rec(out/(name+'.stdout.log')),'stderr':rec(out/(name+'.stderr.log'))};save(out/(name+'.result.json'),r);steps.append(r)
if p.returncode:raise RuntimeError(name+' failed')
def preserve(p):
r=rec(p);dest=out/'package'/p.name
with p.open('rb') as src,dest.open('xb') as dst:shutil.copyfileobj(src,dst)
c=rec(dest)
if rec(p)!=r or any(c[k]!=r[k] for k in ('bytes','sha256')):raise RuntimeError('Copy changed')
outputs.append(c);return dest
try:
run('cargo-lib',['cargo','build','-p','oa-core','--lib','--locked','--offline','--message-format=json'])
messages=[json.loads(x) for x in (out/'cargo-lib.stdout.log').read_text().splitlines() if x.strip()]
candidates=[m for m in messages if m.get('reason')=='compiler-artifact' and m.get('target',{}).get('name')=='oa_core' and 'lib' in m['target']['kind'] and Path(m['manifest_path']).resolve()==(REPO/'crates/oa-core/Cargo.toml').resolve()]
if len(candidates)!=1 or not any(m.get('reason')=='build-finished' and m.get('success') is True for m in messages):raise RuntimeError('Unambiguous successful Cargo artifact required')
artifact=candidates[0];rlibs=[Path(x) for x in artifact['filenames'] if x.endswith('.rlib')]
if len(rlibs)!=1:raise RuntimeError('Exactly one core rlib required')
rlib=rlibs[0].resolve();debug=(TARGET/'debug').resolve();deps=debug/'deps'
if not ((rlib.parent==debug and rlib.name=='liboa_core.rlib') or (rlib.parent==deps and __import__('re').fullmatch(r'liboa_core-[0-9a-f]+\.rlib',rlib.name))):raise RuntimeError('Cargo core artifact outside admitted target/debug locations')
if not deps.is_dir():raise RuntimeError('Admitted dependency directory missing')
save(out/'cargo-artifact.json',{'message':artifact,'rlib':rec(rlib),'dependency_directory':str(deps)})
preserved=preserve(rlib)
run('rustc-version',['rustc','--version','--verbose']);run('rustc-sysroot',['rustc','--print','sysroot'])
sysroot=Path((out/'rustc-sysroot.stdout.log').read_text().strip())
paths=set(p for d in [deps,sysroot/'lib/rustlib/x86_64-pc-windows-gnullvm/lib'] for p in d.rglob('*') if p.is_file() and p.suffix.lower() in ('.rlib','.rmeta','.lib','.a','.dll'))
paths.update(p for p in (sysroot/'bin').glob('*.dll') if p.is_file());paths.add(sysroot/'bin/rustc.exe');linker=shutil.which('clang.exe')
if not linker:raise RuntimeError('Linker missing')
paths.add(Path(linker));libraries=[rec(p) for p in sorted(paths)];save(out/'libraries-before.json',libraries)
run('converter-build',['rustc','--edition=2024',str(HERE/'bundle_converter.rs'),'--crate-name','traceability_bundle_converter','--extern','oa_core='+str(preserved),'-L','dependency='+str(deps),'-C','linker='+linker,'-o',str(out/'package/bundle_converter.exe')])
outputs.append(rec(out/'package/bundle_converter.exe'));preserve(RUNTIME)
afterlibs=[rec(x['path']) for x in libraries];save(out/'libraries-after.json',afterlibs)
if afterlibs!=libraries:raise RuntimeError('Compiler/library inputs changed')
except Exception as e:failure=str(e)
finally:
try:
after=inventory();save(out/'inputs-after.json',after);clean()
if after!=before or configs()!=cfg or any(rec(x['path'])!=x for x in bound):raise RuntimeError('Bound inputs changed')
for r in outputs:
if rec(r['path'])!=r:raise RuntimeError('Preserved output changed')
except Exception as e:failure='; '.join(x for x in [failure,str(e)] if x)
save(out/'summary.json',{'source_commit':PIN,'passed':failure is None,'failure':failure,'inputs_unchanged':after==before,'steps':steps,'outputs':outputs,'library_input_count':len(libraries),'scope':'New supplemental evidence converter build, no GUI or converter execution, not a replacement product gate'})
if failure:raise SystemExit(failure)
print('Supplemental build passed; review receipt before converter admission')
if __name__=='__main__':main()