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
light:
description: >-
Keep CI to a sanity check, for repos whose Actions minutes are
billed: one Linux job (fmt, clippy, tests) on pull requests,
scheduled and manual runs, and on pushes only the container
image, for linux/amd64 alone unless arm64 is set. See the header.
type: boolean
default: false
tests:
description: >-
With light, run the tests too; a repo tested only where it's
worked on sets this to false. Without light, tests always run.
type: boolean
default: true
arm64:
description: >-
With light and container, build the image for linux/arm64 as well
as linux/amd64. Without light, images are always built for both.
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:
if: ${{ !inputs.light }}
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:
if: ${{ !inputs.light }}
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:
if: ${{ !inputs.light }}
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:
if: ${{ !inputs.light }}
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:
if: ${{ !inputs.light }}
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:
if: ${{ !inputs.light }}
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 && !inputs.light
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"
version:
if: inputs.container && github.event_name == 'push' && github.ref_type == 'tag'
permissions:
contents: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- 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
container:
if: >-
${{ !cancelled() && !failure() && inputs.container
&& (!inputs.light || github.event_name == 'push') }}
needs: [fmt, clippy, test, doc, package, msrv, version]
strategy:
fail-fast: false
matrix:
include: >-
${{ fromJSON(inputs.light && !inputs.arm64
&& '[{"platform": "linux/amd64", "runner": "ubuntu-latest"}]'
|| '[{"platform": "linux/amd64", "runner": "ubuntu-latest"},
{"platform": "linux/arm64", "runner": "ubuntu-24.04-arm"}]') }}
runs-on: ${{ matrix.runner }}
env:
PUSH: ${{ github.event_name == 'push' }}
ALONE: ${{ inputs.light && !inputs.arm64 }}
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 }}
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
- 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 }}
tags: ${{ env.PUSH == 'true' && env.ALONE == 'true' && steps.metadata.outputs.tags || '' }}
outputs: >-
${{ env.PUSH != 'true' && 'type=cacheonly'
|| env.ALONE == 'true' && 'type=image,push=true'
|| format('type=image,name={0},push-by-digest=true,name-canonical=true,push=true', env.IMAGE) }}
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' && env.ALONE != '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' && env.ALONE != '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: >-
${{ !cancelled() && needs.container.result == 'success' && inputs.container
&& github.event_name == 'push' && !(inputs.light && !inputs.arm64) }}
needs: container
runs-on: ubuntu-latest
steps:
- 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[@]}"
ok:
if: >-
${{ !inputs.light && always()
|| inputs.light && !cancelled() && github.event_name != 'push' }}
needs: [fmt, clippy, test, doc, package, msrv, no-std, version, container, container-publish]
permissions:
contents: read
runs-on: ubuntu-latest
steps:
- if: ${{ !inputs.light }}
name: Every job passed or was skipped
env:
RESULTS: ${{ join(needs.*.result, ' ') }}
run: |
echo "$RESULTS"
for r in $RESULTS; do
case "$r" in success|skipped) ;; *) exit 1 ;; esac
done
- if: inputs.light
uses: actions/checkout@v7
- if: inputs.light
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
targets: ${{ inputs.no-std && env.NO_STD_TARGET || '' }}
- if: inputs.light && inputs.bevy
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: inputs.light
run: cargo fmt --all --check
- if: inputs.light
uses: Swatinem/rust-cache@v2
with:
save-if: ${{ github.event_name != 'pull_request' }}
- if: inputs.light
run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
- if: inputs.light && inputs.tests
run: cargo test --workspace --all-features --locked
- if: inputs.light && inputs.no-std
run: cargo build --workspace --no-default-features --locked --target "$NO_STD_TARGET"