name: Build
on:
workflow_call:
inputs:
stable:
type: boolean
profile:
type: string
cache:
type: boolean
permissions: {}
defaults:
run:
shell: bash -euxo pipefail {0}
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
runs-on: ubuntu-latest
- target: aarch64-unknown-linux-gnu
runs-on: ubuntu-24.04-arm
- target: x86_64-unknown-linux-musl
runs-on: ubuntu-latest
- target: aarch64-unknown-linux-musl
runs-on: ubuntu-24.04-arm
- target: x86_64-apple-darwin
runs-on: macos-26-intel
- target: aarch64-apple-darwin
runs-on: macos-26
- target: x86_64-pc-windows-msvc
runs-on: windows-latest
- target: aarch64-pc-windows-msvc
runs-on: windows-11-arm
name: Build (${{ matrix.target }})
runs-on: ${{ matrix.runs-on }}
permissions:
contents: read
env:
PROFILE: ${{ inputs.profile }}
TARGET: ${{ matrix.target }}
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with:
persist-credentials: false
- name: Determine version
id: version
run: |
if [[ "$STABLE" = "true" ]]; then
version="$(
cargo metadata --format-version=1 --no-deps |
jq --raw-output \
--arg package shortener \
'.packages[] | select(.name == $package) | .version'
)"
else
version="$GITHUB_SHA"
fi
echo "version=$version" >> "$GITHUB_OUTPUT"
env:
STABLE: ${{ inputs.stable }}
- name: Set up build environment
id: setup-build
run: |
{
case "$TARGET" in
"x86_64-unknown-linux-gnu" | "aarch64-unknown-linux-gnu" )
container=true
rust_ci_image=true
;;
"x86_64-unknown-linux-musl" | "aarch64-unknown-linux-musl" )
echo "RUSTFLAGS=-C target-feature=+crt-static"
container=true
skip_setup_rust=true
;;
"x86_64-apple-darwin")
echo "MACOSX_DEPLOYMENT_TARGET=10.12"
;;
"aarch64-apple-darwin")
echo "MACOSX_DEPLOYMENT_TARGET=11.0"
;;
"x86_64-pc-windows-msvc" | "aarch64-pc-windows-msvc")
echo "RUSTFLAGS=-C target-feature=+crt-static"
;;
esac
} >> "$GITHUB_ENV"
{
echo "container=${container:-false}"
echo "skip_setup_rust=${skip_setup_rust:-false}"
echo "rust_ci_image=${rust_ci_image:-false}"
} >> "$GITHUB_OUTPUT"
- name: Set up Rust
if: ${{ !fromJson(steps.setup-build.outputs.skip_setup_rust) }}
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs |
sh -s -- -y --no-modify-path \
--default-toolchain stable \
--target "$TARGET"
- name: Cache Cargo registry
if: inputs.cache
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae with:
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
key: build-cargo-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
build-cargo-${{ matrix.target }}-
lookup-only: ${{ !(github.event_name == 'push' && github.ref == 'refs/heads/main') }}
- name: Cache build artifacts
id: cache-target
if: inputs.cache
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae with:
path: |
target
key: build-target-${{ matrix.target }}-${{ env.PROFILE }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
build-target-${{ matrix.target }}-${{ env.PROFILE }}-
lookup-only: ${{ !(github.event_name == 'push' && github.ref == 'refs/heads/main') }}
- name: Prepare container
if: fromJson(steps.setup-build.outputs.container)
id: container
run: |
rust_stable="$(gh api /repos/rust-lang/rust/releases/latest --jq '.tag_name')"
case "$TARGET" in
x86_64-unknown-linux-gnu) image="dist-x86_64-linux" ;;
aarch64-unknown-linux-gnu) image="dist-aarch64-linux" ;;
x86_64-unknown-linux-musl) image="rust:$rust_stable-alpine" ;;
aarch64-unknown-linux-musl) image="rust:$rust_stable-alpine" ;;
esac
if [[ "$RUST_CI_IMAGE" = true ]]; then
rust_stable_commit="$(gh api "/repos/rust-lang/rust/commits/refs/tags/$rust_stable" --jq '.sha')"
artifacts_base_url="https://ci-artifacts.rust-lang.org/rustc-builds"
image="$(curl -fsSL "$artifacts_base_url/$rust_stable_commit/image-$image.txt")"
fi
echo "image=$image" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ github.token }}
RUST_CI_IMAGE: ${{ steps.setup-build.outputs.rust_ci_image }}
- name: Start container
if: fromJson(steps.setup-build.outputs.container)
run: |
if [[ "$RUST_CI_IMAGE" = true ]]; then
docker_run_args=(
--env CARGO_HOME=/cargo
--mount "type=bind,source=$(rustc --print sysroot),target=/rustc-sysroot,readonly"
--mount "type=bind,source=$HOME/.cargo,target=/cargo"
)
else
mkdir -p ~/.cargo/registry/index ~/.cargo/registry/cache ~/.cargo/git/db
docker_run_args=(
--mount "type=bind,source=$HOME/.cargo/registry/index,target=/usr/local/cargo/registry/index"
--mount "type=bind,source=$HOME/.cargo/registry/cache,target=/usr/local/cargo/registry/cache"
--mount "type=bind,source=$HOME/.cargo/git/db,target=/usr/local/cargo/git/db"
)
fi
docker pull "$IMAGE"
docker run --detach --rm \
--name build \
--user "$(id -u):$(id -g)" \
--env RUSTFLAGS \
--mount "type=bind,source=$PWD,target=/checkout" \
--workdir /checkout \
"${docker_run_args[@]}" \
"$IMAGE" tail -f /dev/null
if [[ "$RUST_CI_IMAGE" = true ]]; then
echo "PATH=/rustc-sysroot/bin:$(docker exec build sh -c 'echo $PATH')" > docker.env
echo "CARGO=docker exec --env-file docker.env build cargo" >> "$GITHUB_ENV"
else
docker exec --user root build chown -R "$(id -u):$(id -g)" /usr/local/cargo
echo "CARGO=docker exec build cargo" >> "$GITHUB_ENV"
fi
env:
IMAGE: ${{ steps.container.outputs.image }}
RUST_CI_IMAGE: ${{ steps.setup-build.outputs.rust_ci_image }}
- name: Run build
run: |
${CARGO:-cargo} build --locked --all-targets --all-features \
--profile "$PROFILE" \
--target "$TARGET"
- name: Run tests
run: |
${CARGO:-cargo} test --locked --all-targets --all-features \
--profile "$PROFILE" \
--target "$TARGET"
- name: Package
id: package
run: |
${CARGO:-cargo} install --locked --bins --all-features --no-track \
--profile "$PROFILE" \
--target "$TARGET" \
--path . \
--root "$ARTIFACT"
tar czvf "$ARTIFACT.tar.gz" "$ARTIFACT"
echo "artifact=$ARTIFACT.tar.gz" >> "$GITHUB_OUTPUT"
env:
ARTIFACT: shortener-${{ steps.version.outputs.version }}-${{ matrix.target }}
- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with:
name: ${{ steps.package.outputs.artifact }}
path: ${{ steps.package.outputs.artifact }}
- name: Prune build artifacts for caching
if: inputs.cache && !steps.cache-target.outputs.cache-hit
run: |
${CARGO:-cargo} clean --locked --workspace \
--profile "$PROFILE" \
--target "$TARGET"