Skip to content

Workflow reference

Melih Ercan edited this page Sep 20, 2026 · 5 revisions

Workflow reference

Ten workflows, all workflow_dispatch only — nothing runs on push or pull request. Building WebRTC costs about an hour of runner time, so it happens when asked and not otherwise.

Workflow Runner Output Consumed by
WebRtcNativeWindowsStaticLib windows-latest webrtc.lib native C/C++ linking
WebRtcNativeWindowsDynamicLib windows-latest webrtc.dll (+ import lib, PDB) WebRTCme.Bindings.Native
WebRtcNativeLinuxStaticLib ubuntu-latest libwebrtc.a native C/C++ linking
WebRtcNativeLinuxSharedLib ubuntu-latest libwebrtc.so WebRTCme.Bindings.Native
WebRtcNativeMacOsStaticLib macos-latest libwebrtc.a not consumed
WebRtcNativeMacOsSharedLib macos-latest libwebrtc.dylib not consumed
WebRtcNativeAndroidLib ubuntu-latest libwebrtc.aar WebRTCme.Bindings.Maui.Android
WebRtcNativeIosLib macos-latest WebRTC.xcframework (iOS slices) WebRTCme.Bindings.Maui.iOS
WebRtcNativeMacCatalystLib macos-latest WebRTC.xcframework (Catalyst slices) WebRTCme.Bindings.Maui.MacCatalyst
WebRtcNativeInteropWindows windows-latest WebRtcInterop.dll the future Windows binding

Shared skeleton

The six desktop workflows are the same job with three variables: operating system, whether the shared-library patch is applied, and which file is collected.

  1. Checkout this repository (actions/checkout@v4) — needed for the composite action.
  2. Resolve WebRTC branch — see Branch selection. Fails fast on a bad value.
  3. Free disk space (Linux only) — remove preinstalled toolchains; a WebRTC checkout does not fit alongside them.
  4. Install depot_tools and put it first on PATH, so its bundled Python and Git win over the runner's.
  5. Bootstrap gclient — a bare gclient call, which downloads its own dependencies on first run.
  6. Configure Gitgclient refuses to run without an identity. It is a bot identity, not a commit author for this repository.
  7. Fetch WebRTC sourcefetch --nohooks, then fetch the resolved branch-head explicitly and check it out, then gclient sync -D --force --reset.
  8. Patch for a shared library — dynamic/shared workflows only.
  9. Buildgn gen then autoninja -C out/Default webrtc.
  10. Collect — locate the output by name and fail loudly if it is missing.
  11. Upload (actions/upload-artifact@v4) with if-no-files-found: error.

Why the branch-head is fetched explicitly

git checkout branch-heads/NNNN depends on the remote refspec already covering branch-heads. The workflows do not rely on that:

git fetch origin "+refs/branch-heads/${BRANCH}:refs/remotes/branch-heads/${BRANCH}"
git checkout -B "webrtc-${BRANCH}" "refs/remotes/branch-heads/${BRANCH}"

Why the build target is named

autoninja -C out/Default with no target builds the entire tree, including tests. The workflows build webrtc specifically and pass rtc_include_tests=false rtc_build_tools=false rtc_build_examples=false, none of which affect the contents of the library.

Windows specifics

Everything lives on C:. The hosted runner's D: has roughly 14 GB free, nowhere near enough.

DEPOT_TOOLS_WIN_TOOLCHAIN: 0 builds with the Visual Studio installed on the runner instead of Google's internal toolchain, which is not publicly accessible. The install path is discovered with vswhere and exported as GYP_MSVS_OVERRIDE_PATH plus vs<year>_install, rather than being hard-coded to an edition that may change with the runner image. The year is Chromium's name for the release, not the version number: the runners now ship Visual Studio 18, which vs_toolchain.py calls 2026 and looks up via vs2026_install.

depot_tools is downloaded as a zip and extracted with 7z, not cloned — a plain clone on Windows misses the bootstrap step.

The dynamic workflow also collects webrtc.dll.lib (the import library, needed to link from C/C++) and webrtc.dll.pdb (symbols, which make crash dumps from P/Invoke callers readable).

Linux specifics

The Free disk space step removes /usr/share/dotnet, /usr/local/lib/android, /opt/ghc, /usr/local/share/boost, /usr/local/.ghcup, /usr/share/swift and $AGENT_TOOLSDIRECTORY, then runs apt-get clean. Disk usage is printed before and after.

build/install-build-deps arrives with the gclient-pulled build/ directory and has changed name between a shell script and a Python script across branches, so the workflows probe for either.

macOS specifics

The macOS artifacts have no consumer today — WebRTCme targets Mac Catalyst, which is served by the iOS xcframework. These workflows are kept as a check that the tree still builds on Apple silicon; see Platform layers.

macos-latest is Apple silicon, so target_cpu defaults to arm64; choose x64 for Intel Macs. The collect step runs lipo -info so the log records what was actually produced.

macOS ships BSD sed, where -i requires an explicit backup suffix. Every in-place edit is sed -i '' …. Dropping the empty '' makes sed treat the next argument as the suffix and the edit silently goes to the wrong place — this is the single easiest way to break the macOS shared build.

Android specifics

Fetches webrtc_android rather than webrtc; that solution pulls the Android SDK and NDK that build_aar.py needs.

No shared-library patch is involved — build_aar.py already produces an .aar containing JNI shared objects plus the Java API. It does need a patch of its own, though.

The generated-JNI patch

build_aar.py takes classes.jar straight from the dist_jar("libwebrtc") target in sdk/android/BUILD.gn. That target sets direct_deps_only = true, so only the jars of the targets it lists itself are merged, not their dependencies.

