# cargo-grunk Manage cargo's disk usage, both in `$CARGO_HOME` and in project target directories. Grunk deletes only what can be regenerated. Sources are never touched. ``` cargo grunk status # what could be reclaimed, and where it went cargo grunk clean # delete it cargo grunk clean --dry-run # say what a clean would delete, and stop cargo grunk list # the projects grunk manages, and how it found them cargo grunk scan # search for projects, and manage what turns up cargo grunk add # manage one project cargo grunk remove # stop managing it ``` ## What it deletes | Category | What it is | Restored by | | ---------------- | ---------------------------------- | ------------------------------ | | `target` | A project's build output | Rebuilding from local sources | | `registry-src` | Sources unpacked from a `.crate` | Re-extracting a local archive | | `registry-crate` | A downloaded `.crate` tarball | Re-downloading | | `git-checkout` | A working tree of a git dependency | Re-extracting from a local clone | | `git-db` | A bare clone of a git dependency | Re-downloading | The registry index is never deleted: cargo refreshes it on demand and it buys almost nothing. `--category` limits any command to one kind, and is repeatable. ## How it ages things Nothing is deleted until it has gone unused for longer than the age threshold. For the cargo home, "unused" comes from cargo's own `.global-cache` database, which records when each cached artifact was last *used*. File timestamps would be wrong here: the mtime of an unpacked source directory says when it was extracted, which can be months before the last build that depended on it. Cargo 1.76 and newer maintain this database; without it, grunk will say so and leave the cargo home alone. For target directories there is no such record, so the age is the newest mtime in the tree — the moment of the last build. A bare git clone is treated as being as recent as its liveliest checkout, since deleting the clone takes its checkouts with it. ## Why `clean` is quick and `status` is not Finding a target directory's age means one `stat` per file, and a large target runs to six figures of them. That is the entire cost of running grunk. But deciding a target is *too new to touch* does not need the whole walk — it needs one file newer than the cutoff, and a target being actively built is full of them. So `clean` stops at the first one. The walk only runs to the end when nothing is newer, which is exactly when the target is about to be deleted and its size is worth having anyway. Cheap when the answer is "leave it", thorough when it is "delete it". That is why `clean` reports what it keeps as a count rather than a size: it never measured them. `status` does measure everything, and pays for it — it is the command for a person who wants the numbers, not the one for a boot script. Nothing here is cached between runs, and nothing infers a tree's age from its directory's mtime. A directory's mtime does not change when a file inside it is rewritten in place, nor when anything in a subdirectory changes at all — so a freshly rebuilt target would look untouched, and get deleted. ## How it finds projects Nothing in `$CARGO_HOME` records where your projects are, so grunk manages a project only if it has been told to. There are two ways it gets told, and `cargo grunk list` shows which applies to each. - **`crates-toml`** — path installs recorded in `$CARGO_HOME/.crates.toml`. Free, needs no configuration, and works the moment grunk is installed. It only sees packages installed with `cargo install --path`, which is typically a minority of your projects. - **`explicit`** — the config's `projects` list, maintained by `add`, `remove` and `scan`. Everything else. Neither goes looking. That is deliberate: searching a source tree for cargo manifests means walking every file in it — on a large tree, a million of them, to find a few dozen — and that is far too much work to repeat on every run of a command you want in a boot script. So searching is a thing you run, not a thing that runs: ``` cargo grunk scan ~/source ~/work # find projects, and manage what turns up cargo grunk scan --dry-run ~/src # just say what it found ``` `scan` walks the directories given looking for `Cargo.toml`, stopping the moment it finds one on any path — a workspace root sits above its members, so there is nothing deeper worth looking at. It skips anything already managed, reports each project with the size of its build output, and writes the rest into `projects`. Because it looks for manifests rather than build output, it finds projects that have never been built too. Run it when you have new projects worth managing. Nothing else ever walks. Cargo is asked where each project's target directory actually is, so workspace members resolve to the one target directory they share, and a `CARGO_TARGET_DIR` or `build.target-dir` override is respected. A directory is only treated as build output once cargo has actually built into it — proven by cargo's cache tag or, for targets built before cargo began tagging them (pre-1.42), by the `.rustc_info.json` cargo leaves at the root. A `target` directory cargo never wrote to, holding hand-made data, is left alone. ## Configuration `~/.config/grunk.toml`, or wherever `$XDG_CONFIG_HOME` points. Every key is optional, and the file need not exist — without it, grunk manages the projects `.crates.toml` knows about and the cargo home. ```toml # How long data must go unused before `clean` will delete it. # Any humantime duration: "3d", "12h", "1h 30m". Defaults to "3d". min_age = "3d" # The projects grunk manages. `cargo grunk scan`, `add` and `remove` maintain # this list, and preserve the rest of the file as they do. Projects that # .crates.toml already records are managed without appearing here. projects = ["~/work/circuit/frontend"] # Thresholds for particular categories, overriding min_age. [min_age_overrides] registry-crate = "30d" # re-downloading costs a network trip; keep it longer target = "1d" # rebuilding is cheap and these are the big ones ``` `--min-age` on the command line overrides every configured threshold: ``` cargo grunk clean --min-age 0s # take everything, regardless of age ``` ## Concurrency Grunk takes the same lock cargo takes before touching the cargo home, so a concurrent `cargo build` cannot be downloading a crate into a directory being deleted. Files are deleted before their tracking rows, so an interruption leaves cargo refetching something it already has rather than orphaning bytes it has forgotten about. ## Licence Public domain, under the [Unlicense](LICENSE).