mirror of
https://github.com/ValveSoftware/GameNetworkingSockets.git
synced 2026-05-29 16:20:34 +00:00
218 lines
7.1 KiB
Python
Executable File
218 lines
7.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Build and test a single GameNetworkingSockets configuration."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import os
|
|
import shlex
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(
|
|
description="Configure/build/test one explicit configuration.")
|
|
|
|
parser.add_argument("--compiler", choices=["gcc", "clang"], required=True)
|
|
parser.add_argument("--build-dir", required=True)
|
|
parser.add_argument("--build-type", default="RelWithDebInfo")
|
|
parser.add_argument("--sanitizer", choices=["none", "asan", "ubsan", "tsan"], default="none")
|
|
parser.add_argument("--generator", default="Ninja")
|
|
|
|
parser.add_argument("--use-webrtc", action="store_true")
|
|
parser.add_argument("--lto", action="store_true")
|
|
parser.add_argument("--crypto", choices=["default", "libsodium"], default="default")
|
|
parser.add_argument("--crypto25519", choices=["default", "Reference", "libsodium"], default="default")
|
|
|
|
parser.add_argument("--run-tests", action="store_true")
|
|
parser.add_argument(
|
|
"--tests",
|
|
nargs="*",
|
|
default=None,
|
|
help="Test command specs like test_crypto or test_connection:suite-quick",
|
|
)
|
|
parser.add_argument("--targets", nargs="*", default=[])
|
|
|
|
parser.add_argument("--phase", choices=["configure", "build", "test", "all"], default="all")
|
|
parser.add_argument("--no-clean", action="store_true")
|
|
parser.add_argument("--dry-run", action="store_true")
|
|
parser.add_argument("--cmake-arg", action="append", default=[])
|
|
|
|
return parser.parse_args()
|
|
|
|
|
|
def shell_join(cmd: list[str]) -> str:
|
|
return shlex.join(cmd)
|
|
|
|
|
|
def run_cmd(cmd: list[str], env: dict[str, str], cwd: Path, dry_run: bool) -> None:
|
|
print(f"[cmd] {shell_join(cmd)}", flush=True)
|
|
if dry_run:
|
|
return
|
|
subprocess.run(cmd, env=env, cwd=str(cwd), check=True)
|
|
|
|
|
|
def compiler_env(compiler: str, base_env: dict[str, str]) -> dict[str, str]:
|
|
env = dict(base_env)
|
|
if compiler == "gcc":
|
|
env["CC"] = "gcc"
|
|
env["CXX"] = "g++"
|
|
else:
|
|
env["CC"] = "clang"
|
|
env["CXX"] = "clang++"
|
|
return env
|
|
|
|
|
|
def parse_test_specs(specs: list[str] | None) -> list[list[str]]:
|
|
if specs is None:
|
|
specs = ["test_crypto", "test_connection:suite-quick"]
|
|
|
|
commands: list[list[str]] = []
|
|
for spec in specs:
|
|
parts = spec.split(":")
|
|
exe = parts[0]
|
|
args = parts[1:]
|
|
commands.append([exe, *args])
|
|
return commands
|
|
|
|
|
|
def main() -> int:
|
|
# In CI, stdout is usually not a TTY, so force line-buffering for timely logs.
|
|
if hasattr(sys.stdout, "reconfigure"):
|
|
sys.stdout.reconfigure(line_buffering=True)
|
|
|
|
args = parse_args()
|
|
|
|
repo_root = Path(__file__).resolve().parents[1]
|
|
env = compiler_env(args.compiler, os.environ)
|
|
|
|
if args.sanitizer == "tsan":
|
|
supp = repo_root / "tests" / "tsan.supp"
|
|
env["TSAN_OPTIONS"] = f"suppressions={supp}"
|
|
|
|
cmake_args = [
|
|
"cmake",
|
|
"-S",
|
|
".",
|
|
"-B",
|
|
args.build_dir,
|
|
"-G",
|
|
args.generator,
|
|
"-DCMAKE_CXX_COMPILER_LAUNCHER=ccache",
|
|
"-DCMAKE_C_COMPILER_LAUNCHER=ccache",
|
|
"-DBUILD_TESTS=ON",
|
|
"-DBUILD_EXAMPLES=ON",
|
|
"-DENABLE_ICE=ON",
|
|
"-DWERROR=ON",
|
|
f"-DCMAKE_BUILD_TYPE={args.build_type}",
|
|
]
|
|
|
|
if args.sanitizer == "asan":
|
|
cmake_args.append("-DSANITIZE_ADDRESS:BOOL=ON")
|
|
elif args.sanitizer == "ubsan":
|
|
cmake_args.append("-DSANITIZE_UNDEFINED:BOOL=ON")
|
|
elif args.sanitizer == "tsan":
|
|
cmake_args.append("-DSANITIZE_THREAD:BOOL=ON")
|
|
|
|
if args.use_webrtc:
|
|
cmake_args.append("-DUSE_STEAMWEBRTC=ON")
|
|
if args.lto:
|
|
cmake_args.append("-DLTO=ON")
|
|
if args.crypto != "default":
|
|
cmake_args.append(f"-DUSE_CRYPTO={args.crypto}")
|
|
if args.crypto25519 != "default":
|
|
cmake_args.append(f"-DUSE_CRYPTO25519={args.crypto25519}")
|
|
|
|
cmake_args.extend(args.cmake_arg)
|
|
|
|
build_cmd = ["cmake", "--build", args.build_dir, "--", "-v"]
|
|
if args.targets:
|
|
build_cmd.extend(args.targets)
|
|
|
|
test_cmds: list[list[str]] = []
|
|
test_cwd = repo_root / args.build_dir / "bin"
|
|
if args.run_tests:
|
|
for test_parts in parse_test_specs(args.tests):
|
|
test_prog = test_parts[0]
|
|
test_args = test_parts[1:]
|
|
test_path = str((test_cwd / test_prog).resolve())
|
|
if test_prog.endswith(".py"):
|
|
test_cmds.append(["python3", test_path, *test_args])
|
|
else:
|
|
test_cmds.append([test_path, *test_args])
|
|
|
|
# If ccache isn't available in a local environment, remove launcher flags.
|
|
if shutil.which("ccache") is None:
|
|
cmake_args = [
|
|
arg
|
|
for arg in cmake_args
|
|
if not arg.startswith("-DCMAKE_CXX_COMPILER_LAUNCHER=")
|
|
and not arg.startswith("-DCMAKE_C_COMPILER_LAUNCHER=")
|
|
]
|
|
|
|
repro_cmd = ["python3", ".github/run-single-config.py", *sys.argv[1:]]
|
|
|
|
print("\n=== Single Config Build ===")
|
|
print(f"repo_root = {repo_root}")
|
|
print(f"compiler = {args.compiler} (CC={env['CC']} CXX={env['CXX']})")
|
|
print(f"build_dir = {args.build_dir}")
|
|
print(f"build_type = {args.build_type}")
|
|
print(f"sanitizer = {args.sanitizer}")
|
|
print(f"use_webrtc = {int(args.use_webrtc)}")
|
|
print(f"lto = {int(args.lto)}")
|
|
print(f"crypto = {args.crypto}")
|
|
print(f"crypto25519 = {args.crypto25519}")
|
|
print(f"phase = {args.phase}")
|
|
print(f"test_cwd = {test_cwd}")
|
|
print(f"targets = {' '.join(args.targets) if args.targets else '(all default targets)'}")
|
|
print(f"run_tests = {int(args.run_tests)}")
|
|
if args.run_tests:
|
|
print("tests = " + ", ".join(shell_join(x) for x in test_cmds))
|
|
|
|
print("\nRepro command:")
|
|
print(shell_join(repro_cmd))
|
|
|
|
print("\nConfigure command:")
|
|
print(shell_join(cmake_args))
|
|
|
|
print("\nBuild command:")
|
|
print(shell_join(build_cmd))
|
|
|
|
if test_cmds:
|
|
print("\nTest commands:")
|
|
for cmd in test_cmds:
|
|
print(shell_join(cmd))
|
|
|
|
if args.no_clean and args.phase in ("configure", "all"):
|
|
pass
|
|
elif args.phase in ("configure", "all"):
|
|
build_dir = repo_root / args.build_dir
|
|
if build_dir.exists() and not args.dry_run:
|
|
shutil.rmtree(build_dir)
|
|
|
|
try:
|
|
if args.phase in ("configure", "all"):
|
|
run_cmd(cmake_args, env=env, cwd=repo_root, dry_run=args.dry_run)
|
|
|
|
if args.phase in ("build", "all"):
|
|
run_cmd(build_cmd, env=env, cwd=repo_root, dry_run=args.dry_run)
|
|
|
|
if args.phase in ("test", "all"):
|
|
for cmd in test_cmds:
|
|
run_cmd(cmd, env=env, cwd=test_cwd, dry_run=args.dry_run)
|
|
except subprocess.CalledProcessError as exc:
|
|
print("\nBuild/test failed.")
|
|
print("Re-run exactly:")
|
|
print(shell_join(repro_cmd))
|
|
return exc.returncode
|
|
|
|
print("\nSingle-config build completed successfully.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|