A setting to follow XDG Base Directory standard

XDG Base Directory is a standard for locations for storing various
files. Nix has a few files which seem to fit in the standard, but
currently use a custom location directly in the user's ~, polluting
it:

- ~/.nix-profile
- ~/.nix-defexpr
- ~/.nix-channels

This commit adds a config option (use-xdg-base-directories) to follow
the XDG spec and instead use the following locations:

- $XDG_STATE_HOME/nix/profile
- $XDG_STATE_HOME/nix/defexpr
- $XDG_STATE_HOME/nix/channels

If $XDG_STATE_HOME is not set, it is assumed to be ~/.local/state.

Co-authored-by: Théophane Hufschmitt <7226587+thufschmitt@users.noreply.github.com>
Co-authored-by: Tim Fenney <kodekata@gmail.com>
Co-authored-by: pasqui23 <pasqui23@users.noreply.github.com>
Co-authored-by: Artturin <Artturin@artturin.com>
Co-authored-by: John Ericson <Ericson2314@Yahoo.com>
This commit is contained in:
Alexander Bantyev 2021-11-17 23:35:21 +03:00
parent a31d7d4e5e
commit 2384d36083
No known key found for this signature in database
GPG key ID: E081FF12ADCB4AD5
16 changed files with 153 additions and 20 deletions

View file

