flowsdk 0.5.3

Safety-first, realistic, behavior-predictable messaging SDK for MQTT and more.
Documentation
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, '-')"  # Only for stable releases? Or remove if you want pre-releases too.
    
    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!"

      # crates.io cannot publish git-only features. Keep the OpenSSL fork for
      # repository builds, but omit those unavailable features from the public
      # package manifest. The regular Quinn/ring QUIC features remain available.
      - 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: x86_64-apple-darwin
          #  os: macos-15-intel
          #  asset_name_suffix: macos-amd64
          - 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