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
364 changes: 364 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,364 @@
name: Rust CI/CD Pipeline

on:
push:
branches:
- main
paths:
- 'rust/**'
- '.github/workflows/rust.yml'
- 'scripts/**'
- 'changelog.d/**'
pull_request:
types: [opened, synchronize, reopened]
paths:
- 'rust/**'
- '.github/workflows/rust.yml'
- 'scripts/**'
- 'changelog.d/**'
workflow_dispatch:
inputs:
bump_type:
description: 'Version bump type'
required: true
type: choice
options:
- patch
- minor
- major
description:
description: 'Release description (optional)'
required: false
type: string

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
CARGO_TERM_COLOR: always

defaults:
run:
working-directory: rust

jobs:
# REQUIRED CI CHECKS - All must pass before release
# These jobs ensure code quality and tests pass before any release

# Linting and formatting
lint:
name: Lint and Format Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2022-08-22
components: rustfmt

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'

- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
rust/target
key: ${{ runner.os }}-cargo-${{ hashFiles('rust/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-

- name: Check formatting
run: cargo fmt --all -- --check

- name: Check file size limit
working-directory: .
run: node scripts/check-file-size.mjs

# Test on multiple OS
test:
name: Test (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2022-08-22

- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
rust/target
key: ${{ runner.os }}-cargo-${{ hashFiles('rust/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-

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

# Code coverage
coverage:
name: Code Coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2022-08-22
components: llvm-tools-preview

- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
rust/target
key: ${{ runner.os }}-cargo-coverage-${{ hashFiles('rust/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-coverage-

- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov

- name: Generate code coverage
run: cargo llvm-cov --all-features --lcov --output-path lcov.info

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: rust/lcov.info
fail_ci_if_error: false

# Build package - only runs if lint and test pass
build:
name: Build Package
runs-on: ubuntu-latest
needs: [lint, test]
steps:
- uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2022-08-22

- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
rust/target
key: ${{ runner.os }}-cargo-build-${{ hashFiles('rust/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-build-

- name: Build release
run: cargo build --release --verbose

# Check for changelog fragments in PRs
changelog:
name: Changelog Fragment Check
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Check for changelog fragments
working-directory: .
run: |
# Get list of fragment files (excluding README and template)
FRAGMENTS=$(find changelog.d -name "*.md" ! -name "README.md" 2>/dev/null | wc -l)

# Get changed files in PR
CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD)

# Check if any source files changed (excluding docs and config)
SOURCE_CHANGED=$(echo "$CHANGED_FILES" | grep -E "^(rust/src/|rust/tests/|scripts/)" | wc -l)

if [ "$SOURCE_CHANGED" -gt 0 ] && [ "$FRAGMENTS" -eq 0 ]; then
echo "::warning::No changelog fragment found. Please add a changelog entry in changelog.d/"
echo ""
echo "To create a changelog fragment:"
echo " Create a new .md file in changelog.d/ with your changes"
echo ""
echo "See changelog.d/README.md for more information."
# Note: This is a warning, not a failure, to allow flexibility
# Change 'exit 0' to 'exit 1' to make it required
exit 0
fi

echo "Changelog check passed"

# Automatic release on push to main using changelog fragments
# This job automatically bumps version based on fragments in changelog.d/
auto-release:
name: Auto Release
needs: [lint, test, build, coverage]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2022-08-22

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'

- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
working-directory: .

- name: Determine bump type from changelog fragments
id: bump_type
working-directory: .
run: node scripts/get-bump-type.mjs

- name: Check if version already released or no fragments
id: check
working-directory: .
run: |
# Check if there are changelog fragments
if [ "${{ steps.bump_type.outputs.has_fragments }}" != "true" ]; then
# No fragments - check if current version tag exists
CURRENT_VERSION=$(grep -Po '(?<=^version = ")[^"]*' rust/Cargo.toml)
if git rev-parse "v$CURRENT_VERSION" >/dev/null 2>&1; then
echo "No changelog fragments and v$CURRENT_VERSION already released"
echo "should_release=false" >> $GITHUB_OUTPUT
else
echo "No changelog fragments but v$CURRENT_VERSION not yet released"
echo "should_release=true" >> $GITHUB_OUTPUT
echo "skip_bump=true" >> $GITHUB_OUTPUT
fi
else
echo "Found changelog fragments, proceeding with release"
echo "should_release=true" >> $GITHUB_OUTPUT
echo "skip_bump=false" >> $GITHUB_OUTPUT
fi

- name: Collect changelog and bump version
id: version
if: steps.check.outputs.should_release == 'true' && steps.check.outputs.skip_bump != 'true'
working-directory: .
run: |
node scripts/version-and-commit.mjs \
--bump-type "${{ steps.bump_type.outputs.bump_type }}"

- name: Get current version
id: current_version
if: steps.check.outputs.should_release == 'true'
working-directory: .
run: |
CURRENT_VERSION=$(grep -Po '(?<=^version = ")[^"]*' rust/Cargo.toml)
echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT

- name: Build release
if: steps.check.outputs.should_release == 'true'
run: cargo build --release

- name: Create GitHub Release
if: steps.check.outputs.should_release == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
working-directory: .
run: |
node scripts/create-github-release.mjs \
--release-version "${{ steps.current_version.outputs.version }}" \
--repository "${{ github.repository }}"

# Manual release via workflow_dispatch - only after CI passes
manual-release:
name: Manual Release
needs: [lint, test, build, coverage]
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2022-08-22

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'

- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
working-directory: .

- name: Collect changelog fragments
working-directory: .
run: |
# Check if there are any fragments to collect
FRAGMENTS=$(find changelog.d -name "*.md" ! -name "README.md" 2>/dev/null | wc -l)
if [ "$FRAGMENTS" -gt 0 ]; then
echo "Found $FRAGMENTS changelog fragment(s), collecting..."
node scripts/collect-changelog.mjs
else
echo "No changelog fragments found, skipping collection"
fi

- name: Version and commit
id: version
working-directory: .
run: |
node scripts/version-and-commit.mjs \
--bump-type "${{ github.event.inputs.bump_type }}" \
--description "${{ github.event.inputs.description }}"

- name: Build release
if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true'
run: cargo build --release

- name: Create GitHub Release
if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
working-directory: .
run: |
node scripts/create-github-release.mjs \
--release-version "${{ steps.version.outputs.new_version }}" \
--repository "${{ github.repository }}"
Loading
Loading