Skip to main content

Build, Test, and Contribute

1. Why this chapter exists

You can read every other chapter without ever running a command, and you will get nothing useful out of it. Code understanding without an executable loop is theatre. This chapter exists so that by the end of it, you can:

  • compile the workspace,
  • run a focused test against a single function,
  • run the whole CI matrix locally,
  • and reproduce the four checks that CI gates a PR on (test, fmt, clippy, docs).

It also identifies the files that contributors actually touch, so you know where to look for examples of what good change sets look like.

2. Definitions

Definition (MSRV). Minimum Supported Rust Version. Set in rust-toolchain.toml to 1.60.0. The MSRV is binding: a PR that requires a newer compiler is rejected.

Definition (Hot file). A source file modified more than twice in the last twelve months. These are the files where most contributions land; the list is in discovery.md.

Definition (Feature flag set). The exact set of --features flags CI uses. Defined in .github/actions/prepare/action.yml.

3. The code

3.1 The toolchain

rust-toolchain.toml
[toolchain]
channel = "1.60.0"
components = ["clippy", "rustfmt"]

Install rustup, then cd into the repo. The toolchain file will pin your compiler to 1.60.0 automatically. Two facts to keep in mind:

  • clippy and rustfmt are listed as required components; rustup will install them on first use.
  • Many crates in the wider ecosystem have moved past MSRV 1.60. The workspace pins indirect-dev dependencies (dashmap, image, tempfile) to versions that still build on 1.60, as documented in halo2_proofs/Cargo.toml. Do not bump them without coordinating an MSRV update.

3.2 The feature flags CI uses

The composite action that all CI jobs share is:

.github/actions/prepare/action.yml
name: 'Prepare Halo 2'
description: 'Prepares feature flags'
inputs:
beta-features:
description: 'Include beta features'
required: false
default: false
nightly-features:
description: 'Include nightly features'
required: false
default: false
test-dependencies:
description: 'Include test dependencies'
required: false
default: true
outputs:
feature-flags:
description: 'Feature flags'
value: ${{ steps.prepare-flags.outputs.flags }}
runs:
using: 'composite'
steps:
- id: beta
shell: bash
run: echo "feature=beta" >> $GITHUB_OUTPUT
if: inputs.beta-features == 'true'
- id: nightly
shell: bash
run: echo "feature=nightly" >> $GITHUB_OUTPUT
if: inputs.nightly-features == 'true'
- id: test
shell: bash
run: echo "feature=test-dependencies" >> $GITHUB_OUTPUT
if: inputs.test-dependencies == 'true'
- id: prepare-flags
shell: bash
run: >
echo "flags=--no-default-features --features '
batch
dev-graph
gadget-traces
${{ steps.beta.outputs.feature }}
${{ steps.nightly.outputs.feature }}
${{ steps.test.outputs.feature }}
'" >> $GITHUB_OUTPUT

The flag string is:

--no-default-features --features 'batch dev-graph gadget-traces [beta] [nightly] [test-dependencies]'

To reproduce a CI test run locally:

cargo test --release --workspace \
--no-default-features \
--features 'batch dev-graph gadget-traces test-dependencies'

For the beta and nightly variants, append --features beta or --features nightly.

3.3 Test layout

  • Unit tests are inline in the files they test; search for #[cfg(test)] mod tests at the bottom of any .rs file.
  • Integration tests live in halo2_proofs/tests/. The main one is plonk_api.rs with a checked-in proof fixture plonk_api_proof.bin. Any change that alters the byte layout of a proof must regenerate this fixture or fail CI.
  • Examples (halo2_proofs/examples/) are also compiled by CI under the bitrot job.
  • Benchmarks live in halo2_proofs/benches/ with harness = false declared in Cargo.toml; run with cargo bench.

3.4 Local commands you will use daily

# Build the workspace in debug mode.
cargo build --workspace

# Run all tests with the CI feature set.
cargo test --release --workspace \
--no-default-features \
--features 'batch dev-graph gadget-traces test-dependencies'

# Run one test by name (substring match).
cargo test --release -p halo2_proofs plonk_api -- --nocapture

