librtmp2 0.7.0

librtmp2 — RTMP/RTMPS protocol library
Documentation
name: Release

# Builds and packages tagged releases. Push a tag like `v0.1.0` (or run
# manually) to produce source + prebuilt library tarballs and a GitHub Release.

on:
  push:
    tags:
      - 'v*'
  workflow_dispatch:
    inputs:
      tag:
        description: 'Version tag to build (e.g. v0.1.0). Created and pushed automatically if it does not exist yet.'
        required: true

permissions:
  contents: write

concurrency:
  group: release-${{ github.event.inputs.tag || github.ref }}
  cancel-in-progress: true

jobs:
  ensure-tag:
    name: ensure release tag exists
    # Only manual runs need this: a `push: tags:` run only fires once the tag
    # already exists. workflow_dispatch's `tag` input is just a string GitHub
    # does not turn into a real ref, so create and push it here before any
    # other job tries to check it out.
    if: github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with:
          fetch-depth: 0

      - name: Create and push tag if missing
        env:
          TAG: ${{ github.event.inputs.tag }}
        run: |
          if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
            echo "Tag ${TAG} already exists, nothing to do."
            exit 0
          fi

          CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | grep -oP '"\K[0-9]+\.[0-9]+\.[0-9]+')
          if [ "${TAG#v}" != "$CARGO_VERSION" ]; then
            echo "::error::Tag (${TAG}) does not match version in Cargo.toml ($CARGO_VERSION)"
            exit 1
          fi

          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git tag -a "${TAG}" -m "Release ${TAG}"
          git push origin "${TAG}"

  verify:
    name: verify before release
    needs: ensure-tag
    # On `push: tags:` ensure-tag is skipped; on workflow_dispatch it must
    # succeed so verify never checks out a tag that was never created.
    if: always() && (needs.ensure-tag.result == 'success' || needs.ensure-tag.result == 'skipped')
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ github.event.inputs.tag || github.ref }}
          fetch-depth: 0

      - name: Install dependencies
        run: sudo apt-get update && sudo apt-get install -y libssl-dev pkg-config

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable

      - uses: Swatinem/rust-cache@v2

      - name: Build (release, all features)
        run: cargo build --release --all-features --verbose

      - name: Run unit tests
        run: cargo test --all-features --verbose

      - name: Run unit tests (no default features)
        run: cargo test --no-default-features --verbose

  package:
    name: build & package
    needs: verify
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ github.event.inputs.tag || github.ref }}

      - name: Install dependencies
        run: sudo apt-get update && sudo apt-get install -y libssl-dev pkg-config

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable

      - uses: Swatinem/rust-cache@v2

      - name: Resolve version
        id: ver
        run: |
          REF="${{ github.event.inputs.tag || github.ref_name }}"
          VERSION="${REF#v}"
          echo "version=$VERSION" >> "$GITHUB_OUTPUT"
          echo "tag=$REF" >> "$GITHUB_OUTPUT"
          echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"

      - name: Check tag matches Cargo.toml version
        run: |
          CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | grep -oP '"\K[0-9]+\.[0-9]+\.[0-9]+')
          echo "tag version: ${{ steps.ver.outputs.version }}, Cargo.toml version: $CARGO_VERSION"
          if [ "${{ steps.ver.outputs.version }}" != "$CARGO_VERSION" ]; then
            echo "::error::Tag version (${{ steps.ver.outputs.version }}) does not match version in Cargo.toml ($CARGO_VERSION)"
            exit 1
          fi

      - name: Extract changelog section for this version
        env:
          VERSION: ${{ steps.ver.outputs.version }}
        run: |
          awk -v ver="$VERSION" '
            /^## \[/ {
              if (found) exit
              if ($0 ~ ("^## \\[" ver "\\]")) { found=1; next }
              next
            }
            /^\[.*\]:/ { if (found) exit }
            found { print }
          ' CHANGELOG.md > release-notes.md
          if [ ! -s release-notes.md ]; then
            echo "::error::No CHANGELOG.md section found for version $VERSION"
            exit 1
          fi
          cat release-notes.md

      - name: Build release libraries
        run: cargo build --release --all-features --verbose

      - name: Resolve cbindgen version
        id: cbindgen_ver
        run: |
          VERSION=$(grep -oP 'CBINDGEN_VERSION="\K[0-9.]+' scripts/generate-header.sh)
          test -n "$VERSION" || { echo "::error::Could not resolve CBINDGEN_VERSION from scripts/generate-header.sh"; exit 1; }
          echo "version=$VERSION" >> "$GITHUB_OUTPUT"

      - name: Cache cbindgen
        uses: actions/cache@v6
        with:
          path: ~/.cargo/bin/cbindgen
          key: ${{ runner.os }}-cbindgen-${{ steps.cbindgen_ver.outputs.version }}

      - name: Install cbindgen
        run: |
          if ! command -v cbindgen >/dev/null 2>&1 || [ "$(cbindgen --version)" != "cbindgen ${{ steps.cbindgen_ver.outputs.version }}" ]; then
            cargo install --locked --version "${{ steps.cbindgen_ver.outputs.version }}" --force cbindgen
          fi

      - name: Generate C header
        run: bash scripts/generate-header.sh

      - name: Stage dist tree
        run: |
          DIST_DIR="dist/librtmp2-${{ steps.ver.outputs.version }}"
          mkdir -p "$DIST_DIR/lib"
          for artifact in liblibrtmp2.so liblibrtmp2.a; do
            test -f "target/release/$artifact" || {
              echo "::error::Missing target/release/$artifact"
              exit 1
            }
            cp "target/release/$artifact" "$DIST_DIR/lib/"
          done
          cp -r include "$DIST_DIR/"
          cp README.md LICENSE Cargo.toml "$DIST_DIR/"

      - name: Create binary tarball
        run: |
          cd dist
          tar czf "librtmp2-${{ steps.ver.outputs.version }}-linux-x86_64.tar.gz" \
            "librtmp2-${{ steps.ver.outputs.version }}"
          sha256sum "librtmp2-${{ steps.ver.outputs.version }}-linux-x86_64.tar.gz" \
            > "librtmp2-${{ steps.ver.outputs.version }}-linux-x86_64.tar.gz.sha256"

      - name: Create source tarball
        run: |
          VERSION="${{ steps.ver.outputs.version }}"
          STAGE_DIR="$(mktemp -d)/librtmp2-${VERSION}"
          mkdir -p "$STAGE_DIR"
          git archive HEAD | tar -x -C "$STAGE_DIR"
          cp -r include "$STAGE_DIR/"
          tar czf "dist/librtmp2-${VERSION}-src.tar.gz" -C "$(dirname "$STAGE_DIR")" "librtmp2-${VERSION}"
          cd dist
          sha256sum "librtmp2-${VERSION}-src.tar.gz" \
            > "librtmp2-${VERSION}-src.tar.gz.sha256"

      - name: Upload build artifacts
        uses: actions/upload-artifact@v7
        with:
          name: librtmp2-${{ steps.ver.outputs.version }}
          path: |
            dist/*.tar.gz
            dist/*.sha256

      - name: Publish GitHub Release
        uses: softprops/action-gh-release@v3
        with:
          tag_name: ${{ steps.ver.outputs.tag }}
          target_commitish: ${{ steps.ver.outputs.sha }}
          name: librtmp2 ${{ steps.ver.outputs.version }}
          body_path: release-notes.md
          files: |
            dist/*.tar.gz
            dist/*.sha256

  publish-ppa:
    name: publish to Ubuntu PPA
    needs: package
    uses: ./.github/workflows/publish-ppa.yml
    with:
      tag: ${{ github.event.inputs.tag || github.ref_name }}
    secrets: inherit

  publish-crates-io:
    name: publish to crates.io
    needs: package
    uses: ./.github/workflows/publish-crates-io.yml
    with:
      tag: ${{ github.event.inputs.tag || github.ref_name }}
    secrets: inherit