cargo-rake 0.2.0

A configuration-driven build tool that runs named targets declared in a Rakefile.toml
# cargo-rake

A configuration-driven build tool that runs named targets declared in a
`Rakefile.toml`, in dependency order. It ships as both a `cargo` subcommand
(`cargo rake`) and a standalone `rake` binary.

## Installation

The two binaries are distributed through different channels:

- **`cargo-rake`** (the `cargo rake` subcommand) — through cargo.
- **`rake`** (the standalone binary) — through system package managers.

### cargo (`cargo-rake`)

```bash
cargo binstall cargo-rake   # download a pre-built binary, no compile
cargo install cargo-rake    # build and install from crates.io
```

### Arch Linux (`rake`)

Available from the AUR in four mutually-exclusive flavors — pre-built or
built-from-source, each in a stable and a nightly `unstable`-feature variant:

```bash
paru -S rake-bin            # pre-built binary (recommended)
paru -S rake-unstable-bin   # pre-built, nightly unstable build
paru -S rake                # build from source
paru -S rake-unstable       # build from source, nightly unstable build
```

### Debian/Ubuntu (`rake`)

#### From the apt repository (recommended)

The signed apt repository at <https://rustyhorde.github.io/cargo-rake-packages/>
tracks every release, so `apt upgrade` keeps `rake` current. Packages are
available for `amd64` and `arm64`:

```bash
# Add the repository signing key
sudo install -d /etc/apt/keyrings
curl -fsSL https://rustyhorde.github.io/cargo-rake-packages/gpg.key \
    | sudo gpg --dearmor -o /etc/apt/keyrings/rake.gpg

# Add the apt source
echo "deb [arch=amd64,arm64 signed-by=/etc/apt/keyrings/rake.gpg] \
  https://rustyhorde.github.io/cargo-rake-packages/apt stable main" \
    | sudo tee /etc/apt/sources.list.d/rake.list

# Install
sudo apt update
sudo apt install rake
```

The same repository also carries the `rake-unstable` build (the `unstable`
feature is a functional no-op — it only toggles nightly-only lint gates).
`rake-unstable` conflicts with `rake`, so only one can be installed at a time.

#### From a downloaded `.deb`