Since WebRTC moved to jni_zero, the JNI glue no longer lives in the Java libraries. It is generated into separate generated_*_jni_java targets which those libraries depend on and which the dist_jar does not list. The generated classes therefore never reach classes.jar, and an app using the AAR dies on the first line it runs:

java.lang.NoClassDefFoundError: Failed resolution of: Lorg/webrtc/PeerConnectionFactoryJni;
  at org.webrtc.PeerConnectionFactory.initialize(PeerConnectionFactory.java:328)

The symptom is worth recognising because nothing upstream of it complains: the build succeeds, the AAR packages, the artifact uploads, and the binding compiles against it. The classes are simply not there. It is visible in the archive itself —

unzip -p libwebrtc.aar classes.jar | jar -t | grep Jni.class    # empty on an unpatched build

tools/add_generated_jni_to_aar.py adds the eighteen non-test generators to the dist_jar, and the collect step then refuses to publish an archive that still lacks PeerConnectionFactoryJni. The script asserts its anchors and is idempotent; if an assertion fires, update it rather than skipping the step, the same rule the shared-library patch follows.

M152 (branch-heads/7977) is the first branch this repository built where it bites.

The arch input is space-separated ABI names. Leaving it empty builds build_aar.py's own defaults: armeabi-v7a, arm64-v8a, x86, x86_64. The collect step lists the .so files inside the archive so the log shows which ABIs really shipped.

This is the workflow most likely to run out of disk, which is why the space-clearing step matters most here.

iOS and Mac Catalyst specifics

Two workflows, one toolchain. Both fetch webrtc_ios and run tools_webrtc/ios/build_ios_libs.py; only the arch list differs, because Mac Catalyst is an iOS app running on a Mac rather than a separate platform.

Workflow Default arch
WebRtcNativeIosLib device:arm64 simulator:arm64 simulator:x64
WebRtcNativeMacCatalystLib catalyst:arm64 catalyst:x64

They are split to match the split on the bindings side, where iOS and Mac Catalyst are separate projects. The cost is a second macOS run — the priciest runner, and roughly 40 minutes of each run is gclient sync over an identical tree.

Run them against the same branch. Two independently dispatched workflows can resolve to different milestones if a rollover happens between them. When refreshing Apple support, pin webrtc_branch explicitly on both rather than leaving it blank.

build_ios_libs.py accepts these values (ENABLED_ARCHS):

Value Slice
device:arm64 iOS device
simulator:arm64 iOS simulator on Apple silicon
simulator:x64 iOS simulator on Intel
catalyst:arm64 Mac Catalyst on Apple silicon
catalyst:x64 Mac Catalyst on Intel
arm64, x64 legacy aliases, device only

Upstream's own default omits the catalyst slices entirely, which is why WebRtcNativeMacCatalystLib names them explicitly.

An earlier version of the iOS workflow passed --arch arm64 x64. Those legacy aliases mean device only, so the resulting xcframework had no simulator and no Catalyst slice.

The framework is zipped before upload. upload-artifact does not preserve symlinks, and an xcframework that loses its symlinks will not link. The zip is created from inside out_ios_libs so WebRTC.xcframework sits at the archive root, and uploaded with compression-level: 0 because it is already compressed.

Symbols, and the dsyms input

A release build carries no debug information, so a crash inside the shipped framework symbolicates to nothing: nm finds thirteen text symbols, all RTCLog*, and a faulting stack is eight bare addresses that atos cannot resolve. That is fine until something crashes on a libwebrtc-internal thread, where the addresses are the only evidence there is — WebRTCme spent a night in September 2026 bisecting an Apple crash that a symbol table would have named.

Setting dsyms: true adds --extra-gn-args enable_dsyms=true symbol_level=2 and uploads the bundles as a second artifact, webrtc-<platform>-dsyms-m<milestone>-<branch>.

Prefer this over build_config: debug for diagnosing a crash. enable_dsyms puts the debug information in a separate bundle and leaves the linked binary alone, so the framework is still the framework that ships. A debug build changes both the code and its timing, and a racy fault is exactly what timing perturbs — it may stop reproducing.

The flag is off by default, so an ordinary refresh produces the same artifact it always did. Note that --extra-gn-args is declared nargs='*' upstream: it takes both values at once and must come last, because a second occurrence replaces the first rather than adding to it.

The interop workflow is different in kind

WebRtcNativeInteropWindows is the only workflow that builds code from this repository. Everything else compiles Google's tree unmodified apart from the patch.

It adds two steps to the Windows shape:

  • Graft WebRtcInterop into the checkout — copies WebRtcInterop/ to src/WebRtcInterop, because its BUILD.gn imports ../webrtc.gni and only resolves from inside the tree. It fails early if src/Interop.cc is empty, so a stripped copy cannot silently produce nothing.
  • A sixth patch edit — appends "//WebRtcInterop" to the root group("default") deps so ninja reaches the grafted target. Asserted before and after, like the other five.

It then builds the WebRtcInterop target rather than webrtc, and collects every DLL beside it: a component build splits across many, and the shim needs all of them at run time.

The shim itself is unfinished — see Repository layout for what Interop.cc does and does not do yet. The workflow exists so that work is buildable again.

Action versions

Action Version
actions/checkout v4
actions/upload-artifact v4
microsoft/setup-msbuild v2

actions/upload-artifact@v3 was retired by GitHub; workflows still using it fail immediately. Note that v4 artifacts are immutable and same-named uploads no longer merge — each workflow here uploads exactly one artifact per run, so neither matters in practice.

Clone this wiki locally