batools package

Build/tool functionality specific to the Ballistica project.

This stuff can be a bit more sloppy/loosey-goosey since it is not used by the game itself.

Subpackages

Submodules

batools.android module

Functionality related to android builds.

batools.android.androidaddr(archive_dir: str, arch: str, addr: str) → None[source]

Print the source file location for an android program-counter.

command line args: archive_dir architecture addr

batools.android_messages module

Codegen for the typed Android JNI message bus.

Reads a spec module (babasecodegen.android_messages) describing typed messages between Java and C++, and emits:

  • BallisticaJniBridge.java — JNI sender natives, outbound handler interface, and outbound JNI entry points.

  • android_messages_decl.inc — C++ inbound handler abstract base, outbound sender class, install/setter declarations.

  • android_messages_impl.inc — C++ inbound trampolines, JNI native method table, outbound sender bodies, install/setter definitions.

The two .inc files are #include``d from a hand-written ``.h and .cc pair under src/ballistica/base/platform/android/. Adding a new message regenerates these and forces every implementer of AndroidInboundHandler (C++) / BallisticaJniBridge.OutboundHandler (Java) to provide the new method — missing implementations are compile errors.

class batools.android_messages.Dir(*values)[source]

Bases: Enum

Message direction.

JAVA_TO_NATIVE = 'j2n'
NATIVE_TO_JAVA = 'n2j'
class batools.android_messages.Field(name: str, type: str)[source]

Bases: object

A single typed payload field on a Message.

name: str
type: str
class batools.android_messages.Message(name: str, direction: Dir, fields: list[Field] = <factory>, doc: str = '')[source]

Bases: object

A single typed message between Java and C++.

direction: Dir
doc: str = ''
fields: list[Field]
name: str
batools.android_messages.generate_cpp(projroot: str, out_path: str) → None[source]

Emit android_messages_{decl,impl}.inc based on out_path basename.

batools.android_messages.generate_java(projroot: str, out_path: str) → None[source]

Emit BallisticaJniBridge.java to out_path.

batools.androidsdkutils module

Utilities for wrangling Android SDK bits.

batools.androidsdkutils.get_sdk_dir(projroot: str) → str[source]

Return the Android SDK dir, creating local.properties if needed.

Sanity-checks that the dir looks like a real SDK (adb present).

batools.androidsdkutils.run(projroot: str, args: list[str]) → None[source]

Main script entry point.

batools.apidocs module

Finding the modules we flag as not-for-modders.

A documented module is flagged internal by carrying INTERNAL_API_WARNING in its module docstring. The docs build shows such a module’s page with its docstring only (so just the warning and whatever explanation surrounds it), and the project checks in batools.project._checks_apidocs keep the flag consistent. See docs/design/python-api-packages.md.

class batools.apidocs.ApiModule(name: str, path: Path, tree: Module)[source]

Bases: object

What we need to know about one documented module.

batools.apidocs.INTERNAL_API_WARNING = '.. warning::\n\n  This is an internal api and subject to change at any time. Do not use\n  it in mod code.\n'

The exact block marking a module as internal api. Everything keys off this text, so a reworded copy would silently stop counting.

batools.apidocs.flagged_ancestor(modname: str, flagged_pkgs: set[str]) → str | None[source]

The nearest flagged package containing a module, if any.

batools.apidocs.flagged_packages(modules: dict[str, ApiModule]) → set[str][source]

Names of the flagged packages among some gathered modules.

batools.apidocs.gather_documented_modules(projroot: Path) → dict[str, ApiModule][source]

Parse every module that gets a docs page, keyed by dotted name.

batools.apidocs.internal_api_modules(projroot: Path) → set[str][source]

Names of all documented modules that are internal api.

That is, flagged ones plus anything under a flagged package (which the project checks require to be flagged as well).

batools.appmodule module

Generates parts of babase._app.py.

This includes things like subsystem attributes for all feature-sets that want them and default app-intent handling.

batools.appmodule.generate_app_module(projroot: str, feature_sets: dict[str, FeatureSet], existing_data: str) → str[source]

Generate babase._app.py based on its existing version.

batools.apprun module

Utils for wrangling running the app as part of a build.

Manages constructing or downloading it as well as running it.

batools.apprun.acquire_binary(purpose: str, *, gui: bool = False) → str[source]

Return path to a runnable binary, building/downloading as needed.

By default this provides a headless-server binary along with the full server asset bundle (Python scripts + fonts + data, but no audio/textures/meshes). That is enough for the binary to fully boot and is the right choice for the vast majority of tool/test workflows (dummy-module generation, import tests, transport tests, etc.).

Pass gui=True when the caller genuinely needs a GUI binary and the full (including media) asset bundle — e.g. a test that exercises rendering. This only works in environments that have the full asset source tree available; environments like ba-check that strip audio/textures/meshes will fail the asset build in this mode.

By default the binary is built locally so the caller’s working-tree edits are reflected in the run. Set BA_APP_RUN_USE_PREFAB=1 to use a prefab binary instead — useful for environments without a full compiler toolchain (public-repo CI, casual contributors). In repos that publish prefabs via efrocache (spinoff/public) the prefab path is a download; in ballistica-internal it falls back to a remote cmake build via cloudshell.

batools.apprun.python_command(cmd: str, purpose: str, include_project_tools: bool = False, env: Mapping[str, str] | None = None) → None[source]

Run a cmd with a built bin and PYTHONPATH set to its scripts.

batools.apprun.run_headless_capture(*, purpose: str, config_dir: str | None = None, exec_code: str | None = None, env: Mapping[str, str] | None = None, timeout: float = 30.0, udp: bool = False, stop_pattern: str | re.Pattern[str] | None = None, sigterm_grace: float = 5.0) → subprocess.CompletedProcess[bytes][source]

Boot the headless engine, capture stdout+stderr, and return.

Designed for capture-style tests that boot the client, observe a bit of behavior in the logs, and quit. Like python_command, this goes through acquire_binary, so build-vs-prefab is controlled by BA_APP_RUN_USE_PREFAB=1 (default: build).

By default BA_NO_UDP=1 is exported so the binary never opens a UDP socket (and so can’t send UDP either). That sidesteps Claude Code’s sandbox (which blocks 0.0.0.0 binds) and avoids port conflicts on shared CI hosts. Pass udp=True if a test genuinely needs UDP.

If stop_pattern (str or compiled regex) is given, output is streamed and the process is sent SIGTERM as soon as a line matches — much faster than waiting for an apptimer to call _babase.quit. Without it, the binary runs until it exits on its own or timeout elapses.

Stdout and stderr are merged. Returns a CompletedProcess with bytes stdout; callers decode as needed. Never raises on timeout — callers assert on the captured output instead.

batools.apprun.test_runs_disabled() → bool[source]

Are test runs disabled on the current platform?

batools.apprun.test_runs_disabled_reason() → str[source]

Why are test runs disabled here?

batools.assetbundleprofiles module

Declarative definitions of asset-package bundle profiles.

A bundle profile names the set of asset packages baked into a particular build, each with its texture profiles / tier / languages. batools.pcommands2.asset_bundle_build() assembles a profile into .cache/asset_bundle/<cache-dir>/ and the stage_build pcommand copies that into the build’s ba_data/. The two agree purely on the cache-dir name (see bundle_cache_dirname()), so they can’t drift.

