Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Follow XDG Base Directory standard #5588

Merged
merged 1 commit into from
Feb 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions doc/manual/src/command-ref/env-common.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
384 MiB. Setting it to a low value reduces memory consumption, but
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
2 changes: 1 addition & 1 deletion scripts/install-multi-user.sh
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ EOF
cat <<EOF
$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.

Expand Down
8 changes: 5 additions & 3 deletions scripts/install-nix-from-closure.sh
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ fi
# shellcheck source=./nix-profile.sh.in
. "$nix/etc/profile.d/nix.sh"

NIX_LINK="$HOME/.nix-profile"

if ! "$nix/bin/nix-env" -i "$nix"; then
echo "$0: unable to install Nix into your default profile" >&2
exit 1
Expand All @@ -196,7 +198,7 @@ fi
# Install an SSL certificate bundle.
if [ -z "$NIX_SSL_CERT_FILE" ] || ! [ -f "$NIX_SSL_CERT_FILE" ]; then
"$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

# Subscribe the user to the Nixpkgs channel and fetch it.
Expand All @@ -214,8 +216,8 @@ fi

added=
p=
p_sh=$HOME/.nix-profile/etc/profile.d/nix.sh
p_fish=$HOME/.nix-profile/etc/profile.d/nix.fish
p_sh=$NIX_LINK/etc/profile.d/nix.sh
p_fish=$NIX_LINK/etc/profile.d/nix.fish
if [ -z "$NIX_INSTALLER_NO_MODIFY_PROFILE" ]; then
# Make the shell source nix.sh during login.
for i in .bash_profile .bash_login .profile; do
Expand Down
31 changes: 29 additions & 2 deletions scripts/nix-profile-daemon.sh.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,33 @@
if [ -n "${__ETC_PROFILE_NIX_SOURCED:-}" ]; then return; fi
__ETC_PROFILE_NIX_SOURCED=1

export NIX_PROFILES="@localstatedir@/nix/profiles/default $HOME/.nix-profile"
NIX_LINK=$HOME/.nix-profile
balsoft marked this conversation as resolved.
Show resolved Hide resolved
if [ -n "$XDG_STATE_HOME" ]; then
NIX_LINK_NEW="$XDG_STATE_HOME/nix/profile"
else
NIX_LINK_NEW=$HOME/.local/state/nix/profile

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be NIX_LINK_NEW=$HOME/.local/state/nix/profiles/profile/ ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does make you think we want to profile/profiles? That looks like a bug to me.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SuperSandro2000 I'm not familiar with the nix source code, but when I do a single user install on a fresh docker ubuntu, nix adds the following sym link:

~/.nix-profile -> ~/.local/state/nix/profiles/profile

Do we not want this value to match that?

If ~/.nix-profile doesn't exist and $XDG_STATE_HOME is not set, we end up with NIX_LINK_NEW=$HOME/.local/state/nix/profile but this link target doesn't exist on my install.

Copy link
Member

@ncfavier ncfavier Mar 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like there is both ~/.local/share/nix/profiles/profile and ~/.local/state/nix/profile. The former is the actual profile, the latter is a symlink to it. I think this should be simplified by putting the profiles in state and removing the useless indirection (compatibility is only needed for ~/.nix-profile when not using xdg-base-dirs)

(Or keep the profiles in share, we've had enough breakage already)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this is what I called out in #5588 (comment). I think rather than trying to support 4(!) combinations, we should do all the XDG stuff (this and home directories) together so there are just 2 combinations.

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.
if [ -n "${NIX_SSL_CERT_FILE:-}" ]; then
Expand Down Expand Up @@ -34,4 +60,5 @@ else
unset -f check_nix_profiles
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
30 changes: 27 additions & 3 deletions scripts/nix-profile.sh.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,35 @@ if [ -n "$HOME" ] && [ -n "$USER" ]; then

# 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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if we want to check if the terminal supports those escape sequences but I bet it would be safe to assume it does.

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.
# 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.
if [ -e /etc/ssl/certs/ca-certificates.crt ]; then # NixOS, Ubuntu, Debian, Gentoo, Arch
Expand All @@ -31,5 +55,5 @@ if [ -n "$HOME" ] && [ -n "$USER" ]; then
fi

export PATH="$NIX_LINK/bin:$PATH"
unset NIX_LINK
unset NIX_LINK NIX_LINK_NEW
fi
2 changes: 1 addition & 1 deletion src/libexpr/eval.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2496,7 +2496,7 @@ Strings EvalSettings::getDefaultNixPath()
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");

Expand Down
21 changes: 21 additions & 0 deletions src/libstore/globals.hh
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,27 @@ public:
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.
)"};

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).

