Files
NLnetLabs-domain/.github/workflows/ci.yml
T

435 lines
15 KiB
YAML

# ======================================================
# Continuous Integration: making sure the codebase works
# ======================================================
#
# This workflow tests modifications to 'domain', ensuring that 'domain' can
# be used by others successfully. It verifies certain aspects of the codebase,
# such as the formatting and feature flag combinations, and runs the full test
# suite. It runs on Ubuntu and Windows.
#
# NOTE: We don't test on MacOS right now because the CI runner is expensive
# and at this moment (2026-07-09) 'domain' does not have MacOS-specific code.
# It can be re-enabled if necessary.
#
# --- Rust versions
#
# The data cached in `target/` is specific to the Rust compiler version that
# generated it, so we need to be careful to use the same compiler version when
# we re-use cached `target/`s.
#
# Where possible, we use the compiler version that comes pre-installed on the
# CI runners. We assume this is fairly close to the latest Rust stable, and
# that it does not change often.
#
# NOTE: We use `actions-rust-lang/setup-rust-toolchain` to help set up
# Rust, but it does not support using the pre-installed CI version. We
# would like to use it in these cases because it also configures GitHub to
# provide annotations for Rust tool output; we copy their code and do that
# configuration manually (see `rust-annotations.json`).
# See <https://github.com/actions-rust-lang/setup-rust-toolchain/issues/66>.
#
# --- Caches
#
# `$os-$arch-system-$run_id` caches the `target/` generated by `cargo test`
# with all features enabled, using the Rust version pre-installed on the CI
# runner. `os` is in `{Linux, Windows}`, `arch` is `X64`.
#
# `$os-$arch-nightly-clippy-$run_id` caches the `target/` generated by `cargo
# clippy` with all features enabled, using a nightly Rust version. The Rust
# version (in `rustup toolchain` format) is saved to `target/rust-version`.
# `os` is `Linux`, `arch` is `X64`.
#
# NOTE: We assume the Rust version installed on the CI runner does not change
# often. In theory, we could write down the Rust version in the cache and
# explicitly install that version when restoring from the cache; but this
# probably adds unnecessary overhead.
#
# NOTE: GitHub treats cache entries as immutable (although it _does_ allow
# explicitly removing and re-adding them). We include the run ID so the cache
# key is unique every time; when restoring from the cache, we don't specify
# the run ID, and GitHub will automatically find the most recent cache entry
# using a prefix of the full key.
name: CI
# When the workflow runs
# ----------------------
on:
# Execute when a pull request is (re-) opened or its head changes (e.g. new
# commits are added or the commit history is rewritten) ... but only if
# build-related files change.
pull_request:
paths:
- '**.rs'
- 'Cargo.toml'
- 'Cargo.lock'
- '.github/workflows/ci.yml'
# If a pull request is merged, at least one commit is added to the target
# branch. If the target is another pull request, it will be caught by the
# above event. We miss PRs that merge to a non-PR branch, except for the
# 'main' branch.
# Execute when a commit is pushed to 'main' (including merged PRs) or to a
# release tag ... but only if build-related files change.
push:
branches:
- 'main'
- 'releases/**'
paths:
- '**.rs'
- 'Cargo.toml'
- 'Cargo.lock'
- '.github/workflows/ci.yml'
# Rebuild 'main' every week. This will account for changes to dependencies
# and to Rust, either of which can trigger new failures. Rust releases are
# every 6 weeks, on a Thursday; this event runs every Friday. We use this
# moment to update CI caches.
schedule:
- cron: '0 10 * * FRI'
# Allow manual execution of CI, primarily for debugging. We consider these
# rare and also update CI caches when they occur.
workflow_dispatch:
defaults:
run:
shell: bash
# Jobs
# ----------------------------------------------------------------------------
jobs:
# Check Formatting
# ----------------
#
# NOTE: This job is run even if no '.rs' files have changed. Inserting such
# a check would require using a separate workflow file or using third-party
# actions. Most commits do change '.rs' files, and 'cargo-fmt' is pretty
# fast, so optimizing this is not necessary.
check-fmt:
name: Check formatting
runs-on: ubuntu-latest
steps:
# Load the repository.
- name: Checkout repository
uses: actions/checkout@v7
# Don't set up a Rust toolchain; use whatever is on the runner already.
# It will be close enough to the latest Rust stable.
# Enable a problem matcher to get inline annotations.
- name: Enable annotations
run: echo "::add-matcher::.github/workflows/rust-annotations.json"
# Don't restore from the cache; it's not needed here.
# Do the actual formatting check.
- name: Check formatting
run: cargo fmt --all -- --check
# Check
# -----
#
# Rust does not provide any way to check that all possible feature flag
# combinations will succeed, so we need to try them manually here. We assume
# this choice is not influenced by the OS or Rust version. For each feature
# flag combination, we check that everything compiles and that documentation
# succeeds.
check:
name: Check
strategy:
matrix:
features:
# We always enable `domain::new`.
# TODO: The following currently break:
# - "--no-default-features -F unstable-new"
# - "--no-default-features -F unstable-new,alloc"
- "-F unstable-new" # + default features
- "--all-features"
runs-on: ubuntu-latest
env:
RUSTDOCFLAGS: "-D warnings"
steps:
# Load the repository.
- name: Checkout repository
uses: actions/checkout@v7
# Restore a cache of dependencies and 'target'.
- name: Restore a dependency cache
id: cache-restore
uses: actions/cache/restore@v6
with:
path: |
~/.cargo
target/
# Cache by OS and Rust version. Assume the "system" version (i.e.
# whatever is on the CI runner) doesn't change that often. We _could_
# figure out what the CI runner has and key by that, but that would
# probably add unnecessary overhead.
key: ${{ runner.os }}-${{ runner.arch }}-system-
# Don't set up a Rust toolchain; use whatever is on the runner already.
# It will be close enough to the latest Rust stable.
# Enable a problem matcher to get inline annotations.
- name: Enable annotations
run: echo "::add-matcher::.github/workflows/rust-annotations.json"
- name: Check
run: cargo check ${{ matrix.features }}
# `cargo doc` should benefit from the existing work done by `check`, so
# run it immediately afterwards. This eliminates the need for a separate
# `cargo doc` job.
- name: Doc
run: cargo doc --no-deps ${{ matrix.features }}
# Check Examples
# --------------
#
# Make sure examples compile, with the specific feature flags `Cargo.toml`
# indicates they need.
check-examples:
name: Check examples
runs-on: ubuntu-latest
steps:
# Load the repository.
- name: Checkout repository
uses: actions/checkout@v7
# Restore a cache of dependencies and 'target'.
- name: Restore a dependency cache
id: cache-restore
uses: actions/cache/restore@v6
with:
path: |
~/.cargo
target/
# Cache by OS and Rust version. Assume the "system" version (which
# corresponds to whatever is on the CI runner) doesn't change that
# often. We _could_ figure out what the CI runner has and key by that,
# but that would probably add unnecessary overhead.
key: ${{ runner.os }}-${{ runner.arch }}-system-
# Don't set up a Rust toolchain; use whatever is on the runner already.
# It will be close enough to the latest Rust stable.
# Enable a problem matcher to get inline annotations.
- name: Enable annotations
run: echo "::add-matcher::.github/workflows/rust-annotations.json"
# Check the required feature flags for every example.
- name: Check required features of examples
run: |
# Scrape crate metadata and construct the right 'check' commands.
# Cargo doesn't have an option to select the right features for us.
# See: https://github.com/rust-lang/cargo/issues/4663
cargo metadata --no-deps --format-version 1 \
| jq -r '.packages[].targets[]|select(.kind|any(.=="example"))|{name,features:(.["required-features"]+[]|join(","))}|"\(.name) \(.features)"' \
| while read -r name features; do
cargo check --example=$name --no-default-features --features=$features
done
# Check Minimal Versions
# ----------------------
#
# Ensure that 'domain' compiles with the oldest compatible versions of all
# packages, even those 'domain' depends upon indirectly. This is compiled
# with the declared MSRV to make sure Clippy's minimal versions lint didn't
# miss anything.
check-minimal-versions:
name: Check minimal versions
runs-on: ubuntu-latest
env:
RUSTFLAGS: "-D warnings"
steps:
# Load the repository.
- name: Checkout repository
uses: actions/checkout@v7
# Determine the MSRV.
- name: Determine MSRV
id: determine-msrv
run: |
msrv=`cargo metadata --no-deps --format-version 1 | jq -r '.packages[]|select(.name=="domain")|.rust_version'`
echo "msrv=$msrv" >> "$GITHUB_OUTPUT"
# TODO: Caching?
# Set up the Rust toolchain.
- name: Set up Rust (at MSRV)
id: setup-rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: nightly, ${{ steps.determine-msrv.outputs.msrv }}
cache: false
matcher: false
# Enable a problem matcher to get inline annotations.
- name: Enable annotations
run: echo "::add-matcher::.github/workflows/rust-annotations.json"
# Lock all dependencies to their minimal versions.
- name: Lock dependencies to minimal versions
run: cargo +nightly update -Z minimal-versions
# Check that 'domain' compiles.
- name: Check
run: cargo check --all-targets --all-features --locked
# Clippy
# ------
#
# We run Clippy separately, and only on nightly Rust because it offers a
# superset of the lints.
#
# 'cargo clippy' and 'cargo build' can share some state for fast execution,
# but it's faster to execute them in parallel than to establish an ordering
# between them.
clippy:
name: Clippy
runs-on: ubuntu-latest
env:
RUSTFLAGS: "-D warnings"
steps:
# Load the repository.
- name: Checkout repository
uses: actions/checkout@v7
# Restore a cached `target/`.
- name: Restore from cache
if: github.event_name != 'schedule' && github.event_name != 'workflow_dispatch'
id: cache-restore
uses: actions/cache/restore@v6
with:
path: |
~/.cargo
target/
# Cache by OS. Use a recently cached version of Rust nightly.
key: ${{ runner.os }}-${{ runner.arch }}-nightly-clippy-
# Set up the Rust toolchain.
- name: Set up Rust nightly
run: |
# Determine the version to install. Fall back to `nightly`.
[[ -f target/rust-version ]] && version=$(cat target/rust-version)
version=${version:-nightly}
rustup toolchain install --component clippy $version
rustup default $version
# Enable a problem matcher to get inline annotations.
- name: Enable annotations
run: echo "::add-matcher::.github/workflows/rust-annotations.json"
# Do the actually Clippy run.
- name: Check Clippy
run: cargo clippy --all-targets --all-features
# Note the Rust version in the cache.
- name: Note Rust version for the cache
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
run: |
# NOTE: `rustc --version --verbose` also prints a `commit-date`, which
# is easier to parse, but it is unclear whether it perfectly matches
# the date used for the nightly version by rustup. The date presented
# in the first line seems more likely to be the right field.
#
# Sample input:
# > $ rustc --version
# > rustc 1.98.0-nightly (df6ee909e 2026-06-28)
rustc --version \
| grep --extended-regexp --only-matching '[0-9]{4}-[0-9]{2}-[0-9]{2}' \
| sed 's/.*/nightly-&/' \
| tee target/rust-version
# Save to the cache every week.
- name: Save to the cache
uses: actions/cache/save@v6
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
with:
path: |
~/.cargo
target/
key: ${{ runner.os }}-${{ runner.arch }}-nightly-clippy-${{ github.run_id }}
# Test
# ----
#
# Ensure that 'domain' compiles and its test suite passes.
test:
name: Test
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
env:
RUSTFLAGS: "-D warnings"
DOMAIN_FEATURES: "--all-features"
steps:
# Load the repository.
- name: Checkout repository
uses: actions/checkout@v7
# Restore a cached `target/`.
- name: Restore from cache
id: cache-restore
uses: actions/cache/restore@v6
with:
path: |
~/.cargo
target/
key: ${{ runner.os }}-${{ runner.arch }}-system-
# Don't set up a Rust toolchain; use whatever is on the runner already.
# It will be close enough to the latest Rust stable.
# Enable a problem matcher to get inline annotations.
- name: Enable annotations
run: echo "::add-matcher::.github/workflows/rust-annotations.json"
# Prepare the environment on Windows
- name: Prepare Windows environment
if: matrix.os == 'windows-latest'
shell: bash
run: |
# Filter out `openssl` because it's a pain to compile on Windows.
# Cargo doesn't support enabling all but one feature, so determine the
# complete feature list and filter it out manually.
features=`cargo metadata --no-deps --format-version 1 | jq -r '.packages[]|select(.name == "domain")|.features|keys|map(select(.!="openssl"))|join(",")'`
# Overwrite the 'DOMAIN_FEATURES' environment variable.
echo "DOMAIN_FEATURES=--features=$features" >> "$GITHUB_ENV"
# See <https://github.com/actions/runner-images/issues/12432>
echo "CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER=rust-lld" >> "$GITHUB_ENV"
# Build and run the test suite.
- name: Test
run: cargo test --all-targets $DOMAIN_FEATURES
# Test docs.
#
# Passing `--all-targets` to `cargo test` (as in the previous step)
# prevents doc tests from running.
# See <https://github.com/rust-lang/cargo/issues/6669>.
- name: Test docs
run: cargo test --doc $DOMAIN_FEATURES
# Save to the cache.
- name: Save to the cache
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
uses: actions/cache/save@v6
with:
path: |
~/.cargo
target/
key: ${{ runner.os }}-${{ runner.arch }}-system-${{ github.run_id }}
# TODO: Use 'cargo-semver-checks' on releases.