Summary
Today, cargo stores all user-global files in CARGO_HOME (~/.cargo), including
- Caches
- Config
- Installed binaries
Similarly, rustup stores all user-global files in ~/.rustup.
This RFC would provide a way for users to emulate platform-specific paths by providing environment variables to control each of these types of paths as an incremental, transitional step towards eventually supporting platform-specific paths.
Motivation
Benefits include:
- Using a
.cargodirectory is violating the recommendations/rules on most operating systems. (Linux, Windows, macOS. Especially painful on Windows, where dotfiles are not hidden) - Putting caches in designated cache directories allows backup tools to ignore them. (Linux, Windows, macOS. Example: Time Machine ignores the cache directory on macOS)
- It makes it easier for users to manage, share and version-control their configuration files, as configuration files from different applications end up in the same place, instead of being intermingled with cache files. (Linux and macOS)
- Cargo contributes to the slow cleanup of the
$HOMEdirectory by stopping to add its application-private clutter to it. (Linux) - Using a standard directory for binary outputs can allow the user to execute
Cargo-installed binaries without modifying their
PATHvariable. (Linux)
See arch's wiki for a non-exhaustive list of software that has native support for XDG paths and software that allows emulating it, like in this proposal.
While providing full support for XDG paths, and the Windows equivalent, would be ideal, we are going for this scaled back solution for now because
- We'll likely want a transition window anyways and this just splits each phase into its own RFC, rather than deciding on every phase's details up front
- We can get users benefits now without getting bogged down in the details like whether macOS users should use XDG or OS native paths
- This allows us to collect feedback and iterate, rather than predict how important each use case is (e.g. how to lookup the paths)
Guide-level explanation
Users who want to split up ~/.cargo or ~/rustup into platform-specific paths can set
CARGO_CONFIG_HOMECARGO_DATA_HOMECARGO_BIN_HOMECARGO_CONFIG_HOMERUSTUP_CONFIG_HOMERUSTUP_CACHE_HOME
Previously, we had CARGO_HOME and RUSTUP_HOME which allowed moving the directories as a monolith.
A Linux user could run the following migration script
CARGO_HOME=$(realpath ~/cargo)
CARGO_CONFIG_HOME=$(realpath ~/.config/cargo)
mkdir -p $CARGO_CONFIG_HOME
echo CARGO_CONFIG_HOME=$CARGO_CONFIG_HOME >> ~/.bashrc
CARGO_DATA_HOME=$(realpath ~/.local/share/cargo)
mkdir -p $CARGO_DATA_HOME
echo CARGO_DATA_HOME=$CARGO_DATA_HOME >> ~/.bashrc
CARGO_BIN_HOME=$(realpath ~/.local/share/cargo/bin)
echo CARGO_BIN_HOME=$CARGO_BIN_HOME >> ~/.bashrc
CARGO_CACHE_HOME=$(realpath ~/.cache/cargo)
mkdir -p $CARGO_CACHE_HOME
echo CARGO_CACHE_HOME=$CARGO_CACHE_HOME >> ~/.bashrc
RUSTUP_HOME=$(realpath ~/rustup)
RUSTUP_CONFIG_HOME=$(realpath ~/.config/rustup)
mkdir -p $RUSTUP_CONFIG_HOME
echo RUSTUP_CONFIG_HOME=$RUSTUP_CONFIG_HOME >> ~/.bashrc
RUSTUP_CACHE_HOME=$(realpath ~/.cache/rustup)
mkdir -p $RUSTUP_CACHE_HOME
echo RUSTUP_CACHE_HOME=$RUSTUP_CACHE_HOME >> ~/.bashrc
function migrate_cargo_config {
local item=$1
mv $CARGO_HOME/$item $CARGO_CONFIG_HOME/$item
ln -s $CARGO_CONFIG_HOME/$item $CARGO_HOME/$item
}
function migrate_cargo_data {
local item=$1
mv $CARGO_HOME/$item $CARGO_DATA_HOME/$item
ln -s $CARGO_DATA_HOME/$item $CARGO_HOME/$item
}
function migrate_cargo_bin {
mv $CARGO_HOME/bin $CARGO_BIN_HOME
ln -s $CARGO_BIN_HOME/$item $CARGO_HOME/$item
}
function migrate_cargo_cache {
local item=$1
mv $CARGO_HOME/$item $CARGO_CACHE_HOME/$item
ln -s $CARGO_CACHE_HOME/$item $CARGO_HOME/$item
}
function migrate_rustup_config {
local item=$1
mv $RUSTUP_HOME/$item $RUSTUP_CONFIG_HOME/$item
ln -s $RUSTUP_CONFIG_HOME/$item $RUSTUP_HOME/$item
}
function migrate_rustup_cache {
local item=$1
mv $RUSTUP_HOME/$item $RUSTUP_CACHE_HOME/$item
ln -s $RUSTUP_CACHE_HOME/$item $RUSTUP_HOME/$item
}
migrate_cargo_config config.toml
migrate_cargo_data env
migrate_cargo_data .crates.toml
migrate_cargo_data .crates2.json
migrate_cargo_data credentials.toml # avoid backing up secrets
migrate_cargo_bin
migrate_cargo_cache registry
migrate_cargo_cache git
migrate_cargo_cache target # used by "cargo script"
migrate_cargo_cache .package-cache
migrate_cargo_cache .package-cache-mutate
migrate_rustup_config settings.toml
migrate_rustup_cache downloads
migrate_rustup_cache tmp
migrate_rustup_cache toolchains
migrate_rustup_cache update-hashes
Reference-level explanation
We'll add to the confusingly named home package, the following
cargo_config_home: Returns the first matchCARGO_CONFIG_HOME, if sethome::cargo_home()
cargo_data_home: Returns the first matchCARGO_DATA_HOME, if sethome::cargo_home()
cargo_bin_home: Returns the first matchCARGO_BIN_HOME, if sethome::cargo_home().join("bin")
cargo_cache_home: Returns the first matchCARGO_CACHE_HOME, if sethome::cargo_home()
rustup_config_home: Returns the first matchRUSTUP_CONFIG_HOME, if sethome::rustup_home()
rustup_cache_home: Returns the first matchRUSTUP_CACHE_HOME, if set- Return
home::rustup_home()
Cargo will be modified to call these more specific home directories, based on the above migration script.
Each of these new environment variables will be blocked from being set in config.tomls [env] table.
When discovering a workspace root, CARGO_CACHE_HOME will be treated as a stop-path, like CARGO_HOME
Drawbacks
Why should we not do this?
~/.cargo/bin and related files requires both updated rustup and cargo
rustup assumes complete control of ~/.cargo/bin and people might map it to ~/.local/bin which would cause unexpected behavior
Rationale and alternatives
credentials.toml was put under CARGO_DATA_HOME as its program-managed data
CARGO_CONFIG_HOMEmight cause it to get backed up to public git repos, exposing secrets- Ideally people will start migrating to OS-native credential stores
Existing issues:
Previous proposals:
An alternative approach is to rely on caches being throw-away and to instead do
- Read both configs (like git)
- Hard split for cache
Reasons we didn't go with this:
- This doesn't work for
CARGO_BIN_HOME - Rustup doesn't support a layered config for reading from two locations at once
Prior art
git
- Layers both the old
~/.git/configand the new~/config/git/configon top of each other - No user cache
ansible
ANSIBLE_HOMEANSIGLE_HOME_CONFIG(config file path)ANSIBLE_GALAXY_CACHE_DIR
asdf
ASDF_CONFIG_FILEASDF_DATA_DIR
For more, see arch's wiki entry for XDG Base Directory.
Unresolved questions
- Does config fallback work for rustup?
- What should be done for env variables that are empty (treat is unset?) or relative (unset? error?)?
- Is rustup
update-hashescache, state, data, or config? - Is rustup
toolchainscache, state, data, or config? - What all paths should
rustup uninstalldelete?
Future possibilities
Platform-specific paths
Note: The information here is for illustrative purposes and discussing the details is only relevant so far as it might affect a decision being made within this RFC.
Update home package with the following
cargo_config_home: Returns the first matchCARGO_CONFIG_HOME, if setCARGO_HOME, if setcargo_home(), if present- linux or macOS:
$XDG_CONFIG_HOME/cargo, if set~/.config/cargo- windows:
AppData\Roaming\Cargo
cargo_data_home: Returns the first matchCARGO_DATA_HOME, if setCARGO_HOME, if setcargo_home(), if present- linux or macOS:
$XDG_DATA_HOME/cargo, if set~/.local/share/cargo- windows:
- TBD
cargo_bin_home: Returns the first matchCARGO_BIN_HOME, if setCARGO_HOME/bin, if setcargo_home().join("bin"), if present- linux or macOS:
- TBD
- windows:
- TBD
cargo_cache_home: Returns the first matchCARGO_CACHE_HOME, if setCARGO_HOME, if setcargo_home(), if present- linux or macOS:
$XDG_CACHE_HOME/cargo, if set~/.cache/cargo- windows:
AppData\Local\Temp\Cargo
rustup_config_home: ReturnsRUSTUP_CONFIG_HOME, if setRUSTUP_HOME, if setcargo_home(), if present- linux or macOS:
$XDG_CONFIG_HOME/rustup, if set~/.config/rustup- windows:
AppData\Roaming\Rustup
rustup_cache_home: Returns the first matchRUSTUP_CACHE_HOME, if setRUSTUP_HOME, if setcargo_home(), if present- linux or macOS:
$XDG_CACHE_HOME/rustup, if set~/.cache/rustup- windows:
AppData\Local\Temp\Cargo
Windows: Roaming app data is no longer supported on Windows 11. We'll still use these paths to communicate intent to Windows to be future compatible.
macOS: This currently favors XDG over the platform-specific application directories to be more consistent with other CLI developer tooling. The platform-specific application directories can always be emulated by setting the appropriate environment variables. The final decision will be left to the relevant RFC
Open questions
- The paths for macOS
- Local vs roaming on Windows
- Should the higher-precedence-with-presence check be used for
cargo_homeor platform-specific paths? - How do we want to handle the fact that rustup proxies set
CARGO_HOME? When we get to the state when users aren't explicitly setting more specific variables, rustup'sCARGO_HOMEwill win out
Automated migration
Add a rustup subcommand to migrate people to this
Compat symlinks
Have rustup manage symlinks when installing older toolchains
Cargo CLI for reading the values
While applications can call into home to get the values, sometimes a user will want it interactively.
Potential ideas include:
- A
cargo dirsbuilt-in command - Reuse
cargo config cargo --print <target>much likerustc --print <target>
Allow setting CARGO_CONFIG_HOME from .cargo/config.toml
For sharing of caches between host and docker,
rust-lang/cargo#6452
requested config-control over the global cache location.
Being a cache,
all results should be safe to re-calculate,
unless some of the other CARGO_*_HOME variables.