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