diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 00000000..cbe9a572 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,24 @@ +# yaml-language-server: $schema=https://www.schemastore.org/github-workflow.json + +name: Docker + +on: + push: + branches: + - main + - dockerfile-multistage + tags: + - v[0-9]+.* + +jobs: + lint: + name: Lint Dockerfile + permissions: + contents: read + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Run Hadolint + uses: hadolint/hadolint-action@v3.3.0 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..09c59352 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,83 @@ +ARG RUST_TAG=latest + +# This stage sets up everything we need to cross-compile the Rust programs in +# other stages. +FROM --platform=$BUILDPLATFORM "docker.io/library/rust:$RUST_TAG" AS base + +# Install platform-agnostic dependencies. +# +# Check for the existence of APK/APT to determine how to install dependencies. +# hadolint ignore=DL3008,DL3018 # We don't want to pin package versions. +RUN --mount=type=cache,target=/var/cache/apk \ + --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + ( which apk && apk add clang ) || \ + ( \ + which apt-get && \ + apt-get update && \ + apt-get --no-install-recommends --assume-yes install clang \ + ) + +# Install cross-compilation helper scripts. See +# https://github.com/tonistiigi/xx#rust. +COPY --from=docker.io/tonistiigi/xx:latest / / + +# Everything from this point onwards is specific to the target platform. +ARG TARGETPLATFORM + +# Install platform-specific dependencies. +# +# Check for the existence of APK/APT to determine how to install dependencies. +# hadolint ignore=DL3008,DL3018 # We don't want to pin package versions. +RUN --mount=type=cache,target=/var/cache/apk \ + --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + ( which apk && xx-apk add xx-c-essentials ) || \ + ( \ + which apt-get && \ + xx-apt-get update && \ + xx-apt-get --no-install-recommends --assume-yes install xx-c-essentials \ + ) + +# Use the prepared base stage to build `cargo-mutants`. +FROM base AS build-mutants + +# Build cargo-mutants. +# +# Cache `/usr/local/cargo/git/db` and `/usr/local/cargo/registry` (dependencies +# are downloaded here) and `/cargo-mutants` (compilation artifacts are generated +# here). These downloads/artifacts can be cached between invocations of +# `docker build`. +# +# Use `xx-verify` to confirm that the installed binary was correctly +# cross-compiled for the target architecture. +RUN --mount=type=bind,source=src,target=src \ + --mount=type=bind,source=mutants_attrs,target=mutants_attrs \ + --mount=type=bind,source=Cargo.toml,target=Cargo.toml \ + --mount=type=bind,source=Cargo.lock,target=Cargo.lock \ + --mount=type=cache,target=/cargo-mutants \ + --mount=type=cache,target=/usr/local/cargo/git/db \ + --mount=type=cache,target=/usr/local/cargo/registry \ + xx-cargo install --path . --locked --target-dir /cargo-mutants && \ + xx-verify /usr/local/cargo/bin/cargo-mutants + +# Use the prepared base stage to build `cargo-nextest`. +FROM base AS build-nextest + +# Build `cargo-nextest` in a similar fashion to `cargo-mutants` above. +RUN --mount=type=cache,target=/cargo-nextest \ + --mount=type=cache,target=/usr/local/cargo/git/db \ + --mount=type=cache,target=/usr/local/cargo/registry \ + xx-cargo install --locked --target-dir /cargo-nextest cargo-nextest && \ + xx-verify /usr/local/cargo/bin/cargo-nextest + +# Create the final image by adding `cargo-mutants` and `cargo-nextest` to the +# Rust image. +FROM "docker.io/library/rust:$RUST_TAG" AS final + +COPY --from=build-mutants /usr/local/cargo/bin/cargo-mutants /usr/local/cargo/bin/ +COPY --from=build-nextest /usr/local/cargo/bin/cargo-nextest /usr/local/cargo/bin/ + +WORKDIR /app + +ENTRYPOINT [ "cargo", "mutants" ]