# Building ```bash cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release cmake --build build ``` CMake downloads and unpacks the pinned CEF binary distribution on first configure (~125 MB compressed, ~350 MB on disk) into `third_party/cef/current`, verifying its SHA-1. Already have one? `-DCEF_ROOT=/path/to/cef_binary_...`. The version is pinned in `cmake/FetchCEF.cmake` rather than resolved from upstream at configure time, because a build that silently follows stable is a build that breaks on someone else's Tuesday. ## Options | Option | Default | Notes | |---|---|---| | `WEBLINKED_WITH_NDI` | ON | Headers vendored; nothing to install | | `WEBLINKED_WITH_OMT` | ON | Header vendored; nothing to install | | `WEBLINKED_WITH_DECKLINK` | ON | Needs `-DDECKLINK_SDK_DIR` | | `WEBLINKED_WITH_AJA` | OFF | Needs `-DNTV2_DIR` and `-DNTV2_BUILD_DIR` | | `WEBLINKED_WITH_SCREEN` | ON | Fullscreen GPU output. No SDK on macOS or Windows; on Linux see below | | `WEBLINKED_BUILD_TESTS` | ON | Links `weblinked_core` only | Configure output lists which backends were found. A missing SDK disables its backend with a message; it is never a hard error, because most people building this do not have all four. ### NDI Nothing to install. The headers are vendored in `third_party/ndi` under the per-file MIT grant each one carries, so this backend always builds. An installed SDK still wins if you have one — found automatically at `/Library/NDI SDK for Apple`, `C:/Program Files/NDI/NDI 6 SDK`, or via `-DNDI_SDK_DIR=` — which is how you build against a newer SDK than the vendored set. Only headers are ever needed at build time; the library is resolved at run time with `dlopen`. Without a runtime the NDI output reports itself unavailable and names the download, and the rest of the app is unaffected. See [06-ndi-distribution.md](06-ndi-distribution.md). ### OMT `third_party/omt/libomt.h` is vendored (MIT), so this backend builds with no setup. At *run* time you need `libomt` and `libvmx` beside the binary, from the [OMT binary releases](https://github.com/openmediatransport/libomtnet/releases). Published for Windows x64/arm64 and macOS 15+ arm64. **There is no Linux release** — a Linux deployment must build `libomtnet` from source. ### DeckLink The SDK is a free but licence-gated download from Blackmagic and is not vendored. ```bash cmake -S . -B build -DDECKLINK_SDK_DIR="/path/to/Blackmagic DeckLink SDK 12.9" ``` Accepted layouts: `/Mac/include/`, `/Linux/include/`, `/Win/include/`, a bare directory containing `DeckLinkAPI.h`, or a flattened `/Mac/` (as redistributed inside Unreal's BlackmagicMedia plugin, which is a convenient way to build the backend without the SDK archive to hand). On macOS and Linux the SDK's `DeckLinkAPIDispatch.cpp` is compiled in; it dlopens the installed driver, so the binary still runs on a machine with no Desktop Video and simply finds no devices. ### AJA ```bash git clone https://github.com/aja-video/libajantv2 cmake -S libajantv2 -B ajantv2-build && cmake --build ajantv2-build cmake -S . -B build -DWEBLINKED_WITH_AJA=ON \ -DNTV2_DIR=$PWD/libajantv2 -DNTV2_BUILD_DIR=$PWD/ajantv2-build ``` `NTV2_BUILD_DIR` matters: `ntv2version.h` is generated by the SDK's own CMake run and lives in the build tree, not the source tree. Requires libajantv2 18.x. --- ## macOS The output is `build/Release/WebLinked.app`, about 310 MB, most of it Chromium. ### The bundle is six executables CEF on macOS needs the browser process plus five helper `.app` bundles inside `Contents/Frameworks`, named `WebLinked Helper[ (variant)].app`. CEF finds them *by name*; get one wrong and the app starts, shows a window, and dies the moment it needs a renderer. `browser_subprocess_path` is deliberately not set on macOS. ### Two signing traps **Every build is ad-hoc signed, automatically.** An unsigned `.app` that bundles further executables does not become trusted when the user approves the outer app — the nested helpers are SIGKILLed with no dialog and no log entry, which reads exactly like a CEF initialisation bug. **Ad-hoc signatures and hardened runtime do not mix.** With `--options runtime` macOS enforces library validation, requiring every loaded library to share the process's Team ID. An ad-hoc signature has none, so the app fails at launch with: ``` code signature ... not valid for use in process: mapping process and mapped file (non-platform) have different Team IDs ``` which looks like a broken bundle rather than a signing policy. Ad-hoc builds are therefore signed *without* hardened runtime. For distribution: ```bash cmake -S . -B build -DWEBLINKED_CODESIGN_IDENTITY="Developer ID Application: You (TEAMID)" ``` That switches on hardened runtime plus `src/app/WebLinked.entitlements`, which carries `disable-library-validation` (the framework, libndi and libomt are all loaded at run time) and the JIT entitlements V8 needs. Notarise afterwards. ### Universal binaries The CEF binary distribution is per-architecture, so a universal build needs two distributions built separately and `lipo`d together. `CMAKE_OSX_ARCHITECTURES` is set before `project()` — setting it afterwards silently produces a single-arch binary that still claims to be universal, so verify the result with `lipo -info` rather than trusting the build log. --- ## Windows and Linux **Both build and run** — CI builds every release for them, and both were run on real systems in August 2026 (§31 and §32 of [04-verification.md](04-verification.md)). A from-source Linux build on a clean Ubuntu 24.04 needed only the packages listed below and took one `cmake` and one `ninja` with no source changes. Still worth knowing: - **The DeckLink SDK and libajantv2 are the only backends a local build adds.** NDI and OMT have vendored headers and are compiled in everywhere, including CI. - The Windows DeckLink path uses COM (`CoCreateInstance`) rather than the dispatch shim, and that code has still never seen a card. - On Windows and Linux one binary is re-executed for every subprocess type, so `CefExecuteProcess` runs first in `main()` — there are no helper bundles. - Linux needs the usual Chromium runtime libraries (`libnss3`, `libgbm`, `libatk`, X11 or Wayland); CEF's `FIND_LINUX_LIBRARIES` handles the link side. - **The screen output needs X11 and EGL headers on Linux.** Without them it disables itself at configure time with a message, the same way a missing NDI or DeckLink SDK does — so a build that quietly has no `screen` backend is almost always a missing package: ```bash sudo apt-get install -y libx11-dev libegl1-mesa-dev libgles2-mesa-dev ``` The Linux backend is X11 only. A Wayland session with XWayland gets a window; one without does not, and `open()` says so. It also indexes **X screens**, not RandR heads, so a normal multi-monitor Linux desktop presents as one screen — which makes `--screen=1` there not yet useful. The Linux backend has now been run and renders correctly on software GL; the Windows one still has not. - **The KMS output needs libdrm's headers**, and nothing else — no X, no EGL. It is Linux-only and disables itself at configure time without them: ```bash sudo apt-get install -y libdrm-dev ``` For a board with no X server, build with `-DWEBLINKED_WITH_SCREEN=OFF` as well, and the binary no longer links X11, EGL or GLES at all (libcef.so still links X11 itself; run it with `--ozone-platform=headless`). ## In a container `docker/Dockerfile` builds the headless Linux image from this checkout — the repository root is the build context: ```bash docker build -f docker/Dockerfile -t weblinked . ``` It takes CEF's version and linux64 sha1 from `cmake/FetchCEF.cmake`, builds with the screen, shared, DeckLink, AJA, KMS and stream outputs off, and runs the unit suite as a build step. Why each is off, and how to run it, is in [docker/README.md](../docker/README.md). ## Tests ```bash ./build/tests/weblinked_tests # all ./build/tests/weblinked_tests uyvy # substring filter ``` They link `weblinked_core` only — no CEF — so they build and run in seconds. ## The verification tool `tools/ndi_probe.cpp` is an independent NDI receiver used to check what actually reaches the network. It is not part of the application and is not built by CMake: ```bash clang++ -std=c++20 -I"/Library/NDI SDK for Apple/include" tools/ndi_probe.cpp \ "/Library/NDI SDK for Apple/lib/macOS/libndi.dylib" \ -Wl,-rpath,"/Library/NDI SDK for Apple/lib/macOS" -o ndi_probe ``` `tools/mdns_probe.cpp` is the equivalent for the mDNS advertisement. It browses for `_weblinked._tcp`, decodes the TXT record with the *platform's* parser rather than ours, and then connects to the address each record publishes — which is the half `dns-sd -B` and `avahi-browse` leave out, and the half that catches an advertisement pointing somewhere unreachable. ```bash # macOS: dns_sd is in libSystem, so there is nothing to link clang++ -std=c++20 tools/mdns_probe.cpp -o mdns_probe ./mdns_probe --timeout 5 ``` See [04-verification.md](04-verification.md) for what it proves. ## Linux: discovery needs avahi's headers The mDNS backend is built against `avahi-client`'s headers so the compiler checks every signature, but opens the library at run time like libndi — so the binary still runs on a machine without avahi, it just cannot advertise. ```bash sudo apt-get install -y libavahi-client-dev ``` Without them CMake prints `mDNS: no avahi headers` and compiles a stub. That is not a build failure, which is exactly why it is worth noticing: the build succeeds and the release simply cannot be discovered.