Repository navigation
Release #7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| bump: | |
| description: Version bump to release | |
| type: choice | |
| required: true | |
| options: | |
| - patch | |
| - minor | |
| - major | |
| - current | |
| concurrency: | |
| group: release-${{ github.ref_name }} | |
| cancel-in-progress: false | |
| permissions: | |
| contents: write | |
| jobs: | |
| prepare: | |
| name: Prepare release | |
| if: ${{ github.ref == 'refs/heads/main' }} | |
| runs-on: ubuntu-latest | |
| outputs: | |
| crate_name: ${{ steps.version.outputs.crate_name }} | |
| next_version: ${{ steps.version.outputs.next_version }} | |
| tag: ${{ steps.version.outputs.tag }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| submodules: true | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: rustfmt, clippy | |
| - uses: taiki-e/install-action@cargo-llvm-cov | |
| - uses: Swatinem/rust-cache@v2 | |
| - name: Check formatting | |
| run: cargo fmt --all -- --check | |
| - name: Clippy | |
| run: cargo clippy --all-targets --all-features -- -D warnings | |
| - name: Build | |
| run: cargo build --all-targets --all-features | |
| - name: Test | |
| run: cargo test --all-features | |
| - name: Require 90% line coverage in every source file | |
| run: .github/scripts/check-file-coverage.sh 90 target/coverage.json | |
| - name: Build documentation | |
| env: | |
| RUSTDOCFLAGS: -D warnings | |
| run: cargo doc --no-deps --all-features | |
| - name: Compute next version | |
| id: version | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| metadata="$(cargo metadata --format-version 1 --no-deps)" | |
| crate_name="$(jq -r '.packages[0].name' <<< "$metadata")" | |
| current_version="$(jq -r '.packages[0].version' <<< "$metadata")" | |
| if [[ -z "$crate_name" || "$crate_name" == "null" ]]; then | |
| echo "Could not resolve the crate name" >&2 | |
| exit 1 | |
| fi | |
| if [[ -z "$current_version" || "$current_version" == "null" ]]; then | |
| echo "Could not resolve the current crate version" >&2 | |
| exit 1 | |
| fi | |
| IFS=. read -r major minor patch <<< "$current_version" | |
| case "${{ inputs.bump }}" in | |
| current) | |
| ;; | |
| major) | |
| major=$((major + 1)) | |
| minor=0 | |
| patch=0 | |
| ;; | |
| minor) | |
| minor=$((minor + 1)) | |
| patch=0 | |
| ;; | |
| patch) | |
| patch=$((patch + 1)) | |
| ;; | |
| *) | |
| echo "Unsupported bump: ${{ inputs.bump }}" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| next_version="${major}.${minor}.${patch}" | |
| tag="v${next_version}" | |
| git fetch --tags origin | |
| if git rev-parse --verify --quiet "refs/tags/${tag}"; then | |
| if [[ "${{ inputs.bump }}" != "current" ]]; then | |
| echo "Tag ${tag} already exists" >&2 | |
| exit 1 | |
| fi | |
| tagged_version="$( | |
| git show "${tag}:Cargo.toml" \ | |
| | sed -n 's/^version = "\([^"]*\)"/\1/p' \ | |
| | head -n 1 | |
| )" | |
| if [[ "$tagged_version" != "$current_version" ]]; then | |
| echo "Tag ${tag} does not contain version ${current_version}" >&2 | |
| exit 1 | |
| fi | |
| elif [[ "${{ inputs.bump }}" == "current" ]]; then | |
| echo "Tag ${tag} does not exist; choose a semantic version bump" >&2 | |
| exit 1 | |
| fi | |
| { | |
| echo "crate_name=${crate_name}" | |
| echo "current_version=${current_version}" | |
| echo "next_version=${next_version}" | |
| echo "tag=${tag}" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Update crate version | |
| if: ${{ inputs.bump != 'current' }} | |
| env: | |
| CRATE_NAME: ${{ steps.version.outputs.crate_name }} | |
| NEXT_VERSION: ${{ steps.version.outputs.next_version }} | |
| run: | | |
| set -euo pipefail | |
| perl -0pi -e 's/(\[package\][\s\S]*?\nversion = ")[^"]+(")/$1$ENV{NEXT_VERSION}$2/' Cargo.toml | |
| cargo update -p "$CRATE_NAME" --precise "$NEXT_VERSION" | |
| - name: Commit version bump and tag | |
| if: ${{ inputs.bump != 'current' }} | |
| env: | |
| RELEASE_TAG: ${{ steps.version.outputs.tag }} | |
| run: | | |
| set -euo pipefail | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git add Cargo.toml Cargo.lock | |
| git commit -m "Release ${RELEASE_TAG}" | |
| git tag -a "${RELEASE_TAG}" -m "Release ${RELEASE_TAG}" | |
| git push origin "HEAD:${GITHUB_REF_NAME}" | |
| git push origin "${RELEASE_TAG}" | |
| native-bundles: | |
| name: Rust module bundle (${{ matrix.id }}) | |
| needs: prepare | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - id: ubuntu-22.04-x86_64 | |
| os: ubuntu-22.04 | |
| target: x86_64-unknown-linux-gnu | |
| - id: ubuntu-22.04-arm64 | |
| os: ubuntu-22.04-arm | |
| target: aarch64-unknown-linux-gnu | |
| - id: ubuntu-24.04-x86_64 | |
| os: ubuntu-24.04 | |
| target: x86_64-unknown-linux-gnu | |
| - id: ubuntu-24.04-arm64 | |
| os: ubuntu-24.04-arm | |
| target: aarch64-unknown-linux-gnu | |
| - id: macos-15-x86_64 | |
| os: macos-15-intel | |
| target: x86_64-apple-darwin | |
| - id: macos-15-arm64 | |
| os: macos-15 | |
| target: aarch64-apple-darwin | |
| - id: macos-26-x86_64 | |
| os: macos-26-intel | |
| target: x86_64-apple-darwin | |
| - id: macos-26-arm64 | |
| os: macos-26 | |
| target: aarch64-apple-darwin | |
| - id: windows-2022-x86_64 | |
| os: windows-2022 | |
| target: x86_64-pc-windows-msvc | |
| - id: windows-2025-x86_64 | |
| os: windows-2025 | |
| target: x86_64-pc-windows-msvc | |
| - id: windows-11-arm64 | |
| os: windows-11-arm | |
| target: aarch64-pc-windows-msvc | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| ref: ${{ needs.prepare.outputs.tag }} | |
| persist-credentials: false | |
| submodules: true | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| - name: Verify native Rust target | |
| shell: bash | |
| env: | |
| EXPECTED_TARGET: ${{ matrix.target }} | |
| run: | | |
| set -euo pipefail | |
| actual_target="$(rustc -vV | sed -n 's/^host: //p')" | |
| if [[ "$actual_target" != "$EXPECTED_TARGET" ]]; then | |
| echo "expected ${EXPECTED_TARGET}, got ${actual_target}" >&2 | |
| exit 1 | |
| fi | |
| - name: Build installable module | |
| run: cargo build --locked --release --lib | |
| - name: Verify Unix module through TinyBus loader | |
| if: ${{ runner.os != 'Windows' }} | |
| shell: bash | |
| env: | |
| CRATE_NAME: ${{ needs.prepare.outputs.crate_name }} | |
| run: | | |
| set -euo pipefail | |
| library_name="${CRATE_NAME//-/_}" | |
| case "$RUNNER_OS" in | |
| Linux) module="target/release/lib${library_name}.so" ;; | |
| macOS) module="target/release/lib${library_name}.dylib" ;; | |
| *) echo "unsupported Unix runner: ${RUNNER_OS}" >&2; exit 1 ;; | |
| esac | |
| cargo run --locked --example verify_module -- "$module" | |
| - name: Verify Windows module through TinyBus loader | |
| if: ${{ runner.os == 'Windows' }} | |
| shell: pwsh | |
| env: | |
| CRATE_NAME: ${{ needs.prepare.outputs.crate_name }} | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $libraryName = $env:CRATE_NAME.Replace('-', '_') | |
| $module = "target/release/$libraryName.dll" | |
| $verifyRoot = Join-Path $env:RUNNER_TEMP 'tinyhosts-module-verify' | |
| New-Item -ItemType Directory -Force $verifyRoot | Out-Null | |
| $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent() | |
| $security = [System.Security.AccessControl.DirectorySecurity]::new() | |
| $security.SetOwner($identity.User) | |
| $security.SetAccessRuleProtection($true, $false) | |
| $rights = [System.Security.AccessControl.FileSystemRights]::FullControl | |
| $inheritance = [System.Security.AccessControl.InheritanceFlags]'ContainerInherit, ObjectInherit' | |
| $propagation = [System.Security.AccessControl.PropagationFlags]::None | |
| $access = [System.Security.AccessControl.AccessControlType]::Allow | |
| foreach ($sidValue in @( | |
| $identity.User.Value, | |
| 'S-1-5-18', | |
| 'S-1-5-32-544' | |
| )) { | |
| $sid = [System.Security.Principal.SecurityIdentifier]::new($sidValue) | |
| $rule = [System.Security.AccessControl.FileSystemAccessRule]::new( | |
| $sid, | |
| $rights, | |
| $inheritance, | |
| $propagation, | |
| $access | |
| ) | |
| [void]$security.AddAccessRule($rule) | |
| } | |
| Set-Acl -LiteralPath $verifyRoot -AclObject $security | |
| $verifiedModule = Join-Path $verifyRoot "$libraryName.dll" | |
| Copy-Item -LiteralPath $module -Destination $verifiedModule | |
| cargo run --locked --example verify_module -- $verifiedModule | |
| - name: Assemble Unix module package | |
| if: ${{ runner.os != 'Windows' }} | |
| id: unix_package | |
| shell: bash | |
| env: | |
| BUNDLE_ID: ${{ matrix.id }} | |
| CRATE_NAME: ${{ needs.prepare.outputs.crate_name }} | |
| VERSION: ${{ needs.prepare.outputs.next_version }} | |
| run: | | |
| set -euo pipefail | |
| library_name="${CRATE_NAME//-/_}" | |
| case "$RUNNER_OS" in | |
| Linux) module="target/release/lib${library_name}.so" ;; | |
| macOS) module="target/release/lib${library_name}.dylib" ;; | |
| *) echo "unsupported Unix runner: ${RUNNER_OS}" >&2; exit 1 ;; | |
| esac | |
| if [[ ! -f "$module" ]]; then | |
| echo "module artifact is missing: ${module}" >&2 | |
| exit 1 | |
| fi | |
| package_name="${CRATE_NAME}-${VERSION}-${BUNDLE_ID}" | |
| package_root="dist/${package_name}" | |
| mkdir -p "$package_root" | |
| install -m 755 "$module" "$package_root/" | |
| install -m 644 LICENSE MODULE.md "$package_root/" | |
| module_name="$(basename "$module")" | |
| module_hash="$(shasum -a 256 "$package_root/$module_name" | awk '{print $1}')" | |
| printf '"%s" = "%s"\n' "$module_name" "$module_hash" \ | |
| > "$package_root/modules.toml" | |
| tar -C "$package_root" -czf "dist/${package_name}.tar.gz" . | |
| echo "archive=dist/${package_name}.tar.gz" >> "$GITHUB_OUTPUT" | |
| - name: Assemble Windows module package | |
| if: ${{ runner.os == 'Windows' }} | |
| id: windows_package | |
| shell: pwsh | |
| env: | |
| BUNDLE_ID: ${{ matrix.id }} | |
| CRATE_NAME: ${{ needs.prepare.outputs.crate_name }} | |
| VERSION: ${{ needs.prepare.outputs.next_version }} | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $libraryName = $env:CRATE_NAME.Replace('-', '_') | |
| $module = "target/release/$libraryName.dll" | |
| if (-not (Test-Path -LiteralPath $module -PathType Leaf)) { | |
| throw "module artifact is missing: $module" | |
| } | |
| $packageName = "$env:CRATE_NAME-$env:VERSION-$env:BUNDLE_ID" | |
| $packageRoot = "dist/$packageName" | |
| New-Item -ItemType Directory -Force $packageRoot | Out-Null | |
| Copy-Item -LiteralPath $module, 'LICENSE', 'MODULE.md' -Destination $packageRoot | |
| $moduleName = Split-Path -Leaf $module | |
| $hash = (Get-FileHash -LiteralPath "$packageRoot/$moduleName" -Algorithm SHA256).Hash.ToLowerInvariant() | |
| $utf8 = [System.Text.UTF8Encoding]::new($false) | |
| [System.IO.File]::WriteAllText( | |
| "$packageRoot/modules.toml", | |
| ('"{0}" = "{1}"' -f $moduleName, $hash) + [Environment]::NewLine, | |
| $utf8 | |
| ) | |
| $archive = "dist/$packageName.zip" | |
| Compress-Archive -Path "$packageRoot/*" -DestinationPath $archive | |
| "archive=$archive" >> $env:GITHUB_OUTPUT | |
| - name: Upload Unix package | |
| if: ${{ runner.os != 'Windows' }} | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: ${{ needs.prepare.outputs.crate_name }}-${{ matrix.id }} | |
| path: ${{ steps.unix_package.outputs.archive }} | |
| if-no-files-found: error | |
| - name: Upload Windows package | |
| if: ${{ runner.os == 'Windows' }} | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: ${{ needs.prepare.outputs.crate_name }}-${{ matrix.id }} | |
| path: ${{ steps.windows_package.outputs.archive }} | |
| if-no-files-found: error | |
| distro-bundles: | |
| name: Rust module bundle (${{ matrix.id }}) | |
| needs: prepare | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - id: fedora-43-x86_64 | |
| os: ubuntu-24.04 | |
| container: fedora:43 | |
| target: x86_64-unknown-linux-gnu | |
| family: fedora | |
| - id: fedora-43-arm64 | |
| os: ubuntu-24.04-arm | |
| container: fedora:43 | |
| target: aarch64-unknown-linux-gnu | |
| family: fedora | |
| - id: fedora-44-x86_64 | |
| os: ubuntu-24.04 | |
| container: fedora:44 | |
| target: x86_64-unknown-linux-gnu | |
| family: fedora | |
| - id: fedora-44-arm64 | |
| os: ubuntu-24.04-arm | |
| container: fedora:44 | |
| target: aarch64-unknown-linux-gnu | |
| family: fedora | |
| - id: archlinux-rolling-x86_64 | |
| os: ubuntu-24.04 | |
| container: archlinux:base-devel | |
| target: x86_64-unknown-linux-gnu | |
| family: archlinux | |
| runs-on: ${{ matrix.os }} | |
| container: ${{ matrix.container }} | |
| steps: | |
| - name: Install Fedora build tools | |
| if: ${{ matrix.family == 'fedora' }} | |
| run: dnf install -y gcc git gzip make perl tar | |
| - name: Install Arch Linux build tools | |
| if: ${{ matrix.family == 'archlinux' }} | |
| run: pacman -Syu --noconfirm base-devel git | |
| - uses: actions/checkout@v7 | |
| with: | |
| ref: ${{ needs.prepare.outputs.tag }} | |
| persist-credentials: false | |
| submodules: true | |
| - uses: dtolnay/rust-toolchain@stable | |
| - name: Verify native Rust target | |
| env: | |
| EXPECTED_TARGET: ${{ matrix.target }} | |
| run: | | |
| set -euo pipefail | |
| actual_target="$(rustc -vV | sed -n 's/^host: //p')" | |
| if [[ "$actual_target" != "$EXPECTED_TARGET" ]]; then | |
| echo "expected ${EXPECTED_TARGET}, got ${actual_target}" >&2 | |
| exit 1 | |
| fi | |
| - name: Build installable module | |
| run: cargo build --locked --release --lib | |
| - name: Verify module through TinyBus loader | |
| env: | |
| CRATE_NAME: ${{ needs.prepare.outputs.crate_name }} | |
| run: | | |
| set -euo pipefail | |
| library_name="${CRATE_NAME//-/_}" | |
| verify_root="/opt/${CRATE_NAME}-module-verify" | |
| install -d -m 700 "$verify_root" | |
| install -m 755 "target/release/lib${library_name}.so" "$verify_root/" | |
| cargo run --locked --example verify_module -- \ | |
| "$verify_root/lib${library_name}.so" | |
| - name: Assemble distribution module package | |
| id: package | |
| env: | |
| BUNDLE_ID: ${{ matrix.id }} | |
| CRATE_NAME: ${{ needs.prepare.outputs.crate_name }} | |
| VERSION: ${{ needs.prepare.outputs.next_version }} | |
| run: | | |
| set -euo pipefail | |
| library_name="${CRATE_NAME//-/_}" | |
| module="target/release/lib${library_name}.so" | |
| if [[ ! -f "$module" ]]; then | |
| echo "module artifact is missing: ${module}" >&2 | |
| exit 1 | |
| fi | |
| package_name="${CRATE_NAME}-${VERSION}-${BUNDLE_ID}" | |
| package_root="dist/${package_name}" | |
| mkdir -p "$package_root" | |
| install -m 755 "$module" "$package_root/" | |
| install -m 644 LICENSE MODULE.md "$package_root/" | |
| module_name="$(basename "$module")" | |
| module_hash="$(sha256sum "$package_root/$module_name" | awk '{print $1}')" | |
| printf '"%s" = "%s"\n' "$module_name" "$module_hash" \ | |
| > "$package_root/modules.toml" | |
| tar -C "$package_root" -czf "dist/${package_name}.tar.gz" . | |
| echo "archive=dist/${package_name}.tar.gz" >> "$GITHUB_OUTPUT" | |
| - name: Upload distribution package | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: ${{ needs.prepare.outputs.crate_name }}-${{ matrix.id }} | |
| path: ${{ steps.package.outputs.archive }} | |
| if-no-files-found: error | |
| github-release: | |
| name: Create GitHub release | |
| needs: | |
| - prepare | |
| - native-bundles | |
| - distro-bundles | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| ref: ${{ needs.prepare.outputs.tag }} | |
| persist-credentials: false | |
| submodules: true | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| pattern: '*' | |
| path: release-assets | |
| merge-multiple: true | |
| - name: Create release checksum manifest with TinyBus | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| shopt -s nullglob | |
| assets=(release-assets/*.tar.gz release-assets/*.zip) | |
| if [[ ${#assets[@]} -ne 16 ]]; then | |
| echo "expected 16 module archives, found ${#assets[@]}" >&2 | |
| exit 1 | |
| fi | |
| checksum_args=() | |
| for asset in "${assets[@]}"; do | |
| checksum_args+=(--path "$asset") | |
| done | |
| cargo run --manifest-path vendor/tinybus/Cargo.toml --locked \ | |
| --package tinybus --all-features --bin tinybus -- \ | |
| modules checksum "${checksum_args[@]}" \ | |
| --output release-assets/checksum.toml | |
| - name: Create immutable release with module packages | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| RELEASE_TAG: ${{ needs.prepare.outputs.tag }} | |
| REPOSITORY: ${{ github.repository }} | |
| run: | | |
| set -euo pipefail | |
| if gh release view "$RELEASE_TAG" --repo "$REPOSITORY" >/dev/null 2>&1; then | |
| echo "Release ${RELEASE_TAG} already exists; immutable assets are unchanged." | |
| else | |
| gh release create "$RELEASE_TAG" release-assets/* \ | |
| --repo "$REPOSITORY" \ | |
| --verify-tag \ | |
| --title "$RELEASE_TAG" \ | |
| --generate-notes | |
| fi | |
| - name: Verify the published module through TinyBus | |
| shell: bash | |
| env: | |
| RELEASE_TAG: ${{ needs.prepare.outputs.tag }} | |
| REPOSITORY: ${{ github.repository }} | |
| CRATE_NAME: ${{ needs.prepare.outputs.crate_name }} | |
| VERSION: ${{ needs.prepare.outputs.next_version }} | |
| run: | | |
| set -euo pipefail | |
| archive="${CRATE_NAME}-${VERSION}-ubuntu-24.04-x86_64.tar.gz" | |
| release_url="https://github.com/${REPOSITORY}/releases/tag/${RELEASE_TAG}" | |
| sha256="$( | |
| sed -n "s/^\"${archive}\" = \"\([0-9a-f]\{64\}\)\"$/\1/p" \ | |
| release-assets/checksum.toml | |
| )" | |
| if [[ -z "$sha256" ]]; then | |
| echo "checksum missing for ${archive}" >&2 | |
| exit 1 | |
| fi | |
| cargo run --manifest-path vendor/tinybus/Cargo.toml --locked \ | |
| --package tinybus --all-features --example github_module_host -- \ | |
| "$release_url" "$archive" "$sha256" | |
| cargo run --locked --example verify_github_release -- \ | |
| "$release_url" "$archive" "$sha256" |