Skip to content

Spec: phpboyscout/cicd v0.10 — Rust track

  • Repository: gitlab.com/phpboyscout/cicd
  • Released as: v0.10.0 (minor — five additive components; no change to existing components or the v0.8 preset).
  • Driver: every future project derived from rust-tool-base would otherwise copy ~290 lines of Cargo cache + binstall bootstrap + disk-pressure tuning + scanner version pins + release-plz remote setup. Extracting once means a new Rust project's .gitlab-ci.yml becomes ~30 lines of include: blocks, and the painful lessons (CVSS 4.0 in cargo-deny ^0.18, RELEASE_PLZ_TOKENCI_JOB_TOKEN for downstream tag pipelines, CARGO_INCREMENTAL=0 + DWARF line-tables-only for medium-runner disk budget) stay in code, not scattered comments.

Decisions

D1 — Five components, mirroring the Go track

Component Stage Jobs Purpose
rust-lint lint rustfmt, clippy Format + clippy. rustfmt skips target/ cache (source-only); clippy gets the Cargo.lock-keyed target/ cache.
rust-test test test-linux (always), test-macos / test-windows (opt-in), test-integration (opt-in), coverage (opt-in) nextest-driven test matrix. Opt-ins gated by when: never (not just skipped) so disabled paths produce no pipeline-UI noise.
rust-security security cargo-deny, cargo-audit, trivy, gitleaks Four scanners; cargo-* tools bootstrap via cargo-binstall + cargo install.
rust-docs docs cargo-doc cargo doc --no-deps --all-features with RUSTDOCFLAGS=-D warnings.
release-plz release release-plz release-plz release-pr + release-plz release on the default branch; handles the git-remote + token reauth.

D2 — Disk-pressure tuning is baked in

Every Linux test/build job sets:

variables:
  CARGO_INCREMENTAL: "0"
  CARGO_PROFILE_DEV_DEBUG: "line-tables-only"
  CARGO_PROFILE_TEST_DEBUG: "line-tables-only"
before_script:
  - rm -rf "$CARGO_TARGET_DIR/debug/incremental" 2>/dev/null || true

The incremental cache never survives across CI runs but inflates target/ enough to exhaust the SaaS medium runner. Stripping DWARF to line-tables-only keeps backtraces meaningful (file + line) while cutting per-object size enough to fit. Lessons paid for by a rust-tool-base pipeline that died at link time on the medium runner.

D3 — cargo-binstall bootstrap, not curl-pipe

cargo-binstall is bootstrapped via cargo install --locked cargo-binstall (crates.io) rather than the upstream curl-pipe installer from raw.githubusercontent.com. Shared runners intermittently fail to resolve GitHub raw-content, and a curl-pipe failure aborts the job before any real scanner has run. The 2–3 minute cargo install cost is still a net win over compiling cargo-nextest / cargo-llvm-cov / cargo-deny / cargo-audit each from source.

D4 — CVSS 4.0 version pins on cargo-deny + cargo-audit

RustSec began shipping CVSS 4.0 advisories in late 2025. cargo-deny < 0.18 and cargo-audit < 0.22 trip on them with unsupported CVSS version: 4.0. The component defaults pin cargo-deny@^0.18 and cargo-audit@^0.22; consumers override only when they need a specific version for some other reason.

D5 — Opt-in cross-OS + integration + coverage via when: never

rust-test exposes three booleans — enable_cross_os, enable_integration, enable_coverage. When false (the default), the corresponding jobs are not scheduled at all. The rule shape mirrors go-test's enable_e2e:

rules:
  - if: '"$[[ inputs.enable_<x> ]]" != "true"'
    when: never
  - if: '$[[ inputs.if ]]'

Quoting the include-time interpolation as a string and comparing to "true" (also a string) sidesteps GitLab's rule-expression engine treating bare true as a variable reference. Without when: never the disabled job sits in the pipeline UI as "skipped" — fine, but noisy across 50+ MRs.

The cross-OS jobs further gate on $RUN_CROSS_OS_TESTS == "true" even when enable_cross_os = true, so they stay dormant until eligible SaaS macOS / Windows runners exist (otherwise they sit stuck-pending and block the test stage).

D6 — release-plz token handling

The release-plz job has three subtleties the component encapsulates:

  1. Token ≠ CI_JOB_TOKEN. A tag pushed by CI_JOB_TOKEN doesn't trigger downstream tag pipelines (GitLab loop-prevention). The token input defaults to $RELEASE_PLZ_TOKEN and is wired through to a runtime variable (not interpolated into the script string directly) so it doesn't leak into job-config logs.
  2. Detached HEAD reattach. GitLab CI checks out at $CI_COMMIT_SHA detached; release-plz needs a branch with upstream to resolve the base branch. before_script re-attaches to $CI_COMMIT_BRANCH and sets its upstream.
  3. Remote URL swap. The default origin uses CI_JOB_TOKEN; the component swaps it to a gitlab-ci-token:<RELEASE_PLZ_TOKEN>@… URL so release-plz can push the Release MR branch and the version tag against a protected-rules repo.

--forge gitlab is required — release-plz defaults to the GitHub forge and otherwise errors the repository is not hosted in GitHub.

Consumers (post-release)

Project Inline jobs replaced Components consumed
rust-tool-base rustfmt, clippy, cargo-deny, test:linux, test:macos, test:windows, integration-tests:linux, cargo-audit, trivy, gitleaks, cargo-doc, coverage, release-plz all five (with enable_cross_os: true, enable_integration: true, enable_coverage: true)
future projects derived from rust-tool-base (nothing inline yet) inherit the full stack at fork time

Self-test fixtures for the Rust track ship in v0.10.1 (tracked separately) — same sequence as tofu-module-publish v0.7.0 → v0.7.1 and the Go track v0.9.0 → v0.9.1.

Follow-up

  • rust-tool-base will need to keep its project-specific libdbus-1-dev / pkg-config / /etc/machine-id seeding in a before_script: override on the test / clippy jobs, because that's driven by the credentials-linux-persistent feature and isn't generalisable to other Rust projects.
  • A future rust-tools image (binstall + nextest + llvm-cov + deny + audit pre-installed) would let rust-test / rust-security skip the bootstrap. Sibling to infra-tools for Tofu; out of scope for v0.10.