Two kinds of profile exist:

  • Minimal profiles (gui-minimal / headless-minimal) carry a fixed package list – just the builtin construct package – at the baseline flavors (English, fallback_v1 or null textures). Every ordinary build stages one of these; anything else is downloaded at first launch.

  • The ``store`` profile is what shipping store builds use: every asset package the bundled Python code declares a dependency on (discovered by the same # ba_meta require asset-package scan construct-mode runs at boot – so the bundle can never drift from what the code actually needs), at the build target’s device-native texture flavor, with every language flavor. That is exactly the set required to boot and play offline in any locale. The builtin package additionally carries the universal fallback_v1 texture flavor: it is the bootstrap floor construct-mode can always fall back to (and from which it can then download the rest) should the native flavor ever be unusable. See asset-packages.md decision #10.

The native texture flavor is a device-fixed dimension (decision #5/#10), so store is parameterized by form factor: a desktop store build bundles desktop_v1 (BC7), a mobile one mobile_v1 (ASTC). The two materializations cache separately (store-desktop / store-mobile).

class batools.assetbundleprofiles.BundlePackage(apvernum: ApverNum, texture_profile: str, texture_tier: str, languages: tuple[str, ...], extra_texture_profiles: tuple[str, ...] = ())[source]

Bases: object

One asset package within a materialized bundle profile.

apvernum: ApverNum

The asset-package-version’s numeric id.

extra_texture_profiles: tuple[str, ...] = ()

Additional texture flavors to bundle alongside the primary one. A textures coord doesn’t vary by language, so each of these is assembled with just the first language – it contributes only its textures/<profile>... coord.

languages: tuple[str, ...]

Language buckets to include (e.g. ('eng',)).

texture_profile: str

Primary texture flavor, assembled with every language in languages. 'null' ships a single shared empty blob per logical texture (headless builds); real flavors ship image data ('fallback_v1', 'desktop_v1', 'mobile_v1').

texture_tier: str

Texture quality tier (e.g. 'regular').

class batools.assetbundleprofiles.BundleProfile(name: str, form_factors: tuple[str, ...] = ())[source]

Bases: object

A named bundle profile.

form_factors is empty for profiles whose contents don’t depend on the build target; otherwise it lists the accepted values and the caller must supply one (the native texture flavor and the cache dir both derive from it).

form_factors: tuple[str, ...] = ()
name: str
batools.assetbundleprofiles.FALLBACK_TEXTURE_PROFILE = 'fallback_v1'

The universal uncompressed texture flavor (the construct-mode bootstrap floor; see _BUCKET_FALLBACKS in babase).

batools.assetbundleprofiles.NATIVE_TEXTURE_PROFILE_BY_FORM_FACTOR: dict[str, str] = {'desktop': 'desktop_v1', 'mobile': 'mobile_v1'}

Device-native texture profile per build form factor (mirrors the runtime selection in Assets::PreferredTextureProfile).

batools.assetbundleprofiles.all_bundle_languages() → tuple[str, ...][source]

Every language flavor a client can actually request.

The runtime always derives its locale through bacommon.locale.LocaleResolved (obsolete Locale values never get requested), so the resolvable set is exactly the set of language/<x> buckets a fully-offline build must carry.

batools.assetbundleprofiles.bundle_cache_dirname(profile: BundleProfile, form_factor: str | None) → str[source]

The .cache/asset_bundle/<dirname> a profile materializes into.

Form-factor-parameterized profiles get one dir per form factor (store-desktop); others use the bare profile name. Validates that a form factor is supplied exactly when the profile needs one.

batools.assetbundleprofiles.form_factor_for_staging_platform(platform_arg: str) → str[source]

Map a stage_build platform arg to a bundle form factor.

batools.assetbundleprofiles.get_profile(name: str) → BundleProfile[source]

Look up a bundle profile by name (or raise CleanError).

batools.assetbundleprofiles.materialize_profile(profile: BundleProfile, form_factor: str | None, projroot: Path) → list[BundlePackage][source]

Turn a profile (+ form factor) into the concrete package list.

Packages are named by numeric id: the builtin one from its C++ splice, the rest from the bundled code’s require asset-package lines (always concrete; pin-state mutation lives exclusively in tools/pcommand assetpins).

batools.assetpins module

Asset-package pin inspection / update.

assetpins is the single owner of “what asset-package version are we pinned to” across the source tree. It discovers pins in two places:

  • the "assets" entry in pconfig/projectconfig.json (the construct-mode/bootloader pin),

  • per-wrapper # ba_meta require asset-package <id> lines inside Python wrapper modules (server-generated; see the GET /api/v1/admin/asset-package-versions/{id}/python-wrapper REST endpoint).

Per the global build-system design, this is the only build-flow step that talks to the cloud and the only one that mutates checked-in source as part of normal use (other than make update, which is purely about regenerating bookkeeping derived from on-disk source state).

Apverid schema (from the third segment of <account>.<name>.<seg>):

  • <seg> starts with a digit → PROD (allowed anywhere).

  • <seg> matches dev(\d.*)? → DEV (allowed in private/internal-CI only; blocked from public).

  • <seg> matches test\d.* → TEST (allowed in private/internal-CI only; blocked from public).

Bare dev (no trailing digit) is an unresolved pseudo-id meaning “give me the latest dev snapshot”. The build refuses to consume it; assetpins update resolves it via master and writes the resolved devN form back to the pin’s source file.

The update operation takes a TARGET and a VERSION:

  • TARGET: all, an asset-package name (e.g. baclassicassets) matching any pin of that package across accounts, or a file path matching exactly one pin.

  • VERSION: latest (current track, newest version), prod / test / dev (switch to or stay on that track, newest version), or a full third-segment like 260513a (prod), test260512a, or dev260513a (exact pin; account+package come from the pin’s own apverid).

Each pin is independent — moving one pin does not move any other. Track-switching is an explicit, deliberate operation.

class batools.assetpins.Pin(kind: str, file_path: Path, apverid: str, pin_type: PinType, account: str, package: str, wrapper_type: str | None = None, latest_available: str | None = None, account_tag: str | None = None)[source]

Bases: object

One discovered asset-package pin.

account: str

<account> segment from the apverid (e.g. a-0).

account_tag: str | None = None

Account tag (e.g. efro) resolved from account by do_list. None if the account no longer exists (deleted) or the lookup failed — rendered as the raw account id in red.

apverid: str

Current apverid string.

file_path: Path

File the pin lives in, relative to projroot.

kind: str

Pin-kind identifier; one of projectconfig or wrapper.

latest_available: str | None = None

Filled by do_list (master roundtrip); None if not queried.

package: str

<name> segment from the apverid (e.g. baclassicassets).

pin_type: PinType
wrapper_type: str | None = None

For wrapper pins, which loader API the wrapper uses (bascenev1, bauiv1, or the strings-only babase). None for projectconfig pins.

class batools.assetpins.PinType(*values)[source]

Bases: Enum

Classification of an apverid by its third-segment shape.

DEV = 'dev'
PROD = 'prod'
TEST = 'test'
batools.assetpins.classify_apverid(apverid: str) → PinType[source]

Classify an apverid string by its third segment.

Raises CleanError if the apverid is structurally malformed (wrong segment count, unrecognized third-segment shape).

batools.assetpins.describe_apverid_fetch_failure(apverid: str, *, action: str, exc: BaseException, output: str) → str[source]

Explain a failed bacloud fetch for apverid.

action is the phrase for what was attempted, including the apverid (e.g. "assemble package 'a-0.foo.260101a'"); output is whatever the child wrote to stderr (empty if it was not captured). Names the actual cause where we can pin it down, and quotes the child’s own words rather than guessing where we cannot.

batools.assetpins.do_check(projroot: Path) → list[Pin][source]

Return the list of dev/test pins (empty = clean).

Used by the pre-pubsync gate (and by make assetpins-check) to refuse before any committed-source dev/test pin can flow to a public artifact.

batools.assetpins.do_list(projroot: Path) → None[source]

Print discovered pins + whether master has newer versions.

batools.assetpins.do_update(projroot: Path, target_str: str, version_str: str, force: bool = False) → None[source]

Update one or more pins to a chosen version.

target_str: all, an asset-package name (e.g. baclassicassets), or a file path matching exactly one pin.

version_str: latest (track-preserving), prod / test / dev (track-switching), a full third segment (e.g. 260513a, dev260513a, test260512a), or a full <account-or-tag>.<package>.<version> spec to retarget the pin to a different asset-package (e.g. efro.mynewassets.test260518).

force: re-fetch and rewrite wrapper pins even when the resolved version is unchanged. Use after a server-side wrapper format change (which moves no pin) to regenerate every wrapper at its current pinned version. Has no effect on the projectconfig pin (there’s nothing to regenerate — only the apverid string).

batools.assetpins.is_unresolved_dev(apverid: str) → bool[source]

Return True if the apverid is a bare <account>.<name>.dev.

Bare-dev is a request to “use the latest dev snapshot” and must be resolved by assetpins update before any build step can consume it.

batools.assetsmakefile module

Updates src/assets/Makefile based on source assets present.

batools.assetsmakefile.generate_assets_makefile(projroot: str, fname: str, existing_data: str, codegen_manifests: dict[str, str], explicit_sources: set[str]) → dict[str, str][source]

Main script entry point.

batools.base_assets module

Codegen for the base asset set.

The third sibling of batools.ui_assets and batools.scene_assets (see those for the shared reasoning). Reads babasecodegen.base_assets and emits:

  • babase/_generated/base_asset_set.py – the BaseAssetSet class an app-mode carries on its config and hands to babase.set_base_asset_set().

  • base/generated/base_asset_set.h – the C++ struct.

  • base/generated/base_asset_set_unpack.inc – the unpacker (handle-attr style, like the scene flavor).

  • base/generated/base_asset_set_placeholders.inc – the placeholder fill, unique to this set.

The placeholder fill is what makes base different from ui/scene: base draws before and between app-modes (boot, construct-mode, mode switches), so its set can never be allowed to be incomplete. Instead of zombie guards in hot draw paths, every slot an app-mode leaves unsupplied holds a placeholder, and the set is restored to placeholders – not cleared – at app-mode switches. Placeholders are chosen by slot kind and need no asset-package art: solid white textures and black cube maps the engine generates itself, plus the builtin image1x1 mesh and blank sound (which the engine keeps builtin for its own use anyway). A mode that supplies nothing gets a plain world rather than crashes or another mode’s art; a black cube map specifically makes missing reflections a no-op, since reflections are additive.

Not imported at runtime – this is build-time only.

class batools.base_assets.BaseAssetSpec(groups: list[Group] = <factory>)[source]

Bases: object

The complete set of assets base needs supplied to it.

groups: list[Group]
class batools.base_assets.Group(name: str, doc: str, slots: list[Slot] = <factory>)[source]

Bases: object

A logical grouping of slots, emitted as section comments.

doc: str

One-line description of what this group covers.

name: str

Group name (snake_case).

slots: list[Slot]

The slots it holds.

class batools.base_assets.Kind(*values)[source]

Bases: Enum

Kind of engine asset a slot holds.

CUBE_MAP_TEXTURE = 'cube_map_texture'
MESH = 'mesh'
SOUND = 'sound'
TEXTURE = 'texture'
class batools.base_assets.Slot(name: str, kind: Kind, doc: str)[source]

Bases: object

One asset base’s classic-flavored draw paths need supplied.

doc: str

One-line description, emitted as docs on both sides.

kind: Kind

What kind of asset this slot holds.

name: str

Field name on both sides (snake_case); also the C++ member name.

batools.base_assets.TOP_CLASS = 'BaseAssetSet'

Top-level Python class / C++ struct name.

batools.base_assets.generate_cpp(projroot: str, out_path: str) → None[source]

Emit one of the C++ outputs.

batools.base_assets.generate_python(projroot: str, out_path: str) → None[source]

Emit the BaseAssetSet class and the apply function.

batools.base_assets.load_spec(projroot: str) → BaseAssetSpec[source]

Import the spec module and return its spec.

batools.build module

General functionality related to running builds.

class batools.build.LazyBuildCategory(*values)[source]

Bases: Enum

Types of sources.

ASSETS = 'assets_src'
CHECK_ENVIRONMENT = 'check_environment_src'
CMAKE = 'cmake_src'
CODEGEN = 'codegen_src'
DUMMYMODULES = 'dummymodules_src'
RESOURCES = 'resources_src'
VANILLA_COMPLETIONS = 'vanilla_completions_src'
WIN = 'win_src'
class batools.build.PrefabPlatform(*values)[source]

Bases: Enum

Distinct os/cpu-arch/etc. combos we support for prefab builds.

LINUX_ARM64 = 'linux_arm64'
LINUX_X86_64 = 'linux_x86_64'
MAC_ARM64 = 'mac_arm64'
MAC_X86_64 = 'mac_x86_64'
WINDOWS_X86 = 'windows_x86'
WINDOWS_X86_64 = 'windows_x86_64'
static get_current(wsl_targets_windows: bool | None = None) → PrefabPlatform[source]

Get an identifier for the platform running this build.

Pass a bool wsl_targets_windows value to cause WSL to target either native Windows (True) or Linux (False). If this value is not passed, the env var BA_WSL_TARGETS_WINDOWS is used, and if that is not set, the default is False (Linux builds).

Throws a RuntimeError on unsupported platforms.

class batools.build.PrefabTarget(*values)[source]

Bases: Enum

Types of prefab builds able to be run.

GUI_DEBUG = 'gui-debug'
GUI_RELEASE = 'gui-release'
SERVER_DEBUG = 'server-debug'
SERVER_RELEASE = 'server-release'
property buildmode: str

Return the build mode for this target.

property buildtype: str

Return the build type for this target.

batools.build.archive_old_builds(ssh_server: str, builds_dir: str, ssh_args: list[str]) → None[source]

Stuff our old public builds into the ‘old’ dir.

(called after we push newer ones)

batools.build.checkenv() → None[source]

Check for tools necessary to build and run the app.

batools.build.cmake_prep_dir(dirname: str, verbose: bool = False) → None[source]

Create a dir, recreating it when cmake/python/etc. versions change.

Useful to prevent builds from breaking when cmake or other components are updated.

batools.build.filter_server_config_toml(projroot: str, infilepath: str) → str[source]

Add commented-out config options to a server config.

batools.build.get_scene_v1_protocol_version_max(projroot: str) → int | None[source]

Return kProtocolVersionMax as declared in scene_v1.h.

Tooling that runs without an engine (server-config template generation, etc.) reads the newest protocol we support straight from the header so it can never drift from the C++ side.

Returns None if the project has no scene_v1.h (spinoffs that omit the scene_v1 feature-set).

batools.build.in_claude_sandbox() → bool[source]

Are we running inside Claude Code’s command sandbox?

There, all network egress is forced through an authenticating proxy that the JVM/Gradle won’t use, so Gradle can only work offline.

batools.build.lazybuild(target: str, category: LazyBuildCategory, command: str) → None[source]

Run some lazybuild presets.

batools.buildangleapple module

Build ANGLE (GL ES -> Metal) xcframeworks for Apple platforms via gn.

Uses ANGLE’s native gn/depot_tools build (not vcpkg, which can only target macOS) to build ANGLE for macOS, iOS, and tvOS. The iOS/tvOS slices are assembled into .framework-based xcframeworks (libEGL.xcframework / libGLESv2.xcframework); macOS ships as bare libEGL.dylib / libGLESv2.dylib (its consumers load ANGLE as bare dylibs, not frameworks). Plus a shared header tree.

Self-containment is the whole point of this module: everything it creates lives under build/angle-apple/ and all PATH/environment mutations are scoped to the subprocesses it spawns. It never modifies the user’s shell, never writes outside build/, and redirects depot_tools’ normally-global caches (vpython virtualenvs, cipd packages) into the build dir too. Remove build/angle-apple/ to fully clean up – nothing else is left behind.

Layout under build/angle-apple/:

  • depot_tools/ – throwaway depot_tools clone (hermetic clang/gn/ninja);

  • cache/ – depot_tools’ redirected vpython/cipd caches;

  • checkout/ – the ANGLE gclient checkout (source + toolchain), with per-slice gn builds under checkout/out/<variant>/<slice>;

  • artifacts/ – the assembled xcframeworks + headers that gather reads.

Two entry points build into the same artifacts/ dir so gather is agnostic to which produced them (and we never keep two multi-GB checkouts):

  • test_build() – lazy (reuses an existing checkout, incremental); builds every Apple platform at the cheap test variant + the macOS debug (validation) variant. For CI / exercising the pipeline; optimization doesn’t matter.

  • build() – always from scratch; builds the shipping release variant (is_official_build -> ThinLTO + auto-strip + dSYMs) + the macOS debug variant.

The debug/validation variant produces only bare macOS dylibs (no xcframework – its sole consumer is the macOS cmake build, which loads ANGLE as bare dylibs; see _macos_bare_dylibs()). gather() installs the xcframeworks + release macOS dylibs to src/external/angle-apple and the debug macOS dylibs to src/external/angle-apple-debug.

depot_tools fetches a hermetic clang/gn/ninja, so the only host prerequisites are a full Xcode install and a system git – nothing is installed system-wide.

class batools.buildangleapple.Slice(name: str, group: str, target_os: str, target_cpu: str, target_environment: str | None, target_platform: str | None = None)[source]

Bases: object

One (os, cpu, environment, platform) combination we build via gn.

group is the xcframework slice it contributes to: all slices in a group are lipo-merged into one fat framework for that slice (only macos has >1, merging arm64 + x86_64).

group: str
name: str
target_cpu: str
target_environment: str | None
target_os: str
target_platform: str | None = None
batools.buildangleapple.build(projroot: str, *, assemble_only: bool = False) → None[source]

Build the shipping-tier Apple ANGLE xcframeworks from scratch.

Builds the optimized ‘release’ variant (is_official_build -> ThinLTO + stripped binaries + bundled dSYMs), plus the macOS debug/validation bare dylibs.

By default this blows the whole build/angle-apple/ tree away and re-syncs ANGLE first, for a guaranteed-from-scratch clean build (this is what CI and the make angle-apple-build target do). Set BA_ANGLE_APPLE_KEEP_CHECKOUT=1 to instead reuse the existing checkout as-is – skipping the wipe + the gclient re-sync and just recompiling (ninja-incremental) + reassembling. A fast local-iteration escape hatch; leave it unset for any build whose output you intend to ship/commit.

assemble_only skips the clean rebuild and just (re)assembles the xcframeworks from the existing out/release + out/debug slices – for re-emitting after an assembly-logic change without a recompile.

batools.buildangleapple.gather(projroot: str) → None[source]

Install assembled xcframeworks from build/angle-apple/artifacts into src.

Normal xcframeworks + headers + release macOS dylibs -> src/external/angle-apple; the debug macOS dylibs -> src/external/angle-apple-debug.

batools.buildangleapple.test_build(projroot: str) → None[source]

Build all Apple ANGLE xcframeworks at the cheap ‘test’ variant.

Lazy: reuses an existing checkout (incremental sync). For CI / exercising the pipeline; optimization doesn’t matter.

batools.builtinassetids module

Generate C++ id-enum + load-block code for the construct asset-package.

Fetches the asset listing for the projectconfig "assets" pin via tools/bacloud assetpackage _listing – an assembly-free server query that returns every logical asset + kind from the package’s workspace snapshot (no recipes run, no blobs assembled). The same server-side enumeration backs Python wrapper generation, so the C++ id enums and the wrapper namespace can’t drift. Generated content is spliced into two pre-marked autogen sections in checked-in source files:

  • src/ballistica/base/base.h — between the // __AUTOGENERATED_BUILTIN_ASSET_IDS_BEGIN__ and …_END__ markers: the four BuiltinTextureID / BuiltinCubeMapTextureID / BuiltinSoundID / BuiltinMeshID enums + the kBuiltinAssetsApvernum constant (the package’s numeric id, as text, under a comment naming its string id).

  • src/ballistica/base/assets/assets.cc — between the // __AUTOGENERATED_BUILTIN_ASSET_LOAD_BEGIN__ and …_END__ markers inside Assets::StartLoading(): one LoadBuiltinTexture(BuiltinTextureID::kFooBar, "<apvernum>:foo/bar") call per entry.

  • src/ballistica/base/assets/builtin_strings.h / .cc — FULLY generated (no splice markers): the BuiltinStrings typed accessor tree (strings-asset-migration decision D22). Each string-kind listing entry (which carries the brief’s ordered params + author docs) becomes one static method building a pure resource-form LangStr — integer-valued params (count, plus the display-kind wire ints millis/bytes) type as int64_t, text params as LangStr::Sub. String sources must live under a top-level strings/ dir; the remaining path segments map to nested classes (namespaces are barred by the one-namespace-per-featureset rule) and the leaf method name.

This is invoked by assetpins when the pin changes (it talks to the cloud for the listing, so it lives there, not in make update – which only checks the splice matches the pin and stays offline; see check_builtin_asset_ids). All targets live in checked-in files. Idempotent — only writes a target file if the generated result differs from what’s on disk, so a no-op regen leaves the files (and their mtimes) untouched.

class batools.builtinassetids.AssetEntry(kind: AssetKind, logical_name: str)[source]

Bases: object

One asset, post-grouping & validation.

property cpp_enum_entry: str

kMydirHelloworld form.

kind: AssetKind
logical_name: str
class batools.builtinassetids.AssetKind(*values)[source]

Bases: Enum

Which of the four C++ enums an entry belongs to.

CUBE_MAP_TEXTURE = 'cube_map_texture'
MESH = 'mesh'
SOUND = 'sound'
TEXTURE = 'texture'
property cpp_enum_name: str

C++ enum class name for this kind.

property cpp_loader_name: str

C++ LoadBuiltin* function name for this kind.

class batools.builtinassetids.BuildResult(apverid: str, apvernum: int, entries: list[AssetEntry] = <factory>, string_entries: list[StringEntry] = <factory>)[source]

Bases: object

Output collected before writing to disk.

apverid: str
apvernum: int
entries: list[AssetEntry]
entries_for(kind: AssetKind) → list[AssetEntry][source]

Entries of a given asset kind, sorted by enum-entry name.

string_entries: list[StringEntry]
class batools.builtinassetids.StringEntry(logical_name: str, params: list[tuple[str, str]], docs: str)[source]

Bases: object

One string-kind asset, post-validation.

params is the brief’s ordered call signature as (name, kind) pairs. Integer-valued kinds – 'count' (the plural pivot) and the display-formatter wire ints 'millis' / 'bytes' – type as int64_t; 'text' types as LangStr::Sub. docs is the combined author doc text from the .bstr.

property cpp_class_path: list[str]

Nested-class names (path segments between root and leaf).

property cpp_method_name: str

Keyboard form (from the leaf segment).

docs: str
logical_name: str
params: list[tuple[str, str]]
batools.builtinassetids.collect(projroot: Path, apverid: str) → BuildResult[source]

Fetch the asset listing for apverid and build a validated result.

The apverid is passed explicitly (not read from projectconfig) so a caller staging a not-yet-written pin update can generate against the target version. Fetches that version’s asset listing via bacloud (assembly-free; see _fetch_listing()). collect therefore talks to the cloud and must only run from assetpins / gen_builtin_asset_ids – never update_project (which stays offline and merely checks the spliced apverid string).

batools.builtinassetids.compute_splices(projroot: Path, apverid: str, base_h_existing: str | None = None, assets_cc_existing: str | None = None) → dict[str, str][source]

Compute generated contents for all target files.

Returns a dict keyed by project-relative path with the full new file content — the base.h / assets.cc splices (existing content + new autogen section) plus the fully-generated builtin_strings.h / .cc accessor files. Caller decides whether to write — typical use is “write only if contents differ from on-disk”.

base_h_existing / assets_cc_existing let the caller pass in already-read content (e.g. when integrated into a project updater that’s already loaded the file); pass None to read from disk here.

batools.builtinassetids.fetch_apvernum(projroot: Path, apverid: str) → ApverNum[source]

Look up a version’s numeric id from the server (via bacloud).

batools.builtinassetids.generate(projroot: Path, apverid: str, check: bool = False) → bool[source]

Write generated content for apverid into all target files.

Splices the base.h / assets.cc // __AUTOGENERATED_*__ marker sections and fully regenerates the builtin_strings accessor files, writing each file only if the resulting content differs from what’s on disk. Idempotent: a run with no changes leaves the files (and their mtimes) untouched.

Returns True if anything was (or would be) changed. (assetpins stages writes itself via compute_splices(); this convenience wrapper is used by the standalone gen_builtin_asset_ids pcommand.)

batools.builtinassetids.render_enum_block(result: BuildResult) → str[source]

Build the autogen-section content for base.h.

Returns the lines that go between // __AUTOGENERATED_BUILTIN_ASSET_IDS_BEGIN__ and // __AUTOGENERATED_BUILTIN_ASSET_IDS_END__ in base.h (the markers themselves are NOT included).

batools.builtinassetids.render_load_block(result: BuildResult) → str[source]

Build the autogen-section content for assets.cc.

Returns the lines that go between // __AUTOGENERATED_BUILTIN_ASSET_LOAD_BEGIN__ and // __AUTOGENERATED_BUILTIN_ASSET_LOAD_END__ inside Assets::StartLoading() (the markers themselves are NOT included). Lines that would exceed the 80-char cpplint limit wrap after the comma.

batools.builtinassetids.render_strings_cc(result: BuildResult) → str[source]

Render the full generated builtin_strings.cc content.

batools.builtinassetids.render_strings_h(result: BuildResult) → str[source]

Render the full generated builtin_strings.h content.

batools.changelog module

Generates a pretty html changelog from our markdown.

batools.changelog.changelog_version(version: str) → str[source]

Map a build version to its changelog section version.

Pre-release suffixes (1.8.0a1, 1.8.0b3) collapse to their base version (1.8.0) so all alphas/betas of a release accumulate in a single changelog section rather than each getting its own.

batools.changelog.generate(projroot: str) → None[source]

Main script entry point.

batools.changelog.get_changelog_releases(projroot: str) → list[tuple[str, str, str]][source]

Get the versions CHANGELOG.md has sections for, newest first.

Returns (version, date, heading) tuples, where date is the section’s ISO date and heading is the section’s heading text (what GitHub derives its anchor from). Old sections whose headings lack a date are skipped.

batools.changelog.get_version_changelog(version: str, projroot: str) → list[str][source]

Get changelog text for a given version from CHANGELOG.md.

batools.character_ranges module

Generate the engine’s character value spans from the shared table.

bacommon.characterranges is the one table of the allowed span of each numeric value in a character. This emits it as base/generated/character_ranges.h, which the engine’s character parsing clamps with, so the engine cannot disagree with the cloud and the authoring tools that read the same table directly.

batools.character_ranges.generate_cpp() → str[source]

Return the header’s contents.

batools.character_ranges.write_cpp(out_path: str) → None[source]

Write the header, leaving unchanged bytes alone.

batools.character_rig module

Generate the engine’s character rig numbers from the shared table.

bacommon.characterrig is the one home of a character’s fixed rig numbers. This emits them as C++ for the engine to build from, so the engine cannot disagree with anything else reading that module.

Floats are written with Python’s shortest round-tripping form, which names the same value the engine’s hand-written literals did.

batools.character_rig.generate_attachment_rigs_inc() → str[source]

Return the body of the engine’s attachment rig table.

One AttachmentTypeSpec initializer per kind, in the table’s order (which is the engine’s CharacterAttachmentType order); included between the braces of kAttachmentTypeSpecs.

batools.character_rig.generate_spaz_rig_h() → str[source]

Return the header holding the spaz rig’s own numbers.

One kSpazRig* constant per bacommon.characterrig.SpazRig field: a float, or three for a point.

batools.character_rig.write_cpp(out_path: str) → None[source]

Write one generated file, chosen by its name.

batools.checkenvironment module

Generate a standalone check-environment for static analysis.

Builds build/check_environment/ containing the runtime Python tree, C-extension dummy stubs, the efro/efrotools/ bacommon libraries, and minimal mypy/pylint configs rewritten with paths relative to the env root. A small Makefile inside the env exposes mypy and pylint targets — both delegate to a thin _check.py entry that calls efrotools.code.mypy / efrotools.code.pylint directly, so the env shares the in-tree client’s check infrastructure (same args, same SC_SEM_NSEMS_MAX shim, same parallel jobs handling) rather than duplicating it. The env-specific bits are limited to config-file path rewriting and a pconfig/projectconfig.json declaring the env’s source dirs.

Driven by make check_environment via lazybuild. Output also lands at build/check_environment.tar.gz. Intended for downstream consumers (e.g. bamaster’s workspace checker on beef) that need to run mypy/pylint against ballisticakit-API-aware Python code without bundling a full client checkout — the consumer drops user Python files into the env, runs make mypy / make pylint (against bamaster’s venv), and gets the same results the in-tree client targets produce.

batools.checkenvironment.generate_check_environment(projroot: str) → None[source]

Top-level entry: assemble the check-environment dir + tarball.

Ensures make dummymodules is fresh, copies the runtime/library trees, writes config files + a small Makefile, and packages everything into build/check_environment.tar.gz.

batools.cloudmsgs module

Generates typed message-send overloads for baplus’s CloudSubsystem.

The client’s cloud message-send APIs (the callback, blocking, async, and future forms) are generic under the hood but expose typed @overload stacks so callers get message/response type safety. Those stacks are generated into marked sections of baplus/_cloud.py from the registry here by make update (drift fails update-check/CI).

To expose a message on an additional send form (or add a new message type), update its _registry entry and run make update. Response types are derived from each message class’s get_response_types(), so they can’t drift from the wire protocol.

class batools.cloudmsgs.SendForm(*values)[source]

Bases: Enum

A message-send API form on CloudSubsystem.

ASYNC = 'async'
CB = 'cb'
FUTURE = 'future'
SYNC = 'sync'
batools.cloudmsgs.generate_cloud_module(projroot: str, existing_data: str) → str[source]

Generate baplus/_cloud.py based on its existing version.

batools.codegen module

Generate code from input files.

Used for various code-generation applications.

batools.codegen.gen_pyembed(projroot: str, in_path: str, out_path: str, *, encrypt: bool, ctx_var: str = 'internal_py_context') → None[source]

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.

batools.codegenbuild module

Functionality used in codegen-builds (dynamically generated sources).

batools.codegenbuild.gen_binding_code(projroot: str, in_path: str, out_path: str) → None[source]

Generate binding_foo.inc file.

batools.codegenmakefile module

Procedurally regenerates our code Makefile.

This Makefiles builds our generated code such as encrypted python strings, node types, etc). Outputs land in generated/ / _generated/ dirs per feature-set; see docs/design/codegen.md for why that’s the convention.

batools.codegenmakefile.ASSET_SET_GROUPS: dict[str, str] = {'base': '_add_base_asset_set_targets', 'scene_v1': '_add_scene_asset_set_targets', 'ui_v1': '_add_ui_asset_set_targets'}

The asset-set codegen groups, keyed by the feature-set that OWNS each set’s outputs. Registration gates are derived from these keys (present featureset -> targets registered), and every registered dst is validated against the owner’s generated dirs – so a set can’t be registered under the wrong featureset conditional. (The 2026-08-31 bug: all three groups sat under a hand-written ui_v1 gate, so core+base spinoffs never generated babase’s asset set and failed to import babase; only public CI’s spinoff test caught it.)

class batools.codegenmakefile.CodegenMakefileGenerator(projroot: str, existing_data: str)[source]

Bases: object

Thing that does the thing.

run() → dict[str, str][source]

Do the thing.

class batools.codegenmakefile.Target(src: list[str], dst: str, cmd: str, mkdir: bool = False)[source]

Bases: object

A target to be added to the Makefile.

cmd: str
dst: str
emit() → str[source]

Gen a Makefile target.

mkdir: bool = False
src: list[str]
batools.codegenmakefile.generate_codegen_makefile(projroot: str, existing_data: str) → dict[str, str][source]

Update the project codegen Makefile.

Returns file names and contents.

batools.cpp_enums module

Generate C++ enums from a module of Python ones.

The engine’s enums that Python also needs are defined once, in Python (bacommon/nativeenums.py), and this emits their C++ twins as shared/generated/enums.h. Each C++ entry carries the Python member’s int value, followed by a kLast sentinel one past the highest, which native code uses to range-check values arriving from Python.

The source lives in bacommon rather than a feature set’s own package because every project needs the header, including ones built without the base feature set. It is loaded by file path, so it should import nothing but enum.

batools.cpp_enums.cpp_name(member: Enum) → str[source]

PICK_UP_PRESS -> kPickUpPress.

batools.cpp_enums.generate_cpp(src_path: str, src_display_path: str) → str[source]

Return the header’s contents.

batools.cpp_enums.load_enums(src_path: str) → list[type[Enum]][source]

Return the enum classes a Python source file defines, in order.

batools.cpp_enums.write_cpp(src_path: str, out_path: str, projroot: str) → None[source]

Write the header, leaving unchanged bytes alone.

batools.docker module

General functionality related to docker builds.

batools.docker.docker_build(platform: str | None = 'linux/amd64', headless_build: bool | str | None = None, build_type: str | None = None) → None[source]

Build docker image. platform == ‘linux/arm64’ or platform == ‘linux/amd64’

batools.docker.docker_compose(platform: str | None = 'linux/amd64', headless_build: bool | str | None = None, build_type: str | None = None) → None[source]

Compose docker image. platform == ‘linux/arm64’ or platform == ‘linux/amd64’

batools.docker.docker_remove_images() → None[source]

Remove the bombsquad images loaded in docker.

batools.docker.docker_save_images() → None[source]

Saves bombsquad images loaded into docker.

batools.docker.get_docker_image_name(headless_build: bool | str, build_type: str) → str[source]

Get name of docker images in predefined format.

batools.docs module

Documentation generation functionality.

class batools.docs.AttributeInfo(name: str, attr_type: str | None = None, docs: str | None = None)[source]

Bases: object

Info about an attribute of a class.

attr_type: str | None = None
docs: str | None = None
name: str
class batools.docs.SphinxSettings(project_name: str, project_author: str, copyright: str, version: str, buildnum: int, logo_small: str, logo_large: str)[source]

Bases: object

Our settings for sphinx stuff.

buildnum: int
copyright: str
logo_large: str
logo_small: str
project_author: str
project_name: str
version: str
batools.docs.generate_sphinx_docs() → None[source]

Run docs generation with sphinx.

batools.docs.get_sphinx_settings(projroot: str) → SphinxSettings[source]

Settings for our Sphinx runs.

batools.docs.parse_docs_attrs(attrs: list[AttributeInfo], docs: str) → str[source]

Given a docs str, parses attribute descriptions contained within.

batools.dummymodule module

Generates dummy .py modules based on binary modules.

This allows us to use code introspection tools such as pylint without spinning up the engine, and also allows external scripts to import game scripts successfully (albeit with limited functionality).

class batools.dummymodule.DummyModuleDef[source]

Bases: object

Defines custom dummy module generation behavior.

batools.dummymodule.generate_dummy_modules(projroot: str) → None[source]

Generate all dummy-modules.

batools.dumpsymbols module

Stage debug symbols for the modules named by a Windows crash dump.

The dump-driven counterpart to prefabsymbols. That one asks “what binaries do I have locally?” and fetches symbols for them, which is right when you just built something. A crash report inverts the question: you have a dump from a build you may never have had, and you want whatever symbols it needs.

A minidump answers that itself – every loaded module carries the CodeView key of the pdb built with it – so this walks the dump’s module list, fetches each key the server knows about, and lays the results out so a symbolicating tool can find them.

batools.dumpsymbols.stage_symbols_for_dump(dump_path: str, host: str | None = None) → str[source]

Fetch and stage symbols for every module a dump names.

Returns the symbols root, suitable as a debugger symbol path. Modules the server has no symbols for are reported and skipped – most of a dump’s modules are system dlls we will never have pdbs for, so that is the normal case rather than an error.

batools.featureset module

Functionality for working with spinoff feature-sets.

Feature-sets are logical groupings of functionality that can be stripped out of or added in to spinoff dst projects. This allows for more high level dependency management and organization than would be possible through filtering alone.

class batools.featureset.FeatureSet(name: str)[source]

Bases: object

Defines a feature-set.

allow_as_soft_requirement: bool

If True, feature-set ‘foo_bar’, will be allowed to be listed as a soft-requirement of other feature sets and its python-app-subsystem will be annotated as type ‘bafoobar.FooBarSubsystem | None’ instead of simply ‘bafoobar.FooBarSubsystem’. This forces type-checked code to account for the possibility that it will not be present. Note that this currently requires has_python_app_subsystem to be True (because if a soft-required feature-set is missing we must assume that is the case anyway because there’s no way to know).

apply_config(config_path: str) → None[source]

Apply a user config to this feature-set.

cpp_namespace_check_disable_files: set[str]

Paths of files we should disable c++ namespace checks for. (generally external-originating code that doesn’t conform to our ballistica feature-set based namespace scheme)

dummy_module_def: DummyModuleDef

Override this to customize how your dummy module is generated.

classmethod get_active() → FeatureSet[source]

Return the FeatureSet currently being defined.

For use by settings scripts.

classmethod get_all_for_project(project_root: str) → list[FeatureSet][source]

Return all feature-sets for the current project.

has_python_app_subsystem: bool

If True, for feature-set ‘foo_bar’, the build system will define a ‘ba*.app.foo_bar’ attr which points to a lazy loaded instance of type ‘bafoobar.FooBarSubsystem’.

has_python_binary_module: bool

Whether this featureset defines a native Python module within its C++ code. The build process will try to create dummy modules for all native modules, so to avoid errors you must tell it if you don’t have one.

property name: str

Our base name.

property name_camel: str

Camel case name (foo_bar -> FooBar). Used for classes, etc.

property name_compact: str

Compact name variation (foo_bar -> foobar). Used for Python bits.

property name_python_binary_module: str

Python binary module name (foo_bar -> _bafoobar).

property name_python_package: str

Python package name (foo_bar -> bafoobar).

property name_python_package_codegen: str

The name of our codegen Python package.

property name_python_package_tests: str

The name of our Python tests package.

property name_title: str

Title name variation (foo_bar -> Foo Bar). For pretty stuff.

property path_config_file: str

Project-relative path to the file defining this feature-set.

property path_native_source: str

Project-relative path for this feature-set’s native source.

Note that this does not mean that such source actually exists; this just shows where it would.

property path_python_package: str

Project-relative path for this feature-set’s Python package.

Note that this does not mean that the package actually exists; this just shows where it would.

property path_python_package_codegen: str

Project-relative path for this feature-set’s Python codegen package.

Note that this does not mean that the package actually exists; this just shows where it would.

property path_python_package_tests: str

Project-relative path for this feature-set’s Python tests package.

Note that this does not mean that the package actually exists; this just shows where it would.

property paths: list[str]

Return all file/dir paths associated with this feature-set.

Paths are project relative and may not actually exist; this just gives their theoretical locations.

python_app_subsystem_dependencies: set[str]

By default, Python app subsystems will be created in alphabetical order based on their feature set name. All subsystem callbacks adhere to this ordering. If there are any feature sets whose subsystems should always be created before this one’s, list them here. Note that this does not affect whether or not the feature set is included in the build; only the init order in cases when it is.

requirements: set[str]

Other feature-sets this one requires. Any spinoff project that includes our feature-set will implicitly include our requirements as well. We are allowed to access Python modules of our requirements directly, unlike soft-requirements where we must limit our access to their app subsystem.

classmethod resolve_requirements(featuresets: list[FeatureSet], reqs: set[str]) → set[str][source]

Resolve all required feature-sets based on a given set of them.

Throws descriptive CleanErrors if any are missing.

soft_requirements: set[str]

Feature-sets we can use but can survive without. All usage of soft requirements must be through app-subsystems (ba*.app.foo_bar for feature-set foo_bar, etc.). We must be prepared for these subsystems to be missing (set to None) and we must never import their modules directly (since they might not exist). Note that all featuresets we soft-require must have ‘allow_as_soft_requirement’ enabled. While it is possible to programmatically check for the presence of any feature-set, officially listing soft-requirements ensures that any expected app-subsystems are in place even for feature-sets not included in the spinoff project (though be aware their type annotations will be ‘Any | None’ in that case instead of the usual ‘FooBarSubsystem | None’ due to ‘FooBarSubsystem’ not actually existing).

static validate_name(name: str) → None[source]

Validate a standard snake-case feature-set name.

Throws descriptive CleanErrors if provided name is invalid.

batools.flatpakbuildenv module

Generation of the offline Python build environment used by Flatpak.

Flatpak builds (and Flathub builds in particular) run with no network access, and a virtual environment cannot be moved between machines: its bin/pythonX.Y symlink, its pyvenv.cfg home key and all of its script shebangs name the absolute path of the interpreter that created it. So the project venv has to be created inside the build sandbox, from files flatpak-builder has already fetched as declared sources.

make flatpak-build-env first compiles pconfig/requirements_build_lock.txt: the roots in pconfig/requirements_build.txt (what building the app needs, as opposed to the full dev environment) constrained to the versions in pconfig/requirements_lock.txt. This module then turns that lockfile into pconfig/flatpak/python-build-env.yml, a flatpak-builder module supplying uv plus one wheel per package, staged where the main module’s make env can install from them with uv in offline mode.

Both outputs are committed.

class batools.flatpakbuildenv.LockedPackage(name: str, version: str, hashes: list[str])[source]

Bases: object

A single pinned package as recorded in a uv lockfile.

hashes: list[str]
name: str
version: str
batools.flatpakbuildenv.generate(projroot: str) → None[source]

Regenerate the flatpak module from the build lockfile.

batools.flatpakbuildenv.parse_lockfile(path: str) → list[LockedPackage][source]

Parse a uv pip compile --generate-hashes lockfile.

batools.flatpakbuildenv.select_distributions(pkg: LockedPackage) → list[tuple[str | None, dict[str, Any]]][source]

Pick the smallest set of files that covers every build arch.

Returns (flatpak arch or None for ‘any arch’, pypi file) pairs. A pure-Python wheel covers everything; otherwise we take one manylinux wheel per arch so each builder only downloads what it can use.

batools.flatpakbuildenv.write_flatpak_module(path: str, packages: list[LockedPackage], lock_name: str) → None[source]

Write the flatpak-builder module supplying uv and the wheels.

batools.iossim module

Build a Ballistica Xcode target for a simulator and run it there.

Powers make ios / make tvos – the simulator analogue of make mac. Unlike a macOS app (a runnable binary we can subprocess.run directly), an iOS/tvOS .app must be installed into a Simulator and launched via simctl. This handles the whole lifecycle: build-for-sim, pick + boot a device (foolproof defaults), install, launch, and stream the engine’s os_log.

batools.iossim.build_for_sim(*, project: str, scheme: str, configuration: str, platform: str, extra_cc_defines: Sequence[str] | None = None) → tuple[str, str][source]

Build the scheme for the simulator.

Returns (app_path, bundle_id) for the built product. Also used by external drivers (e.g. test_game_run’s ios leg) that manage their own install/launch lifecycle.

extra_cc_defines entries (e.g. FOO=1) are appended to the build’s GCC_PREPROCESSOR_DEFINITIONS on top of the project’s own (C-family only; Swift compilation conditions are separate).

batools.iossim.ensure_booted_device(platform: str) → str[source]

Return a booted sim udid for a platform, picking/booting if needed.

See _ensure_booted_device for the selection rules.

batools.iossim.install_app(udid: str, app_path: str) → None[source]

Install a built .app onto a booted sim.

batools.iossim.run(*, project: str, scheme: str, configuration: str, platform: str) → None[source]

Build the scheme for the simulator, then install + launch + log it.

batools.iossim.stream_log(udid: str, subsystem: str) → None[source]

Stream the engine’s os_log for a booted sim until interrupted.

batools.pcommandmain module

A collection of commands for use with this project.

All top level functions here can be run by passing them as the first argument on the command line. (or pass no arguments to get a list of them).

batools.pcommandmain.run_pcommand_main() → None[source]

Do the thing.

batools.pcommands module

A nice collection of ready-to-use pcommands for this package.

batools.pcommands.android_sdk_utils() → None[source]

Wrangle android sdk stuff.

batools.pcommands.androidaddr() → None[source]

Return the source file location for an android program-counter.

command line args: archive_dir architecture addr

batools.pcommands.angle_apple_build() → None[source]

Build shipping-tier Apple ANGLE xcframeworks from scratch.

Always starts clean and builds the optimized ‘release’ variant (is_official_build -> ThinLTO + stripped binaries + bundled dSYMs) plus the macOS debug-validation xcframeworks, assembling into build/angle-apple/artifacts/. Self-contained under build/angle-apple/; needs Xcode + system git. Follow with ‘make angle-apple-gather’ to install.

Pass ‘–assemble-only’ to skip the rebuild and just (re)assemble the xcframeworks from the existing build/angle-apple/checkout/out slices – for re-emitting after an assembly-logic change without a full build.

batools.pcommands.angle_apple_gather() → None[source]

Install assembled Apple ANGLE xcframeworks into the source tree.

Copies build/angle-apple/artifacts/ into src/external/angle-apple (normal set + headers) and src/external/angle-apple-debug (macOS debug set).

batools.pcommands.angle_apple_test_build() → None[source]

Build all Apple ANGLE xcframeworks at the cheap ‘test’ variant.

For CI / exercising the gn build pipeline – optimization doesn’t matter. Lazily reuses an existing checkout under build/angle-apple/ (incremental sync), builds mac/iOS/tvOS plus the macOS debug-validation variant, and assembles .xcframeworks into build/angle-apple/artifacts/. Self-contained (depot_tools fetches a hermetic clang/gn/ninja); needs Xcode + system git. Follow with ‘make angle-apple-gather’ to install into the source tree.

batools.pcommands.archive_old_builds() → None[source]

Stuff our old public builds into the ‘old’ dir.

(called after we push newer ones)

batools.pcommands.capitalize() → None[source]

Print args capitalized.

batools.pcommands.check_clean_safety() → None[source]

Ensure all files are are added to git or in gitignore.

Use to avoid losing work if we accidentally do a clean without adding something.

batools.pcommands.checkenv() → None[source]

Check for tools necessary to build and run the app.

batools.pcommands.cmake_prep_dir() → None[source]

Create dir & recreate when cmake/python/etc. version changes.

Useful to prevent builds from breaking when cmake or other components are updated.

batools.pcommands.efro_gradle() → None[source]

Calls ./gradlew with some extra magic.

batools.pcommands.efrocache_get() → None[source]

Get a file from efrocache.

batools.pcommands.efrocache_update() → None[source]

Build & push files to efrocache for public access.

batools.pcommands.ensure_prefab_platform() → None[source]

Ensure we are running on a particular prefab platform.

Note that prefab platform may not exactly match hardware/os. For example, when running in Linux under a WSL environment, the prefab platform may be Windows; not Linux. Also, a 64-bit os may be targeting a 32-bit platform.

batools.pcommands.gen_binding_code() → None[source]

Generate a binding_foo.inc file.

batools.pcommands.gen_check_environment() → None[source]

Generate a standalone mypy/pylint check environment.

batools.pcommands.gen_cpp_enums() → None[source]

Generate C++ enums from a module of Python ones.

Takes the Python source file and the header to write (see batools.cpp_enums).

batools.pcommands.gen_docs_sphinx() → None[source]

Generate sphinx documentation.

batools.pcommands.gen_dummy_modules() → None[source]

Generate all dummy modules.

batools.pcommands.gen_vanilla_completions() → None[source]

Generate a JSON completion index for the vanilla API.

batools.pcommands.genchangelog() → None[source]

Gen a pretty html changelog.

batools.pcommands.get_changelog() → None[source]

Print the changelog for a specified version

batools.pcommands.lazy_increment_build() → None[source]

Increment build number only if C++ sources have changed.

This is convenient to place in automatic commit/push scripts. It could make sense to auto update build number when scripts/assets change too, but a build number change requires rebuilding all binaries so I’ll leave that as an explicit choice to save work.

batools.pcommands.lazybuild() → None[source]

Run a build command only if an input has changed.

batools.pcommands.logcat() → None[source]

Get logcat command for filtering.

batools.pcommands.make_prefab() → None[source]

Run prefab builds for the current platform.

batools.pcommands.prefab_binary_path() → None[source]

Print the path to the current prefab binary.

batools.pcommands.prefab_platform() → None[source]

Print the current prefab-platform value.

batools.pcommands.prefab_run_var() → None[source]

Print the current platform prefab run target var.

batools.pcommands.printcolors() → None[source]

Print all colors available in efro.terminals.TerminalColor.

batools.pcommands.prune_includes() → None[source]

Check for unnecessary includes in C++ files.

Pass –commit to actually modify files.

batools.pcommands.push_ipa() → None[source]

Construct and push ios IPA for testing.

batools.pcommands.python_android_build() → None[source]

Build Android Python lib using new in-tree build script.

batools.pcommands.python_android_build_debug() → None[source]

Build Android Python lib using new in-tree script (debug).

batools.pcommands.python_android_gather() → None[source]

Gather Android Python build into project.

batools.pcommands.python_android_patch_old() → None[source]

Patches Python to prep for building for Android (old pipeline).

batools.pcommands.python_android_patch_ssl_old() → None[source]

Patches Python ssl to prep for building for Android (old pipeline).

batools.pcommands.python_apple_gather() → None[source]

Gather Apple Python slices into XCFramework and copy to project.

batools.pcommands.python_build_android_old() → None[source]

Build an embeddable Python lib for Android (old pipeline).

batools.pcommands.python_build_android_old_debug() → None[source]

Build embeddable Android Python lib (old pipeline, debug ver).

batools.pcommands.python_build_apple() → None[source]

Build one Apple Python slice using new in-tree script.

batools.pcommands.python_gather() → None[source]

Gather build python components into the project.

This assumes all embeddable py builds have been run successfully.

batools.pcommands.python_gather_android_old() → None[source]

python_gather but only android bits (old pipeline).

batools.pcommands.python_version_android() → None[source]

Print Android embedded Python version.

batools.pcommands.python_version_android_base() → None[source]

Print built Python base version.

batools.pcommands.python_winprune() → None[source]

Prune unneeded files from windows python.

batools.pcommands.resize_image() → None[source]

Resize an image and save it to a new location.

args: xres, yres, src, dst

batools.pcommands.stage_build() → None[source]

Stage assets for a build.

batools.pcommands.static_dependencies_build_debug() → None[source]

Build static dependencies for Android and Apple platforms.

batools.pcommands.update_project() → None[source]

Update project files.

This command is in charge of generating Makefiles, IDE project files, etc. based on the current structure of the project. It can also perform sanity checks or cleanup tasks.

Updating should be explicitly run by the user through commands such as ‘make update’, ‘make check’ or ‘make preflight’. Other make targets should avoid running this command as it can modify the project structure arbitrarily which is not a good idea in the middle of a build.

If this command is invoked with a –check argument, it should not modify any files but instead fail if any modifications would have been made. (used in CI builds to make sure things are kosher).

batools.pcommands.upper() → None[source]

Print args uppercased.

batools.pcommands.version() → None[source]

Check app versions.

batools.pcommands.warm_start_asset_build() → None[source]

Prep asset builds to run faster.

batools.pcommands2 module

A nice collection of ready-to-use pcommands for this package.

batools.pcommands2.android_archive_unstripped_libs() → None[source]

Copy libs to a build archive.

batools.pcommands2.asset_bundle_build() → None[source]

Assemble a named asset-bundle profile for the projectconfig pins.

Args: a bundle-profile name (see batools.assetbundleprofiles) plus --form-factor <desktop|mobile> for form-factor-specific profiles (store). See batools._assetbundlebuild.build() for the full story.

batools.pcommands2.assetpins() → None[source]

Inspect and update asset-package pins.

Usage:

tools/pcommand assetpins
tools/pcommand assetpins update <TARGET> <VERSION> [--force]

TARGET:  all | <package-name> | <file-path>
VERSION: latest | prod | test | dev | <version>
         | <account-or-tag>.<package>.<version>

Examples:

make assetpins

Show current pins. Same as tools/pcommand assetpins.

make assetpins-latest

Pin everything to the latest version in its current track (dev/test/prod). Same as tools/pcommand assetpins update all latest.

tools/pcommand assetpins update myassetpack dev

Pin every myassetpack to the latest dev version of itself.

tools/pcommand assetpins update all prod

Pin everything to the latest prod version of itself.

tools/pcommand assetpins update pconfig/projectconfig.json test260513

Pin one specific file to a specific version.

tools/pcommand assetpins update myoldassets efro.newassets.test260518

Pin every myoldassets to a specific version. With this long form you can switch assetpacks completely.

Full semantics (pin discovery, apverid schema/tracks, --force) live in the batools.assetpins module docstring.

batools.pcommands2.clean_orphaned_assets() → None[source]

Remove asset files that are no longer part of the build.

batools.pcommands2.gen_base_asset_set_cpp() → None[source]

Generate the base_asset_set .h / _unpack.inc / _placeholders.inc.

batools.pcommands2.gen_base_asset_set_py() → None[source]

Generate babase.BaseAssetSet from the base-asset spec.

Emits babase/_generated/base_asset_set.py from src/codegen/babasecodegen/base_assets.py. Companion to gen_base_asset_set_cpp; the base sibling of gen_scene_asset_set_py.

batools.pcommands2.gen_builtin_asset_ids() → None[source]

Generate builtin-asset C++ (id-enums, load-block, strings API).

Fetches the asset listing for the projectconfig "assets" pin via assetpackage _listing (assembly-free) and splices generated content into the // __AUTOGENERATED_BUILTIN_ASSET_IDS_*__ autogen section in src/ballistica/base/base.h and the // __AUTOGENERATED_BUILTIN_ASSET_LOAD_*__ section inside Assets::StartLoading() in src/ballistica/base/assets/assets.cc, and fully regenerates the BuiltinStrings typed accessor files src/ballistica/base/assets/builtin_strings.{h,cc}. Idempotent — only rewrites a file if its generated content differs from on-disk.

Normally invoked indirectly by assetpins when the pin changes (it talks to the cloud, so it lives there, NOT in make update – update_project only checks the splice matches the pin and stays offline). Standalone invocation exists for manual regen. Pass --check to fail (without writing) if either file would change.

batools.pcommands2.gen_character_ranges_cpp() → None[source]

Generate character_ranges.h from the shared span table.

Emits base/generated/character_ranges.h from tools/bacommon/characterranges.py (see batools.character_ranges).

batools.pcommands2.gen_character_rig_cpp() → None[source]

Generate one of the engine’s character rig files.

Emits C++ from tools/bacommon/characterrig.py; which file is chosen by the output path’s name (see batools.character_rig).

batools.pcommands2.gen_monolithic_register_modules() → None[source]

Generate .h file for registering py modules.

batools.pcommands2.gen_python_init_module() → None[source]

Generate a basic __init__.py.

batools.pcommands2.gen_scene_asset_set_cpp() → None[source]

Generate scene_asset_set.h / scene_asset_set_unpack.inc.

batools.pcommands2.gen_scene_asset_set_py() → None[source]

Generate bascenev1.SceneV1AssetSet from the scene-asset spec.

Emits bascenev1/_generated/scene_asset_set.py from src/codegen/bascenev1codegen/scene_assets.py. Companion to gen_scene_asset_set_cpp; the scene sibling of gen_ui_asset_set_py.

batools.pcommands2.gen_special_chars_cpp() → None[source]

Generate special_chars.h from the shared special-char enum.

Emits base/generated/special_chars.h from tools/bacommon/text.py (see batools.special_chars).

batools.pcommands2.gen_ui_asset_set_cpp() → None[source]

Generate ui_asset_set.h / ui_asset_set_unpack.inc from spec.

batools.pcommands2.gen_ui_asset_set_py() → None[source]

Generate the bauiv1.UIAssetSet dataclass from the ui-asset spec.

Emits bauiv1/_generated/<set>.py from src/codegen/bauiv1codegen/ui_assets.py. Companion to gen_ui_asset_set_cpp, which emits the matching native struct and the unpacker between them; generating all three from one spec is what keeps a renamed or added slot from silently drifting.

batools.pcommands2.get_modern_make() → None[source]

Print name of a modern make command.

batools.pcommands2.py_examine() → None[source]

Run a python examination at a given point in a given file.

batools.pcommands2.spinoff_check_submodule_parent() → None[source]

Make sure this dst proj has a submodule parent.

batools.pcommands2.spinoff_test() → None[source]

Test spinoff functionality.

batools.pcommands2.tests_warm_start() → None[source]

Warm-start some stuff needed by tests.

This keeps logs clearer by showing any binary builds/downloads we need to do instead of having those silently happen as part of tests.

batools.pcommands2.update_cmake_prefab_lib() → None[source]

Update prefab internal libs; run as part of a build.

batools.pcommands2.win_ci_binary_build() → None[source]

Simple windows binary build for ci.

batools.pcommands2.win_ci_install_prereqs() → None[source]

Install bits needed for basic win ci.

batools.pcommands2.wsl_build_check() → None[source]

Sanity-check the environment before we hand off to MSBuild.

Usage: tools/pcommand wsl_build_check <msbuild-path>. Verifies both that we’re building from a Windows drive and that the Visual Studio we build with is actually present, explaining what to do about it if not.

batools.pcommands2.wsl_path_to_win() → None[source]

Forward escape slashes in a provided win path arg.

batools.pcommands3 module

A nice collection of ready-to-use pcommands for this package.

batools.pcommands3.compose_docker_arm64_gui_debug() → None[source]

Build the docker image with bombsquad cmake for arm64.

batools.pcommands3.compose_docker_arm64_gui_release() → None[source]

Build the docker image with bombsquad cmake for arm64.

batools.pcommands3.compose_docker_arm64_server_debug() → None[source]

Build the docker image with bombsquad cmake server for arm64.

batools.pcommands3.compose_docker_arm64_server_release() → None[source]

Build the docker image with bombsquad cmake server for arm64.

batools.pcommands3.compose_docker_gui_debug() → None[source]

Build the docker image with bombsquad debug cmake gui.

batools.pcommands3.compose_docker_gui_release() → None[source]

Build the docker image with bombsquad cmake gui.

batools.pcommands3.compose_docker_server_debug() → None[source]

Build the docker image with bombsquad debug cmake server.

batools.pcommands3.compose_docker_server_release() → None[source]

Build the docker image with bombsquad cmake server.

batools.pcommands3.flatpak_add_release() → None[source]

Generate the flatpak releases.xml from CHANGELOG.md.

Args: [version]

Writes pconfig/flatpak/net.froemling.bombsquad.releases.xml, which the flatpak build installs. The file is not in git; it is rebuilt whole each time, so CHANGELOG.md stays the only record of releases. It lists the newest few changelog versions, up to and including version (default: the newest), each dated from its changelog heading and linking to its changelog section. The release workflow runs this with the release tag before packing the prebuilt-inputs archive that carries the file to Flathub.

batools.pcommands3.flatpak_prebuilt_inputs() → None[source]

Pack the parts of a flatpak build’s tree that aren’t in git.

Writes build/flatpak/bombsquad_prebuilt_inputs.tar.xz: what make flatpak-prefetch fetched (built assets, resources, the prebuilt plus lib for each arch, the gui asset bundle and the content-store blobs it references, the app icon, and releases.xml, generated from CHANGELOG.md; see flatpak_add_release). Flathub builds take the code from git and extract this over it.

The archive also carries a list of its own files, .flatpak-prebuilt-inputs, which the manifest uses to mark exactly those files fresh after extracting them over the checkout.

batools.pcommands3.gen_pyembed() → None[source]

Gen a pyembed .inc file using compiled bytecode.

Args: <in_path> <out_path> [encrypt={0,1}] [ctx=<var_name>]

Replaces gen_encrypted_python_code (encrypt=1) and gen_flat_data_code (encrypt=0) for pyembed modules.

batools.pcommands3.generate_flathub_manifest() → None[source]

Generate a Flathub manifest for Ballistica into build/flathub. This function is intended to be run within a GitHub Actions workflow.

Replaces everything in build/flathub (except .git and flathub.json) with the manifest, its python-build-env module, and a generated bombsquad-sources.yml: git at the release’s tag plus that release’s prebuilt-inputs archive. The release is the one for the tag that triggered the workflow, or else the latest one.

batools.pcommands3.generate_flatpak_build_env() → None[source]

Regenerate the offline Python build env used by flatpak builds.

Rewrites pconfig/requirements_build_lock.txt and pconfig/flatpak/python-build-env.yml from the main lockfile. Needs network access (it reads PyPI file metadata); the outputs are committed so the builds themselves stay offline.

batools.pcommands3.remove_docker_images() → None[source]

Remove the bombsquad images loaded in docker.

batools.pcommands3.require_unsandboxed() → None[source]

Fail right away if running inside Claude Code’s command sandbox.

args: a short description of what is being attempted (for the error message).

For build targets that cannot produce a correct result there: release archives and anything that uploads. Gradle is forced offline in the sandbox, so steps that need the network (Crashlytics mapping/symbol uploads, dependency fetches) fail or are skipped – and a release artifact that merely looks built is worse than no artifact. Failing in the first second, with the reason, beats finding out after a full build (or after shipping it).

batools.pcommands3.save_docker_images() → None[source]

Saves bombsquad images loaded into docker.

batools.pcommands4 module

A nice collection of ready-to-use pcommands for this package.

batools.pcommands4.assetworkspace() → None[source]

Get/put an asset-package source workspace via a fast local cache.

Maintains a persistent local checkout of a cloud asset-package source workspace under .cache/asset_package_sources/<NAME>/ (gitignored; bacloud syncs only diffs, so repeat gets are fast) and wraps bacloud workspace get/put against it.

bacloud itself guards against mid-air collisions: a get stashes the workspace’s snapshot id in a .bacloudstate.json and a put is rejected if the workspace has changed since (put --force overrides). So the only discipline is the standard cycle: get -> edit the files under the printed path -> put.

That guard covers writes only. Reading a stale checkout is unguarded and fails silently – content copied out of one looks entirely valid to everything downstream – so path and status check freshness and warn. Always ``get`` before you read, not just before you write, especially when copying content between workspaces.

Subcommands:

assetworkspace get <NAME> [--fleet <FLEET>]
assetworkspace put <NAME> [--force] [--fleet <FLEET>]
assetworkspace path <NAME> [--fleet <FLEET>]
assetworkspace status <NAME> [--fleet <FLEET>]

<NAME> is the case-sensitive cloud workspace name (e.g. BaBuiltinAssets). path prints the cache dir on stdout (so it stays usable in command substitution) plus a staleness warning on stderr; status reports whether the checkout is current. Both are best-effort: no api key or no network means no verdict rather than a failure. --fleet targets a non-default master fleet (sets BA_FLEET for the underlying bacloud call; flag form keeps the command signature stable for sandbox permission grants).

batools.pcommands4.cst_test() → None[source]

Test filtering a Python file using LibCST.

batools.pcommands4.dump_symbols_fetch() → None[source]

Fetch debug symbols for the modules a Windows crash dump names.

Takes a path to a .dmp. Every module in a minidump records the CodeView key of its pdb, so this can fetch symbols for a build you never had locally – which is the normal case for a crash report. Staged under build/windows-symbols/<pdb>/<key>/, a layout a debugger symbol path can point straight at. Honors BA_FLEET (default prod).

batools.pcommands4.ios_sim_log() → None[source]

Stream engine os_log from the booted iOS/tvOS sim.

Usage: tools/pcommand ios_sim_log [device-udid] (default booted). Mirrors make android-log.

batools.pcommands4.ios_sim_run() → None[source]

Build an iOS/tvOS scheme for the simulator and run it there.

Usage: tools/pcommand ios_sim_run <project> <scheme> <config> <ios|tvos>. Honors the IOS_SIM_DEVICE (name/udid; default auto-picks the newest available) and IOS_LOG_SUBSYSTEM env vars. Powers make ios / make tvos – the Simulator analogue of make mac.

batools.pcommands4.prefab_symbols_fetch() → None[source]

Fetch debug symbols for the Windows prefab binaries present.

Looks up symbols by each binary’s content hash from the master server’s recent-build archives and drops the .pdb next to its exe, after which native stack traces in fatal-error output come out fully symbolicated. Symbols are retained for recent builds only. Honors BA_FLEET for developer setups (default prod).

batools.pcommands4.push_apk_to_archive() → None[source]

Publish an already-built android apk to a bamaster archive.

The android counterpart to push_ipa_to_archive. Takes the path to the apk gradle produced (the build targets pass $(AN_APK)); pass –archive-id to override the default (‘android-test-builds’).

batools.pcommands4.push_ipa_to_archive() → None[source]

Construct an ios IPA and publish it to a bamaster archive.

Like push_ipa but uploads into the archive system (signed-URL GCS storage) instead of rsyncing to the staging server. Pass –archive-id to override the default (‘ios-test-builds’).

batools.prefabsymbols module

Fetch debug symbols for prefab binaries on demand.

Prefab binaries ship without debug-symbol files to keep downloads small, but symbols for recent builds are archived server-side and can be fetched for any binary by its content hash. With a symbols file sitting next to its binary, native stack traces in fatal-error output come out fully symbolicated (file and line numbers included), which makes crash reports vastly more useful.

Currently this covers the Windows prefab binaries (.pdb files); other platforms either embed their debug info in the binary itself (Linux) or are not yet covered (macOS).

batools.prefabsymbols.fetch_prefab_symbols() → None[source]

Fetch symbol files for all present Windows prefab binaries.

Covers both our own binaries (looked up by exe content hash) and the third-party dlls we ship alongside them – currently ANGLE, looked up by the CodeView key baked into each dll. A crash inside ANGLE is otherwise unresolvable past module+offset, and prefab builds ship ANGLE, so fetching only our own symbols would leave a common crash class unsymbolicated.

batools.pruneincludes module

Utility to scan for unnecessary includes in c++ files.

class batools.pruneincludes.Pruner(commit: bool, paths: list[str])[source]

Bases: object

Wrangles a prune operation.

run() → None[source]

Do the thing.

batools.reexportdocs module

Gather re-export docs info for sphinx.

For every documented module with __all__, finds entries that are instance re-exports (not classes/functions/modules), traces them to their canonical home, and extracts the #: comment block plus type info. The sphinx generator uses this to inject forward-declare blocks into filtered consumer __init__.py files so re-exported instances get documented at every public package, not only at the canonical home.

Design rationale: docs/design/python-api-packages.md. Anything in a package’s __all__ is part of that package’s public API and should appear on its docs page.

Hard error if any public instance lacks a #: comment block at its canonical home. Forces every public instance to be documented rather than silently producing a stub.

class batools.reexportdocs.Injection(name: str, type_str: str, comment: str)[source]

Bases: object

One forward-declare to inject into a consumer __init__.py.

comment: str
name: str
type_str: str
batools.reexportdocs.apply_injections(injections: dict[str, list[Injection]]) → None[source]

Prepend forward-declare blocks to filtered consumer files.

Inserts the synthesized block after any from __future__ statements (so it doesn’t disrupt PEP 236 ordering) and before everything else. Idempotent in the sense that each make docs rebuilds from a fresh copy of sources, so we never need to detect previously-injected content.

batools.reexportdocs.gather_reexport_injections(projroot: str, filtered_ba_data_dir: str) → dict[str, list[Injection]][source]

Orchestrate: spawn worker, parse JSON, return injections.

Returns a mapping from absolute filtered-init-file path to a list of injections to prepend at that file’s top.

Raises CleanError if any public instance lacks a #: comment at its canonical home.

batools.resourcesmakefile module

Generate our resources Makefile.

(builds things like icons, banners, images, etc.)

class batools.resourcesmakefile.ResourcesMakefileGenerator(projroot: str, existing_data: str, projname: str)[source]

Bases: object

Does the thing.

run() → str[source]

Does the thing.

class batools.resourcesmakefile.Target(src: list[str], dst: str, cmd: str, mkdir: bool = False)[source]

Bases: object

A target to be added to the Makefile.

cmd: str
dst: str
emit() → str[source]

Gen a Makefile target.

mkdir: bool = False
src: list[str]

batools.scene_assets module

Codegen for the scene_v1 asset set.

The sibling of batools.ui_assets (see that module’s docstring for the shared reasoning: one spec drives the Python class, the C++ struct, and the unpacker between them, so nothing can drift). Reads bascenev1codegen.scene_assets and emits:

  • bascenev1/_generated/scene_asset_set.py – the SceneV1AssetSet class an app-mode carries on its config and hands to bascenev1.set_scene_asset_set().

  • scene_v1/generated/scene_asset_set.h – the C++ struct.

  • scene_v1/generated/scene_asset_set_unpack.inc – the unpacker.

It differs from the ui flavor at the native boundary, in a way that is load-bearing: slots hold handles end to end, and the C++ unpacker reads each handle’s package id and name and loads the underlying base-level asset itself (Assets::GetPackageTexture etc.). It cannot pass loaded bascenev1.Texture objects the way the ui flavor passes bauiv1.Texture ones, because scene-flavor Python assets are context-bound scene assets (the streamed kind) while the nodes draw raw engine assets client-locally. Reading the private _apvernum/_name attrs native-side is the sanctioned boundary for that (same as ClassicPython::QualifiedRefFromHandle_).

Every slot has a default (scene_v1’s own package), so the set constructs bare and an app-mode overrides only what it wants to reskin. Nothing loads until the set is applied at activation, so replaced defaults are never loaded at all.

Not imported at runtime – this is build-time only.

class batools.scene_assets.Group(name: str, doc: str, default_module: str, slots: list[Slot] = <factory>)[source]

Bases: object

A logical grouping of slots, emitted as section comments.

default_module: str

Wrapper module the slots’ default paths hang off.

doc: str

One-line description of what this group covers.

name: str

Group name (snake_case).

slots: list[Slot]

The slots it holds.

class batools.scene_assets.Kind(*values)[source]

Bases: Enum

Kind of engine asset a slot holds.

MESH = 'mesh'
SOUND = 'sound'
TEXTURE = 'texture'
class batools.scene_assets.SceneAssetSpec(groups: list[Group] = <factory>)[source]

Bases: object

The complete set of assets scene_v1 needs supplied to it.

groups: list[Group]
class batools.scene_assets.Slot(name: str, kind: Kind, doc: str, default: str, default_module: str | None = None)[source]

Bases: object

One asset the scene_v1 node layer needs supplied to it.

default: str

Attr path of this slot’s fallback art on the group’s default_module, e.g. 'meshes.eye_ball'. Deliberately not derived from name – a slot and the asset backing it are free to be named differently (see scorch_mesh).

default_module: str | None = None

Wrapper module default hangs off, when it is not the group’s own.

doc: str

One-line description, emitted as docs on both sides.

kind: Kind

What kind of asset this slot holds.

name: str

Field name on both sides (snake_case); also the C++ member name.

batools.scene_assets.TOP_CLASS = 'SceneV1AssetSet'

Top-level Python class / C++ struct name.

batools.scene_assets.generate_cpp(projroot: str, out_path: str) → None[source]

Emit the C++ struct header or the unpacker include.

batools.scene_assets.generate_python(projroot: str, out_path: str) → None[source]

Emit the SceneV1AssetSet class and the apply function.

batools.scene_assets.load_spec(projroot: str) → SceneAssetSpec[source]

Import the spec module and return its spec.

batools.special_chars module

Generate the engine’s special-character table from the shared enum.

bacommon.text.SpecialChar is the one list of the custom characters the engine can draw. This emits it as base/generated/special_chars.h (the C++ enum plus each character’s UTF-8 form), so the engine cannot disagree with Python code and servers that use the enum directly.

batools.special_chars.cpp_name(char: SpecialChar) → str[source]

LEFT_ARROW -> kLeftArrow.

batools.special_chars.generate_cpp() → str[source]

Return the header’s contents.

batools.special_chars.write_cpp(out_path: str) → None[source]

Write the header, leaving unchanged bytes alone.

batools.staging module

Stage files for builds.

class batools.staging.BuildStager(projroot: str)[source]

Bases: object

Context for a run of the tool.

run(args: list[str]) → None[source]

Do the thing.

batools.staging.stage_build(projroot: str, args: list[str] | None = None) → None[source]

Stage assets for a build.

batools.toplevelmakefile module

Updates top level Makefile based on project elements present.

batools.toplevelmakefile.generate_top_level_makefile(projroot: str, existing_data: str) → str[source]

Main script entry point.

batools.ui_assets module

Codegen for the ui_v1 asset set.

Reads a spec module (bauiv1codegen.ui_assets) naming every asset the ui_v1 widget layer draws itself with, and emits:

  • bauiv1/_generated/ui_asset_set.py – the mutable UIAssetSet class an app-mode builds (as part of its app-mode config) and the set_ui_asset_set() function that applies one.

  • ui_v1/generated/ui_asset_set.h – the C++ structs holding the resolved assets.

  • ui_v1/generated/ui_asset_set_unpack.inc – the Python-object -> struct unpacker, #include-d from a hand-written .cc.

Generating all three from one spec is the whole point: the unpacker is the real drift surface (it is what ties a Python attr name to a struct member), so hand-writing it would relocate the sync problem rather than remove it. With it generated, a renamed or dropped slot is a compile error at every C++ use site and a type error at the Python site that builds the set.

Type definitions live here rather than in the spec module so that spinoff projects which omit the ui_v1 featureset still type-check the codegen module cleanly, and so the spec module is self-typecheckable in spinoffs that lack tools/batoolsinternal/. Same rationale as batools.android_messages; see docs/design/spinoff.md.

Not imported at runtime – this is build-time only.

Groups

Slots are organized into named groups (chrome, toolbar), each emitted as its own dataclass/struct nested inside the top-level set. Groups are a logical division of what the ui draws – window furniture versus the persistent toolbar – and deliberately not a division by which asset-package the art happens to live in. ui_v1 declares what it needs; filling those slots from one package or five is entirely the supplying app-mode’s business, and moving an asset between packages must never change anything here.

Every group is required. A partially-supplied set is not representable, which is what lets the C++ side treat every member as non-null while widgets are drawing. If some group later becomes genuinely optional (a ui with no toolbar at all), that is a spec-level change – make its field | None and give the struct a presence flag – rather than something callers can do by accident.

Where defaults live

A slot’s fallback art (for the slots ui_v1 has art of its own for) is named in the spec as an attr path on the group’s default_module (e.g. 'textures.back_icon') and emitted into the generated UIAssetSet.__init__ as plain attribute access on the wrapper module. Every default is thus a real typed expression: rename an asset and the wrapper attribute disappears, so mypy fails on the regenerated module; add a slot with no default and every UIAssetSet(...) construction is missing an argument, so mypy fails at the app-mode that builds one. Nothing can drift without a type error, and no raw logical-path string ever reaches runtime.

Slots with defaults are also what makes restyling cheap: an app-mode constructs a set supplying only the required slots and then reassigns whichever defaulted attrs it wants changed, so new slots never break existing skins.

class batools.ui_assets.Group(name: str, doc: str, slots: list[Slot] = <factory>, default_module: str | None = None)[source]

Bases: object

A logical grouping of slots, emitted as its own nested type.

all_defaulted() → bool[source]

Whether every slot here has fallback art.

Only then can the group as a whole be omitted when building a set.

clsname() → str[source]

The generated class/struct name for this group.

default_module: str | None = None

Wrapper module the slots’ default paths hang off, imported by the generated module. Required if any slot has a default.

doc: str

One-line description of what this group covers.

name: str

Field name of the group on the top-level set (snake_case).

slots: list[Slot]

The slots it holds.

class batools.ui_assets.Kind(*values)[source]

Bases: Enum

Kind of engine asset a slot holds.

MESH = 'mesh'
SOUND = 'sound'
TEXTURE = 'texture'
class batools.ui_assets.Slot(name: str, kind: Kind, doc: str, default: str | None = None, default_module: str | None = None)[source]

Bases: object

One asset the ui_v1 widget layer needs supplied to it.

default: str | None = None

Attr path of this slot’s fallback art on the group’s default_module, e.g. 'textures.window_hsmall_vmed'. The generator emits it as plain attribute access, so a wrong path is a type error in the generated module rather than a runtime surprise. Deliberately not derived from name – a slot and the asset backing it are free to be named differently.

None means ui_v1 has no art of its own for this slot, so whoever builds the set must supply it.

default_module: str | None = None

Wrapper module default hangs off, when it is not the group’s own (a group’s slots usually share a package, but need not: the standin character icon lives with the characters, the mask it is cut with with the menu art).

doc: str

One-line description, emitted as docs on both sides.

kind: Kind

What kind of asset this slot holds.

name: str

Field name on both sides (snake_case); also the C++ member name.

batools.ui_assets.TOP_CLASS = 'UIAssetSet'

Top-level Python class / C++ struct name.

class batools.ui_assets.UIAssetSpec(groups: list[Group] = <factory>)[source]

Bases: object

The complete set of assets ui_v1 needs supplied to it.

groups: list[Group]
batools.ui_assets.generate_cpp(projroot: str, out_path: str) → None[source]

Emit the C++ struct header or the unpacker include.

batools.ui_assets.generate_python(projroot: str, out_path: str) → None[source]

Emit the UIAssetSet class and the apply function app-modes call.

batools.ui_assets.load_spec(projroot: str) → UIAssetSpec[source]

Import the spec module and return its spec.

batools.vanillacompletions module

Generate a JSON completion index for the vanilla Ballistica API.

Driven by make vanilla_completions via lazybuild. Output lands at build/vanilla_completions.json and is consumed by sibling projects (e.g. bamaster’s workspace code editor) that copy it into their own source trees.

We rely on the same dummy-modules + PYTHONPATH setup the sphinx docs generator uses (see batools.docs.generate_sphinx_docs): import the real runtime Python under src/assets/ba_data/python with the C-extension stubs from build/dummymodules available, then walk symbols with inspect.getmembers.

batools.vanillacompletions.generate_vanilla_completions(projroot: str) → None[source]

Top-level entry: ensure dummy-modules and dispatch the worker.

Runs as the orchestrator side. Keeps the orchestrator’s own interpreter clean (no imports of babase/etc. here) by shelling out to a worker subprocess that does the real introspection work.

batools.version module

Util to get ballisticakit versions.

class batools.version.Mode(*values)[source]

Bases: Enum

Mode we can run this command in.

API = 'api'
BUILD = 'build'
INFO = 'info'
VERSION = 'version'
batools.version.get_current_api_version(projroot: str = '') → int[source]

Pull current api version from the project.

batools.version.get_current_version(projroot: str = '') → tuple[str, int][source]

Pull current version and build_number from the project.

These live canonically in pconfig/projectconfig.json (version + engine_build_number) – the one file guaranteed present in every build and tooling context (pcommand itself locates PROJROOT by it), so reading them here works even in the curated cloudshell envs that sync only a subset of the tree. make inc keeps the embedded copies in ballistica.cc / baenv.py / gradle / xcode in sync with these.

batools.version.run(projroot: str, args: list[str]) → None[source]

Main entry point for this script.

batools.winsymbols module

Windows debug-symbol (.pdb) lookup by CodeView key.

Windows binaries carry a CodeView record naming the exact pdb built alongside them: a GUID plus an ‘age’ counter, emitted by the linker and changing whenever the binary changes. Debuggers match pdbs to binaries on that key, and – crucially – a minidump records it for every loaded module. So a crash dump states exactly which pdb each of its modules needs, even for a build you have never had locally.

That is what lets us archive third-party symbols (notably ANGLE’s libGLESv2.pdb) and find the right one for an arbitrary crash. Our own prefab binaries are instead archived by exe content hash, which works because you fetch those for a binary you already have; see prefabsymbols.py. Both key kinds go through the same lookup endpoint.

batools.winsymbols.codeview_key_from_pe(path: str) → tuple[str, str] | None[source]

Return (pdb_name, key) for a PE file, or None if absent.

pdb_name is the base name recorded in the binary – the name a debugger will look for, which is not always what the pdb file was called on disk (see prefab-symbols.md’s CodeView gotcha).

batools.winsymbols.codeview_keys_from_minidump(path: str) → list[tuple[str, str, str]][source]

Return (module_path, pdb_name, key) for a dump’s modules.

Paths are as recorded in the dump (so callers can tell an OS dll from ours – see is_system_module_path()).

Modules without a CodeView record (plenty of system dlls ship without one here) are skipped rather than reported, since there is nothing to look up for them.

batools.winsymbols.fetch_symbols_for_key(key: str, host: str) → tuple[str, bytes] | None[source]

Fetch a symbols file by lookup key; None if the server has none.

Shared by every caller so the endpoint, error handling and the ‘not published / expired’ case stay in one place.

batools.winsymbols.is_system_module_path(path: str) → bool[source]

Whether a dump module path looks like an OS-supplied binary.

Used to skip lookups we know will miss: a dump lists ~125 modules, nearly all of them Windows’ own dlls, and querying each one turns a two-second job into a two-minute one for no gain.

batools.wslbuild module

Sanity checks for Visual Studio builds driven from WSL.

These run before we hand off to MSBuild. Everything here exists to turn an environment problem into a clear explanation, since the raw failures (a case-sensitivity compile error, a ‘not found’ from the shell) say nothing about what to fix.

batools.wslbuild.check_msbuild(msbuild_path: str) → None[source]

Make sure the msbuild we’re about to invoke actually exists.

Without this, a missing (or differently-placed) Visual Studio just gets us an inscrutable /bin/sh: MSBuild.exe: not found, which tells the user nothing about what to install.

batools.wslbuild.check_win_drive() → None[source]

Make sure we’re building on a windows drive.

batools.xcodeproject module

XCode related functionality.

class batools.xcodeproject.Updater(projroot: str, path: str, existing_data: str, sources: list[str], projname: str)[source]

Bases: object

Does the thing.

add_paths(parent_pbxgrp: Any) → None[source]

Do the thing.

hash_inputs(sources: list[str], include_us: bool, project_source: str) → str[source]

Make a simple hash based on inputs to the project.

mod_app_delegate_mm() → None[source]

Set per-file compiler flags.

project: Any
run(force: bool = False) → str[source]

Do the thing.

batools.xcodeproject.update_xcode_project(projroot: str, path: str, existing_data: str, all_source_files: list[str], projname: str, force: bool = False) → str[source]

Given an xcode project, update it for the current set of files.