Skip to content
Open
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
3 changes: 3 additions & 0 deletions .github/actions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
102 changes: 102 additions & 0 deletions .github/actions/setup-rust-toolchain-from-file/README.md
Original file line number Diff line number Diff line change
@@ -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=<path>::rust-toolchain file not found`.
- **No `channel` line**: `error file=<path>::no channel = "..." line found in <file>`.

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`.
78 changes: 78 additions & 0 deletions .github/actions/setup-rust-toolchain-from-file/action.yml
Original file line number Diff line number Diff line change
@@ -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 }}