name: Rust CI
on:
workflow_call:
inputs:
bevy:
description: >-
The workspace builds Bevy (with --all-features, at least): install
the system libraries it needs on Linux (BEVY_LINUX_PACKAGES).
type: boolean
default: false
no-std:
description: >-
The crate works without std: build it with --no-default-features
for NO_STD_TARGET, which has no std at all, on stable and the MSRV.
type: boolean
default: false
container:
description: >-
The repo ships as a container image, from the Dockerfile at its
root: test on Linux only, where it runs, then build the image for
linux/amd64 and linux/arm64. Pull requests only build it; pushes publish it
to ghcr.io/{owner}/{repo}, as latest from main and as X.Y.Z and X.Y
from a vX.Y.Z tag, which must match Cargo.toml's version.
type: boolean
default: false
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
NO_STD_TARGET: thumbv7em-none-eabi
BEVY_LINUX_PACKAGES: >-
libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev
libx11-dev libxrandr-dev libxcursor-dev libxi-dev libxinerama-dev
libegl1-mesa-dev libgl1-mesa-dev libgles2-mesa-dev
IMAGE: ghcr.io/${{ github.repository }}
jobs:
fmt:
permissions:
contents: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- run: cargo fmt --all --check
clippy:
permissions:
contents: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- if: inputs.bevy && runner.os == 'Linux'
name: Install Bevy's Linux libraries
run: |
# shellcheck disable=SC2086 # one argument per package
sudo apt-get update && sudo apt-get install -y --no-install-recommends $BEVY_LINUX_PACKAGES
- uses: Swatinem/rust-cache@v2
- run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
test:
permissions:
contents: read
strategy:
fail-fast: false
matrix:
os: ${{ fromJSON(inputs.container && '["ubuntu-latest"]' || '["ubuntu-latest", "macos-latest", "windows-latest"]') }}
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- if: inputs.bevy && runner.os == 'Linux'
name: Install Bevy's Linux libraries
run: |
# shellcheck disable=SC2086 # one argument per package
sudo apt-get update && sudo apt-get install -y --no-install-recommends $BEVY_LINUX_PACKAGES
- uses: Swatinem/rust-cache@v2
- run: cargo test --workspace --all-features --locked
doc:
permissions:
contents: read
runs-on: ubuntu-latest
env:
RUSTDOCFLAGS: -D warnings
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- if: inputs.bevy && runner.os == 'Linux'
name: Install Bevy's Linux libraries
run: |
# shellcheck disable=SC2086 # one argument per package
sudo apt-get update && sudo apt-get install -y --no-install-recommends $BEVY_LINUX_PACKAGES
- uses: Swatinem/rust-cache@v2
- run: cargo doc --workspace --all-features --no-deps --locked
package:
permissions:
contents: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Dry-run cargo publish
run: |
publishable=$(cargo metadata --no-deps --format-version 1 \
| jq '[.packages[] | select(.publish != [])] | length')
if [ "$publishable" -eq 0 ]; then
echo "Nothing here is published to crates.io"
exit 0
fi
cargo publish --workspace --dry-run --locked
msrv:
permissions:
contents: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Read rust-version
id: msrv
run: |
msrv=$(cargo metadata --no-deps --format-version 1 \
| jq -r '[.packages[].rust_version // empty] | sort_by(split(".") | map(tonumber)) | last // empty')
echo "version=$msrv" >> "$GITHUB_OUTPUT"
[ -n "$msrv" ] || echo "No rust-version in Cargo.toml; nothing to check"
- if: steps.msrv.outputs.version != ''
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ steps.msrv.outputs.version }}
targets: ${{ inputs.no-std && env.NO_STD_TARGET || '' }}
- if: inputs.bevy && runner.os == 'Linux'
name: Install Bevy's Linux libraries
run: |
# shellcheck disable=SC2086 # one argument per package
sudo apt-get update && sudo apt-get install -y --no-install-recommends $BEVY_LINUX_PACKAGES
- if: steps.msrv.outputs.version != ''
uses: Swatinem/rust-cache@v2
- if: steps.msrv.outputs.version != ''
run: cargo test --workspace --all-features --locked
- if: steps.msrv.outputs.version != '' && inputs.no-std
run: cargo build --workspace --no-default-features --locked --target "$NO_STD_TARGET"
no-std:
permissions:
contents: read
if: inputs.no-std
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ env.NO_STD_TARGET }}
- uses: Swatinem/rust-cache@v2
- run: cargo build --workspace --no-default-features --locked --target "$NO_STD_TARGET"
container:
if: inputs.container
needs: [fmt, clippy, test, doc, package, msrv]
strategy:
fail-fast: false
matrix:
include:
- platform: linux/amd64
runner: ubuntu-latest
- platform: linux/arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
env:
PUSH: ${{ github.event_name == 'push' }}
steps:
- uses: actions/checkout@v7
- uses: docker/setup-buildx-action@v4
- if: env.PUSH == 'true'
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- id: metadata
uses: docker/metadata-action@v6
with:
images: ${{ env.IMAGE }}
- id: arch
run: echo "arch=${PLATFORM#linux/}" >> "$GITHUB_OUTPUT"
env:
PLATFORM: ${{ matrix.platform }}
- id: build
uses: docker/build-push-action@v7
with:
context: .
platforms: ${{ matrix.platform }}
labels: ${{ steps.metadata.outputs.labels }}
outputs: ${{ env.PUSH == 'true' && format('type=image,name={0},push-by-digest=true,name-canonical=true,push=true', env.IMAGE) || 'type=cacheonly' }}
cache-from: type=gha,scope=container-${{ steps.arch.outputs.arch }}
cache-to: type=gha,mode=max,scope=container-${{ steps.arch.outputs.arch }}
- if: env.PUSH == 'true'
name: Export digest
env:
DIGEST: ${{ steps.build.outputs.digest }}
run: |
mkdir -p "$RUNNER_TEMP/digests"
touch "$RUNNER_TEMP/digests/${DIGEST#sha256:}"
- if: env.PUSH == 'true'
uses: actions/upload-artifact@v7
with:
name: digest-${{ steps.arch.outputs.arch }}
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1
container-publish:
if: inputs.container && github.event_name == 'push'
needs: container
runs-on: ubuntu-latest
steps:
- if: github.ref_type == 'tag'
uses: actions/checkout@v7
- if: github.ref_type == 'tag'
uses: dtolnay/rust-toolchain@stable
- if: github.ref_type == 'tag'
name: Check tag matches Cargo.toml version
run: |
version=${GITHUB_REF_NAME#v}
mismatched=$(cargo metadata --no-deps --format-version 1 \
| jq -r --arg v "$version" '.packages as $all
| [$all[] | select(.publish != [])] | if length > 0 then . else $all end
| .[] | select(.version != $v) | "\(.name) \(.version)"')
if [ -n "$mismatched" ]; then
echo "::error::Tag $GITHUB_REF_NAME does not match Cargo.toml: $mismatched"
exit 1
fi
- uses: actions/download-artifact@v8
with:
path: ${{ runner.temp }}/digests
pattern: digest-*
merge-multiple: true
- uses: docker/setup-buildx-action@v4
- uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- id: metadata
uses: docker/metadata-action@v6
with:
images: ${{ env.IMAGE }}
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
- name: Tag the images as one
working-directory: ${{ runner.temp }}/digests
env:
TAGS: ${{ steps.metadata.outputs.tags }}
run: |
args=()
while read -r tag; do
[ -n "$tag" ] && args+=(--tag "$tag")
done <<<"$TAGS"
for digest in *; do
args+=("$IMAGE@sha256:$digest")
done
docker buildx imagetools create "${args[@]}"