Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 0 additions & 103 deletions .github/workflows/libmoq.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,106 +106,3 @@ jobs:
RELEASE_TITLE: "libmoq v${{ steps.parse.outputs.version }}"
RELEASE_PREV_TAG: ${{ steps.prev_tag.outputs.tag }}
run: .github/scripts/release.sh create artifacts

# The OBS plugin (cpp/obs) statically links libmoq, so it ships with every
# libmoq release: rebuild it against the release we just published (no
# second cargo build, guaranteed-matching ABI) and cut a parallel
# obs-moq-v<version> release. Runs after `release` so the libmoq archives
# the plugin's CMake fetches are already live.
obs-build:
name: OBS plugin (${{ matrix.target }})
needs: release
runs-on: ${{ matrix.os }}

# macOS + Windows only. These link the ffmpeg the obs-deps bundle ships
# (the same one OBS uses), so the binaries are portable. A Linux build
# would link nix/distro ffmpeg and not be portable, so Linux is
# build-from-source (see doc/bin/obs.md) and ships no prebuilt binary.
strategy:
fail-fast: false
matrix:
include:
- target: aarch64-apple-darwin
os: macos-latest
- target: x86_64-pc-windows-msvc
# The plugin preset uses the Visual Studio 17 2022 generator.
os: windows-2022

steps:
# Shallow: this job only compiles, it never reads git history or tags.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Parse version
id: parse
shell: bash
run: .github/scripts/release.sh parse-version libmoq

# Native Xcode / Visual Studio; libobs/Qt6/ffmpeg come from the obs-deps
# bundle and libmoq from the release we just published (--libmoq-release).
- name: Build and package
shell: bash
env:
TARGET: ${{ matrix.target }}
VERSION: ${{ steps.parse.outputs.version }}
run: |
./cpp/obs/build.sh --target "$TARGET" --libmoq-release "$VERSION" --output dist

- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: obs-moq-${{ matrix.target }}
path: dist/*

obs-release:
name: OBS plugin release
needs: obs-build
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false

- name: Parse version
id: parse
run: .github/scripts/release.sh parse-version libmoq

# Only the obs-moq archives: the libmoq-* artifacts from the build job
# belong to the libmoq release, not this one.
- name: Download artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
path: artifacts
pattern: obs-moq-*
merge-multiple: true

# release.sh prev-tag keys off the current tag, but the trigger here is a
# libmoq-v* tag, so it can't find the previous obs-moq tag. Take the
# newest existing obs-moq tag (excluding this version, for safe re-runs)
# so the release notes span only since the last obs-moq release.
- name: Find previous obs-moq tag
id: prev_tag
env:
VERSION: ${{ steps.parse.outputs.version }}
run: |
cur="obs-moq-v${VERSION}"
# awk (not grep -vx | head) so an empty result is exit 0, not 1:
# under `set -eo pipefail` the inaugural release has no prior tag and
# grep's exit 1 would fail the step. Empty tag is fine downstream.
prev=$(git tag --list 'obs-moq-v*' --sort=-v:refname | awk -v cur="$cur" '$0 != cur { print; exit }')
echo "tag=${prev}" >>"$GITHUB_OUTPUT"

# Cut a parallel obs-moq-v<version> release at this commit (gh creates
# the tag).
- name: Create or update release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: "obs-moq-v${{ steps.parse.outputs.version }}"
RELEASE_TITLE: "obs-moq v${{ steps.parse.outputs.version }}"
RELEASE_PREV_TAG: ${{ steps.prev_tag.outputs.tag }}
run: .github/scripts/release.sh create artifacts
10 changes: 5 additions & 5 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ name: Nightly
# no artifacts with the default one, which is several minutes of duplicate work
# that a PR gate can't justify for how rarely it catches something. `obs` is
# diff-independent because its PR-time counterpart can't be: obs.yml has to
# name the paths that can break an external link to libmoq.a, and no list of
# paths covers every one of them (see the comment there). `windows` and `macos`
# name the paths that can break an external link to a Rust staticlib, and no
# list of paths covers every one of them (see the comment there). `windows` and `macos`
# are diff-independent because what they catch is a whole target's worth of
# `#[cfg(target_os = ...)]` code that no Linux job compiles at all, and those
# runners are throttled too hard to gate every PR on.
Expand Down Expand Up @@ -112,9 +112,9 @@ jobs:
if: ${{ !cancelled() }}
run: nix develop --command just rs audit

# The OBS plugin and libmoq's C fixtures are the only things that link
# libmoq.a from outside cargo, so a stale `rs/libmoq/native-libs/*.txt`
# breaks them and nothing else. obs.yml lists the inputs that can do that,
# The OBS plugin (over moq-ffi) and libmoq's C fixtures link a Rust
# staticlib from outside cargo, so a stale `rs/libmoq/native-libs/*.txt`
# breaks them and no cargo build. obs.yml lists the inputs that can do that,
# but a list of paths can't be complete: a build script emitting
# `cargo::rustc-link-lib` needs no manifest change, and widening that
# filter to `rs/**` would run a link on nearly every PR. Being
Expand Down
45 changes: 23 additions & 22 deletions .github/workflows/obs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,33 @@ name: OBS
#
# Linux is the platform that needs no bundle at all -- nixpkgs has obs-studio,
# Qt6 and ffmpeg, so the dev shell is the whole dependency set. The plugin is
# platform-independent C++ over libmoq's C ABI, so a Linux compile catches the
# breakage a macOS developer would otherwise ship uncompiled.
# platform-independent C++ over the generated moq-ffi bindings (cpp/moq), so a
# Linux compile catches the breakage a macOS developer would otherwise ship
# uncompiled.
#
# Narrow trigger, like swift.yml: the plugin's own sources, the libmoq header
# they call through, and the flake that supplies the toolchain.
# Narrow trigger, like swift.yml: the plugin's own sources, the C++ package and
# the moq-ffi API it is generated from, and the flake that supplies the
# toolchain, including uniffi-bindgen-cpp.
#
# libmoq's own C fixtures (`just rs c-tests`) run here too. They link libmoq.a
# from outside cargo the same way, which is too slow for nextest's hang budget
# and needs the debug staticlib this job has just built, so this is the one
# PR gate that already has everything they need. They also compile the C doc
# samples, hence `doc/lib/c/**` in the trigger.
# from outside cargo the same way the plugin links moq-ffi, which is too slow
# for nextest's hang budget, so this job, which already does that kind of link,
# is where they gate PRs. They also compile the C doc samples, hence
# `doc/lib/c/**` in the trigger.
#
# The manifests and Cargo.lock are in there for the one way a crate the plugin
# never names can break it: linking libmoq.a from outside cargo needs the native
# libraries in `rs/libmoq/native-libs/*.txt`, which are maintained by hand. A
# dependency that starts pulling in a new one leaves that list stale, and every
# Rust gate stays green, because cargo passes the flag itself and only this job
# links without it.
# never names can break it: linking a Rust staticlib from outside cargo needs
# the native libraries in `rs/libmoq/native-libs/*.txt`, which moq-ffi and
# libmoq share and which are maintained by hand. A dependency that starts
# pulling in a new one leaves that list stale, and every Rust gate stays green,
# because cargo passes the flag itself and only this job links without it.
#
# Manifests and build scripts rather than Cargo.lock alone, because neither a
# feature toggle nor a build script has to touch the lockfile. libmoq takes
# moq-native with default features, so flipping one of those defaults on
# (`jemalloc` gates `dep:tikv-jemallocator`) changes what libmoq.a needs at link
# time while Cargo.lock stays byte-identical, since moq-relay already put that
# package in the graph; a build.rs emitting `cargo::rustc-link-lib` needs no
# manifest change at all.
# feature toggle nor a build script has to touch the lockfile. Flipping a
# default feature on (`jemalloc` gates `dep:tikv-jemallocator`) changes what
# the staticlib needs at link time while Cargo.lock stays byte-identical, since
# moq-relay already put that package in the graph; a build.rs emitting
# `cargo::rustc-link-lib` needs no manifest change at all.
#
# A list of paths still can't be complete here -- a vendored C source or a
# .cargo/config.toml could do it too -- and `rs/**` would run this on nearly
Expand All @@ -49,6 +50,8 @@ on:
types: [opened, synchronize, reopened, closed]
paths:
- "cpp/obs/**"
- "cpp/moq/**"
- "rs/moq-ffi/**"
- "rs/libmoq/**"
- "doc/lib/c/**"
- "doc/lib/samples.sh"
Expand Down Expand Up @@ -94,7 +97,7 @@ jobs:

# cache.yml warms the default-branch store on `main`. Pull requests restore
# it; the action refuses to publish from a pull request. It holds
# dev-profile artifacts, which is why `just obs ci` builds libmoq in debug
# dev-profile artifacts, which is why `just obs ci` builds moq-ffi in debug
# rather than the preset's release.
- name: Rust cache
uses: ./.github/actions/rust-cache
Expand All @@ -104,7 +107,5 @@ jobs:
- name: Build
run: nix develop --command just obs ci

# The plugin build above already produced the debug libmoq.a, so the
# cargo step inside is a no-op and this is just a cc and a run.
- name: libmoq C fixtures
run: nix develop --command just rs c-tests
120 changes: 120 additions & 0 deletions .github/workflows/release-cpp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,123 @@ jobs:
RELEASE_TAG: ${{ github.ref_name }}
RELEASE_PREV_TAG: ${{ steps.prev_tag.outputs.tag }}
run: RELEASE_TITLE="moq C++ v${RELEASE_TAG#cpp-v}" .github/scripts/release.sh create artifacts

# The OBS plugin (cpp/obs) statically links the C++ package, so it ships with
# every release: rebuild it against the release just published (no second
# cargo build, guaranteed-matching bindings) and cut a parallel
# obs-moq-v<version> release. The plugin's version is its own,
# cpp/obs/VERSION. Runs after `release` so the archives
# the plugin's CMake fetches are already live.
obs-build:
name: OBS plugin (${{ matrix.target }})
needs: release
runs-on: ${{ matrix.os }}
Comment on lines +119 to +122

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Run OBS builds in non-tag dry-run workflows

During the nightly workflow_call and manual dry-run, release is skipped by its github.ref_type == 'tag' condition, and GitHub documents that a job needing a skipped job is also skipped unless its condition permits continuation. Consequently this obs-build matrix never tests macOS or Windows packaging before a release tag, despite the workflow's stated dry-run contract; depend on build for non-tag runs or add an explicit tag-aware condition. GitHub Actions documentation.

AGENTS.md reference: AGENTS.md:L23-L23

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this as is. obs-build passes --moq-release, which fetches the cpp-v<version> archives from the release that release just published, so a dry-run has nothing to link against. It also matches libmoq.yml, which this job moved from. The plugin's compile is gated by obs.yml on PRs and by the nightly on Linux; the macOS and Windows builds are on the manual list for the cpp line's final review.

(Written by Claude Opus 5.5)


# macOS + Windows only. These link the ffmpeg the obs-deps bundle ships
# (the same one OBS uses), so the binaries are portable. A Linux build
# would link nix/distro ffmpeg and not be portable, so Linux is
# build-from-source (see doc/bin/obs.md) and ships no prebuilt binary.
strategy:
fail-fast: false
matrix:
include:
- target: aarch64-apple-darwin
os: macos-latest
- target: x86_64-pc-windows-msvc
# The plugin preset uses the Visual Studio 17 2022 generator.
os: windows-2022

steps:
# Shallow: this job only compiles, it never reads git history or tags.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

# The build job already checked the tag against this file.
- name: Read C++ version
id: parse
shell: bash
run: echo "version=$(tr -d '[:space:]' < cpp/moq/VERSION)" >>"$GITHUB_OUTPUT"

# Native Xcode / Visual Studio; libobs/Qt6/ffmpeg come from the obs-deps
# bundle and the C++ package from the release just published (--moq-release).
# build.sh versions the plugin from cpp/obs/VERSION.
- name: Build and package
shell: bash
env:
TARGET: ${{ matrix.target }}
MOQ_RELEASE: ${{ steps.parse.outputs.version }}
run: |
./cpp/obs/build.sh --target "$TARGET" --moq-release "$MOQ_RELEASE" --output dist

- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: obs-moq-${{ matrix.target }}
path: dist/*

obs-release:
name: OBS plugin release
needs: obs-build
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false

- name: Read plugin version
id: parse
run: echo "version=$(tr -d '[:space:]' < cpp/obs/VERSION)" >>"$GITHUB_OUTPUT"

# An obs-moq tag names one build. A later C++ release without a plugin
# version bump would overwrite that release's archives, so refuse it; a
# re-run of this same release finds its tag on this commit and carries on.
- name: Refuse a reused plugin version
env:
VERSION: ${{ steps.parse.outputs.version }}
run: |
tag="obs-moq-v${VERSION}"
if git rev-parse -q --verify "refs/tags/$tag" >/dev/null &&
[ "$(git rev-parse "refs/tags/$tag^{commit}")" != "$(git rev-parse HEAD)" ]; then
echo "::error::$tag already names another commit; bump cpp/obs/VERSION"
exit 1
fi

# Only the obs-moq archives: the moq-cpp-* artifacts from the build job
# belong to the C++ release, not this one.
- name: Download artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
path: artifacts
pattern: obs-moq-*
merge-multiple: true

# release.sh prev-tag keys off the current tag, but the trigger here is a
# cpp-v* tag, so it can't find the previous obs-moq tag. Take the newest
# existing obs-moq tag (excluding this version, for safe re-runs) so the
# release notes span only since the last obs-moq release.
- name: Find previous obs-moq tag
id: prev_tag
env:
VERSION: ${{ steps.parse.outputs.version }}
run: |
cur="obs-moq-v${VERSION}"
# awk (not grep -vx | head) so an empty result is exit 0, not 1:
# under `set -eo pipefail` the inaugural release has no prior tag and
# grep's exit 1 would fail the step. Empty tag is fine downstream.
prev=$(git tag --list 'obs-moq-v*' --sort=-v:refname | awk -v cur="$cur" '$0 != cur { print; exit }')
echo "tag=${prev}" >>"$GITHUB_OUTPUT"

# Cut a parallel obs-moq-v<version> release at this commit (gh creates
# the tag).
- name: Create or update release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: "obs-moq-v${{ steps.parse.outputs.version }}"
RELEASE_TITLE: "obs-moq v${{ steps.parse.outputs.version }}"
RELEASE_PREV_TAG: ${{ steps.prev_tag.outputs.tag }}
run: .github/scripts/release.sh create artifacts
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ A quest deleted on `dev` is done, even while `main` still lists it.

| Change in | Also update |
|---|---|
| `rs/moq-ffi` | `rs/libmoq`, `{py,swift,kt,dart}/`, `go/wrapper/moq/*.go` (the `go/ffi` and `dart/moq_ffi` bindings regenerate automatically, but a new method needs a hand-written wrapper too, like `py/moq-rs` or `dart/moq`), `cpp/moq`, `doc/lib/{py,swift,kt,go,dart,c,cpp}` |
| `rs/moq-ffi` | `rs/libmoq`, `{py,swift,kt,dart}/`, `go/wrapper/moq/*.go` (the `go/ffi` and `dart/moq_ffi` bindings regenerate automatically, but a new method needs a hand-written wrapper too, like `py/moq-rs` or `dart/moq`), `cpp/moq`, `cpp/obs/src`, `doc/lib/{py,swift,kt,go,dart,c,cpp}`, `doc/bin/obs.md` |
| `rs/moq-net` wire/API | `js/net`, `doc/concept`, `drafts/draft-lcurley-moq-lite.md` (if the wire spec changes) |
| `rs/hang` catalog/container | `js/hang`, `doc/concept`, `drafts/draft-lcurley-moq-hang.md` (if the format spec changes) |
| `rs/moq-token` | `js/token` |
Expand All @@ -107,7 +107,7 @@ A quest deleted on `dev` is done, even while `main` still lists it.
| `rs/moq-cli` | `doc/bin/cli.md` |
| `rs/moq-token-cli` | `doc/bin/relay/auth.md`, `doc/lib/rs/moq-token.md`, `doc/lib/rs/index.md` |
| `rs/moq-gst` | `doc/bin/gstreamer.md` |
| `rs/libmoq` C ABI (`moq.h`) | `cpp/obs/src`, `doc/bin/obs.md` |
| `rs/libmoq` C ABI (`moq.h`) | `doc/lib/c` |
| `js/{watch,publish}` UI/API | `demo/web` if it consumes the API |
| a kramdown-rfc construct new to `drafts/` | `doc/.vitepress/drafts.ts`, which translates the drafts into `/draft/` site pages |

Expand Down
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,5 @@ Releases are cut separately; bump only when asked. Each package's version lives
- **Python**: `py/moq-rs/pyproject.toml` only; `py/moq-ffi` follows the `moq-ffi-v*` tag and Rust crate.
- **Swift**: `swift/VERSION`. **Kotlin**: `moq.version` in `kt/gradle.properties`. **Dart**: `version` in `dart/moq/pubspec.yaml`. Their FFI counterparts track the Rust crate.
- **Go**: `go/wrapper/VERSION` holds a human-owned `MAJOR.MINOR` line; CI derives the patch, so only edit it for a breaking API. Leave the placeholder FFI version in `go.mod` alone.
- **OBS**: the plugin takes the `libmoq` crate's version (`cpp/obs/CMakeLists.txt`), so release-plz owns it.
- **C++**: `cpp/moq/VERSION`, human-owned; a `cpp-v<version>` tag releases it.
- **OBS**: `cpp/obs/VERSION`, human-owned; each C++ release also cuts `obs-moq-v<version>`, and refuses one whose version already names an earlier release.
Loading
Loading