name: docs.rs
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
workflow_dispatch:
schedule:
- cron: '0 6 * * 1'
concurrency:
group: docsrs-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
jobs:
build:
name: docs build (docs.rs environment)
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- uses: Swatinem/rust-cache@v2
with:
key: docsrs
- name: build the documentation the way docs.rs will
shell: bash
run: |
set -uo pipefail
# The feature set comes FROM the manifest, never from a copy pasted
# here: a hand-copied list stops matching the moment the metadata
# changes, and then this job certifies a configuration docs.rs does
# not build.
eval "$(python3 - <<'PY'
import re, shlex
s = open('Cargo.toml').read()
m = re.search(r'^\[package\.metadata\.docs\.rs\]\n(.*?)(?=^\[)', s, re.S | re.M)
if not m:
raise SystemExit("no [package.metadata.docs.rs] section — cannot know what docs.rs builds")
sec = m.group(1)
feats = re.search(r'^features\s*=\s*\[(.*?)\]', sec, re.S | re.M)
feats = re.findall(r'"([^"]+)"', feats.group(1)) if feats else []
nodef = re.search(r'^no-default-features\s*=\s*true', sec, re.M) is not None
rustdoc = re.search(r'^rustdoc-args\s*=\s*\[(.*?)\]', sec, re.S | re.M)
rustdoc = re.findall(r'"([^"]+)"', rustdoc.group(1)) if rustdoc else []
# rustc-args becomes RUSTFLAGS on docs.rs and applies to DEPENDENCIES.
# That is what broke this crate for three releases; refuse it outright
# rather than faithfully reproducing a known-bad configuration.
if re.search(r'^rustc-args\s*=', sec, re.M):
raise SystemExit(
"[package.metadata.docs.rs] declares rustc-args. docs.rs turns it into "
"RUSTFLAGS for every dependency, which is what made lexical-util fail to "
"compile (E0557) and left pmat with no docs on crates.io. See issue #988.")
# An empty parse must not silently become "build with no features":
# that is a green tick for a configuration nobody ships. The manifest
# declares no-default-features today, so an empty list would document
# almost nothing and still exit 0.
if nodef and not feats:
raise SystemExit(
"parsed no features from [package.metadata.docs.rs] while no-default-features "
"is set — the parser is broken, not the manifest. Refusing to certify a build "
"of nothing.")
print(f"FEATURES={shlex.quote(','.join(feats))}")
print(f"NODEFAULT={'1' if nodef else '0'}")
print(f"RUSTDOCFLAGS={shlex.quote(' '.join(rustdoc))}")
PY
)" || { echo "::error::could not read docs.rs metadata from Cargo.toml"; exit 1; }
echo "features: ${FEATURES:-<none>}"
echo "no-default: ${NODEFAULT}"
echo "RUSTDOCFLAGS: ${RUSTDOCFLAGS:-<none>}"
args=(--no-deps)
[ "$NODEFAULT" = "1" ] && args+=(--no-default-features)
[ -n "${FEATURES:-}" ] && args+=(--features "$FEATURES")
# DOCS_RS is what the crate's own build script keys off to skip
# network work, exactly as on docs.rs.
export DOCS_RS=1
export RUSTDOCFLAGS
if ! cargo +nightly doc "${args[@]}"; then
echo "::error::the documentation does not build in the docs.rs environment. This is what ships to docs.rs — it will fail there too."
exit 1
fi
# Exit 0 is not evidence. The build must have PRODUCED documentation:
# measuring nothing and calling it success is the defect this repo
# keeps finding.
doc_dir="$(cargo metadata --format-version 1 --no-deps | python3 -c 'import json,sys;print(json.load(sys.stdin)["target_directory"])')/doc"
index="$doc_dir/pmat/index.html"
if [ ! -s "$index" ]; then
echo "::error::cargo doc reported success but produced no $index — nothing was documented"
exit 1
fi
pages=$(find "$doc_dir/pmat" -name '*.html' | wc -l)
echo "documented $pages pages"
if [ "$pages" -lt 100 ]; then
echo "::error::only $pages HTML pages generated; the public API is far larger than that, so this build documented almost nothing"
exit 1
fi
published:
name: published version builds on docs.rs
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- name: docs.rs reports doc_status true for the released version
shell: bash
run: |
set -uo pipefail
ver=$(python3 -c "import re;print(re.search(r'^version = \"([^\"]+)\"', open('Cargo.toml').read(), re.M).group(1))")
echo "manifest version: $ver"
# Not yet published is not a failure — this leg speaks about releases.
code=$(curl -s -o /tmp/st.json -w '%{http_code}' "https://docs.rs/crate/pmat/$ver/status.json")
if [ "$code" = "404" ]; then
echo "::notice::pmat $ver is not on docs.rs yet — nothing to report"
exit 0
fi
if [ "$code" != "200" ]; then
echo "::error::docs.rs status API returned HTTP $code; cannot determine whether the docs built, and an unmeasured check must not pass"
exit 1
fi
cat /tmp/st.json
status=$(python3 -c "import json;print(json.load(open('/tmp/st.json')).get('doc_status'))")
if [ "$status" != "True" ]; then
echo "::error::docs.rs reports doc_status=$status for pmat $ver — the published crate has NO documentation. https://docs.rs/crate/pmat/$ver/builds"
exit 1
fi
echo "pmat $ver documents cleanly on docs.rs"