# Released under the MIT License. See LICENSE for details.#"""Functionality used in meta-builds (dynamically generated sources)."""from__future__importannotationsimportosimportjsonfromefro.terminalimportClr# Can be plugged into hashes/etc to give us a convenient way to blow away# all built meta output on CI/etc. (by incrementing this value).META_BUILD_MAGIC_NUMBER=1
[docs]defgen_flat_data_code(projroot:str,in_path:str,out_path:str,var_name:str)->None:"""Generate a C++ include file from a Python file."""out_dir=os.path.dirname(out_path)ifnotos.path.exists(out_dir):os.makedirs(out_dir,exist_ok=True)withopen(in_path,'rb')asinfileb:svalin=infileb.read()# JSON should do the trick for us here as far as char escaping/etc.# There's corner cases where it can differ from C strings but in this# simple case we shouldn't run into them.sval_out=f'const char* {var_name} ='# Store in ballistica's simple xor encryption to at least# slightly slow down hackers.sval=svalinsval1:bytes|Nonesval1=svalwhilesval1:sval_out+=' '+json.dumps(sval1[:1000].decode())sval1=sval1[1000:]sval_out+=';\n'pretty_path=os.path.abspath(out_path)ifpretty_path.startswith(projroot+'/'):pretty_path=pretty_path[len(projroot)+1:]print(f'Meta-building {Clr.BLD}{pretty_path}{Clr.RST}')withopen(out_path,'w',encoding='utf-8')asoutfile:outfile.write(sval_out)
[docs]defgen_binding_code(projroot:str,in_path:str,out_path:str)->None:"""Generate binding_foo.inc file."""out_dir=os.path.dirname(out_path)ifnotos.path.exists(out_dir):os.makedirs(out_dir,exist_ok=True)withopen(in_path,encoding='utf-8')asinfile:pycode=infile.read()# Double quotes cause errors.if'"'inpycode:raiseRuntimeError('bindings file can\'t contain double quotes.')# Look for all lines associating some Python value with a constant.entries=[l.strip().split(', # ')forlinpycode.splitlines()ifl.startswith(' ')and'#'inl]ifnotall(len(l)==2forlinentries):raiseRuntimeError('malformatted data.')# Our C++ code first execs our input as a string.ccode='{\n'f'// Python code from {in_path}:\n''const char* bindcode ='forlineinpycode.splitlines():ccode+=f'\n "{line}\\n"'ccode+=(';\n''\n''// Exec the Python code in an empty context.\n''auto ctx = PythonRef::Stolen(PyDict_New());\n')ccode+=('bool success = PythonCommand(bindcode, "'+os.path.basename(in_path)+'").Exec(true,'' *ctx, *ctx);\n''if (!success) {\n'' FatalError("Error fetching required Python objects.");\n''}\n')# Then it grabs the 'values' var that should have been defined.ccode+=('\n'"// Grab the 'values' list that the binding code created.\n"'auto bindvals = ctx.DictGetItem("values");\n''if (!bindvals.exists() || !PyList_Check(*bindvals)) {\n'' FatalError("Error binding required Python objects.");\n''}\n''\n''// Pull our various obj_ values from the values list.\n')# Then it pulls the individual values out of the returned tuple.fori,entryinenumerate(entries):storecmd=('objs_.StoreCallable'ifentry[1].endswith('Class')orentry[1].endswith('Call')else'objs_.Store')ccode+=(f'{storecmd}(ObjID::{entry[1]},'f' PyList_GET_ITEM(bindvals.get(), {i}));\n')ccode+='}\n'pretty_path=os.path.abspath(out_path)ifpretty_path.startswith(projroot+'/'):pretty_path=pretty_path[len(projroot)+1:]print(f'Meta-building {Clr.BLD}{pretty_path}{Clr.RST}')withopen(out_path,'w',encoding='utf-8')asoutfile:outfile.write(ccode)