fricklerhandwerk marked this conversation as resolved.
Show resolved Hide resolved
[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` |
)"
};
};


Expand Down
4 changes: 2 additions & 2 deletions src/libstore/profiles.cc
Original file line number Diff line number Diff line change
Expand Up @@ -282,15 +282,15 @@ std::string optimisticLockProfile(const Path & profile)

Path profilesDir()
{
auto profileRoot = getDataDir() + "/nix/profiles";
auto profileRoot = createNixStateDir() + "/profiles";
createDirs(profileRoot);
return profileRoot;
}


Path getDefaultProfile()
{
Path profileLink = getHome() + "/.nix-profile";
Path profileLink = settings.useXDGBaseDirectories ? createNixStateDir() + "/profile" : getHome() + "/.nix-profile";
try {
auto profile =
getuid() == 0
Expand Down
5 changes: 3 additions & 2 deletions src/libstore/profiles.hh
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,9 @@ std::string optimisticLockProfile(const Path & profile);
profiles. */
Path profilesDir();

/* Resolve ~/.nix-profile. If ~/.nix-profile doesn't exist yet, create
it. */
/* Resolve the default profile (~/.nix-profile by default, $XDG_STATE_HOME/
nix/profile if XDG Base Directory Support is enabled), and create if doesn't
exist */
Path getDefaultProfile();

}
13 changes: 13 additions & 0 deletions src/libutil/util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,19 @@ Path getDataDir()
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()
{
Expand Down
6 changes: 6 additions & 0 deletions src/libutil/util.hh
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,12 @@ Path getDataDir();
/* Return the path of the current executable. */
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
list of created directories, in order of creation. */
Paths createDirs(const Path & path);
Expand Down
4 changes: 2 additions & 2 deletions src/nix-channel/nix-channel.cc
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,8 @@ static int main_nix_channel(int argc, char ** argv)
{
// Figure out the name of the `.nix-channels' file to use
auto home = getHome();
channelsList = home + "/.nix-channels";
nixDefExpr = home + "/.nix-defexpr";
channelsList = settings.useXDGBaseDirectories ? createNixStateDir() + "/channels" : home + "/.nix-channels";
nixDefExpr = settings.useXDGBaseDirectories ? createNixStateDir() + "/defexpr" : home + "/.nix-defexpr";

// Figure out the name of the channels profile.
profile = profilesDir() + "/channels";
Expand Down
7 changes: 5 additions & 2 deletions src/nix-env/nix-env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1289,7 +1289,7 @@ static void opSwitchProfile(Globals & globals, Strings opFlags, Strings opArgs)
throw UsageError("exactly one argument expected");

Path profile = absPath(opArgs.front());
Path profileLink = getHome() + "/.nix-profile";
Path profileLink = settings.useXDGBaseDirectories ? createNixStateDir() + "/profile" : getHome() + "/.nix-profile";

switchLink(profileLink, profile);
}
Expand Down Expand Up @@ -1393,7 +1393,10 @@ static int main_nix_env(int argc, char * * argv)
Globals globals;

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 = "*";

if (!pathExists(globals.instSource.nixExprPath)) {
Expand Down
6 changes: 4 additions & 2 deletions tests/common.sh.in
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export NIX_REMOTE=$NIX_REMOTE_
unset NIX_PATH
export TEST_HOME=$TEST_ROOT/test-home
export HOME=$TEST_HOME
unset XDG_STATE_HOME
unset XDG_DATA_HOME
unset XDG_CONFIG_HOME
unset XDG_CONFIG_DIRS
unset XDG_CACHE_HOME
Expand Down Expand Up @@ -62,8 +64,8 @@ readLink() {
}

clearProfiles() {
profiles="$HOME"/.local/share/nix/profiles
rm -rf $profiles
profiles="$HOME"/.local/state/nix/profiles
rm -rf "$profiles"
}

clearStore() {
Expand Down
13 changes: 13 additions & 0 deletions tests/nix-channel.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@ nix-channel --remove xyzzy
[ -e $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.
rm -rf $TEST_ROOT/foo
mkdir -p $TEST_ROOT/foo
Expand Down
8 changes: 8 additions & 0 deletions tests/nix-profile.sh
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ nix profile history
nix profile history | grep "packages.$system.default: ∅ -> 1.0"
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.
printf NixOS > $flake1Dir/who
printf 2.0 > $flake1Dir/version
Expand Down