# Format check (CI-equivalent).
cargo fmt --all -- --check

# Format in place.
cargo fmt --all

# Clippy (MSRV) with all targets and warnings denied.
cargo clippy --no-default-features \
--features 'batch dev-graph gadget-traces test-dependencies' \
--all-targets -- -D warnings

# Run the simple-example.
cargo run --release --example simple-example -p halo2_proofs

# Build the rendered API docs.
cargo doc --workspace --all-features --document-private-items --open

3.5 The CI graph

The PR checks are split across several workflows:

.github/workflows/ci.yml
name: CI checks

on:
pull_request:
push:
branches: main

permissions: {}

jobs:
test:
name: Test on ${{ matrix.os }}${{ matrix.name_suffix }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
stage: [stable, beta, nightly]
os: [ubuntu-latest, windows-latest, macOS-latest]
include:
- stage: beta
name_suffix: " with beta features"
extra_flags: --features beta
- stage: nightly
name_suffix: " with nightly features"
extra_flags: --features nightly

steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- id: prepare
uses: ./.github/actions/prepare
- name: Run tests
run: >
cargo test
--verbose
--release
--workspace
${{ steps.prepare.outputs.feature-flags }}
${{ matrix.extra_flags }}
- name: Verify working directory is clean
run: git diff --exit-code

test-32-bit:
name: Test on i686-unknown-linux-gnu${{ matrix.name_suffix }}
runs-on: ubuntu-latest
strategy:
matrix:
stage: [stable, beta, nightly]
include:
- stage: beta
name_suffix: " with beta features"
- stage: nightly
name_suffix: " with nightly features"

steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- id: prepare
uses: ./.github/actions/prepare
with:
beta-features: ${{ matrix.stage == 'beta' }}
nightly-features: ${{ matrix.stage == 'nightly' }}
- name: Install cross-platform support dependencies
run: sudo apt install gcc-multilib
- run: rustup target add i686-unknown-linux-gnu
- name: Run tests
run: >
cargo test
--verbose
--release
--workspace
--target i686-unknown-linux-gnu
${{ steps.prepare.outputs.feature-flags }}

build:
name: Build target ${{ matrix.target }}
runs-on: ubuntu-latest
strategy:
matrix:
target:
- wasm32-unknown-unknown
- wasm32-wasi

steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- id: prepare
uses: ./.github/actions/prepare
with:
nightly-features: true
test-dependencies: false
- name: Add target
run: rustup target add ${{ matrix.target }}
- name: cargo build
run: >
cargo build
${{ steps.prepare.outputs.feature-flags }}
--target ${{ matrix.target }}

bitrot:
name: Bitrot check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: ./.github/actions/prepare
# Check bitrot with stable (as we don't need benchmarks or the test-dev-graph
# feature flag to work with MSRV).
- uses: dtolnay/rust-toolchain@stable
id: toolchain
- run: rustup override set "${TOOLCHAIN}"
shell: sh
env:
TOOLCHAIN: ${{steps.toolchain.outputs.name}}
- run: sudo apt-get -y install libfontconfig1-dev
# Build benchmarks and all-features to prevent bitrot
- name: Build benchmarks
run: cargo build --benches --examples --all-features

book:
name: Book tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: ./.github/actions/prepare
# Build with stable so the .rmeta files match what mdBook expects.
- uses: dtolnay/rust-toolchain@stable
id: toolchain
- run: rustup override set "${TOOLCHAIN}"
shell: sh
env:
TOOLCHAIN: ${{steps.toolchain.outputs.name}}
- name: cargo build
run: cargo build
- name: Setup mdBook
uses: peaceiris/actions-mdbook@ee69d230fe19748b7abf22df32acaa93833fad08 # v2.0.0
with:
mdbook-version: '0.5.1'
- name: Test halo2 book
run: mdbook test -L target/debug/deps book/

codecov:
name: Code coverage
runs-on: ubuntu-latest
container:
image: xd009642/tarpaulin:develop-nightly
options: --security-opt seccomp=unconfined
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- id: prepare
uses: ./.github/actions/prepare
with:
nightly-features: true
# Extend the timeout to 3600 to ensure the code coverage test pass
- name: Generate coverage report
run: >
cargo tarpaulin
--engine llvm
${{ steps.prepare.outputs.feature-flags }}
--timeout 3600
--out xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3.1.4

doc-links:
name: Intra-doc links
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: ./.github/actions/prepare
- run: sudo apt-get -y install libfontconfig1-dev
- run: cargo fetch
# Ensure intra-documentation links all resolve correctly
# Requires #![deny(intra_doc_link_resolution_failure)] in crates.
- name: Check intra-doc links
run: cargo doc --all --all-features --document-private-items

fmt:
name: Rustfmt
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: ./.github/actions/prepare
- run: cargo fmt --all -- --check

The notable jobs:

  • test: matrix over {stable, beta, nightly} x {ubuntu, windows, macOS}, running cargo test --release --workspace.
  • test-32-bit: i686 cross-compile and test. Catches usize-as-32-bit bugs.
  • build: cross-compiles to wasm32-unknown-unknown and wasm32-wasi. WASM-incompatible imports break here.
  • bitrot: builds benchmarks and examples with --all-features using the stable toolchain.
  • book: runs mdbook test against the book/ directory; the book includes runnable Rust examples that must compile.
  • codecov: runs cargo tarpaulin with nightly features and uploads coverage.
  • doc-links: runs cargo doc --all --all-features --document-private-items with #![deny(rustdoc::broken_intra_doc_links)].
  • fmt: cargo fmt --all -- --check.
  • lints-stable.yml and lints-beta.yml: clippy at MSRV / beta with -D warnings.

3.6 Hot files (where contributions land)

The most-changed files in the last twelve months. Each is a likely landing point for newcomer PRs:

3.7 Good first issues

The repo does not use a good-first-issue label, but two categories of open issues are within reach of a newcomer:

  • A-book issues (example: #800, #810): they only require editing book/src/....
  • Sanity-check or documentation improvements (e.g. #778, #794): small, well-scoped edits.

A complete list is at zcash/halo2/issues.

3.8 The PR checklist

Before pushing a PR, run locally:

  1. cargo fmt --all and verify with cargo fmt --all -- --check.
  2. cargo clippy --no-default-features --features 'batch dev-graph gadget-traces test-dependencies' --all-targets -- -D warnings.
  3. The CI test command above.
  4. cargo doc --workspace --all-features --document-private-items to verify no broken intra-doc links.
  5. If you touched any file that affects proof bytes, regenerate halo2_proofs/tests/plonk_api_proof.bin.
  6. Update the relevant CHANGELOG.md in either halo2_proofs/ or halo2_gadgets/. The entry goes under ## [Unreleased].

The repo does not require signed commits, but it does require Apache-2.0 / MIT dual-licensing of contributions (the standard sentence is in README.md).

4. Failure modes

  • Forgetting --release. Running cargo test without --release takes 10 to 100x longer because polynomial FFTs dominate runtime. The CI matrix always uses --release; you should too.
  • Forgetting the feature flags. Running cargo test with default features omits dev-graph (and so omits a number of tests). Always use the CI flag string above.
  • Bumping an indirect-dev dependency past MSRV. The pins in halo2_proofs/Cargo.toml exist precisely to keep MSRV at 1.60. If a tool nags you to update one, check its new MSRV first.
  • Editing plonk_api_proof.bin by hand. This file is binary and is regenerated by the test suite when you set the right env var; never edit it manually.

5. Spec pointers

6. Exercises

  1. Run cargo test --release -p halo2_proofs plonk_api from a clean clone. Time how long it takes. Report which test names were executed.
  2. Find the line in halo2_proofs/Cargo.toml that pins dashmap to < 5.5.0. Note the inline comment that explains why; this is the canonical example of an MSRV-driven indirect-dep pin.
  3. Pick one open A-book issue from zcash/halo2/issues. In two sentences each, identify (a) which book file would need to change, (b) which chapter of this course covers the same material.
  4. Open halo2_proofs/CHANGELOG.md and find the 0.3.1 entry. Note what the soundness bug was; you will see this again in chapter 10 (multiopen).

Answers in the code