import inspect
import os
import shutil
import sys
from contextlib import contextmanager, suppress
from invoke import call, task
MEDIASOUP_BUILDTYPE = os.getenv("MEDIASOUP_BUILDTYPE") or "Release"
WORKER_DIR = os.path.dirname(
os.path.abspath(inspect.getframeinfo(inspect.currentframe()).filename)
)
MEDIASOUP_OUT_DIR = os.getenv("MEDIASOUP_OUT_DIR") or f"{WORKER_DIR}/out"
MEDIASOUP_INSTALL_DIR = (
os.getenv("MEDIASOUP_INSTALL_DIR") or f"{MEDIASOUP_OUT_DIR}/{MEDIASOUP_BUILDTYPE}"
)
BUILD_DIR = os.getenv("BUILD_DIR") or f"{MEDIASOUP_INSTALL_DIR}/build"
PIP_INVOKE_DIR = f"{MEDIASOUP_OUT_DIR}/pip_invoke"
PIP_MESON_NINJA_DIR = f"{MEDIASOUP_OUT_DIR}/pip_meson_ninja"
PIP_RUFF_DIR = f"{MEDIASOUP_OUT_DIR}/pip_ruff"
NUM_CORES = (
len(os.sched_getaffinity(0)) if hasattr(os, "sched_getaffinity") else os.cpu_count()
)
PYTHON = os.getenv("PYTHON") or sys.executable
MESON = os.getenv("MESON") or f"{PIP_MESON_NINJA_DIR}/bin/meson"
MESON_VERSION = os.getenv("MESON_VERSION") or "1.9.1"
MESON_ARGS = (
os.getenv("MESON_ARGS")
if os.getenv("MESON_ARGS")
else "--vsenv"
if os.name == "nt"
else ""
)
NINJA_VERSION = os.getenv("NINJA_VERSION") or "1.10.2.4"
RUFF_VERSION = os.getenv("RUFF_VERSION") or "0.15.15"
NPM = os.getenv("NPM") or "npm"
DOCKER = os.getenv("DOCKER") or "docker"
PTY_SUPPORTED = os.name != "nt" and sys.stdout.isatty()
SHELL = "/bin/sh" if os.name != "nt" else None
os.environ["PYTHONDONTWRITEBYTECODE"] = "true"
if os.name == "nt":
os.environ["NINJA"] = f"{PIP_MESON_NINJA_DIR}/bin/ninja.exe"
else:
os.environ["NINJA"] = f"{PIP_MESON_NINJA_DIR}/bin/ninja"
PYTHONPATH = os.getenv("PYTHONPATH") or ""
if os.name == "nt":
os.environ["PYTHONPATH"] = (
f"{PIP_INVOKE_DIR};{PIP_MESON_NINJA_DIR};{PIP_RUFF_DIR};{PYTHONPATH}"
)
else:
os.environ["PYTHONPATH"] = (
f"{PIP_INVOKE_DIR}:{PIP_MESON_NINJA_DIR}:{PIP_RUFF_DIR}:{PYTHONPATH}"
)
@contextmanager
def cd_worker():
original_dir = os.getcwd()
os.chdir(WORKER_DIR)
try:
yield
finally:
os.chdir(original_dir)
@task
def meson_ninja(ctx):
if os.path.isfile(MESON):
return
try:
ctx.run(
f'"{PYTHON}" -m pip install --system --upgrade --no-user --target "{PIP_MESON_NINJA_DIR}" pip setuptools',
echo=True,
hide=True,
shell=SHELL,
)
except Exception:
ctx.run(
f'"{PYTHON}" -m pip install --upgrade --no-user --target "{PIP_MESON_NINJA_DIR}" pip setuptools',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
pip_build_binaries = (
"--no-binary :all:"
if os.path.isfile("/etc/NIXOS") or os.path.isdir("/etc/guix")
else ""
)
ctx.run(
f'"{PYTHON}" -m pip install --upgrade --no-user --target "{PIP_MESON_NINJA_DIR}" {pip_build_binaries} meson=={MESON_VERSION} ninja=={NINJA_VERSION}',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
@task(pre=[meson_ninja])
def setup(ctx, meson_args=MESON_ARGS):
if MEDIASOUP_BUILDTYPE == "Release":
with cd_worker():
ctx.run(
f'"{MESON}" setup --prefix "{MEDIASOUP_INSTALL_DIR}" --bindir "" --libdir "" --buildtype release -Db_ndebug=true {meson_args} "{BUILD_DIR}"',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
elif MEDIASOUP_BUILDTYPE == "Debug":
with cd_worker():
ctx.run(
f'"{MESON}" setup --prefix "{MEDIASOUP_INSTALL_DIR}" --bindir "" --libdir "" --buildtype debug {meson_args} "{BUILD_DIR}"',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
else:
with cd_worker():
ctx.run(
f'"{MESON}" setup --prefix "{MEDIASOUP_INSTALL_DIR}" --bindir "" --libdir "" --buildtype {MEDIASOUP_BUILDTYPE} -Db_ndebug=if-release {meson_args} "{BUILD_DIR}"',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
@task
def clean(ctx):
shutil.rmtree(MEDIASOUP_INSTALL_DIR, ignore_errors=True)
@task
def clean_build(ctx):
shutil.rmtree(BUILD_DIR, ignore_errors=True)
@task
def clean_pip(ctx):
shutil.rmtree(PIP_MESON_NINJA_DIR, ignore_errors=True)
shutil.rmtree(PIP_RUFF_DIR, ignore_errors=True)
@task(pre=[meson_ninja])
def clean_subprojects(ctx):
with cd_worker():
ctx.run(
f'"{MESON}" subprojects purge --include-cache --confirm',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
@task
def clean_all(ctx):
with cd_worker():
with suppress(Exception):
ctx.run(
f'"{MESON}" subprojects purge --include-cache --confirm',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
shutil.rmtree(MEDIASOUP_OUT_DIR, ignore_errors=True)
shutil.rmtree("include/FBS", ignore_errors=True)
@task(pre=[meson_ninja])
def check_wrap_status(ctx):
with cd_worker():
ctx.run(f'"{MESON}" wrap status', echo=True, pty=PTY_SUPPORTED, shell=SHELL)
@task(pre=[meson_ninja])
def update_wrap_file(ctx, subproject):
with cd_worker():
ctx.run(
f'"{MESON}" subprojects update --reset {subproject}',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
@task(pre=[setup])
def flatc(ctx):
with cd_worker():
ctx.run(
f'"{MESON}" compile -C "{BUILD_DIR}" flatbuffers-generator',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
@task(pre=[setup, flatc], default=True)
def mediasoup_worker(ctx):
if os.getenv("MEDIASOUP_WORKER_BIN"):
print(
"skipping mediasoup-worker compilation due to the existence of the MEDIASOUP_WORKER_BIN environment variable"
)
return
with cd_worker():
ctx.run(
f'"{MESON}" compile -C "{BUILD_DIR}" -j {NUM_CORES} mediasoup-worker',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
with cd_worker():
ctx.run(
f'"{MESON}" install -C "{BUILD_DIR}" --no-rebuild --tags mediasoup-worker',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
@task(pre=[setup, flatc])
def libmediasoup_worker(ctx):
with cd_worker():
ctx.run(
f'"{MESON}" compile -C "{BUILD_DIR}" -j {NUM_CORES} libmediasoup-worker',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
with cd_worker():
ctx.run(
f'"{MESON}" install -C "{BUILD_DIR}" --no-rebuild --tags libmediasoup-worker',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
@task(pre=[setup, flatc])
def xcode(ctx):
with cd_worker():
ctx.run(
f'"{MESON}" setup --buildtype {MEDIASOUP_BUILDTYPE.lower()} --backend xcode "{MEDIASOUP_OUT_DIR}/xcode"',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
def _install_ruff(ctx):
if os.path.isdir(PIP_RUFF_DIR):
return
ctx.run(
f'"{PYTHON}" -m pip install --upgrade --no-user --target="{PIP_RUFF_DIR}" ruff=={RUFF_VERSION}',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
@task
def lint(ctx):
with cd_worker():
ctx.run(
f'"{NPM}" run lint --prefix scripts/',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
_install_ruff(ctx)
with cd_worker():
ctx.run(f'"{PYTHON}" -m ruff check', echo=True, pty=PTY_SUPPORTED, shell=SHELL)
ctx.run(
f'"{PYTHON}" -m ruff format --check',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
@task
def format(ctx):
with cd_worker():
ctx.run(
f'"{NPM}" run format --prefix scripts/',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
_install_ruff(ctx)
with cd_worker():
ctx.run(
f'"{PYTHON}" -m ruff check --fix', echo=True, pty=PTY_SUPPORTED, shell=SHELL
)
ctx.run(f'"{PYTHON}" -m ruff format', echo=True, pty=PTY_SUPPORTED, shell=SHELL)
@task(pre=[call(setup, meson_args=MESON_ARGS + " -Dms_build_tests=true"), flatc])
def tidy(ctx):
with cd_worker():
ctx.run(
f'"{NPM}" run tidy --prefix scripts/',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
@task(pre=[call(setup, meson_args=MESON_ARGS + " -Dms_build_tests=true"), flatc])
def tidy_fix(ctx):
with cd_worker():
ctx.run(
f'"{NPM}" run tidy:fix --prefix scripts/',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
@task(pre=[call(setup, meson_args=MESON_ARGS + " -Dms_build_tests=true"), flatc])
def test(ctx):
with cd_worker():
ctx.run(
f'"{MESON}" compile -C "{BUILD_DIR}" -j {NUM_CORES} mediasoup-worker-test',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
with cd_worker():
ctx.run(
f'"{MESON}" install -C "{BUILD_DIR}" --no-rebuild --tags mediasoup-worker-test',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
mediasoup_worker_test = (
"mediasoup-worker-test.exe" if os.name == "nt" else "mediasoup-worker-test"
)
mediasoup_test_tags = os.getenv("MEDIASOUP_TEST_TAGS") or ""
with cd_worker():
ctx.run(
f'"{BUILD_DIR}/{mediasoup_worker_test}" --invisibles --colour-mode=ansi {mediasoup_test_tags}',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
@task(
pre=[
call(
setup,
meson_args=MESON_ARGS
+ " -Dms_build_tests=true -Db_sanitize=address -Db_lundef=false",
),
flatc,
]
)
def test_asan_address(ctx):
with cd_worker():
ctx.run(
f'"{MESON}" compile -C "{BUILD_DIR}" -j {NUM_CORES} mediasoup-worker-test-asan-address',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
with cd_worker():
ctx.run(
f'"{MESON}" install -C "{BUILD_DIR}" --no-rebuild --tags mediasoup-worker-test-asan-address',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
mediasoup_test_tags = os.getenv("MEDIASOUP_TEST_TAGS") or ""
with cd_worker():
ctx.run(
f'"{BUILD_DIR}/mediasoup-worker-test-asan-address" --invisibles {mediasoup_test_tags}',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
env={
**os.environ,
"ASAN_OPTIONS": "halt_on_error=1:print_stacktrace=1:detect_leaks=1:symbolize=1:detect_stack_use_after_return=1:strict_init_order=1:check_initialization_order=1:detect_container_overflow=1",
},
)
@task(
pre=[
call(
setup,
meson_args=MESON_ARGS
+ " -Dms_build_tests=true -Db_sanitize=undefined -Db_lundef=false",
),
flatc,
]
)
def test_asan_undefined(ctx):
with cd_worker():
ctx.run(
f'"{MESON}" compile -C "{BUILD_DIR}" -j {NUM_CORES} mediasoup-worker-test-asan-undefined',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
with cd_worker():
ctx.run(
f'"{MESON}" install -C "{BUILD_DIR}" --no-rebuild --tags mediasoup-worker-test-asan-undefined',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
mediasoup_test_tags = os.getenv("MEDIASOUP_TEST_TAGS") or ""
with cd_worker():
ctx.run(
f'"{BUILD_DIR}/mediasoup-worker-test-asan-undefined" --invisibles {mediasoup_test_tags}',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
env={
**os.environ,
"UBSAN_OPTIONS": "halt_on_error=1:print_stacktrace=1:suppressions=ubsan_suppressions.txt",
},
)
@task(
pre=[
call(
setup,
meson_args=MESON_ARGS
+ " -Dms_build_fuzzer=true -Db_sanitize=address -Db_lundef=false",
),
flatc,
]
)
def fuzzer(ctx):
with cd_worker():
ctx.run(
f'"{MESON}" compile -C "{BUILD_DIR}" -j {NUM_CORES} mediasoup-worker-fuzzer',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
with cd_worker():
ctx.run(
f'"{MESON}" install -C "{BUILD_DIR}" --no-rebuild --tags mediasoup-worker-fuzzer',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
@task
def fuzzer_run_all(ctx):
with cd_worker():
ctx.run(
f'LSAN_OPTIONS=verbosity=1:log_threads=1 "{BUILD_DIR}/mediasoup-worker-fuzzer" -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus deps/webrtc-fuzzer-corpora/corpora/stun-corpus deps/webrtc-fuzzer-corpora/corpora/rtp-corpus deps/webrtc-fuzzer-corpora/corpora/rtcp-corpus',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
@task
def docker(ctx):
if os.getenv("DOCKER_NO_CACHE") == "true":
with cd_worker():
ctx.run(
f'"{DOCKER}" build -f Dockerfile --no-cache --tag mediasoup/docker:latest .',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
else:
with cd_worker():
ctx.run(
f'"{DOCKER}" build -f Dockerfile --tag mediasoup/docker:latest .',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
@task
def docker_run(ctx):
with cd_worker():
ctx.run(
f'"{DOCKER}" run --name=mediasoupDocker -it --rm --privileged --cap-add SYS_PTRACE -v "{WORKER_DIR}/../:/foo bar/mediasoup" mediasoup/docker:latest',
echo=True,
pty=True, shell=SHELL,
)
@task
def docker_alpine(ctx):
if os.getenv("DOCKER_NO_CACHE") == "true":
with cd_worker():
ctx.run(
f'"{DOCKER}" build -f Dockerfile.alpine --no-cache --tag mediasoup/docker-alpine:latest .',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
else:
with cd_worker():
ctx.run(
f'"{DOCKER}" build -f Dockerfile.alpine --tag mediasoup/docker-alpine:latest .',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
@task
def docker_alpine_run(ctx):
with cd_worker():
ctx.run(
f'"{DOCKER}" run --name=mediasoupDockerAlpine -it --rm --privileged --cap-add SYS_PTRACE -v "{WORKER_DIR}/../:/foo bar/mediasoup" mediasoup/docker-alpine:latest',
echo=True,
pty=True, shell=SHELL,
)
@task
def docker_386(ctx):
if os.getenv("DOCKER_NO_CACHE") == "true":
with cd_worker():
ctx.run(
f'"{DOCKER}" build --platform linux/386 -f Dockerfile.386 --no-cache --tag mediasoup/docker-386:latest .',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
else:
with cd_worker():
ctx.run(
f'"{DOCKER}" build --platform linux/386 -f Dockerfile.386 --tag mediasoup/docker-386:latest .',
echo=True,
pty=PTY_SUPPORTED,
shell=SHELL,
)
@task
def docker_386_run(ctx):
with cd_worker():
ctx.run(
f'"{DOCKER}" run --name=mediasoupDocker386 -it --rm --privileged --cap-add SYS_PTRACE -v "{WORKER_DIR}/../:/foo bar/mediasoup" mediasoup/docker-386:latest',
echo=True,
pty=True, shell=SHELL,
)