Source code for batools.metabuild

# Released under the MIT License. See LICENSE for details.
#
"""Functionality used in meta-builds (dynamically generated sources)."""
from __future__ import annotations

import os
import json

from efro.terminal import Clr

# 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] def gen_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) if not os.path.exists(out_dir): os.makedirs(out_dir, exist_ok=True) with open(in_path, 'rb') as infileb: 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 = svalin sval1: bytes | None sval1 = sval while sval1: sval_out += ' ' + json.dumps(sval1[:1000].decode()) sval1 = sval1[1000:] sval_out += ';\n' pretty_path = os.path.abspath(out_path) if pretty_path.startswith(projroot + '/'): pretty_path = pretty_path[len(projroot) + 1 :] print(f'Meta-building {Clr.BLD}{pretty_path}{Clr.RST}') with open(out_path, 'w', encoding='utf-8') as outfile: outfile.write(sval_out)
[docs] def gen_binding_code(projroot: str, in_path: str, out_path: str) -> None: """Generate binding_foo.inc file.""" out_dir = os.path.dirname(out_path) if not os.path.exists(out_dir): os.makedirs(out_dir, exist_ok=True) with open(in_path, encoding='utf-8') as infile: pycode = infile.read() # Double quotes cause errors. if '"' in pycode: raise RuntimeError('bindings file can\'t contain double quotes.') # Look for all lines associating some Python value with a constant. entries = [ l.strip().split(', # ') for l in pycode.splitlines() if l.startswith(' ') and '#' in l ] if not all(len(l) == 2 for l in entries): raise RuntimeError('malformatted data.') # Our C++ code first execs our input as a string. ccode = '{\n' f'// Python code from {in_path}:\n' 'const char* bindcode =' for line in pycode.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. for i, entry in enumerate(entries): storecmd = ( 'objs_.StoreCallable' if entry[1].endswith('Class') or entry[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) if pretty_path.startswith(projroot + '/'): pretty_path = pretty_path[len(projroot) + 1 :] print(f'Meta-building {Clr.BLD}{pretty_path}{Clr.RST}') with open(out_path, 'w', encoding='utf-8') as outfile: outfile.write(ccode)
# Docs-generation hack; import some stuff that we likely only forward-declared # in our actual source code so that docs tools can find it. from typing import (Coroutine, Any, Literal, Callable, Generator, Awaitable, Sequence, Self) import asyncio from concurrent.futures import Future from pathlib import Path from enum import Enum