wasm: in-browser OCR via ONNX Runtime Web interop (#157 stage 1) #501
Workflow file for this run
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: CI | |
| on: | |
| push: | |
| branches: [master] | |
| pull_request: | |
| branches: [master] | |
| # Manual re-run from the Actions tab / `gh workflow run`. Left blank it runs lint | |
| # + test only; set `force_version` to (re)publish that exact version — use it to | |
| # release a version a failed/blocked run skipped (e.g. the master-ruleset reject). | |
| workflow_dispatch: | |
| inputs: | |
| force_version: | |
| description: "Force a release at this exact version (e.g. 0.3.0). Blank = lint+test only, no publish." | |
| required: false | |
| default: "" | |
| # Cancel superseded PR runs, but never interrupt a master run (it may publish). | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| env: | |
| CARGO_TERM_COLOR: always | |
| # Keep target/ small enough to cache and restore: no incremental artifacts | |
| # (useless for one-shot CI builds, gigabytes on disk) and no debuginfo in | |
| # dev/test binaries (the bulk of a debug target; tests behave the same, | |
| # backtraces just lose line numbers). Without this the cached target/ grew | |
| # past 6 GB compressed (~20 GB unpacked) and cache *restore* started dying | |
| # with ENOSPC on the runner disk — which is also why every cache key below | |
| # carries a `v2` generation: the old oversized entries must never match. | |
| CARGO_INCREMENTAL: "0" | |
| CARGO_PROFILE_DEV_DEBUG: "0" | |
| CARGO_PROFILE_TEST_DEBUG: "0" | |
| # Rust version for the style/lint gates. Pinned on purpose: rustfmt and clippy | |
| # change their output/lints between releases, so a floating `stable` would let | |
| # a new toolchain fail CI on otherwise-unrelated commits. Bump this manually | |
| # (and fix any new findings in the same PR) when you want to adopt a newer one. | |
| LINT_TOOLCHAIN: "1.96.0" | |
| jobs: | |
| lint: | |
| name: fmt · clippy (pinned) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| # rustup is preinstalled on GitHub-hosted runners; use it directly rather | |
| # than a marketplace toolchain action (the docling-project org allowlists | |
| # only GitHub-owned / vetted actions). | |
| - name: Install Rust ${{ env.LINT_TOOLCHAIN }} | |
| run: | | |
| rustup toolchain install ${{ env.LINT_TOOLCHAIN }} --profile minimal --component rustfmt --component clippy | |
| rustup default ${{ env.LINT_TOOLCHAIN }} | |
| - uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry/index | |
| ~/.cargo/registry/cache | |
| ~/.cargo/git/db | |
| target | |
| key: ${{ runner.os }}-cargo-v2-lint-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: ${{ runner.os }}-cargo-v2-lint- | |
| - name: Format | |
| run: cargo fmt --all --check | |
| - name: Clippy | |
| run: cargo clippy --workspace --all-targets -- -D warnings | |
| # actions/cache saves target/ in its post-step, i.e. after this runs: | |
| # drop what is cheap to recreate (incremental data shouldn't exist with | |
| # CARGO_INCREMENTAL=0; examples and final bin-crate executables relink | |
| # in seconds — the compiled library deps are what's worth keeping), so | |
| # the cache stays well under the runner's disk headroom. | |
| - name: Trim target before cache save | |
| if: always() | |
| shell: bash | |
| run: | | |
| rm -rf target/debug/examples target/debug/incremental \ | |
| target/release/examples target/release/incremental | |
| find target/debug target/release -maxdepth 1 -type f ! -name '*.d' -size +5M -exec rm -f {} + 2>/dev/null || true | |
| test: | |
| name: test (stable) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| # Tests stay on the current stable so they catch real compiler breakage. | |
| - name: Install Rust (stable) | |
| run: | | |
| rustup toolchain install stable --profile minimal | |
| rustup default stable | |
| - uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry/index | |
| ~/.cargo/registry/cache | |
| ~/.cargo/git/db | |
| target | |
| key: ${{ runner.os }}-cargo-v2-test-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: ${{ runner.os }}-cargo-v2-test- | |
| # Pure-Rust unit tests + the output-regression suite. The PDF/image/METS | |
| # snapshot tests need pdfium + the ONNX models, so they are NOT run here — | |
| # this job downloads no ML models and stays fast. --locked doubles as the | |
| # guard that keeps the committed Cargo.lock in sync with the manifests | |
| # (a Cargo.toml change without its lock update fails here). | |
| - name: Test | |
| run: cargo test --workspace --locked | |
| # actions/cache saves target/ in its post-step, i.e. after this runs: | |
| # drop what is cheap to recreate (incremental data shouldn't exist with | |
| # CARGO_INCREMENTAL=0; examples and final bin-crate executables relink | |
| # in seconds — the compiled library deps are what's worth keeping), so | |
| # the cache stays well under the runner's disk headroom. | |
| - name: Trim target before cache save | |
| if: always() | |
| shell: bash | |
| run: | | |
| rm -rf target/debug/examples target/debug/incremental \ | |
| target/release/examples target/release/incremental | |
| find target/debug target/release -maxdepth 1 -type f ! -name '*.d' -size +5M -exec rm -f {} + 2>/dev/null || true | |
| # Validate the declared `rust-version` floors against the committed | |
| # Cargo.lock (rust-lang.org/2023/08/29/committing-lockfiles: a committed | |
| # lock is what makes MSRV checkable at all). Two tiers: docling-core keeps a | |
| # lower floor (no ort/pdfium), the rest of the workspace sits at the ort | |
| # floor. Keep these toolchains in sync with the `rust-version` values in the | |
| # manifests — a weekly dependency-update PR that pulls a dep with a higher | |
| # floor turns this job red, forcing a conscious pin or floor bump. | |
| msrv: | |
| name: msrv (1.85 core · 1.88 workspace) | |
| runs-on: ubuntu-latest | |
| env: | |
| MSRV_CORE: "1.85" | |
| MSRV_WORKSPACE: "1.88" | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Install Rust ${{ env.MSRV_CORE }} + ${{ env.MSRV_WORKSPACE }} | |
| run: rustup toolchain install ${{ env.MSRV_CORE }} ${{ env.MSRV_WORKSPACE }} --profile minimal | |
| - uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry/index | |
| ~/.cargo/registry/cache | |
| ~/.cargo/git/db | |
| target | |
| key: ${{ runner.os }}-cargo-v2-msrv-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: ${{ runner.os }}-cargo-v2-msrv- | |
| - name: Check docling-core on ${{ env.MSRV_CORE }} | |
| run: cargo +${{ env.MSRV_CORE }} check -p docling-core --locked | |
| - name: Check workspace on ${{ env.MSRV_WORKSPACE }} | |
| run: cargo +${{ env.MSRV_WORKSPACE }} check --workspace --locked | |
| # actions/cache saves target/ in its post-step, i.e. after this runs: | |
| # drop what is cheap to recreate (incremental data shouldn't exist with | |
| # CARGO_INCREMENTAL=0; examples and final bin-crate executables relink | |
| # in seconds — the compiled library deps are what's worth keeping), so | |
| # the cache stays well under the runner's disk headroom. | |
| - name: Trim target before cache save | |
| if: always() | |
| shell: bash | |
| run: | | |
| rm -rf target/debug/examples target/debug/incremental \ | |
| target/release/examples target/release/incremental | |
| find target/debug target/release -maxdepth 1 -type f ! -name '*.d' -size +5M -exec rm -f {} + 2>/dev/null || true | |
| # Build-verify the GPU execution-provider features (#74) without GPU | |
| # runners: `cargo check` proves the feature plumbing and the EP registration | |
| # code compile against the matching `ort` feature set. Each provider is | |
| # checked on the platform its ONNX Runtime binaries exist for — actually | |
| # *running* on GPU is out of scope for CI (no GPU hardware here). | |
| ep-features: | |
| name: check (${{ matrix.features }}, ${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # `slug` is the features list with `-` instead of `,` — actions/cache | |
| # rejects keys containing commas. | |
| include: | |
| - os: ubuntu-latest | |
| features: cuda | |
| slug: cuda | |
| - os: ubuntu-latest | |
| features: cuda,tensorrt | |
| slug: cuda-tensorrt | |
| - os: macos-latest | |
| features: coreml | |
| slug: coreml | |
| - os: windows-latest | |
| features: directml | |
| slug: directml | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Install Rust (stable) | |
| run: | | |
| rustup toolchain install stable --profile minimal | |
| rustup default stable | |
| - uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry/index | |
| ~/.cargo/registry/cache | |
| ~/.cargo/git/db | |
| target | |
| key: ${{ runner.os }}-cargo-v2-ep-${{ matrix.slug }}-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: ${{ runner.os }}-cargo-v2-ep-${{ matrix.slug }}- | |
| - name: Check docling-cli with GPU features | |
| run: cargo check -p docling-cli --features "${{ matrix.features }}" | |
| # docling-rag mirrors the same EP features (ingest conversion + the | |
| # onnx-embed embedder session route through docling-pdf's ep module). | |
| - name: Check docling-rag with GPU features | |
| run: cargo check -p docling-rag --features "${{ matrix.features }},onnx-embed" | |
| - name: Test docling-pdf EP selection with GPU features | |
| run: 'cargo test -p docling-pdf --lib --features "${{ matrix.features }}" ep::' | |
| # actions/cache saves target/ in its post-step, i.e. after this runs: | |
| # drop what is cheap to recreate (incremental data shouldn't exist with | |
| # CARGO_INCREMENTAL=0; examples and final bin-crate executables relink | |
| # in seconds — the compiled library deps are what's worth keeping), so | |
| # the cache stays well under the runner's disk headroom. | |
| - name: Trim target before cache save | |
| if: always() | |
| shell: bash | |
| run: | | |
| rm -rf target/debug/examples target/debug/incremental \ | |
| target/release/examples target/release/incremental | |
| find target/debug target/release -maxdepth 1 -type f ! -name '*.d' -size +5M -exec rm -f {} + 2>/dev/null || true | |
| # The declarative converters compile for the browser (#79): check the | |
| # feature-sliced `docling` on wasm32, build the wasm-bindgen crate, and log | |
| # the gzipped module size (the number docs/README quote — ~1.9 MB). | |
| wasm: | |
| name: wasm32 (declarative converters) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Install Rust (stable + wasm32 target) | |
| run: | | |
| rustup toolchain install stable --profile minimal | |
| rustup default stable | |
| rustup target add wasm32-unknown-unknown | |
| - uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry/index | |
| ~/.cargo/registry/cache | |
| ~/.cargo/git/db | |
| target | |
| key: ${{ runner.os }}-cargo-v2-wasm-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: ${{ runner.os }}-cargo-v2-wasm- | |
| - name: Check docling (no default features) on wasm32 | |
| run: cargo check -p docling --no-default-features --target wasm32-unknown-unknown --locked | |
| - name: Check docling (pdf-text) on wasm32 | |
| run: cargo check -p docling --no-default-features --features pdf-text --target wasm32-unknown-unknown --locked | |
| - name: Build docling-wasm (release) | |
| run: cargo build -p docling-wasm --target wasm32-unknown-unknown --release --locked | |
| - name: Host-side tests | |
| run: cargo test -p docling-wasm --locked | |
| - name: Module size | |
| run: | | |
| wasm=target/wasm32-unknown-unknown/release/docling_wasm.wasm | |
| gzip -9 -c "$wasm" | wc -c | xargs -I{} echo "docling_wasm.wasm: $(stat -c%s "$wasm") bytes raw, {} bytes gzipped" | |
| # actions/cache saves target/ in its post-step, i.e. after this runs: | |
| # drop what is cheap to recreate (incremental data shouldn't exist with | |
| # CARGO_INCREMENTAL=0; examples and final bin-crate executables relink | |
| # in seconds — the compiled library deps are what's worth keeping), so | |
| # the cache stays well under the runner's disk headroom. | |
| - name: Trim target before cache save | |
| if: always() | |
| shell: bash | |
| run: | | |
| rm -rf target/debug/examples target/debug/incremental \ | |
| target/release/examples target/release/incremental | |
| find target/debug target/release -maxdepth 1 -type f ! -name '*.d' -size +5M -exec rm -f {} + 2>/dev/null || true | |
| publish: | |
| name: release & publish | |
| needs: [lint, test] | |
| if: >- | |
| (github.event_name == 'push' && github.ref == 'refs/heads/master') || | |
| (github.event_name == 'workflow_dispatch' && github.event.inputs.force_version != '') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write # push the version-bump commit + tag | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 # full history + tags, for the semantic version bump | |
| # Push the release commit + tag as an admin PAT so it satisfies the | |
| # `master` ruleset; the default GITHUB_TOKEN can't be granted a bypass. | |
| token: ${{ secrets.RELEASE_PAT }} | |
| - name: Install Rust (stable) | |
| run: | | |
| rustup toolchain install stable --profile minimal | |
| rustup default stable | |
| - uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry/index | |
| ~/.cargo/registry/cache | |
| ~/.cargo/git/db | |
| target | |
| key: ${{ runner.os }}-cargo-v2-publish-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: ${{ runner.os }}-cargo-v2-publish- | |
| # Decide the next version from conventional commits since the last tag (or | |
| # use force_version on a manual dispatch); if there is one, bump + commit + | |
| # tag it, push to master, and publish the crates (in dependency order, | |
| # skipping any already on crates.io). A no-op when no release-worthy commit | |
| # landed. The release commit is pushed with RELEASE_PAT (an admin token, so | |
| # it satisfies the master ruleset) and carries [skip ci], so it does not | |
| # re-trigger CI. | |
| - name: Release & publish | |
| id: release | |
| env: | |
| CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} | |
| FORCE_VERSION: ${{ github.event.inputs.force_version }} | |
| run: bash scripts/ci/release.sh | |
| # Cut a GitHub Release for the tag release.sh just pushed, using the commit | |
| # list it assembled as the description. Skipped when the release step was a | |
| # no-op (no release-worthy commits). Idempotent: if a release for the tag | |
| # already exists (e.g. a forced re-publish), refresh its notes instead of | |
| # failing. | |
| - name: Create GitHub Release | |
| if: steps.release.outputs.released == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.RELEASE_PAT }} | |
| run: | | |
| tag="v${{ steps.release.outputs.version }}" | |
| notes="${{ steps.release.outputs.notes_file }}" | |
| if gh release view "$tag" >/dev/null 2>&1; then | |
| gh release edit "$tag" --title "$tag" --notes-file "$notes" | |
| else | |
| gh release create "$tag" --title "$tag" --notes-file "$notes" --verify-tag | |
| fi | |
| # actions/cache saves target/ in its post-step, i.e. after this runs: | |
| # drop what is cheap to recreate (incremental data shouldn't exist with | |
| # CARGO_INCREMENTAL=0; examples and final bin-crate executables relink | |
| # in seconds — the compiled library deps are what's worth keeping), so | |
| # the cache stays well under the runner's disk headroom. | |
| - name: Trim target before cache save | |
| if: always() | |
| shell: bash | |
| run: | | |
| rm -rf target/debug/examples target/debug/incremental \ | |
| target/release/examples target/release/incremental | |
| find target/debug target/release -maxdepth 1 -type f ! -name '*.d' -size +5M -exec rm -f {} + 2>/dev/null || true |