Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions github-actions/build/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Preserve the action commit in source archives downloaded by GitHub Actions.
source-commit.txt export-subst
150 changes: 150 additions & 0 deletions github-actions/build/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# docs.rs build action

A composite GitHub Action that runs the docs.rs build with nearly 100% parity
with our production systems.

It uses:

- `cargo package --allow-dirty --no-verify`, so this checks the files that would
be published.
- the [docs.rs sandbox image][linux-image] and
- the same sandbox CPU / memory / disk limits, and
- the crate's [docs.rs metadata settings](https://docs.rs/about/metadata).

## Why test your crate with this action?

A successful local `cargo doc` build can still fail after publishing to docs.rs.
Adding this action to your crate's pull-request CI catches differences in the
published package, [documentation settings][docsrs-metadata], and build
environment before you release a version with broken hosted documentation.

It helps catch common failures described in the
[docs.rs build documentation](https://docs.rs/about/builds):

- **[docs.rs metadata][docsrs-metadata]:** The build uses
[`[package.metadata.docs.rs]`][docsrs-metadata], including selected features,
disabled default features, targets, and rustdoc options. This exercises
configurations that your normal tests or `cargo doc` command might never
build.
- **Missing system dependencies:** A native library or executable installed on
your development machine might be absent from the docs.rs image. Building in
that image reveals those dependencies; use the default
[full image][linux-image] for the closest match to the libraries available on
docs.rs.
- [**Read-only filesystem:**](https://docs.rs/about/builds#read-only-directories)
The sandbox mounts the crate's source directory read-only. Build scripts that
generate files in the source tree can fail here even when they work locally;
generated build files should go in `OUT_DIR`.
- **Out-of-memory failures:** The sandbox applies docs.rs's default memory
limit, helping expose documentation builds that succeed on a larger
development machine but exceed the hosted build's budget. The CLI reports peak
sandbox memory usage alongside build results.

The build also starts from a packaged crate, which can reveal files accidentally
excluded from publication. This is a useful rehearsal for publishing, though
nightly toolchain changes and crate-specific resource limits on docs.rs can
still affect the eventual hosted build.

## Alternative: cargo-docs-rs

[`cargo-docs-rs`](https://github.com/syphar/cargo-docs-rs) is a lighter-weight
alternative for local development or CI. It runs `cargo rustdoc` with
docs.rs-style options derived from
[`[package.metadata.docs.rs]`][docsrs-metadata], making it useful for checking
feature selection and documentation flags without downloading the sandbox image.

It runs in your existing environment, so it does not reproduce the docs.rs
image's system dependencies, read-only source mount, or sandbox memory limit.
Use this action when you also want to test those environment constraints.

## Usage

This action lives at `github-actions/build` in the docs.rs repository. In the
beta-phase, we're referencing using `main`. We'll do version numbers when we
fully release the tool.

```yaml
name: test docs.rs documentation build
on: [push, pull_request]
permissions:
contents: read

jobs:
docs-rs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: rust-lang/docs.rs/github-actions/build@main
with:
package: my-crate
strict: "true"
```

The caller checks out the crate before invoking the action. Use a Linux host
with Bash, rustup, Cargo, and a running Docker daemon accessible to the runner
user. GitHub-hosted Ubuntu runners provide these prerequisites. Run directly on
the host: a job-level `container:` or Docker action controlling sibling
containers is not supported by `docs_rs_build`.

The action installs stable Rust for compiling the runner without changing the
caller's default toolchain. Documentation uses the CLI's default nightly
toolchain.

The `docs_rs_build` CLI is installed from the same repository snapshot as the
action using `GITHUB_ACTION_PATH`. Selecting `@main`, a tag, or a commit
therefore selects both the action and runner together, without a second Git
checkout or a separate runner revision input.

## Inputs

| Input | Default | Meaning |
| ------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `path` | `.` | Crate or workspace directory relative to the checkout root. |
| `package` | empty | Cargo package specification; required for virtual workspaces. If omitted, Cargo must select exactly one package. |
| `small-image` | `false` | Use the smaller [linux-micro image][linux-micro-image] instead of the full [linux image][linux-image]. Accepts `true` or `false`. |
| `strict` | `false` | Also fail for JSON, coverage, or additional-target failures. Accepts `true` or `false`. |

Default failure behavior matches the runner: setup, packaging, default-target
HTML, and missing library documentation fail the action. `strict: 'true'`
additionally makes auxiliary build failures fatal.

To select a workspace member, run from the workspace root (the default
`path: .`) and specify its package name:

```yaml
- uses: rust-lang/docs.rs/github-actions/build@main
with:
package: my-crate
```

For faster image downloads, when the smaller image provides the native
dependencies your crate needs:

```yaml
- uses: rust-lang/docs.rs/github-actions/build@main
with:
small-image: "true"
```

The [full image][linux-image] provides closer parity with docs.rs. The
[micro image][linux-micro-image] has fewer native libraries.

## Artifacts

The `workspace` output gives the absolute path containing rustwide state and
generated documentation. The runner prints artifact locations in its summary;
the action does not upload documentation artifacts automatically.

Each invocation builds one package. Use separate matrix jobs to check multiple
crates concurrently. Host toolchain installation and Docker image downloads
require network access.

## References

- [docs_rs_build documentation](../../crates/bin/docs_rs_build/README.md)
- [Swatinem/rust-cache](https://github.com/Swatinem/rust-cache)
- [GitHub composite action metadata](https://docs.github.com/en/actions/reference/workflows-and-actions/metadata-syntax#runs-for-composite-actions)

[linux-image]: https://github.com/rust-lang/crates-build-env/tree/master/linux
[linux-micro-image]: https://github.com/rust-lang/crates-build-env/tree/master/linux-micro
[docsrs-metadata]: https://docs.rs/about/metadata
145 changes: 145 additions & 0 deletions github-actions/build/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
name: docs.rs build
description: Build a packaged Rust crate with the docs.rs build runner and sandbox.

inputs:
path:
description: Package or workspace directory, relative to the repository root.
default: '.'
package:
description: Cargo package specification. Required for virtual workspaces.
default: ''
small-image:
description: Use the smaller sandbox image with fewer native libraries. Accepts true or false.
default: 'false'
strict:
description: Also fail on JSON, coverage, or additional-target build failures.
default: 'false'

outputs:
workspace:
description: Absolute rustwide workspace path containing build state and artifacts.
value: ${{ steps.prepare.outputs.workspace }}

runs:
using: composite
steps:
- name: Prepare build workspace
id: prepare
shell: bash
working-directory: ${{ github.workspace }}
env:
CRATE_PATH: ${{ inputs.path }}
STRICT: ${{ inputs.strict }}
SMALL_IMAGE: ${{ inputs.small-image }}
run: |
if [[ "$RUNNER_OS" != Linux ]]; then
echo '::error::docs.rs builds require a Linux host with Docker.'
exit 1
fi
case "$STRICT" in
true|false) ;;
*) echo '::error::strict must be true or false'; exit 1 ;;
esac
case "$SMALL_IMAGE" in
true|false) ;;
*) echo '::error::small-image must be true or false'; exit 1 ;;
esac
if [[ ! -f "$CRATE_PATH/Cargo.toml" ]]; then
echo '::error::No Cargo.toml found at the requested path.'
exit 1
fi
docker info > /dev/null
echo "workspace=$RUNNER_TEMP/docsrs-build" >> "$GITHUB_OUTPUT"

# Install the CLI from the action's source archive so both use the same
# commit, even if the requested branch/tag moves while this job runs.
# Archives have no .git directory. export-subst embeds their exact commit
# in source-commit.txt; github.sha would identify the calling crate instead.
# This SHA supplies the CLI's build metadata and keys its binary cache.
- name: Read the bundled runner revision
id: runner-source
shell: bash
run: |
# Use this repository's .git explicitly so an archived action nested
# in a caller's checkout cannot pick up the caller's commit by accident.
source_sha=$(git --git-dir="$GITHUB_ACTION_PATH/../../.git" rev-parse HEAD 2>/dev/null ||
cat "$GITHUB_ACTION_PATH/source-commit.txt")
echo "sha=$source_sha" >> "$GITHUB_OUTPUT"
echo "Building docs_rs_build from commit $source_sha"

- name: Install Rust stable for the build runner
shell: bash
run: rustup toolchain install --profile minimal --no-self-update stable

- name: Cache the docs.rs build runner
id: runner-cache
uses: Swatinem/rust-cache@v2
with:
key: docs-rs-build-runner-commit-v1-${{ steps.runner-source.outputs.sha }}
add-rust-environment-hash-key: 'false'
cache-targets: 'false'
cache-on-failure: 'true'

- name: Restore the rustwide workspace
id: workspace-cache
uses: actions/cache/restore@v5
with:
path: ${{ steps.prepare.outputs.workspace }}
# Rustwide manages changes to packages, images, and toolchains itself.
# Bump the version only when the workspace layout becomes incompatible.
key: docs-rs-workspace-v1-${{ runner.os }}-${{ runner.arch }}

# Keep image download time separate from CLI installation and doc builds.
- name: Pull the docs.rs Docker image
shell: bash
env:
SMALL_IMAGE: ${{ inputs.small-image }}
run: |
# Keep these names aligned with the CLI's default and --small-image.
image=ghcr.io/rust-lang/crates-build-env/linux
if [[ "$SMALL_IMAGE" == true ]]; then
image=ghcr.io/rust-lang/crates-build-env/linux-micro
fi
docker pull "$image"

- name: Install docs.rs build runner
# Swatinem/rust-cache restores the binary and Cargo installation metadata,
# but cargo install --path always rebuilds even when those were restored.
# Skip installation on an exact commit-cache hit to reuse the binary;
# otherwise build from the same source archive that provided this action.
if: steps.runner-cache.outputs.cache-hit != 'true'
shell: bash
env:
CARGO_TARGET_DIR: ${{ runner.temp }}/docsrs-runner-target
GIT_SHA: ${{ steps.runner-source.outputs.sha }}
run: cargo +stable install --path "$GITHUB_ACTION_PATH/../../crates/bin/docs_rs_build" --locked --force

- name: Build documentation as docs.rs
id: build
shell: bash
working-directory: ${{ github.workspace }}
env:
CRATE_PATH: ${{ inputs.path }}
PACKAGE: ${{ inputs.package }}
STRICT: ${{ inputs.strict }}
SMALL_IMAGE: ${{ inputs.small-image }}
BUILD_WORKSPACE: ${{ steps.prepare.outputs.workspace }}
run: |
args=(--workspace "$BUILD_WORKSPACE")
if [[ -n "$PACKAGE" ]]; then
args+=(--package "$PACKAGE")
fi
if [[ "$SMALL_IMAGE" == true ]]; then
args+=(--small-image)
fi
if [[ "$STRICT" == true ]]; then
args+=(--strict)
fi
docs_rs_build "${args[@]}" -- "$CRATE_PATH"

- name: Save the rustwide workspace
if: ${{ !cancelled() && steps.build.outcome != 'skipped' && steps.build.outcome != '' && steps.workspace-cache.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v5
with:
path: ${{ steps.prepare.outputs.workspace }}
key: ${{ steps.workspace-cache.outputs.cache-primary-key }}
1 change: 1 addition & 0 deletions github-actions/build/source-commit.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
$Format:%H$
Loading