Pre-built `.deb` packages are also attached to each
[GitHub release](https://github.com/rustyhorde/cargo-rake/releases) if you prefer
not to add the repository. `dpkg -i` runs as root and works from any location:

```bash
sudo dpkg -i rake_*_amd64.deb
```

### Fedora/RHEL (`rake`)

#### From the dnf repository (recommended)

Pre-built `.rpm` packages for `x86_64` and `aarch64` are served from the signed
dnf repository at <https://rustyhorde.github.io/cargo-rake-packages/>, so
`dnf upgrade` keeps `rake` current:

```bash
# Add the repository (imports the signing key on first install)
sudo dnf config-manager \
    --add-repo https://rustyhorde.github.io/cargo-rake-packages/rpm/rake.repo

# Install
sudo dnf install rake
```

> On older releases the subcommand is `sudo dnf config-manager addrepo
> --from-repofile=…`, and on dnf 4 you may need `sudo dnf install
> dnf-plugins-core` first.

The `rake-unstable` build is available from the same repository by name; it
conflicts with `rake`, so only one can be installed at a time.

#### From a downloaded `.rpm`

`.rpm` files are also attached to each
[GitHub release](https://github.com/rustyhorde/cargo-rake/releases) for direct
installation:

```bash
sudo dnf install ./rake-*.x86_64.rpm   # or: sudo rpm -i rake-*.x86_64.rpm
```

### macOS (`rake`)

Pre-compiled binaries for **Apple Silicon (aarch64)** are published to the
[`rustyhorde/rake`](https://github.com/rustyhorde/homebrew-rake) Homebrew tap.
Intel Macs are not currently supported.

```bash
brew tap rustyhorde/rake
brew install rake
```

## Rakefile.toml

A `Rakefile.toml` declares named **targets**. Each target owns an ordered array
of named **commands** plus an optional `depends_on` list:

```toml
# Optional: the Rust toolchain this project targets. When set, both `rake` and
# `cargo rake` verify/install the channel (via rustup) and pin the run to it.
# Omit it to use whatever toolchain is already active.
# toolchain = "stable"            # default: unset (active toolchain)

[target.build]

[[target.build.command]]
name = "compile"                  # label shown in --list and errors (required)
cmd  = ["cargo", "build", "--all-features"]   # program + args, spawned directly (required)
# skip_on_error = false           # default: a non-zero exit aborts the target

[target.all]
depends_on = ["build"]            # default: [] — run these targets first, in order
# tools     = []                  # default: [] — external tools to ensure (see below)

[[target.all.command]]
name = "release"
cmd  = ["cargo", "build", "--release"]

[[target.all.command]]
name = "test"
cmd  = ["cargo", "test"]
skip_on_error = true              # tolerate a failure and keep going
```

- **`cmd`** is a program followed by its arguments. It is spawned directly — no
  shell is involved — so it behaves the same on every platform.
- **`[[target.<t>.command]]`** is a TOML array of tables. Each entry needs a
  `name` (a label used in `--list` output and error messages) and a `cmd`.
  Commands run in **array (declaration) order**. (TOML table headers are
  absolute, so the `target.<t>.command` prefix is required on each entry.)
- **`skip_on_error`** (per command, default `false`): when `true`, a non-zero
  exit from that command is tolerated and the target continues with its
  remaining commands instead of aborting.
- **`depends_on`** lists other targets that must run, in order, before this one.
- **`tools`** lists external tools (by name) the target needs; see below.

### Tools

A target can declare external tools (cargo subcommands and the like) it depends
on. Tools are defined once in a top-level `[tool.<name>]` table and referenced
by name from a target's `tools` list:

```toml
[tool.matrix]
crate   = "cargo-matrix"                       # crates.io name (for the update check)
check   = ["cargo-matrix", "--version"]        # presence probe (zero exit = installed)
install = ["cargo", "install", "cargo-matrix"] # run when missing or out of date
update  = false                                # default; see below

[target.clippy]
tools = ["matrix"]

[[target.clippy.command]]
name = "clippy"
cmd  = ["cargo", "matrix", "clippy", "--all-targets", "--", "-Dwarnings"]
```

Before a target's commands run, each tool it references is ensured:

- The **`check`** command probes whether the tool is already installed: it must
  **exit 0 when the tool is present**. For a cargo subcommand this means
  `["cargo", "<sub>", "--version"]` — the bare `cargo-<sub>` binary rejects
  `--version` and exits non-zero, which would make the tool look perpetually
  absent. When absent, `rake` prints an `Installing` notice and runs
  **`install`**.
- **`update`** (default `false`): when `true`, the installed version (parsed
  from `check`'s output) is compared against the latest reported by the tool's
  **`semver_check`** mode and re-installed if a newer one exists. The only mode
  today is `"crates-io"` (the default), which queries the crates.io API and so
  needs a **`crate`** name; a failed version check — or a `check` whose output
  has no parseable version — is non-fatal and keeps the installed version. With
  `update = false`, the already-installed version is used as-is.
- A failed `install` aborts the run (unlike a tolerated command failure).

Each tool is ensured at most once per run, even when several targets reference
it.

### Execution

A target runs after its transitive dependencies. Within a target, commands run
in array order and execution **stops at the first command that exits non-zero**,
aborting the dependency chain — unless that command sets `skip_on_error = true`,
in which case the failure is tolerated and execution continues. The process
exits with the code of the command that stopped the run (or the last command if
all succeeded).

## Usage

```bash
cargo rake <target>        # run a target (and its dependencies)
cargo rake --list          # list all targets and their commands
cargo rake --file path/to/Rakefile.toml <target>

rake <target>              # the standalone binary, same interface
```

When no target is named, the `default` target runs.