From bc4bec8b806cef23cdb05749d2a07f08b2b41b08 Mon Sep 17 00:00:00 2001 From: Gareth Morgan Date: Tue, 10 Nov 2026 21:34:13 -0500 Subject: [PATCH 1/3] camera_streamer: add deploy_sender.py to build and try argus_sender on a Jetson without activating it check and discover only read. deploy copies the committed argus_sender source into a new release directory under ~/camera-streamer/releases/, configures and builds it on the target and runs the camera-free unit tests; it never stops, replaces or starts a service and refuses an existing release. run starts a built release for a bounded time with a config from this machine, stop signals the senders that run started, and linktest measures TCP throughput from the target to this machine, connecting the way the camera stream does. Co-Authored-By: Claude Sonnet 5.5 Signed-off-by: Gareth Morgan --- .../argus_sender/deploy/deploy_sender.py | 312 ++++++++++++++++++ 1 file changed, 312 insertions(+) create mode 100755 examples/camera_streamer/argus_sender/deploy/deploy_sender.py diff --git a/examples/camera_streamer/argus_sender/deploy/deploy_sender.py b/examples/camera_streamer/argus_sender/deploy/deploy_sender.py new file mode 100755 index 0000000000..69752c156c --- /dev/null +++ b/examples/camera_streamer/argus_sender/deploy/deploy_sender.py @@ -0,0 +1,312 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Build and try argus_sender on a Jetson from this checkout, without activating it. + +Run on the desktop: + + deploy_sender.py user@orin check read-only: release, camera processes, disk + deploy_sender.py user@orin discover read-only: platform files, tuning files, camera units + deploy_sender.py user@orin deploy [--with-sipl] [--name NAME] + deploy_sender.py user@orin run --name NAME --config FILE [--seconds N] + deploy_sender.py user@orin stop stop the senders that `run` started + deploy_sender.py user@orin linktest [--mb N] TCP throughput from the target to this machine + +`deploy` copies the committed source of argus_sender (git archive of HEAD) into a new release directory under +~/camera-streamer/releases/ on the target, configures and builds it there and runs the unit tests that need no camera. +It never stops, replaces or starts a service and never opens a camera; a release directory that already exists is +refused rather than overwritten. `run` starts a built release for a bounded time with a config from this machine and +opens the cameras; it prints a note, and does not stop anything, if another camera process is running. +""" + +from __future__ import annotations + +import argparse +import re +import shlex +import subprocess +import sys +from pathlib import Path + +RELEASES = "~/camera-streamer/releases" +SIPL_ROOT = "/usr/src/jetson_sipl_api/sipl" +SOURCE_ROOT = "examples/camera_streamer/argus_sender" +NAME = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]*") + + +class DeployError(Exception): + """A failure with a message worth showing as is.""" + + +def ssh_command(target: str, remote: str) -> list[str]: + return ["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=10", target, remote] + + +def remote(target: str, script: str, dry_run: bool) -> None: + """Run a bash script on the target; its output goes straight to this terminal.""" + command = ssh_command(target, "bash -lc " + shlex.quote(script)) + if dry_run: + print("DRY-RUN", shlex.join(command)) + return + result = subprocess.run(command, check=False) + if result.returncode != 0: + raise DeployError(f"remote command failed with exit code {result.returncode}") + + +def repo_root() -> Path: + out = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], + capture_output=True, + text=True, + check=False, + ) + if out.returncode != 0: + raise DeployError("run this inside the IsaacCapture checkout") + return Path(out.stdout.strip()) + + +def git_output(root: Path, *args: str) -> str: + return subprocess.run( + ["git", "-C", str(root), *args], capture_output=True, text=True, check=True + ).stdout.strip() + + +def cmd_check(target: str, dry_run: bool) -> None: + remote( + target, + r""" +set -u +echo "host: $(hostname) kernel: $(uname -r)" +head -1 /etc/nv_tegra_release 2>/dev/null || echo "no /etc/nv_tegra_release: not a Jetson" +echo "--- processes that may own the cameras" +ps -eo pid,etime,cmd | grep -Ei 'nvargus|argus_sender|sipl|camera' | grep -v grep | cut -c1-160 || true +echo "--- SIPL and multimedia packages" +for path in /usr/src/jetson_sipl_api /usr/src/jetson_multimedia_api; do [ -d "$path" ] && echo "present: $path" || echo "missing: $path"; done +echo "--- disk" +df -h ~ | tail -1 +""", + dry_run, + ) + + +def cmd_discover(target: str, dry_run: bool) -> None: + remote( + target, + r""" +set -u +echo "--- SIPL platform configuration candidates" +find /opt /usr/share /var/nvidia ~ -maxdepth 6 \( -iname 'shw5g*.json' -o -iname 'camera-platform*.json' \) 2>/dev/null | head -20 +echo "--- NITO tuning files" +ls -la /var/nvidia/nvcam/settings/sipl 2>/dev/null | head -20 || echo "none" +echo "--- camera units (read-only)" +systemctl list-unit-files 2>/dev/null | grep -Ei 'camera|argus|sipl' || echo "none" +systemctl cat camera-argus-sender.service 2>/dev/null | head -30 +echo "--- video devices and driver" +ls /dev/video* 2>/dev/null | head; lsmod 2>/dev/null | grep -Ei 'tegra_camera|nvhost_vi|max9296|max96712|sg8a' | head +""", + dry_run, + ) + + +def cmd_linktest(target: str, megabytes: int, dry_run: bool) -> None: + """Measure TCP throughput from the target to this machine, connecting the way the camera stream does. + + The target listens and sends; this machine connects and counts, like camera_receiver and argus_sender. + """ + import socket + import time + + port = 5611 + host = target.rsplit("@", 1)[-1] + sender = ( + "import socket;l=socket.socket();l.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1);" + "l.bind(('0.0.0.0',%d));l.listen(1);l.settimeout(20);c,_=l.accept();b=bytes(1<<20)\n" + "for _ in range(%d): c.sendall(b)\n" + "c.close()" % (port, megabytes) + ) + command = ssh_command(target, f"python3 -c {shlex.quote(sender)}") + if dry_run: + print("DRY-RUN", shlex.join(command)) + return + process = subprocess.Popen(command) + try: + connection = None + for _ in range(40): + try: + connection = socket.create_connection((host, port), timeout=2) + break + except OSError: + time.sleep(0.5) + if connection is None: + raise DeployError(f"could not connect to {host}:{port}") + connection.settimeout(60) + total, start = 0, time.monotonic() + while True: + chunk = connection.recv(1 << 20) + if not chunk: + break + total += len(chunk) + seconds = time.monotonic() - start + finally: + process.wait(timeout=30) + print( + f"{host} -> this machine: {total / 1e6:.1f} MB in {seconds:.2f} s = {total * 8 / seconds / 1e6:.1f} Mbit/s" + ) + + +def cmd_stop(target: str, dry_run: bool) -> None: + """Ask the senders that `run` started (./build/argus_sender --config run-config.yaml) to stop (SIGINT, so they drain and free the cameras).""" + remote( + target, + r""" +pids=$(pgrep -f "^\./build/argus_sender --config run-config.yaml" || true) +if [ -z "$pids" ]; then echo "no sender from ~/camera-streamer/releases is running"; exit 0; fi +echo "signalling: $pids" +kill -INT $pids +for _ in $(seq 1 20); do + pgrep -f "^\./build/argus_sender --config run-config.yaml" >/dev/null || { echo "stopped"; exit 0; } + sleep 1 +done +echo "still running after 20 s; leaving it alone" >&2 +exit 1 +""", + dry_run, + ) + + +def cmd_run(target: str, name: str, config: Path, seconds: int, dry_run: bool) -> None: + """Run a deployed release for a bounded time, in the foreground, with a config from this machine.""" + if not NAME.fullmatch(name): + raise DeployError(f"release name {name!r} is not a plain file name") + if not config.is_file(): + raise DeployError(f"{config} is not a file") + directory = f"{RELEASES}/{name}" + guard = f""" +set -e +d={directory} +[ -x $d/build/argus_sender ] || {{ echo "no built sender in $d" >&2; exit 1; }} +# Taking the cameras is intended; say who else may hold them, but do not stop anything. +if systemctl is-active --quiet camera-argus-sender.service 2>/dev/null; then + echo "note: camera-argus-sender.service is active and may hold the cameras" >&2 +fi +if pgrep -f '/argus_sender' >/dev/null; then + echo "note: another camera process is running" >&2 +fi +""" + upload = ssh_command(target, f"cat > {directory}/run-config.yaml") + if dry_run: + print("DRY-RUN", shlex.join(upload), "<", config) + else: + with config.open("rb") as handle: + if subprocess.run(upload, stdin=handle, check=False).returncode != 0: + raise DeployError("uploading the config failed") + remote( + target, + guard + + f""" +cd $d +# SIGINT first: the sender drains its encoder and returns every camera buffer on SIGINT/SIGTERM. +timeout --signal=INT --kill-after=15 {seconds} ./build/argus_sender --config run-config.yaml || echo "sender exited with status $?" +""", + dry_run, + ) + + +def cmd_deploy(target: str, with_sipl: bool, name: str | None, dry_run: bool) -> None: + root = repo_root() + if git_output(root, "status", "--porcelain", "--", SOURCE_ROOT): + raise DeployError( + f"{SOURCE_ROOT} has uncommitted changes; commit them (the release is a git archive of HEAD)" + ) + commit = git_output(root, "rev-parse", "--short=9", "HEAD") + release = name or commit + if not NAME.fullmatch(release): + raise DeployError(f"release name {release!r} is not a plain file name") + directory = f"{RELEASES}/{release}" + + print(f"==> [1/3] preparing {directory} on {target} (commit {commit})", flush=True) + remote( + target, + f'set -e; d={directory}; [ ! -e $d ] || {{ echo "release $d already exists" >&2; exit 1; }}; mkdir -p $d/src', + dry_run, + ) + + print("==> [2/3] copying the committed source", flush=True) + archive = ["git", "-C", str(root), "archive", "--format=tar", f"HEAD:{SOURCE_ROOT}"] + extract = ssh_command(target, f"tar -x -C {directory}/src") + if dry_run: + print("DRY-RUN", shlex.join(archive), "|", shlex.join(extract)) + else: + producer = subprocess.Popen(archive, stdout=subprocess.PIPE) + consumer = subprocess.run(extract, stdin=producer.stdout, check=False) + producer.stdout.close() + if producer.wait() != 0 or consumer.returncode != 0: + raise DeployError("copying the source failed") + + print("==> [3/3] configuring, building and testing on the target", flush=True) + options = "-DCMAKE_BUILD_TYPE=Release -DDISABLE_MULTIPROCESS=ON" + if with_sipl: + options += f" -DWITH_SIPL=ON -DSIPL_ROOT={SIPL_ROOT}" + remote( + target, + f""" +set -e +cd {directory} +cmake -S src -B build {options} -DBUILD_TESTING=ON +cmake --build build -j "$(nproc)" +# Only the tests that need no camera; ctest does not open any device. +(cd build && ctest --output-on-failure) || echo "some tests failed (see above); the build itself succeeded" +./build/argus_sender --help >/dev/null 2>&1 || true +echo "built: {directory}/build/argus_sender (not activated, no camera opened)" +""", + dry_run, + ) + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument("target", help="user@host of the Jetson") + sub = parser.add_subparsers(dest="command", required=True) + sub.add_parser("check", help="read-only look at the target") + sub.add_parser("discover", help="read-only: SIPL platform files and existing units") + sub.add_parser( + "stop", help="stop the senders started from ~/camera-streamer/releases" + ) + link = sub.add_parser("linktest", help="TCP throughput from the target to this PC") + link.add_argument("--mb", type=int, default=32, help="megabytes to send") + run = sub.add_parser("run", help="run a deployed release for a bounded time") + run.add_argument("--name", required=True, help="release directory name") + run.add_argument( + "--config", required=True, type=Path, help="sender config to upload" + ) + run.add_argument("--seconds", type=int, default=60, help="stop after this long") + deploy = sub.add_parser("deploy", help="copy, build and test; never activates") + deploy.add_argument( + "--with-sipl", action="store_true", help="also build the SIPL backend" + ) + deploy.add_argument("--name", help="release directory name (default: commit)") + parser.add_argument( + "--dry-run", action="store_true", help="print the commands instead of running" + ) + args = parser.parse_args(argv) + try: + if args.command == "check": + cmd_check(args.target, args.dry_run) + elif args.command == "stop": + cmd_stop(args.target, args.dry_run) + elif args.command == "linktest": + cmd_linktest(args.target, args.mb, args.dry_run) + elif args.command == "run": + cmd_run(args.target, args.name, args.config, args.seconds, args.dry_run) + elif args.command == "discover": + cmd_discover(args.target, args.dry_run) + else: + cmd_deploy(args.target, args.with_sipl, args.name, args.dry_run) + except DeployError as error: + print(f"error: {error}", file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From ec6d8380674ef6a3484f12c03d3dc0769d26c819 Mon Sep 17 00:00:00 2001 From: Gareth Morgan Date: Tue, 10 Nov 2026 23:17:00 -0500 Subject: [PATCH 2/3] camera_streamer: make deploy_sender.py report real failures A failing unit test now fails the deploy instead of being echoed and ignored. A run that reaches its time limit is a success, and any other sender exit status is propagated instead of being turned into success. The scripts that run on the target use [[ ]] for tests, as the repository's shell rule requires, and run takes repeatable --env NAME=VALUE options for the sender. Co-Authored-By: Claude Sonnet 5.5 Signed-off-by: Gareth Morgan --- .../argus_sender/deploy/deploy_sender.py | 50 ++++++++++++++----- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/examples/camera_streamer/argus_sender/deploy/deploy_sender.py b/examples/camera_streamer/argus_sender/deploy/deploy_sender.py index 69752c156c..189efa9bba 100755 --- a/examples/camera_streamer/argus_sender/deploy/deploy_sender.py +++ b/examples/camera_streamer/argus_sender/deploy/deploy_sender.py @@ -8,7 +8,7 @@ deploy_sender.py user@orin check read-only: release, camera processes, disk deploy_sender.py user@orin discover read-only: platform files, tuning files, camera units deploy_sender.py user@orin deploy [--with-sipl] [--name NAME] - deploy_sender.py user@orin run --name NAME --config FILE [--seconds N] + deploy_sender.py user@orin run --name NAME --config FILE [--seconds N] [--env NAME=VALUE] deploy_sender.py user@orin stop stop the senders that `run` started deploy_sender.py user@orin linktest [--mb N] TCP throughput from the target to this machine @@ -16,7 +16,8 @@ ~/camera-streamer/releases/ on the target, configures and builds it there and runs the unit tests that need no camera. It never stops, replaces or starts a service and never opens a camera; a release directory that already exists is refused rather than overwritten. `run` starts a built release for a bounded time with a config from this machine and -opens the cameras; it prints a note, and does not stop anything, if another camera process is running. +opens the cameras; it prints a note, and does not stop anything, if another camera process is running. `run` succeeds when the +sender ends at its time limit and fails with the sender's own exit status otherwise; `deploy` fails if any unit test fails. """ from __future__ import annotations @@ -81,7 +82,7 @@ def cmd_check(target: str, dry_run: bool) -> None: echo "--- processes that may own the cameras" ps -eo pid,etime,cmd | grep -Ei 'nvargus|argus_sender|sipl|camera' | grep -v grep | cut -c1-160 || true echo "--- SIPL and multimedia packages" -for path in /usr/src/jetson_sipl_api /usr/src/jetson_multimedia_api; do [ -d "$path" ] && echo "present: $path" || echo "missing: $path"; done +for path in /usr/src/jetson_sipl_api /usr/src/jetson_multimedia_api; do [[ -d "$path" ]] && echo "present: $path" || echo "missing: $path"; done echo "--- disk" df -h ~ | tail -1 """, @@ -160,7 +161,7 @@ def cmd_stop(target: str, dry_run: bool) -> None: target, r""" pids=$(pgrep -f "^\./build/argus_sender --config run-config.yaml" || true) -if [ -z "$pids" ]; then echo "no sender from ~/camera-streamer/releases is running"; exit 0; fi +if [[ -z "$pids" ]]; then echo "no sender from ~/camera-streamer/releases is running"; exit 0; fi echo "signalling: $pids" kill -INT $pids for _ in $(seq 1 20); do @@ -174,17 +175,23 @@ def cmd_stop(target: str, dry_run: bool) -> None: ) -def cmd_run(target: str, name: str, config: Path, seconds: int, dry_run: bool) -> None: +def cmd_run( + target: str, name: str, config: Path, seconds: int, env: list[str], dry_run: bool +) -> None: """Run a deployed release for a bounded time, in the foreground, with a config from this machine.""" if not NAME.fullmatch(name): raise DeployError(f"release name {name!r} is not a plain file name") if not config.is_file(): raise DeployError(f"{config} is not a file") directory = f"{RELEASES}/{name}" + for item in env: + if not re.fullmatch(r"[A-Z_][A-Z0-9_]*=[A-Za-z0-9_.:/-]*", item): + raise DeployError(f"--env {item!r} is not NAME=VALUE with a plain value") + env_prefix = "env " + " ".join(env) + " " if env else "" guard = f""" set -e d={directory} -[ -x $d/build/argus_sender ] || {{ echo "no built sender in $d" >&2; exit 1; }} +[[ -x $d/build/argus_sender ]] || {{ echo "no built sender in $d" >&2; exit 1; }} # Taking the cameras is intended; say who else may hold them, but do not stop anything. if systemctl is-active --quiet camera-argus-sender.service 2>/dev/null; then echo "note: camera-argus-sender.service is active and may hold the cameras" >&2 @@ -206,7 +213,11 @@ def cmd_run(target: str, name: str, config: Path, seconds: int, dry_run: bool) - + f""" cd $d # SIGINT first: the sender drains its encoder and returns every camera buffer on SIGINT/SIGTERM. -timeout --signal=INT --kill-after=15 {seconds} ./build/argus_sender --config run-config.yaml || echo "sender exited with status $?" +status=0 +{env_prefix}timeout --signal=INT --kill-after=15 {seconds} ./build/argus_sender --config run-config.yaml || status=$? +# 124 is the time limit this command set; any other nonzero status is the sender's own and fails the run. +if [[ $status -eq 124 ]]; then echo "sender stopped at its {seconds} s time limit"; exit 0; fi +if [[ $status -ne 0 ]]; then echo "sender exited with status $status" >&2; exit $status; fi """, dry_run, ) @@ -227,7 +238,7 @@ def cmd_deploy(target: str, with_sipl: bool, name: str | None, dry_run: bool) -> print(f"==> [1/3] preparing {directory} on {target} (commit {commit})", flush=True) remote( target, - f'set -e; d={directory}; [ ! -e $d ] || {{ echo "release $d already exists" >&2; exit 1; }}; mkdir -p $d/src', + f'set -e; d={directory}; [[ ! -e $d ]] || {{ echo "release $d already exists" >&2; exit 1; }}; mkdir -p $d/src', dry_run, ) @@ -254,10 +265,9 @@ def cmd_deploy(target: str, with_sipl: bool, name: str | None, dry_run: bool) -> cd {directory} cmake -S src -B build {options} -DBUILD_TESTING=ON cmake --build build -j "$(nproc)" -# Only the tests that need no camera; ctest does not open any device. -(cd build && ctest --output-on-failure) || echo "some tests failed (see above); the build itself succeeded" -./build/argus_sender --help >/dev/null 2>&1 || true -echo "built: {directory}/build/argus_sender (not activated, no camera opened)" +# Only the tests that need no camera; ctest does not open any device. A failing test fails the deploy. +(cd build && ctest --output-on-failure) +echo "built and tested: {directory}/build/argus_sender (not activated, no camera opened)" """, dry_run, ) @@ -280,6 +290,13 @@ def main(argv: list[str] | None = None) -> int: "--config", required=True, type=Path, help="sender config to upload" ) run.add_argument("--seconds", type=int, default=60, help="stop after this long") + run.add_argument( + "--env", + action="append", + default=[], + metavar="NAME=VALUE", + help="environment variable for the sender (repeatable)", + ) deploy = sub.add_parser("deploy", help="copy, build and test; never activates") deploy.add_argument( "--with-sipl", action="store_true", help="also build the SIPL backend" @@ -297,7 +314,14 @@ def main(argv: list[str] | None = None) -> int: elif args.command == "linktest": cmd_linktest(args.target, args.mb, args.dry_run) elif args.command == "run": - cmd_run(args.target, args.name, args.config, args.seconds, args.dry_run) + cmd_run( + args.target, + args.name, + args.config, + args.seconds, + args.env, + args.dry_run, + ) elif args.command == "discover": cmd_discover(args.target, args.dry_run) else: From b911001df0c490ff650ce1d4e529437bf97f1ce7 Mon Sep 17 00:00:00 2001 From: Gareth Morgan Date: Tue, 10 Nov 2026 21:33:40 -0500 Subject: [PATCH 3/3] camera_streamer: add cross_build.sh to type-check and cross-compile argus_sender on a PC syntax runs clang over every sender source against an Orin sysroot's headers, and build produces an aarch64 argus_sender (linked, not run) with an Arm GNU toolchain. The sysroot and toolchain live under ORIN_CROSS_ROOT; the script header lists what they must contain. TESTING.md notes that tests which include Argus headers compile this way but run only on the Orin. Co-Authored-By: Claude Sonnet 5.5 Signed-off-by: Gareth Morgan --- .../camera_streamer/argus_sender/TESTING.md | 7 ++ .../argus_sender/cross_build.sh | 68 +++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100755 examples/camera_streamer/argus_sender/cross_build.sh diff --git a/examples/camera_streamer/argus_sender/TESTING.md b/examples/camera_streamer/argus_sender/TESTING.md index 3a8e0a20c9..ef27073b65 100644 --- a/examples/camera_streamer/argus_sender/TESTING.md +++ b/examples/camera_streamer/argus_sender/TESTING.md @@ -34,6 +34,13 @@ The deployment snapshot/restore checks run from `argus_sender/`: python3 -m pytest tests/test_deployment.py -q ``` +## Cross-compiling on a PC + +`cross_build.sh syntax` type-checks every sender source against the Orin's headers, and `cross_build.sh build` +produces an aarch64 `argus_sender` that is linked but not run. Both use the sysroot and Arm toolchain from +`ORIN_CROSS_ROOT`; the script header says what the sysroot must contain. Tests that include Argus headers compile +this way but run only on the Orin. + ## 120-frame hardware smoke test The opt-in test opens the devices in diff --git a/examples/camera_streamer/argus_sender/cross_build.sh b/examples/camera_streamer/argus_sender/cross_build.sh new file mode 100755 index 0000000000..bd1fdc20ab --- /dev/null +++ b/examples/camera_streamer/argus_sender/cross_build.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# Cross-compile argus_sender for a Jetson on an x86 PC, using an Orin sysroot and an Arm GNU toolchain under +# ORIN_CROSS_ROOT (default ~/github/orin-cross-39.2.1): +# /sysroot the Jetson Linux root: the L4T packages (including nvidia-l4t-multimedia, which +# holds libnvv4l2), the Multimedia API and, for the SIPL backend, the SIPL API +# /arm-gnu-toolchain-*/ Arm's aarch64-none-linux-gnu toolchain +# /orin-aarch64.cmake a CMake toolchain file naming both +# `syntax` also needs clang, and the yaml-cpp sources that a first `build` fetches into /build-streamer. +# +# cross_build.sh syntax type-check every source against the Orin headers (clang, no linking) +# cross_build.sh build configure and build the aarch64 argus_sender (extra arguments go to cmake) + +set -euo pipefail + +mode="${1:-build}" +shift || true +project_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +root="${ORIN_CROSS_ROOT:-${HOME}/github/orin-cross-39.2.1}" +sysroot="${root}/sysroot" +mmapi="${sysroot}/usr/src/jetson_multimedia_api" +toolchain="${root}/orin-aarch64.cmake" + +for path in "${sysroot}" "${mmapi}/argus/include" "${toolchain}" "${sysroot}/usr/lib/aarch64-linux-gnu/nvidia/libnvv4l2.so"; do + if [[ ! -e "${path}" ]]; then + echo "error: ${path} is missing; see the header of this script" >&2 + exit 1 + fi +done + +case "${mode}" in +syntax) + cxx_include="$(ls -d "${root}"/arm-gnu-toolchain*/aarch64-none-linux-gnu/include/c++/* | head -n1)" + yaml_include="${root}/build-streamer/_deps/yaml_cpp-src/include" + if [[ ! -d "${yaml_include}" ]]; then + echo "error: ${yaml_include} is missing; run 'cross_build.sh build' once to fetch yaml-cpp" >&2 + exit 1 + fi + status=0 + for source in "${project_dir}"/src/*.cpp; do + # The SIPL headers need C++17; the rest of the sender is C++14. + standard=c++14 + [[ "${source}" == */SiplCapture.cpp ]] && standard=c++17 + clang++ --target=aarch64-linux-gnu --sysroot="${sysroot}" -std="${standard}" -fsyntax-only -Wno-everything \ + -I"${sysroot}/usr/src/jetson_sipl_api/sipl/include" -I"${sysroot}/usr/src/jetson_sipl_api/sipl/include/nvsci" -I"${sysroot}/usr/src/jetson_sipl_api/sipl/include/query/include" \ + -DNVMMAPI_SUPPORTED -I"${project_dir}/src" -I"${mmapi}/argus/include" -I"${mmapi}/include" \ + -I"${mmapi}/samples/common/classes" -I/usr/local/cuda/targets/sbsa-linux/include \ + -I"${sysroot}/usr/include/drm" -I"${yaml_include}" -isystem "${sysroot}/usr/include/aarch64-linux-gnu" \ + -isystem "${cxx_include}" -isystem "${cxx_include}/aarch64-none-linux-gnu" \ + "${source}" || status=1 + done + exit "${status}" + ;; +build) + build_dir="${root}/build-streamer" + cmake -S "${project_dir}" -B "${build_dir}" -DCMAKE_TOOLCHAIN_FILE="${toolchain}" -DCMAKE_BUILD_TYPE=Release \ + -DDISABLE_MULTIPROCESS=ON -DMMAPI_ROOT="${mmapi}" -DDRM_INCLUDE_DIR="${sysroot}/usr/include/drm" \ + -DBUILD_TESTING=OFF -DSIPL_ROOT="${sysroot}/usr/src/jetson_sipl_api/sipl" "$@" + cmake --build "${build_dir}" --target argus_sender -j "$(nproc)" + echo "Built ${build_dir}/argus_sender (aarch64; not run)" + ;; +*) + echo "usage: cross_build.sh syntax|build [cmake args]" >&2 + exit 2 + ;; +esac