From 208ba0573732e420984f07a82a567a6bae7ff472 Mon Sep 17 00:00:00 2001 From: Jacobus Geluk Date: Thu, 28 May 2026 13:01:24 +0100 Subject: [PATCH] feat(actions): setup-rust-toolchain-from-file Add a composite action that reads the channel from rust-toolchain.toml and installs the toolchain, optionally followed by Swatinem/rust-cache. Every Rust CI job otherwise repeats ~30 lines of boilerplate (sed-parse the channel, call setup-rust-toolchain, call rust-cache) that have no single source of truth and drift independently across jobs. This action collapses that into one uses: line per job, with optional inputs for components, the cache shared-key, and a cache opt-out for lint-only jobs. Inputs: - path default rust-toolchain.toml - components comma-separated rustup components (passed verbatim) - cache "true" (default) or "false" to skip rust-cache - cache-shared-key shared-key passed to Swatinem/rust-cache Output: - toolchain the resolved channel (e.g. nightly-2025-08-29) Includes a README documenting usage, inputs, outputs, failure modes, and the motivation. Indexed under "Setup Actions" in the actions README. --- .github/actions/README.md | 3 + .../setup-rust-toolchain-from-file/README.md | 102 ++++++++++++++++++ .../setup-rust-toolchain-from-file/action.yml | 78 ++++++++++++++ 3 files changed, 183 insertions(+) create mode 100644 .github/actions/setup-rust-toolchain-from-file/README.md create mode 100644 .github/actions/setup-rust-toolchain-from-file/action.yml diff --git a/.github/actions/README.md b/.github/actions/README.md index a3446af..d2e251c 100644 --- a/.github/actions/README.md +++ b/.github/actions/README.md @@ -28,6 +28,9 @@ the action name to view its documentation: - **[setup-dioxus](setup-dioxus/README.md)** - Install Dioxus CLI - **[setup-ekg-cli](setup-ekg-cli/README.md)** - Build EKG CLI (project-specific) +- **[setup-rust-toolchain-from-file](setup-rust-toolchain-from-file/README.md)** - + Read the channel from `rust-toolchain.toml` and install the toolchain, + optionally adding the Swatinem shared rust-cache ### Version Management Actions diff --git a/.github/actions/setup-rust-toolchain-from-file/README.md b/.github/actions/setup-rust-toolchain-from-file/README.md new file mode 100644 index 0000000..8d4c9ac --- /dev/null +++ b/.github/actions/setup-rust-toolchain-from-file/README.md @@ -0,0 +1,102 @@ +# setup-rust-toolchain-from-file + +Read the Rust channel from `rust-toolchain.toml` and install that exact +toolchain, optionally adding the +[`Swatinem/rust-cache`](https://github.com/Swatinem/rust-cache) shared +target cache. Collapses the boilerplate every Rust CI job otherwise +repeats — sed-parse the channel out of `rust-toolchain.toml`, call +`actions-rust-lang/setup-rust-toolchain`, then call `rust-cache` — into +a single composite action. + +## Usage + +```yaml +- name: Checkout code + uses: actions/checkout@v6 + +- name: Set up Rust toolchain + uses: dataroadinc/github-actions/.github/actions/setup-rust-toolchain-from-file@main + with: + components: clippy,rustfmt + cache-shared-key: my-repo-ci +``` + +For lint-only or format-only jobs that do not build, skip the cache: + +```yaml +- name: Set up Rust toolchain + uses: dataroadinc/github-actions/.github/actions/setup-rust-toolchain-from-file@main + with: + components: rustfmt + cache: "false" +``` + +## Inputs + +| Input | Default | Description | +| ----- | ------- | ----------- | +| `path` | `rust-toolchain.toml` | Path (relative to checkout root) of the rust-toolchain file to read. | +| `components` | `""` | Comma-separated rustup components forwarded to `actions-rust-lang/setup-rust-toolchain`. | +| `cache` | `"true"` | When `"true"`, invokes `Swatinem/rust-cache`. Set to `"false"` for jobs that do not compile. | +| `cache-shared-key` | `""` | `shared-key` passed to `Swatinem/rust-cache`. Multiple jobs in the same workflow should share a value. | + +## Outputs + +| Output | Description | +| ------ | ----------- | +| `toolchain` | The toolchain channel resolved from the file (e.g. `nightly-2025-08-29`). | + +## What it does + +1. Resolves the channel from `rust-toolchain.toml` (one `channel = "..."` line under `[toolchain]`). +2. Calls + [`actions-rust-lang/setup-rust-toolchain@v1`](https://github.com/actions-rust-lang/setup-rust-toolchain) + with that channel and the requested components. +3. Optionally calls + [`Swatinem/rust-cache@v2`](https://github.com/Swatinem/rust-cache) + with the supplied shared key. + +## Why a composite action + +Without this action, every Rust job in a workflow repeats the same +three blocks (≈30 lines each): + +```yaml +- name: Resolve Rust toolchain + id: rust_toolchain + run: | + VERSION="$(sed -n 's/^channel = "\([^"]*\)"/\1/p' rust-toolchain.toml | head -n 1)" + if [ -z "$VERSION" ]; then + echo "Failed to resolve Rust toolchain channel" >&2 + exit 1 + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + +- name: Set up Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: ${{ steps.rust_toolchain.outputs.version }} + components: clippy,rustfmt + +- name: Cache Rust artifacts + uses: Swatinem/rust-cache@v2 + with: + shared-key: my-repo-ci +``` + +A workflow with 8 Rust jobs ends up carrying ≈240 lines of boilerplate +that drift independently and have no single source of truth. This +action makes that block one `uses:` line per job. + +## Failure modes + +- **Missing file**: `error file=::rust-toolchain file not found`. +- **No `channel` line**: `error file=::no channel = "..." line found in `. + +Both surface via `::error` annotations that GitHub renders inline on +the PR diff. + +## Versioning + +Use `@main` while iterating. After this and sibling actions stabilise, +the repository bumps to `v1.0.0` and consumers should pin to `@v1`. diff --git a/.github/actions/setup-rust-toolchain-from-file/action.yml b/.github/actions/setup-rust-toolchain-from-file/action.yml new file mode 100644 index 0000000..0f1791f --- /dev/null +++ b/.github/actions/setup-rust-toolchain-from-file/action.yml @@ -0,0 +1,78 @@ +name: Setup Rust toolchain from file +description: >- + Read the channel from rust-toolchain.toml and install that exact toolchain, + optionally adding the Swatinem shared rust-cache. Collapses the ~30 lines of + boilerplate each Rust CI job otherwise repeats (sed-parse the channel, call + setup-rust-toolchain, call rust-cache) into a single composite action. + +inputs: + path: + description: >- + Path (relative to the checkout root) of the rust-toolchain file to read. + Defaults to rust-toolchain.toml at the repository root. + required: false + default: rust-toolchain.toml + components: + description: >- + Comma-separated list of rustup components to install alongside the + toolchain. Forwarded verbatim to actions-rust-lang/setup-rust-toolchain. + Leave empty to install only the toolchain. + required: false + default: "" + cache: + description: >- + Whether to invoke Swatinem/rust-cache after toolchain setup. + `true` (default) enables the cache; `false` skips it for jobs that do not + build (for example, lint-only or formatting jobs). + required: false + default: "true" + cache-shared-key: + description: >- + Shared cache key passed to Swatinem/rust-cache as `shared-key`. + Multiple jobs in the same workflow should set the same value so they + share a target/ cache. Ignored when `cache` is `false`. + required: false + default: "" + +outputs: + toolchain: + description: >- + The toolchain channel resolved from the rust-toolchain file. + Example: `nightly-2025-08-29`. + value: ${{ steps.resolve.outputs.toolchain }} + +runs: + using: composite + steps: + - name: Resolve Rust toolchain channel + id: resolve + shell: bash + run: | + set -e + file='${{ inputs.path }}' + if [ ! -f "$file" ]; then + echo "::error file=$file::rust-toolchain file not found" + exit 1 + fi + # `rust-toolchain.toml` always has exactly one `channel = "..."` line + # under `[toolchain]`. We tolerate but defensively ignore additional + # matches by taking only the first via `head -n 1`. + channel="$(sed -n 's/^channel = "\([^"]*\)"/\1/p' "$file" | head -n 1)" + if [ -z "$channel" ]; then + echo "::error file=$file::no `channel = \"...\"` line found in $file" >&2 + exit 1 + fi + echo "toolchain=$channel" >> "$GITHUB_OUTPUT" + echo "Resolved Rust toolchain channel: $channel" + + - name: Set up Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: ${{ steps.resolve.outputs.toolchain }} + components: ${{ inputs.components }} + + - name: Cache Rust artifacts + if: ${{ inputs.cache == 'true' }} + uses: Swatinem/rust-cache@v2 + with: + shared-key: ${{ inputs.cache-shared-key }}