#!/usr/bin/env python
import os
import sys
from methods import print_error
libname = "hyperswarm_peer"
projectdir = "project"
localEnv = Environment(tools=["default"], PLATFORM="")
customs = ["custom.py"]
customs = [os.path.abspath(path) for path in customs]
opts = Variables(customs, ARGUMENTS)
opts.Update(localEnv)
Help(opts.GenerateHelpText(localEnv))
env = localEnv.Clone()
if not (os.path.isdir("godot-cpp") and os.listdir("godot-cpp")):
print_error("""godot-cpp is not available within this folder.
Either clone it as a submodule or symlink it:
git submodule update --init --recursive
# or
ln -s /path/to/godot-cpp godot-cpp""")
sys.exit(1)
env = SConscript("godot-cpp/SConstruct", {"env": env, "customs": customs})
# Allow overriding CC/CXX via environment variables (e.g. for Zig cross-compilation)
if os.environ.get("CC"):
env["CC"] = os.environ["CC"]
if os.environ.get("CXX"):
env["CXX"] = os.environ["CXX"]
# Zig doesn't recognize .os extension — use .o for shared object files
if "zig" in os.environ.get("CC", ""):
env["SHOBJSUFFIX"] = ".o"
env.Append(CPPPATH=["src/"])
# liboverlay is header-only and self-contained; it is included by path so it can
# still be lifted out of this repo by moving the directory.
env.Append(CPPPATH=["liboverlay/include"])
# libcompact — src/compact_encoding.h forwards its byte work here, so the
# GDExtension carries it too. Symlinked into thirdparty/ the same way libudx is.
compact_path = "thirdparty/libcompact"
env.Append(CPPPATH=[os.path.join(compact_path, "include"),
os.path.join("thirdparty", "libutf", "include")])
compact_sources = Glob(os.path.join(compact_path, "src", "*.c"))
# libudx — link against the holepunchto UDP transport
udx_path = "thirdparty/libudx"
env.Append(CPPPATH=[os.path.join(udx_path, "include")])
# libudx source files — compile directly (static linking)
# Use glob to pick up all .c files; exclude platform-specific I/O we don't need.
_udx_all = Glob(os.path.join(udx_path, "src", "*.c"))
# KEYED ON THE TARGET, NOT THE HOST. This asked sys.platform — the platform of
# the Python running scons — which answers the wrong question the moment the two
# differ. Cross-compiling to Windows from Linux, it would skip io_win.c and build
# io_posix.c INTO A WINDOWS BINARY: a udx that calls into POSIX socket APIs on a
# platform that has Winsock, discovered as a pile of link errors far from here, or
# worse, not discovered. env["platform"] is what scons was asked to build for, and
# is already what the rest of this file uses to name its output directory.
_udx_skip = {"io_win.c"} if env["platform"] != "windows" else {"io_posix.c"}
udx_sources = [f for f in _udx_all if os.path.basename(str(f)) not in _udx_skip]
# libuv (required by libudx) and libsodium (Noise: ChaCha20-Poly1305, Curve25519,
# BLAKE2b). On Linux both come from the system via pkg-config.
#
# There are no mingw packages for either, so a Windows cross build takes them from
# a prefix built by tools/build-mingw-deps.sh — same VERSIONS as the Linux host
# packages (libuv 1.48.0, libsodium 1.0.18), so a Windows-only fault cannot turn
# out to be a version difference.
#
# Still pkg-config, just pointed at that prefix: the .pc files carry libuv's
# Windows link line (psapi, user32, advapi32, iphlpapi, userenv, ws2_32, dbghelp,
# ole32, shell32), and a hand-maintained copy of that list would rot the first
# time libuv changed it. `libuv-static` rather than `libuv` because it resolves to
# `-l:libuv.a` — the shared build also installs an import library, and linking
# that would leave the DLL depending on a libuv.dll nobody ships.
if env["platform"] == "windows":
mingw_prefix = os.path.abspath("thirdparty/mingw-prefix")
if not os.path.isfile(os.path.join(mingw_prefix, "lib", "libsodium.a")):
print("ERROR: no mingw prefix at " + mingw_prefix)
print(" run tools/build-mingw-deps.sh to build libuv + libsodium for mingw")
Exit(1)
# env["ENV"], NOT os.environ: ParseConfig spawns pkg-config through SCons's
# own execution environment, which does not inherit changes made to
# os.environ. Setting the wrong one leaves pkg-config searching the system
# path, where it finds neither .pc file and silently contributes no flags —
# surfacing much later as "sodium.h: No such file or directory".
env["ENV"]["PKG_CONFIG_PATH"] = os.path.join(mingw_prefix, "lib", "pkgconfig")
env.ParseConfig("pkg-config --cflags --libs libuv-static libsodium")
# Without this, libsodium's headers declare its symbols __declspec(dllimport)
# and every one of them fails to link against the static archive.
env.Append(CPPDEFINES=["SODIUM_STATIC=1"])
else:
env.ParseConfig("pkg-config --cflags --libs libuv")
env.ParseConfig("pkg-config --cflags --libs libsodium")
# ---- Sanitizer build (opt-in, never shipped) -------------------------------
# `CC=gcc CXX=g++ scons platform=linux target=template_debug sanitize=address`
#
# This exists because a crash INSIDE the extension was otherwise unattributable:
# glibc reports "free(): double free detected in tcache 2" and aborts, gdb shows
# only stripped Godot frames, and the fault does not reproduce under gdb because
# it is timing-sensitive. Guessing at it is exactly the failure mode this repo's
# own postmortems warn about.
#
# Build with the SYSTEM compiler, not the zig toolchain in custom.py: the runtime
# has to match the libasan that gets preloaded into an uninstrumented Godot.
#
# ASAN_OPTIONS=detect_leaks=0 \
# LD_PRELOAD=$(gcc -print-file-name=libasan.so) \
# godot4 --headless --path project --script res://layer13_textures.gd
#
# The .so it produces OVERWRITES the normal one — rebuild without `sanitize=`
# before shipping or measuring anything.
san = ARGUMENTS.get("sanitize", "")
if san:
print("*** SANITIZER BUILD (-fsanitize=%s) — do not ship this .so ***" % san)
env.Append(CCFLAGS=["-fsanitize=" + san, "-fno-omit-frame-pointer", "-g"])
env.Append(LINKFLAGS=["-fsanitize=" + san])
sources = Glob("src/*.cpp") + udx_sources + compact_sources
if env["target"] in ["editor", "template_debug"]:
try:
doc_data = env.GodotCPPDocData("src/gen/doc_data.gen.cpp", source=Glob("doc_classes/*.xml"))
sources.append(doc_data)
except AttributeError:
print("Not including class reference as we're targeting a pre-4.3 baseline.")
suffix = env["suffix"].replace(".dev", "").replace(".universal", "")
lib_filename = "{}{}{}{}".format(
env.subst("$SHLIBPREFIX"), libname, suffix, env.subst("$SHLIBSUFFIX")
)
library = env.SharedLibrary(
"bin/{}/{}".format(env["platform"], lib_filename),
source=sources,
)
copy = env.Install("{}/bin/{}/".format(projectdir, env["platform"]), library)
default_args = [library, copy]
Default(*default_args)