"""Compare reference CW descriptors with the repository's compiled descriptor set.""" import argparse import json import re from pathlib import Path from google.protobuf import descriptor_pb2 as d def messages(files): result = {} def visit(rows, prefix): for row in rows: full = prefix+'.'+row.name result[full] = row visit(row.nested_type, full) for file in files: visit(file.message_type, '.'+file.package) return result def main(): p = argparse.ArgumentParser(description=__doc__) p.add_argument('--reference', type=Path, required=True) p.add_argument('--current', type=Path, required=True) p.add_argument('--commands', type=Path, required=True) p.add_argument('--output', type=Path, required=True) args = p.parse_args() reference = d.FileDescriptorProto.FromString(args.reference.read_bytes()) current = d.FileDescriptorSet.FromString(args.current.read_bytes()) fm, cm = messages([reference]), messages(current.file) current_names = {int(v):k for k,v in re.findall(r'"([^"]+)":\s*(\d+)',args.commands.read_text())} commands = [{'id':v.number,'ffgo_name':v.name[3:],'current_name':current_names.get(v.number)} for enum in reference.enum_type if enum.name == 'CmdGridFightType' for v in enum.value if v.number] mapping, conflicts = {}, [] def match(a,b,seen): if (a,b) in seen: return True seen.add((a,b)) if a not in fm or b not in cm: return False xf, yf = ({v.number:v for v in rows.field} for rows in (fm[a],cm[b])) if xf.keys()!=yf.keys(): return False for num,u in xf.items(): v=yf[num] if u.type!=v.type or u.label!=v.label or u.HasField('oneof_index')!=v.HasField('oneof_index'): return False if u.type==u.TYPE_MESSAGE and not match(u.type_name,v.type_name,seen): return False # Preserve the partition of fields into oneofs, independent of names. groups = lambda fields: sorted(sorted(v.number for v in fields if v.HasField('oneof_index') and v.oneof_index==i) for i in set(v.oneof_index for v in fields if v.HasField('oneof_index'))) return groups(fm[a].field)==groups(cm[b].field) for row in commands: row['same_name']=row['ffgo_name']==row['current_name'] a='.'+reference.package+'.'+row['ffgo_name'] candidates=[k for k in cm if k.rsplit('.',1)[-1]==row['current_name']] seen=set() row['wire_shape_equal']=len(candidates)==1 and match(a,candidates[0],seen) if row['wire_shape_equal']: for k,v in seen: if k in mapping and mapping[k]!=v: conflicts.append([k,mapping[k],v]) mapping[k]=v args.output.mkdir(parents=True,exist_ok=True) provenance={'reference_descriptor_sha256':__import__('hashlib').sha256(args.reference.read_bytes()).hexdigest(),'current_descriptor_sha256':__import__('hashlib').sha256(args.current.read_bytes()).hexdigest(),'limitations':['Equal wire shapes do not establish enum meanings, field semantics, runtime success, or permission to register numeric aliases.']} (args.output/'cw-command-comparison.json').write_text(json.dumps({'provenance':provenance,'commands':commands},indent=2)) (args.output/'cw-message-mapping.json').write_text(json.dumps({'provenance':provenance,'messages':mapping,'conflicts':conflicts},indent=2)) print(json.dumps({'commands':len(commands),'same_names':sum(x['same_name'] for x in commands),'equal_wire_shapes':sum(x['wire_shape_equal'] for x in commands),'mapped_messages':len(mapping),'conflicts':len(conflicts),'shape_differences':[x for x in commands if not x['wire_shape_equal']]})) if __name__=='__main__': main()