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​