Files
Cesarus85--mjolnir-vr/tools/ci_build.py
T
MrBruh a7c8eac379 Lucide speaker icons for voice chat
Voice chat's speaker and muted speaker are Lucide's volume-2 and volume-x (1.53.0, ISC: port/assets/icons/lucide, credited in AUTHORS.md, its licence shipped beside the game), drawn white into the menus' bitmap voice/speaker by tools/ce_menus.py and tinted, in place of the speaker made of rectangles. In the lobby, on the scoreboard and in the list of who talks, the icon is centred on the middle of the text's capitals (draw_unicode_string_capital_middle).
2026-10-09 17:11:42 +13:00

129 lines
5.9 KiB
Python

#!/usr/bin/env python3
"""Builds one native port in one configuration, as the GitHub workflow does
(.github/workflows/build.yml), and collects what it built into dist/:
python tools/ci_build.py linux debug
python tools/ci_build.py android release
Builds are portable (any x86-64 processor), so they run on other
computers. Debug builds skip link-time and profile-guided optimisation,
which only make the build slower; release builds use both, as a local
release build does (profile-guided optimisation needs clang 22 or later,
and is skipped with an older one). CI_COMPILER_LAUNCHER (ccache, say) is
passed on as --compiler-launcher. A build of the main branch gets the run's
number (HALO_BUILD_NUMBER), which its release is named after and the
self-updater compares.
"""
import argparse
import os
import shutil
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
# what each port's build leaves, and what goes into dist/
OUTPUTS = {
"linux": ["build/linux/halo"],
"windows": ["build/windows/halo.exe", "build/windows/SDL3.dll"],
"android": [], # the APK, below
}
APKS = {
"debug": "port/android/app/build/outputs/apk/debug/app-debug.apk",
"release": "port/android/app/build/outputs/apk/release/app-release.apk",
}
def run(command, cwd=ROOT):
print("+", " ".join(str(part) for part in command), flush=True)
subprocess.run([str(part) for part in command], cwd=cwd, check=True)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument("platform", choices=sorted(OUTPUTS))
parser.add_argument("config", choices=["debug", "release"])
args = parser.parse_args()
configure = [sys.executable, "configure.py", "--portable"]
if args.config == "release":
configure.append("--release")
else:
configure += ["--lto=off", "--pgo=off"]
launcher = os.environ.get("CI_COMPILER_LAUNCHER")
if launcher:
configure += ["--compiler-launcher", launcher]
# a build of main knows its number, which names its release (build-<n>),
# for the self-updater (port/linux/src/updater.c, and the Android app);
# other builds have none, and never look for updates
if os.environ.get("GITHUB_REF") == "refs/heads/main" and os.environ.get("GITHUB_RUN_NUMBER", "").isdigit():
os.environ["HALO_BUILD_NUMBER"] = os.environ["GITHUB_RUN_NUMBER"]
print(f"build number {os.environ['HALO_BUILD_NUMBER']}", flush=True)
run(configure)
if args.platform == "android":
# the native part, then the app around it (Gradle's variant of the
# same name: release is signed with the debug key, not debuggable)
run(["ninja", "android"])
gradlew = "gradlew.bat" if os.name == "nt" else "./gradlew"
run([gradlew, "--console=plain", "-q", f"assemble{args.config.capitalize()}"], cwd=ROOT / "port/android")
outputs = [APKS[args.config]]
else:
run(["ninja", args.platform])
outputs = OUTPUTS[args.platform]
dist = ROOT / "dist" / f"halo-{args.platform}-{args.config}"
if dist.exists():
shutil.rmtree(dist)
dist.mkdir(parents=True)
for output in outputs:
shutil.copy2(ROOT / output, dist)
print(f"{output} -> {dist.relative_to(ROOT)}", flush=True)
if args.platform == "windows":
# the symbols of halo.exe and SDL3.dll, apart (players do not need
# them): the workflow uploads them to Sentry, which turns the crash
# reports' minidumps into function names and lines
# (port/windows/src/win32_crash.c), and tools/symbolize_crash.py
# reads debug.txt's crash lines with them
symbols = ROOT / "dist" / f"halo-windows-{args.config}-symbols"
if symbols.exists():
shutil.rmtree(symbols)
symbols.mkdir(parents=True)
for pdb in [ROOT / "build/windows/halo.pdb", *sorted((ROOT / "build/windows/third_party").glob("SDL3-*/lib/x86/SDL3.pdb"))]:
shutil.copy2(pdb, symbols)
print(f"{pdb.relative_to(ROOT)} -> {symbols.relative_to(ROOT)}", flush=True)
# the disc image readers (port/linux/src/xiso.c, and the Android app's
# XisoExtractor.java) follow extract-xiso, whose license asks binaries
# to carry its notice
shutil.copy2(ROOT / "port/third_party/extract-xiso/LICENSE.TXT", dist / "extract-xiso-LICENSE.txt")
if args.platform == "linux":
# the self-updater's TLS (port/third_party/mbedtls), whose Apache
# license asks the same
shutil.copy2(ROOT / "port/third_party/mbedtls/LICENSE", dist / "mbedtls-LICENSE.txt")
# internet play's UPnP (port/third_party/miniupnpc), in every build,
# whose BSD license asks binaries to carry its notice
shutil.copy2(ROOT / "port/third_party/miniupnpc/LICENSE", dist / "miniupnpc-LICENSE.txt")
# the text's fonts (port/assets/fonts), embedded in every build, whose
# SIL Open Font License asks each copy to carry it
shutil.copy2(ROOT / "port/assets/fonts/Overpass-OFL.txt", dist / "Overpass-OFL.txt")
# the menus' XML parser (port/third_party/expat), in every build, whose
# MIT license asks copies to carry its notice
shutil.copy2(ROOT / "port/third_party/expat/COPYING", dist / "expat-COPYING.txt")
# voice chat's codec (port/third_party/opus), in every build, whose BSD
# license asks binaries to carry its notice
shutil.copy2(ROOT / "port/third_party/opus/COPYING", dist / "opus-COPYING.txt")
# voice chat's speaker icons (port/assets/icons/lucide, drawn into the
# menus' bitmaps), whose ISC license asks copies to carry its notice
shutil.copy2(ROOT / "port/assets/icons/lucide/LICENSE", dist / "lucide-LICENSE.txt")
# internet play's MQTT brokers, a file beside the game (network.brokers_file;
# Android's APK has its own copy)
if args.platform != "android":
shutil.copy2(ROOT / "port/assets/network/brokers.txt", dist / "brokers.txt")
return 0
if __name__ == "__main__":
sys.exit(main())