Files
vaultwarden/.github/workflows/build.yml
T
Daniel García 840dcae418 Speed up the Build workflow
- Pass the MariaDB client library to mysqlclient-sys through its environment variables, so a cached build no longer recompiles mysqlclient-sys, diesel and diesel_migrations in every MySQL build.
- Split each channel into two parallel jobs, keeping the required check names through a result job per channel.
- Drop the sqlite,mysql,postgresql,enable_mimalloc test: enable_mimalloc is still tested on and off.
- Only save caches on main, so pull requests and branches no longer push main's caches out of the 10 GB limit.
2026-11-13 21:39:48 +01:00

304 lines
12 KiB
YAML

name: Build
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
on:
push:
paths:
- ".github/workflows/build.yml"
- "src/**"
- "migrations/**"
- "Cargo.*"
- "build.rs"
- "rust-toolchain.toml"
- "rustfmt.toml"
- "diesel.toml"
- "docker/Dockerfile.j2"
- "docker/DockerSettings.yaml"
- "macros/**"
pull_request:
paths:
- ".github/workflows/build.yml"
- "src/**"
- "migrations/**"
- "Cargo.*"
- "build.rs"
- "rust-toolchain.toml"
- "rustfmt.toml"
- "diesel.toml"
- "docker/Dockerfile.j2"
- "docker/DockerSettings.yaml"
- "macros/**"
defaults:
run:
shell: bash
jobs:
build:
name: Build and Test ${{ matrix.channel }} (part ${{ matrix.part }})
runs-on: ubuntu-24.04
timeout-minutes: 120
# Make warnings errors, this is to prevent warnings slipping through.
# This is done globally to prevent rebuilds when the RUSTFLAGS env variable changes.
env:
RUSTFLAGS: "-Dwarnings"
# Which parts passed, read by the `result` job below. Matrix jobs combine their outputs.
outputs:
rust-toolchain-1: ${{ steps.passed.outputs.rust-toolchain-1 }}
rust-toolchain-2: ${{ steps.passed.outputs.rust-toolchain-2 }}
msrv-1: ${{ steps.passed.outputs.msrv-1 }}
msrv-2: ${{ steps.passed.outputs.msrv-2 }}
strategy:
fail-fast: false
matrix:
channel:
- "rust-toolchain" # The version defined in rust-toolchain
- "msrv" # The supported MSRV
# Each step builds the whole vaultwarden crate again, so the steps are split over two jobs of
# about the same length that run in parallel.
part:
- 1 # Several backends together, and the lints
- 2 # One backend at a time
steps:
# Install dependencies
- name: "Install dependencies Ubuntu"
run: |
# Separate commands, so a failing update fails the step. The image already has Oracle's MySQL client
# library, so a skipped install would otherwise go unnoticed and build against that one.
sudo apt-get update
sudo apt-get install -y --no-install-recommends openssl build-essential libmariadb-dev-compat libpq-dev libssl-dev pkg-config
# Pass the MariaDB client library to mysqlclient-sys through its environment variables.
# When it finds the library with pkg-config, its build script makes cargo rerun it whenever
# anything in the library directory changes, and the install above changes that directory on
# every run. So every MySQL build recompiled mysqlclient-sys, diesel and diesel_migrations,
# even with a cached build. Cargo compares environment variables by their value instead.
lib_dir="$(pkg-config --variable=libdir mysqlclient)"
version="$(pkg-config --modversion mysqlclient)"
{
echo "MYSQLCLIENT_NO_PKG_CONFIG=1"
echo "LIBMARIADB_NO_PKG_CONFIG=1"
echo "MYSQLCLIENT_LIB_DIR=${lib_dir}"
echo "MYSQLCLIENT_VERSION=${version}"
} | tee -a "${GITHUB_ENV}"
# End Install dependencies
# Checkout the repo
- name: "Checkout"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
fetch-depth: 0
# End Checkout the repo
# Determine rust-toolchain version
- name: Init Variables
id: toolchain
env:
CHANNEL: ${{ matrix.channel }}
run: |
if [[ "${CHANNEL}" == 'rust-toolchain' ]]; then
RUST_TOOLCHAIN="$(grep -m1 -oP 'channel.*"(\K.*?)(?=")' rust-toolchain.toml)"
elif [[ "${CHANNEL}" == 'msrv' ]]; then
RUST_TOOLCHAIN="$(grep -m1 -oP 'rust-version\s.*"(\K.*?)(?=")' Cargo.toml)"
else
RUST_TOOLCHAIN="${CHANNEL}"
fi
echo "RUST_TOOLCHAIN=${RUST_TOOLCHAIN}" | tee -a "${GITHUB_OUTPUT}"
# End Determine rust-toolchain version
- name: "Install toolchain ${{steps.toolchain.outputs.RUST_TOOLCHAIN}} as default"
env:
CHANNEL: ${{ matrix.channel }}
RUST_TOOLCHAIN: ${{steps.toolchain.outputs.RUST_TOOLCHAIN}}
run: |
# Remove the rust-toolchain.toml
rm rust-toolchain.toml
# Install the correct toolchain version
rustup toolchain install "${RUST_TOOLCHAIN}" --profile minimal --no-self-update
# If this matrix is the `rust-toolchain` flow, also install rustfmt and clippy
if [[ "${CHANNEL}" == 'rust-toolchain' ]]; then
rustup component add --toolchain "${RUST_TOOLCHAIN}" rustfmt clippy
fi
# Set as the default toolchain
rustup default "${RUST_TOOLCHAIN}"
# Show environment
- name: "Show environment"
run: |
rustc -vV
cargo -vV
# End Show environment
# Enable Rust Caching
- name: Rust Caching
uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
with:
# Use a custom prefix-key to force a fresh start. This is sometimes needed with bigger changes.
# Like changing the build host from Ubuntu 20.04 to 22.04 for example.
# Only update when really needed! Use a <year>.<month>[.<inc>] format.
prefix-key: "v2025.09-rust"
# The two parts build the dependencies with different features, so each has its own cache.
key: part-${{ matrix.part }}
# Only main saves caches. The repository can keep 10 GB of caches, and the ones saved by pull
# requests and other branches were mostly copies of main's that pushed other caches out.
save-if: ${{ github.ref == 'refs/heads/main' }}
# End Enable Rust Caching
# Run cargo tests
# Part 1 tests several features together, part 2 tests each database backend on its own.
# enable_mimalloc only selects the allocator, so it is tested on and off, not with every combination.
- name: "test features: sqlite,mysql,postgresql,enable_mimalloc,s3"
id: test_sqlite_mysql_postgresql_mimalloc_s3
if: ${{ !cancelled() && matrix.part == 1 }}
run: |
cargo test --profile ci --features sqlite,mysql,postgresql,enable_mimalloc,s3
- name: "test features: sqlite,mysql,postgresql"
id: test_sqlite_mysql_postgresql
if: ${{ !cancelled() && matrix.part == 1 }}
run: |
cargo test --profile ci --features sqlite,mysql,postgresql
- name: "test features: sqlite"
id: test_sqlite
if: ${{ !cancelled() && matrix.part == 2 }}
run: |
cargo test --profile ci --features sqlite
- name: "test features: mysql"
id: test_mysql
if: ${{ !cancelled() && matrix.part == 2 }}
run: |
cargo test --profile ci --features mysql
- name: "test features: postgresql"
id: test_postgresql
if: ${{ !cancelled() && matrix.part == 2 }}
run: |
cargo test --profile ci --features postgresql
# The test seeder only exists with this feature, which no release build has
- name: "test features: sqlite,unsafe_test_mode"
id: test_unsafe_test_mode
if: ${{ !cancelled() && matrix.part == 2 }}
run: |
cargo test --profile ci --features sqlite,unsafe_test_mode
# End Run cargo tests
# Run cargo clippy, and fail on warnings
- name: "clippy features: sqlite,mysql,postgresql,enable_mimalloc,s3"
id: clippy
if: ${{ !cancelled() && matrix.channel == 'rust-toolchain' && matrix.part == 1 }}
run: |
cargo clippy --profile ci --features sqlite,mysql,postgresql,enable_mimalloc,s3
- name: "clippy features: sqlite,unsafe_test_mode"
id: clippy_unsafe_test_mode
if: ${{ !cancelled() && matrix.channel == 'rust-toolchain' && matrix.part == 1 }}
run: |
cargo clippy --profile ci --features sqlite,unsafe_test_mode
# End Run cargo clippy
# Run cargo fmt (Only run on rust-toolchain defined version)
- name: "check formatting"
id: formatting
if: ${{ !cancelled() && matrix.channel == 'rust-toolchain' && matrix.part == 1 }}
run: |
cargo fmt --all -- --check
# End Run cargo fmt
# Check for any previous failures, if there are stop, else continue.
# This is useful so all test/clippy/fmt actions are done, and they can all be addressed
- name: "Some checks failed"
if: ${{ failure() }}
env:
TEST_DB_M_S3: ${{ steps.test_sqlite_mysql_postgresql_mimalloc_s3.outcome }}
TEST_DB: ${{ steps.test_sqlite_mysql_postgresql.outcome }}
TEST_SQLITE: ${{ steps.test_sqlite.outcome }}
TEST_MYSQL: ${{ steps.test_mysql.outcome }}
TEST_POSTGRESQL: ${{ steps.test_postgresql.outcome }}
TEST_UNSAFE_TEST_MODE: ${{ steps.test_unsafe_test_mode.outcome }}
CLIPPY: ${{ steps.clippy.outcome }}
CLIPPY_UNSAFE_TEST_MODE: ${{ steps.clippy_unsafe_test_mode.outcome }}
FMT: ${{ steps.formatting.outcome }}
run: |
# Only list the checks this part runs
row() {
if [[ "${2}" != "skipped" ]]; then
echo "|${1}|${2}|" >> "${GITHUB_STEP_SUMMARY}"
fi
}
echo "### :x: Checks Failed!" >> "${GITHUB_STEP_SUMMARY}"
echo "" >> "${GITHUB_STEP_SUMMARY}"
echo "|Job|Status|" >> "${GITHUB_STEP_SUMMARY}"
echo "|---|------|" >> "${GITHUB_STEP_SUMMARY}"
row "test (sqlite,mysql,postgresql,enable_mimalloc,s3)" "${TEST_DB_M_S3}"
row "test (sqlite,mysql,postgresql)" "${TEST_DB}"
row "test (sqlite)" "${TEST_SQLITE}"
row "test (mysql)" "${TEST_MYSQL}"
row "test (postgresql)" "${TEST_POSTGRESQL}"
row "test (sqlite,unsafe_test_mode)" "${TEST_UNSAFE_TEST_MODE}"
row "clippy (sqlite,mysql,postgresql,enable_mimalloc,s3)" "${CLIPPY}"
row "clippy (sqlite,unsafe_test_mode)" "${CLIPPY_UNSAFE_TEST_MODE}"
row "fmt" "${FMT}"
echo "" >> "${GITHUB_STEP_SUMMARY}"
echo "Please check the failed jobs and fix where needed." >> "${GITHUB_STEP_SUMMARY}"
echo "" >> "${GITHUB_STEP_SUMMARY}"
exit 1
# Check for any previous failures, if there are stop, else continue.
# This is useful so all test/clippy/fmt actions are done, and they can all be addressed
- name: "All checks passed"
id: passed
if: ${{ success() }}
env:
PART: ${{ matrix.channel }}-${{ matrix.part }}
run: |
echo "### :tada: Checks Passed!" >> "${GITHUB_STEP_SUMMARY}"
echo "" >> "${GITHUB_STEP_SUMMARY}"
echo "${PART}=success" >> "${GITHUB_OUTPUT}"
# The required status checks are named after these jobs, one per channel, so they stay the same
# however the build job is split. It runs even when a part failed or the run was cancelled, because
# a skipped job counts as passed for a required check.
result:
name: Build and Test ${{ matrix.channel }}
needs: build
if: ${{ always() }}
runs-on: ubuntu-24.04
timeout-minutes: 5
strategy:
fail-fast: false
matrix:
channel:
- "rust-toolchain"
- "msrv"
steps:
- name: "Check that both parts passed"
env:
CHANNEL: ${{ matrix.channel }}
PASSED: ${{ toJSON(needs.build.outputs) }}
run: |
for part in 1 2; do
if [[ "$(jq -r --arg part "${CHANNEL}-${part}" '.[$part] // ""' <<< "${PASSED}")" != "success" ]]; then
echo "::error::Build and Test ${CHANNEL} (part ${part}) did not pass"
exit 1
fi
done