@ -91,3 +91,16 @@ Most Nix commands interpret the following environment variables:
variable sets the initial size of the heap in bytes. It defaults to variable sets the initial size of the heap in bytes. It defaults to
384 MiB. Setting it to a low value reduces memory consumption, but 384 MiB. Setting it to a low value reduces memory consumption, but
will increase runtime due to the overhead of garbage collection. will increase runtime due to the overhead of garbage collection.
## XDG Base Directory
New Nix commands conform to the [XDG Base Directory Specification], and use the following environment variables to determine locations of various state and configuration files:
- [`XDG_CONFIG_HOME`]{#env-XDG_CONFIG_HOME} (default `~/.config`)
- [`XDG_STATE_HOME`]{#env-XDG_STATE_HOME} (default `~/.local/state`)
- [`XDG_CACHE_HOME`]{#env-XDG_CACHE_HOME} (default `~/.cache`)
Classic Nix commands can also be made to follow this standard using the [`use-xdg-base-directories`] configuration option.
[XDG Base Directory Specification]: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
[`use-xdg-base-directories`]: ../command-ref/conf-file.md#conf-use-xdg-base-directories

View file

@ -136,7 +136,7 @@ EOF
cat <<EOF cat <<EOF
$step. Delete the files Nix added to your system: $step. Delete the files Nix added to your system:
sudo rm -rf /etc/nix $NIX_ROOT $ROOT_HOME/.nix-profile $ROOT_HOME/.nix-defexpr $ROOT_HOME/.nix-channels $HOME/.nix-profile $HOME/.nix-defexpr $HOME/.nix-channels sudo rm -rf "/etc/nix" "$NIX_ROOT" "$ROOT_HOME/.nix-profile" "$ROOT_HOME/.nix-defexpr" "$ROOT_HOME/.nix-channels" "$ROOT_HOME/.local/state/nix" "$ROOT_HOME/.cache/nix" "$HOME/.nix-profile" "$HOME/.nix-defexpr" "$HOME/.nix-channels" "$HOME/.local/state/nix" "$HOME/.cache/nix"
and that is it. and that is it.

View file

@ -188,6 +188,8 @@ fi
# shellcheck source=./nix-profile.sh.in # shellcheck source=./nix-profile.sh.in
. "$nix/etc/profile.d/nix.sh" . "$nix/etc/profile.d/nix.sh"
NIX_LINK="$HOME/.nix-profile"
if ! "$nix/bin/nix-env" -i "$nix"; then if ! "$nix/bin/nix-env" -i "$nix"; then
echo "$0: unable to install Nix into your default profile" >&2 echo "$0: unable to install Nix into your default profile" >&2
exit 1 exit 1
@ -196,7 +198,7 @@ fi
# Install an SSL certificate bundle. # Install an SSL certificate bundle.
if [ -z "$NIX_SSL_CERT_FILE" ] || ! [ -f "$NIX_SSL_CERT_FILE" ]; then if [ -z "$NIX_SSL_CERT_FILE" ] || ! [ -f "$NIX_SSL_CERT_FILE" ]; then
"$nix/bin/nix-env" -i "$cacert" "$nix/bin/nix-env" -i "$cacert"
export NIX_SSL_CERT_FILE="$HOME/.nix-profile/etc/ssl/certs/ca-bundle.crt" export NIX_SSL_CERT_FILE="$NIX_LINK/etc/ssl/certs/ca-bundle.crt"
fi fi
# Subscribe the user to the Nixpkgs channel and fetch it. # Subscribe the user to the Nixpkgs channel and fetch it.
@ -214,8 +216,8 @@ fi
added= added=
p= p=
p_sh=$HOME/.nix-profile/etc/profile.d/nix.sh p_sh=$NIX_LINK/etc/profile.d/nix.sh
p_fish=$HOME/.nix-profile/etc/profile.d/nix.fish p_fish=$NIX_LINK/etc/profile.d/nix.fish
if [ -z "$NIX_INSTALLER_NO_MODIFY_PROFILE" ]; then if [ -z "$NIX_INSTALLER_NO_MODIFY_PROFILE" ]; then
# Make the shell source nix.sh during login. # Make the shell source nix.sh during login.
for i in .bash_profile .bash_login .profile; do for i in .bash_profile .bash_login .profile; do

View file

@ -2,7 +2,33 @@
if [ -n "${__ETC_PROFILE_NIX_SOURCED:-}" ]; then return; fi if [ -n "${__ETC_PROFILE_NIX_SOURCED:-}" ]; then return; fi
__ETC_PROFILE_NIX_SOURCED=1 __ETC_PROFILE_NIX_SOURCED=1
export NIX_PROFILES="@localstatedir@/nix/profiles/default $HOME/.nix-profile" NIX_LINK=$HOME/.nix-profile
if [ -n "$XDG_STATE_HOME" ]; then
NIX_LINK_NEW="$XDG_STATE_HOME/nix/profile"
else
NIX_LINK_NEW=$HOME/.local/state/nix/profile
fi
if ! [ -e "$NIX_LINK" ]; then
NIX_LINK="$NIX_LINK_NEW"
else
if [ -t 2 ] && [ -e "$NIX_LINK_NEW" ]; then
warning="\033[1;35mwarning:\033[0m"
printf "$warning Both %s and legacy %s exist; using the latter.\n" "$NIX_LINK_NEW" "$NIX_LINK" 1>&2
if [ "$(realpath "$NIX_LINK")" = "$(realpath "$NIX_LINK_NEW")" ]; then
printf " Since the profiles match, you can safely delete either of them.\n" 1>&2
else
# This should be an exceptionally rare occasion: the only way to get it would be to
# 1. Update to newer Nix;
# 2. Remove .nix-profile;
# 3. Set the $NIX_LINK_NEW to something other than the default user profile;
# 4. Roll back to older Nix.
# If someone did all that, they can probably figure out how to migrate the profile.
printf "$warning Profiles do not match. You should manually migrate from %s to %s.\n" "$NIX_LINK" "$NIX_LINK_NEW" 1>&2
fi
fi
fi
export NIX_PROFILES="@localstatedir@/nix/profiles/default $NIX_LINK"
# Set $NIX_SSL_CERT_FILE so that Nixpkgs applications like curl work. # Set $NIX_SSL_CERT_FILE so that Nixpkgs applications like curl work.
if [ -n "${NIX_SSL_CERT_FILE:-}" ]; then if [ -n "${NIX_SSL_CERT_FILE:-}" ]; then
@ -34,4 +60,5 @@ else
unset -f check_nix_profiles unset -f check_nix_profiles
fi fi
export PATH="$HOME/.nix-profile/bin:@localstatedir@/nix/profiles/default/bin:$PATH" export PATH="$NIX_LINK/bin:@localstatedir@/nix/profiles/default/bin:$PATH"
unset NIX_LINK

View file

@ -2,11 +2,35 @@ if [ -n "$HOME" ] && [ -n "$USER" ]; then
# Set up the per-user profile. # Set up the per-user profile.
NIX_LINK=$HOME/.nix-profile NIX_LINK="$HOME/.nix-profile"
if [ -n "$XDG_STATE_HOME" ]; then
NIX_LINK_NEW="$XDG_STATE_HOME/nix/profile"
else
NIX_LINK_NEW="$HOME/.local/state/nix/profile"
fi
if ! [ -e "$NIX_LINK" ]; then
NIX_LINK="$NIX_LINK_NEW"
else
if [ -t 2 ] && [ -e "$NIX_LINK_NEW" ]; then
warning="\033[1;35mwarning:\033[0m"
printf "$warning Both %s and legacy %s exist; using the latter.\n" "$NIX_LINK_NEW" "$NIX_LINK" 1>&2
if [ "$(realpath "$NIX_LINK")" = "$(realpath "$NIX_LINK_NEW")" ]; then
printf " Since the profiles match, you can safely delete either of them.\n" 1>&2
else
# This should be an exceptionally rare occasion: the only way to get it would be to
# 1. Update to newer Nix;
# 2. Remove .nix-profile;
# 3. Set the $NIX_LINK_NEW to something other than the default user profile;
# 4. Roll back to older Nix.
# If someone did all that, they can probably figure out how to migrate the profile.
printf "$warning Profiles do not match. You should manually migrate from %s to %s.\n" "$NIX_LINK" "$NIX_LINK_NEW" 1>&2
fi
fi
fi
# Set up environment. # Set up environment.
# This part should be kept in sync with nixpkgs:nixos/modules/programs/environment.nix # This part should be kept in sync with nixpkgs:nixos/modules/programs/environment.nix
export NIX_PROFILES="@localstatedir@/nix/profiles/default $HOME/.nix-profile" export NIX_PROFILES="@localstatedir@/nix/profiles/default $NIX_LINK"
# Set $NIX_SSL_CERT_FILE so that Nixpkgs applications like curl work. # Set $NIX_SSL_CERT_FILE so that Nixpkgs applications like curl work.
if [ -e /etc/ssl/certs/ca-certificates.crt ]; then # NixOS, Ubuntu, Debian, Gentoo, Arch if [ -e /etc/ssl/certs/ca-certificates.crt ]; then # NixOS, Ubuntu, Debian, Gentoo, Arch
@ -31,5 +55,5 @@ if [ -n "$HOME" ] && [ -n "$USER" ]; then
fi fi
export PATH="$NIX_LINK/bin:$PATH" export PATH="$NIX_LINK/bin:$PATH"
unset NIX_LINK unset NIX_LINK NIX_LINK_NEW
fi fi

View file

@ -2496,7 +2496,7 @@ Strings EvalSettings::getDefaultNixPath()
res.push_back(s ? *s + "=" + p : p); res.push_back(s ? *s + "=" + p : p);
}; };
add(getHome() + "/.nix-defexpr/channels"); add(settings.useXDGBaseDirectories ? getStateDir() + "/nix/defexpr/channels" : getHome() + "/.nix-defexpr/channels");
add(settings.nixStateDir + "/profiles/per-user/root/channels/nixpkgs", "nixpkgs"); add(settings.nixStateDir + "/profiles/per-user/root/channels/nixpkgs", "nixpkgs");
add(settings.nixStateDir + "/profiles/per-user/root/channels"); add(settings.nixStateDir + "/profiles/per-user/root/channels");

View file

@ -975,6 +975,27 @@ public:
resolves to a different location from that of the build machine. You resolves to a different location from that of the build machine. You
can enable this setting if you are sure you're not going to do that. can enable this setting if you are sure you're not going to do that.
)"}; )"};
Setting<bool> useXDGBaseDirectories{
this, false, "use-xdg-base-directories",
R"(
If set to `true`, Nix will conform to the [XDG Base Directory Specification] for files in `$HOME`.
The environment variables used to implement this are documented in the [Environment Variables section](@docroot@/installation/env-variables.md).
[XDG Base Directory Specification]: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
> **Warning**
> This changes the location of some well-known symlinks that Nix creates, which might break tools that rely on the old, non-XDG-conformant locations.
In particular, the following locations change:
| Old | New |
|-------------------|--------------------------------|
| `~/.nix-profile` | `$XDG_STATE_HOME/nix/profile` |
| `~/.nix-defexpr` | `$XDG_STATE_HOME/nix/defexpr` |
| `~/.nix-channels` | `$XDG_STATE_HOME/nix/channels` |
)"
};
}; };

