name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
jobs:
fmt:
name: Format Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- name: Check formatting
run: cargo fmt -- --check
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: Cache
uses: Swatinem/rust-cache@v2
- name: Run Clippy on full workspace
run: cargo clippy --all-targets --workspace -- -D warnings
test:
name: Test
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
rust: [stable, beta]
include:
- os: ubuntu-latest
rust: nightly
steps:
- uses: actions/checkout@v6
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
- name: Cache
uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.rust }}
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
- name: Build documentation
run: cargo doc --no-deps
features:
name: Feature Tests
runs-on: ubuntu-latest
strategy:
matrix:
features:
- ""
- "python"
steps:
- uses: actions/checkout@v6
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache
uses: Swatinem/rust-cache@v2
- name: Build with features
run: cargo build --no-default-features --features "${{ matrix.features }}"
if: matrix.features != ''
- name: Build without features
run: cargo build --no-default-features
if: matrix.features == ''
cli:
name: CLI Build
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v6
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache
uses: Swatinem/rust-cache@v2
- name: Build CLI
run: cargo build -p pdf_oxide_cli
- name: Test CLI --help
run: cargo run -p pdf_oxide_cli -- --help
mcp:
name: MCP Build
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v6
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache
uses: Swatinem/rust-cache@v2
- name: Build MCP server
run: cargo build -p pdf_oxide_mcp
python:
name: Python Bindings
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache
uses: Swatinem/rust-cache@v2
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Create virtual environment
run: uv venv
- name: Install maturin
run: uv pip install maturin pytest
- name: Build Python package
run: uv run maturin build --release --features python --out dist
- name: Install wheel
shell: bash
run: |
wheel=$(ls dist/*.whl)
uv pip install "$wheel"
- name: Test Python bindings
run: uv run python -c "import pdf_oxide; print(pdf_oxide.VERSION)"
wasm-build:
name: WASM Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
components: clippy
- name: Cache
uses: Swatinem/rust-cache@v2
- name: Build WASM
run: cargo build --lib --target wasm32-unknown-unknown --features wasm
- name: Clippy WASM
run: cargo clippy --target wasm32-unknown-unknown --no-default-features --features wasm --lib -- -D warnings
ffi-build:
name: FFI Build (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v6
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache
uses: Swatinem/rust-cache@v2
with:
key: ffi-${{ matrix.os }}
- name: Build library (produces cdylib + rlib)
run: cargo build --release --lib
go:
name: Go Bindings (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v6
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: '1.22'
- name: Cache
uses: Swatinem/rust-cache@v2
with:
key: go-${{ matrix.os }}
- name: Build library (produces cdylib + rlib)
run: cargo build --release --lib
- name: Stage native lib for Go
shell: bash
run: |
case "${{ matrix.os }}" in
ubuntu-latest)
mkdir -p go/lib/linux_amd64
cp target/release/libpdf_oxide.so go/lib/linux_amd64/
;;
macos-latest)
mkdir -p go/lib/darwin_arm64
cp target/release/libpdf_oxide.dylib go/lib/darwin_arm64/
;;
windows-latest)
mkdir -p go/lib/windows_amd64
cp target/release/pdf_oxide.dll go/lib/windows_amd64/
;;
esac
- name: Go build
working-directory: go
run: go build ./...
- name: Go vet
working-directory: go
run: go vet ./...
- name: Go format check
working-directory: go
shell: bash
run: |
diff=$(gofmt -l .)
if [ -n "$diff" ]; then
echo "The following Go files need formatting:"
echo "$diff"
echo
echo "Run 'gofmt -w .' to fix."
exit 1
fi
nodejs:
name: Node.js Bindings (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v6
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '20'
- name: Cache
uses: Swatinem/rust-cache@v2
with:
key: nodejs-${{ matrix.os }}
- name: Build library (produces cdylib + rlib)
run: cargo build --release --lib
- name: Install dependencies
working-directory: js
run: npm install --ignore-scripts
- name: TypeScript type check
working-directory: js
run: npx tsc --noEmit
csharp:
name: C# Bindings (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v6
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Set up .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Cache
uses: Swatinem/rust-cache@v2
with:
key: csharp-${{ matrix.os }}
- name: Build library (produces cdylib + rlib)
run: cargo build --release --lib
- name: Build C# library
working-directory: csharp/PdfOxide
run: dotnet build -c Release -p:TreatWarningsAsErrors=true
- name: Build C# tests
working-directory: csharp/PdfOxide.Tests
run: dotnet build -c Release -p:TreatWarningsAsErrors=true
- name: Stage native lib for C# tests
shell: bash
run: |
case "${{ matrix.os }}" in
ubuntu-latest)
cp target/release/libpdf_oxide.so csharp/PdfOxide.Tests/bin/Release/net8.0/
;;
macos-latest)
cp target/release/libpdf_oxide.dylib csharp/PdfOxide.Tests/bin/Release/net8.0/
;;
windows-latest)
cp target/release/pdf_oxide.dll csharp/PdfOxide.Tests/bin/Release/net8.0/
;;
esac
- name: Run C# tests
working-directory: csharp/PdfOxide.Tests
run: dotnet test -c Release --no-build --verbosity normal
coverage:
name: Code Coverage
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' || github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v6
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: llvm-tools-preview
- name: Cache
uses: Swatinem/rust-cache@v2
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
- name: Generate coverage
run: |
cargo llvm-cov --lib --tests --lcov --output-path lcov.info \
--ignore-filename-regex '(cid_mappings/|adobe_glyph_list\.rs|python\.rs|wasm\.rs|ffi\.rs|ocr/|rendering/|bin/)'
- name: Enforce 85% coverage threshold
run: |
cargo llvm-cov report \
--ignore-filename-regex '(cid_mappings/|adobe_glyph_list\.rs|python\.rs|wasm\.rs|ffi\.rs|ocr/|rendering/|bin/)' \
--fail-under-lines 85
- name: Upload coverage to Codecov
if: always()
uses: codecov/codecov-action@v6
with:
files: ./lcov.info
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
bench:
name: Benchmark Smoke Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache
uses: Swatinem/rust-cache@v2
- name: Build benchmarks
run: cargo bench --no-run
audit:
name: Security Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Run security audit
uses: actions-rust-lang/audit@v1
deny:
name: Dependency Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Check dependencies
uses: EmbarkStudios/cargo-deny-action@v2
with:
arguments: ""