Source code for batools.meta

# Released under the MIT License. See LICENSE for details.
#
"""Generate code from input files.

Used for various metaprogramming type applications.
"""

from __future__ import annotations

import os
import marshal
from typing import TYPE_CHECKING

from efro.terminal import Clr

if TYPE_CHECKING:
    pass

# XOR key used to obfuscate embedded pyembed bytecode. This is
# obfuscation (slows down casual inspection), not security — the key
# is literally sitting right here. Must match the C++-side key in
# ballistica::core::PyembedExec at core/python/core_python.cc.
_PYEMBED_XOR_KEY = b'create an account'


def _pyembed_xor(data: bytes) -> bytes:
    """Byte-level XOR obfuscation (symmetrical)."""
    key = _PYEMBED_XOR_KEY
    key_len = len(key)
    return bytes(b ^ key[i % key_len] for i, b in enumerate(data))


def _emit_byte_array(data: bytes, indent: str = '  ') -> str:
    """Render bytes as a compact C uint8_t array initializer body.

    Packs 24 bytes per line with no space after commas to keep .inc
    files on disk modest — each byte is ``0xNN,`` (5 chars) rather
    than ``0xNN, `` (6 chars). ~120 char lines, which is fine since
    these are generated and scrolling through them is a non-use-case.
    """
    lines = []
    for i in range(0, len(data), 24):
        chunk = data[i : i + 24]
        hex_bytes = ','.join(f'0x{b:02x}' for b in chunk)
        lines.append(f'{indent}{hex_bytes},')
    return '\n'.join(lines)


[docs] def gen_pyembed( projroot: str, in_path: str, out_path: str, *, encrypt: bool, ctx_var: str = 'internal_py_context', ) -> None: """Generate a pyembed .inc from a .py file using compiled bytecode. The emitted .inc embeds two marshaled ``CodeType`` blobs — one compiled with ``optimize=0`` (debug) and one with ``optimize=1`` (release) — under ``#if BA_DEBUG_BUILD`` / ``#else``, so each C++ build links only the blob matching its own ``-O`` level. Replaces both ``gen_encrypted_python_code`` and ``gen_flat_data_code``. The .inc expands to a ``PyembedExec`` call using ``ctx_var`` as the execution context (``internal_py_context`` by default — most featuresets set up that local variable before the ``#include``). Setting ``encrypt=True`` XOR-obfuscates the bytecode so casual inspection of the binary doesn't trivially reveal the source. """ from batools.project import project_centric_path os.makedirs(os.path.dirname(out_path), exist_ok=True) with open(in_path, 'r', encoding='utf-8') as infile: src = infile.read() fname = os.path.basename(in_path) # Compile twice, once per -O level. code_debug = compile(src, fname, 'exec', optimize=0) code_release = compile(src, fname, 'exec', optimize=1) bytes_debug = marshal.dumps(code_debug) bytes_release = marshal.dumps(code_release) if encrypt: bytes_debug = _pyembed_xor(bytes_debug) bytes_release = _pyembed_xor(bytes_release) encrypted_literal = 'true' if encrypt else 'false' out = ( '// Generated by gen_pyembed; do not edit by hand.\n' f'// Source: {fname}\n' '{\n' '#if BA_DEBUG_BUILD\n' ' static constexpr uint8_t kPyembedBytes[] = {\n' f'{_emit_byte_array(bytes_debug)}\n' ' };\n' '#else\n' ' static constexpr uint8_t kPyembedBytes[] = {\n' f'{_emit_byte_array(bytes_release)}\n' ' };\n' '#endif\n' ' ballistica::core::PyembedExec(\n' ' kPyembedBytes, sizeof(kPyembedBytes),\n' f' "{fname}", {ctx_var},\n' f' /*encrypted=*/{encrypted_literal});\n' '}\n' ) pretty_path = project_centric_path(projroot=projroot, path=out_path) print(f'Meta-building {Clr.BLD}{pretty_path}{Clr.RST}') with open(out_path, 'w', encoding='utf-8') as outfile: outfile.write(out)
# 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