View file

@ -282,7 +282,7 @@ std::string optimisticLockProfile(const Path & profile)
Path profilesDir() Path profilesDir()
{ {
auto profileRoot = getDataDir() + "/nix/profiles"; auto profileRoot = createNixStateDir() + "/profiles";
createDirs(profileRoot); createDirs(profileRoot);
return profileRoot; return profileRoot;
} }
@ -290,7 +290,7 @@ Path profilesDir()
Path getDefaultProfile() Path getDefaultProfile()
{ {
Path profileLink = getHome() + "/.nix-profile"; Path profileLink = settings.useXDGBaseDirectories ? createNixStateDir() + "/profile" : getHome() + "/.nix-profile";
try { try {
auto profile = auto profile =
getuid() == 0 getuid() == 0

View file

@ -72,8 +72,9 @@ std::string optimisticLockProfile(const Path & profile);
profiles. */ profiles. */
Path profilesDir(); Path profilesDir();
/* Resolve ~/.nix-profile. If ~/.nix-profile doesn't exist yet, create /* Resolve the default profile (~/.nix-profile by default, $XDG_STATE_HOME/
it. */ nix/profile if XDG Base Directory Support is enabled), and create if doesn't
exist */
Path getDefaultProfile(); Path getDefaultProfile();
} }

View file

@ -608,6 +608,19 @@ Path getDataDir()
return dataDir ? *dataDir : getHome() + "/.local/share"; return dataDir ? *dataDir : getHome() + "/.local/share";
} }
Path getStateDir()
{
auto stateDir = getEnv("XDG_STATE_HOME");
return stateDir ? *stateDir : getHome() + "/.local/state";
}
Path createNixStateDir()
{
Path dir = getStateDir() + "/nix";
createDirs(dir);
return dir;
}
std::optional<Path> getSelfExe() std::optional<Path> getSelfExe()
{ {

View file

@ -158,6 +158,12 @@ Path getDataDir();
/* Return the path of the current executable. */ /* Return the path of the current executable. */
std::optional<Path> getSelfExe(); std::optional<Path> getSelfExe();
/* Return $XDG_STATE_HOME or $HOME/.local/state. */
Path getStateDir();
/* Create the Nix state directory and return the path to it. */
Path createNixStateDir();
/* Create a directory and all its parents, if necessary. Returns the /* Create a directory and all its parents, if necessary. Returns the
list of created directories, in order of creation. */ list of created directories, in order of creation. */
Paths createDirs(const Path & path); Paths createDirs(const Path & path);

View file

@ -164,8 +164,8 @@ static int main_nix_channel(int argc, char ** argv)
{ {
// Figure out the name of the `.nix-channels' file to use // Figure out the name of the `.nix-channels' file to use
auto home = getHome(); auto home = getHome();
channelsList = home + "/.nix-channels"; channelsList = settings.useXDGBaseDirectories ? createNixStateDir() + "/channels" : home + "/.nix-channels";
nixDefExpr = home + "/.nix-defexpr"; nixDefExpr = settings.useXDGBaseDirectories ? createNixStateDir() + "/defexpr" : home + "/.nix-defexpr";
// Figure out the name of the channels profile. // Figure out the name of the channels profile.
profile = profilesDir() + "/channels"; profile = profilesDir() + "/channels";

View file

@ -1289,7 +1289,7 @@ static void opSwitchProfile(Globals & globals, Strings opFlags, Strings opArgs)
throw UsageError("exactly one argument expected"); throw UsageError("exactly one argument expected");
Path profile = absPath(opArgs.front()); Path profile = absPath(opArgs.front());
Path profileLink = getHome() + "/.nix-profile"; Path profileLink = settings.useXDGBaseDirectories ? createNixStateDir() + "/profile" : getHome() + "/.nix-profile";
switchLink(profileLink, profile); switchLink(profileLink, profile);
} }
@ -1393,7 +1393,10 @@ static int main_nix_env(int argc, char * * argv)
Globals globals; Globals globals;
globals.instSource.type = srcUnknown; globals.instSource.type = srcUnknown;
globals.instSource.nixExprPath = getHome() + "/.nix-defexpr"; {
Path nixExprPath = settings.useXDGBaseDirectories ? createNixStateDir() + "/defexpr" : getHome() + "/.nix-defexpr";
globals.instSource.nixExprPath = nixExprPath;
}
globals.instSource.systemFilter = "*"; globals.instSource.systemFilter = "*";
if (!pathExists(globals.instSource.nixExprPath)) { if (!pathExists(globals.instSource.nixExprPath)) {

View file

@ -27,6 +27,8 @@ export NIX_REMOTE=$NIX_REMOTE_
unset NIX_PATH unset NIX_PATH
export TEST_HOME=$TEST_ROOT/test-home export TEST_HOME=$TEST_ROOT/test-home
export HOME=$TEST_HOME export HOME=$TEST_HOME
unset XDG_STATE_HOME
unset XDG_DATA_HOME
unset XDG_CONFIG_HOME unset XDG_CONFIG_HOME
unset XDG_CONFIG_DIRS unset XDG_CONFIG_DIRS
unset XDG_CACHE_HOME unset XDG_CACHE_HOME
@ -62,8 +64,8 @@ readLink() {
} }
clearProfiles() { clearProfiles() {
profiles="$HOME"/.local/share/nix/profiles profiles="$HOME"/.local/state/nix/profiles
rm -rf $profiles rm -rf "$profiles"
} }
clearStore() { clearStore() {

View file

@ -12,6 +12,19 @@ nix-channel --remove xyzzy
[ -e $TEST_HOME/.nix-channels ] [ -e $TEST_HOME/.nix-channels ]
[ "$(cat $TEST_HOME/.nix-channels)" = '' ] [ "$(cat $TEST_HOME/.nix-channels)" = '' ]
# Test the XDG Base Directories support
export NIX_CONFIG="use-xdg-base-directories = true"
nix-channel --add http://foo/bar xyzzy
nix-channel --list | grep -q http://foo/bar
nix-channel --remove xyzzy
unset NIX_CONFIG
[ -e $TEST_HOME/.local/state/nix/channels ]
[ "$(cat $TEST_HOME/.local/state/nix/channels)" = '' ]
# Create a channel. # Create a channel.
rm -rf $TEST_ROOT/foo rm -rf $TEST_ROOT/foo
mkdir -p $TEST_ROOT/foo mkdir -p $TEST_ROOT/foo

View file

@ -56,6 +56,14 @@ nix profile history
nix profile history | grep "packages.$system.default: ∅ -> 1.0" nix profile history | grep "packages.$system.default: ∅ -> 1.0"
nix profile diff-closures | grep 'env-manifest.nix: ε → ∅' nix profile diff-closures | grep 'env-manifest.nix: ε → ∅'
# Test XDG Base Directories support
export NIX_CONFIG="use-xdg-base-directories = true"
nix profile remove 1
nix profile install $flake1Dir
[[ $($TEST_HOME/.local/state/nix/profile/bin/hello) = "Hello World" ]]
unset NIX_CONFIG
# Test upgrading a package. # Test upgrading a package.
printf NixOS > $flake1Dir/who printf NixOS > $flake1Dir/who
printf 2.0 > $flake1Dir/version printf 2.0 > $flake1Dir/version