name: Release
run-name: >-
Release ${{ inputs.release_ref || github.ref_name }}
using ${{ inputs.release_script_ref || github.ref_name }}
${{ inputs.dry_run && '(dry run)' || '' }}
permissions:
contents: read
on:
workflow_dispatch:
inputs:
release_ref:
description: 'Branch, tag, or SHA to build and test (defaults to the selected workflow branch)'
required: false
default: ''
type: string
release_script_ref:
description: 'Branch containing this release workflow (must match "Use workflow from")'
required: false
default: ''
type: string
dry_run:
description: 'Build and validate packages without publishing crates or creating/uploading a release'
required: true
default: true
type: boolean
push:
tags:
- 'v*'
jobs:
create-release:
name: Create Release
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
version: ${{ steps.get_version.outputs.version }}
crate_version: ${{ steps.get_version.outputs.crate_version }}
tag: ${{ steps.get_version.outputs.tag }}
steps:
- name: Validate release workflow branch
if: github.event_name == 'workflow_dispatch' && inputs.release_script_ref != ''
shell: bash
env:
EXPECTED_REF: ${{ inputs.release_script_ref }}
WORKFLOW_REF: ${{ github.ref_name }}
run: |
EXPECTED_REF="${EXPECTED_REF#refs/heads/}"
if [ "$EXPECTED_REF" != "$WORKFLOW_REF" ]; then
echo "Error: release_script_ref ($EXPECTED_REF) does not match the branch selected in 'Use workflow from' ($WORKFLOW_REF)."
exit 1
fi
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.event_name == 'workflow_dispatch' && (inputs.release_ref || github.ref_name) || github.ref }}
- name: Get release version
id: get_version
shell: bash
run: |
if [ "${{ github.event_name }}" = "push" ]; then
TAG="${GITHUB_REF_NAME#refs/tags/}"
CRATE_VERSION="${TAG#v}"
CRATE_VERSION="${CRATE_VERSION#V}"
else
CRATE_VERSION=$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -n 1)
CRATE_VERSION="${CRATE_VERSION#v}"
CRATE_VERSION="${CRATE_VERSION#V}"
TAG="v${CRATE_VERSION}"
fi
echo "version=$CRATE_VERSION" >> "$GITHUB_OUTPUT"
echo "crate_version=$CRATE_VERSION" >> "$GITHUB_OUTPUT"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
- name: Resolve release commit
id: release_commit
shell: bash
run: echo "sha=$(git rev-parse HEAD^{commit})" >> "$GITHUB_OUTPUT"
- name: Create Release
id: create_release
if: github.event_name != 'workflow_dispatch' || !inputs.dry_run
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.get_version.outputs.tag }}
commitish: ${{ steps.release_commit.outputs.sha }}
release_name: FlowSDK ${{ steps.get_version.outputs.version }}
body: |
## FlowSDK ${{ steps.get_version.outputs.version }}
draft: true
prerelease: ${{ contains(steps.get_version.outputs.version, '-') }}
publish-crates:
name: Publish to Crates.io
needs: create-release
runs-on: ubuntu-latest
if: "!contains(needs.create-release.outputs.version, '-')"
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.event_name == 'workflow_dispatch' && (inputs.release_ref || github.ref_name) || github.ref }}
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Install protoc
uses: arduino/setup-protoc@v3
with:
version: '25.x'
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Verify Tag matches Cargo.toml
env:
CRATE_VERSION: ${{ needs.create-release.outputs.crate_version }}
run: |
CORE_VERSION=$(grep "^version" Cargo.toml | head -n 1 | cut -d '"' -f 2)
FFI_VERSION=$(grep "^version" flowsdk_ffi/Cargo.toml | head -n 1 | cut -d '"' -f 2)
echo "Crate version: $CRATE_VERSION"
echo "Core: $CORE_VERSION"
echo "FFI: $FFI_VERSION"
if [ "$CRATE_VERSION" != "$CORE_VERSION" ]; then
echo "Error: Crate version ($CRATE_VERSION) mismatch with Core ($CORE_VERSION)"
exit 1
fi
if [ "$CRATE_VERSION" != "$FFI_VERSION" ]; then
echo "Error: Crate version ($CRATE_VERSION) mismatch with FFI ($FFI_VERSION)"
exit 1
fi
echo "✅ Versions match confirmed!"
- name: Prepare crates.io manifests
shell: bash
run: |
sed -i '/^quinn-openssl = /d' Cargo.toml
sed -i '/^quinn-proto-openssl = /d' Cargo.toml
sed -i '/^quic-openssl = /d' Cargo.toml
sed -i '/^quic-proto-openssl = /d' Cargo.toml
sed -i '/^quinn-proto-openssl = /d' flowsdk_ffi/Cargo.toml
sed -i '/^quic-openssl = \[/,/^\]/d' flowsdk_ffi/Cargo.toml
- name: Publish flowsdk (Core)
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish -p flowsdk --allow-dirty ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run && '--dry-run' || '' }}
- name: Wait for Index Propagation
if: github.event_name != 'workflow_dispatch' || !inputs.dry_run
run: sleep 40
- name: Publish flowsdk_ffi (Bindings)
if: github.event_name != 'workflow_dispatch' || !inputs.dry_run
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish -p flowsdk_ffi --allow-dirty ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run && '--dry-run' || '' }}
- name: Validate flowsdk_ffi (Bindings)
if: github.event_name == 'workflow_dispatch' && inputs.dry_run
run: cargo check -p flowsdk_ffi
- name: Validate flowsdk_ffi (Bindings)
if: github.event_name == 'workflow_dispatch' && inputs.dry_run
run: cargo check -p flowsdk_ffi
build-and-upload:
name: Build binaries
needs: create-release
runs-on: ${{ matrix.os }}
permissions:
contents: write
strategy:
matrix:
include:
- target: x86_64-unknown-linux-musl
os: ubuntu-latest
asset_name_suffix: linux-amd64
- target: aarch64-unknown-linux-musl
os: ubuntu-24.04-arm
asset_name_suffix: linux-arm64
- target: aarch64-apple-darwin
os: macos-15
asset_name_suffix: macos-arm64
- target: x86_64-pc-windows-msvc
os: windows-latest
asset_name_suffix: windows-amd64
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.event_name == 'workflow_dispatch' && (inputs.release_ref || github.ref_name) || github.ref }}
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install musl toolchain
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install --yes musl-tools
- name: Install protoc
uses: arduino/setup-protoc@v3
with:
version: '25.x'
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Cache dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-release-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}
- name: Build release binaries
if: runner.os != 'Linux'
run: cargo build --release --workspace --target ${{ matrix.target }}
- name: Build static Linux release binaries
if: runner.os == 'Linux'
env:
CC_x86_64_unknown_linux_musl: musl-gcc
CC_aarch64_unknown_linux_musl: musl-gcc
run: |
cargo build --release --package mqtt-grpc-proxy --target ${{ matrix.target }}
cargo build --release --package flowsdk --examples --no-default-features --features quic,rustls-tls --target ${{ matrix.target }}
cargo build --release --package mqtt_ring_bench --features quic --target ${{ matrix.target }}
- name: Create release package (Unix)
if: runner.os != 'Windows'
run: |
mkdir -p release-package
cp target/${{ matrix.target }}/release/r-proxy release-package/
cp target/${{ matrix.target }}/release/s-proxy release-package/
cp README.md release-package/
cp mqtt_grpc_duality/README.md release-package/PROXY-README.md
if [ "$RUNNER_OS" = "Linux" ]; then
mkdir -p release-package/examples
cargo metadata --no-deps --format-version 1 \
| jq -r '.packages[] | select(.name == "flowsdk") | .targets[] | select(.kind | index("example")) | .name' \
| xargs -r -I{} cp target/${{ matrix.target }}/release/examples/{} release-package/examples/
cp target/${{ matrix.target }}/release/mqtt_ring_bench release-package/
fi
tar -czf flowsdk-${{ needs.create-release.outputs.version }}-${{ matrix.asset_name_suffix }}.tar.gz -C release-package .
- name: Create release package (Windows)
if: runner.os == 'Windows'
run: |
mkdir release-package
cp target/${{ matrix.target }}/release/r-proxy.exe release-package/
cp target/${{ matrix.target }}/release/s-proxy.exe release-package/
cp README.md release-package/
cp mqtt_grpc_duality/README.md release-package/PROXY-README.md
Compress-Archive -Path release-package/* -DestinationPath flowsdk-${{ needs.create-release.outputs.version }}-${{ matrix.asset_name_suffix }}.zip
- name: Upload Release Asset (Unix)
if: runner.os != 'Windows' && (github.event_name != 'workflow_dispatch' || !inputs.dry_run)
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: ./flowsdk-${{ needs.create-release.outputs.version }}-${{ matrix.asset_name_suffix }}.tar.gz
asset_name: flowsdk-${{ needs.create-release.outputs.version }}-${{ matrix.asset_name_suffix }}.tar.gz
asset_content_type: application/gzip
- name: Upload Release Asset (Windows)
if: runner.os == 'Windows' && (github.event_name != 'workflow_dispatch' || !inputs.dry_run)
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: ./flowsdk-${{ needs.create-release.outputs.version }}-${{ matrix.asset_name_suffix }}.zip
asset_name: flowsdk-${{ needs.create-release.outputs.version }}-${{ matrix.asset_name_suffix }}.zip
asset_content_type: application/zip