Rust is a fancy new programming language with many good reasons to recommend it, but one of the things I like most is the tooling built around it: Cargo is one of the best language-specific package managers available for any language, while rustup is a friendly installer that will give you the latest versions of the compiler, Cargo, etc. while making it easy to keep them up-to-date.
Unfortunately, while these tools are designed to be easy to use, they’re
also designed to be cross-platform, which makes life easy and pleasant
for people who work exclusively on Rust across many platforms, but less so
for people who work with many tools on one platform: it’s another set of
configuration files and cache directories to clean up, another entry on
$PATH, and so forth.
While there is a Rust RFC suggesting that Cargo (and hence, rustup) follow platform-specific conventions out of the box, apparently there’s some technical issues that make it difficult in practice. Also, it’s not immediately obvious what the platform conventions of POSIX are: the POSIX standard has never mandated any specific conventions for storing per-user information, and Cargo and rustup don’t do anything wildly uncommon.
While I appreciate that a universal convention might be difficult to
identify and implement, most of the sofware I personally use on a daily
basis follows very well-defined conventions. Specifically, the XDG Base
Directory Specification which says that per-user configuration
should go in
~/.config/appname and caches of re-generatable or
re-downloadable data should go into
~/.cache/appname. The base directory
spec has a fairly straight-forward extension in file-hierarchy,
~/.local/bin for per-user binaries that should be available
from the shell. Since I’ve already learned those conventions and adapted
them into my workflow, I want to make Cargo and rustup follow them
without having to modify the software myself, and convince upstream to
adopt my changes.
If you already have Cargo or rustup installed, maybe you can migrate your old configuration but to keep things simple let’s just move them out of the way:
mkdir ~/old-rust-bits mv ~/.cargo ~/.rustup ~/.multirust ~/old-rust-bits
Create the directories where we want Cargo and rustup to actually store their stuff:
mkdir -p .local/bin mkdir -p .cache/cargo mkdir -p .cache/rustup/downloads mkdir -p .cache/rustup/tmp mkdir -p .cache/rustup/toolchains mkdir -p .cache/rustup/update-hashes mkdir -p .config/rustup
Create the old directories that Cargo and rustup look in, and fill them with symlinks to the corresponding XDG-compliant locations:
mkdir -p .cargo mkdir -p .rustup ln -sfn .rustup .multirust ln -sfn ../.local/bin .cargo/bin ln -sfn ../.cache/cargo .cargo/registry ln -sfn ../.cache/rustup/downloads .rustup/downloads ln -sfn ../.cache/rustup/tmp .rustup/tmp ln -sfn ../.cache/rustup/toolchains .rustup/toolchains ln -sfn ../.cache/rustup/update-hashes .rustup/update-hashes ln -sfn ../.config/rustup/settings.toml .rustup/settings.toml
Now you should be able to install rustup. Note that we pass
--no-modify-path to the installer, because the whole premise of this
operation is that you want rust installed in a location your environment
already knows about, right?
curl https://sh.rustup.rs -sSf | sh -s -- --no-modify-path
Once that’s done, test that you can run all the shiny new tools:
rustup --version cargo --version rustc --version
You might even want to install something, to verify Cargo will put things into the right places:
cargo install ripgrep
I created the above list of symlinks by running rustup and cargo in an isolated environment, looking at what files and directories they created, and deciding which XDG base-directory they each belonged to. If future versions of Cargo or rustup change their directory layout, or if the current versions create other files or directories in circumstances I haven’t exercised, these instructions will need updating.
Properly following the XDG Base Directory Specification requires
you to respect environment variables like
$XDG_CONFIG_DIRS, which this example does not do. I don’t think it’s
possible to do that without modifying the source-code to these tools,
EDIT: It has been pointed out that if you make rustup
install things into a shared directory like
~/.local/bin, you can
rustup self uninstall, or it will blow away things it did
not install. Be careful!