diff --git a/doc/manual/rl-next/nix-build-out-link.md b/doc/manual/rl-next/nix-build-out-link.md
new file mode 100644
index 000000000..64b2ed1da
--- /dev/null
+++ b/doc/manual/rl-next/nix-build-out-link.md
@@ -0,0 +1,13 @@
+---
+synopsis: Change `nix-build -o ""` to behave like `--no-out-link`
+cls: [2103]
+category: Fixes
+credits: lilyball
+---
+
+[`nix-build`](@docroot@/command-ref/nix-build.md)now treats [--out-link](@docroot@/command-ref/nix-build.md#opt-out-link) ''
+the same as [`--no-out-link`](@docroot@/command-ref/nix-build.md#opt-no-out-link). This matches
+[`nix build`](@docroot@/command-ref/new-cli/nix3-build.md) behavior. Previously when building the default output it
+would have resulted in throwing an error saying the current working directory already exists, and when building any
+other output it would have resulted in a symlink starting with a hyphen such as `-doc`, which is a footgun for
+terminal commands.
diff --git a/doc/manual/rl-next/temp-dir.md b/doc/manual/rl-next/temp-dir.md
new file mode 100644
index 000000000..b9ba3f89e
--- /dev/null
+++ b/doc/manual/rl-next/temp-dir.md
@@ -0,0 +1,15 @@
+---
+synopsis: Add a `temp-dir` setting to set the temporary directory location
+issues: [7731, 8995, fj#112, fj#253]
+cls: [2103]
+category: Improvements
+credits: lilyball
+---
+
+[`temp-dir`](@docroot@/command-ref/conf-file.md#conf-temp-dir) can now be set in the Nix
+configuration to change the temporary directory. This can be used to relocate all temporary files
+to another filesystem without affecting the `TMPDIR` env var inherited by interactive
+`nix-shell`/`nix shell` shells or `nix run` commands.
+
+Also on macOS, the `TMPDIR` env var is no longer unset for interactive shells when pointing
+to a per-session `/var/folders/` directory.
diff --git a/src/legacy/nix-build.cc b/src/legacy/nix-build.cc
index 1107bc443..9e8cc68a9 100644
--- a/src/legacy/nix-build.cc
+++ b/src/legacy/nix-build.cc
@@ -25,6 +25,7 @@
#include "legacy.hh"
#include "shlex.hh"
#include "nix-build.hh"
+#include "temporary-dir.hh"
extern char * * environ __attribute__((weak)); // Man what even is this
@@ -55,8 +56,6 @@ static void main_nix_build(int argc, char * * argv)
std::string script;
std::vector savedArgs;
- AutoDelete tmpDir(createTempDir("", myName));
-
std::string outLink = "./result";
// List of environment variables kept for --pure
@@ -102,7 +101,6 @@ static void main_nix_build(int argc, char * * argv)
MyArgs myArgs(myName, [&](Strings::iterator & arg, const Strings::iterator & end) {
if (*arg == "--help") {
- deletePath(tmpDir);
showManPage(myName);
}
@@ -113,7 +111,7 @@ static void main_nix_build(int argc, char * * argv)
; // obsolete
else if (*arg == "--no-out-link" || *arg == "--no-link")
- outLink = (Path) tmpDir + "/result";
+ outLink = "";
else if (*arg == "--attr" || *arg == "-A")
attrPaths.push_back(getArg(*arg, arg, end));
@@ -198,6 +196,10 @@ static void main_nix_build(int argc, char * * argv)
if (packages && fromArgs)
throw UsageError("'-p' and '-E' are mutually exclusive");
+ AutoDelete tmpDir(createTempDir("", myName));
+ if (outLink.empty())
+ outLink = (Path) tmpDir + "/result";
+
auto store = openStore();
auto evalStore = myArgs.evalStoreUrl ? openStore(*myArgs.evalStoreUrl) : store;
@@ -416,8 +418,6 @@ static void main_nix_build(int argc, char * * argv)
// Set the environment.
auto env = getEnv();
- auto tmp = defaultTempDir();
-
if (pure) {
decltype(env) newEnv;
for (auto & i : env)
@@ -428,7 +428,8 @@ static void main_nix_build(int argc, char * * argv)
env["__ETC_PROFILE_SOURCED"] = "1";
}
- env["NIX_BUILD_TOP"] = env["TMPDIR"] = env["TEMPDIR"] = env["TMP"] = env["TEMP"] = tmp;
+ // Don't use defaultTempDir() here! We want to preserve the user's TMPDIR for the shell
+ env["NIX_BUILD_TOP"] = env["TMPDIR"] = env["TEMPDIR"] = env["TMP"] = env["TEMP"] = getEnvNonEmpty("TMPDIR").value_or("/tmp");
env["NIX_STORE"] = store->storeDir;
env["NIX_BUILD_CORES"] = std::to_string(settings.buildCores);
diff --git a/src/legacy/nix-channel.cc b/src/legacy/nix-channel.cc
index 2f79919dd..f4dfa88b6 100644
--- a/src/legacy/nix-channel.cc
+++ b/src/legacy/nix-channel.cc
@@ -6,6 +6,7 @@
#include "legacy.hh"
#include "fetchers.hh"
#include "eval-settings.hh" // for defexpr
+#include "temporary-dir.hh"
#include "users.hh"
#include "nix-channel.hh"
diff --git a/src/libcmd/cmd-profiles.cc b/src/libcmd/cmd-profiles.cc
index cfce4789e..dcec6526d 100644
--- a/src/libcmd/cmd-profiles.cc
+++ b/src/libcmd/cmd-profiles.cc
@@ -6,6 +6,7 @@
#include "logging.hh"
#include "names.hh"
#include "store-api.hh"
+#include "temporary-dir.hh"
#include "url-name.hh"
namespace nix
diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc
index da60bf331..a35889b19 100644
--- a/src/libfetchers/git.cc
+++ b/src/libfetchers/git.cc
@@ -5,6 +5,7 @@
#include "processes.hh"
#include "tarfile.hh"
#include "store-api.hh"
+#include "temporary-dir.hh"
#include "url-parts.hh"
#include "pathlocks.hh"
#include "users.hh"
diff --git a/src/libfetchers/mercurial.cc b/src/libfetchers/mercurial.cc
index a1e4c1918..b5ea7998a 100644
--- a/src/libfetchers/mercurial.cc
+++ b/src/libfetchers/mercurial.cc
@@ -2,6 +2,7 @@
#include "cache.hh"
#include "processes.hh"
#include "store-api.hh"
+#include "temporary-dir.hh"
#include "url-parts.hh"
#include "users.hh"
diff --git a/src/libfetchers/tarball.cc b/src/libfetchers/tarball.cc
index bf8bbedd1..dac3608df 100644
--- a/src/libfetchers/tarball.cc
+++ b/src/libfetchers/tarball.cc
@@ -5,6 +5,7 @@
#include "store-api.hh"
#include "archive.hh"
#include "tarfile.hh"
+#include "temporary-dir.hh"
#include "types.hh"
#include "split.hh"
diff --git a/src/libstore/binary-cache-store.cc b/src/libstore/binary-cache-store.cc
index d4197b3df..c001f1eca 100644
--- a/src/libstore/binary-cache-store.cc
+++ b/src/libstore/binary-cache-store.cc
@@ -8,6 +8,7 @@
#include "remote-fs-accessor.hh"
#include "nar-info-disk-cache.hh" // IWYU pragma: keep
#include "nar-accessor.hh"
+#include "temporary-dir.hh"
#include "thread-pool.hh"
#include "signals.hh"
#include "strings.hh"
diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc
index 84f267f99..a507c4078 100644
--- a/src/libstore/build/local-derivation-goal.cc
+++ b/src/libstore/build/local-derivation-goal.cc
@@ -3,6 +3,7 @@
#include "indirect-root-store.hh"
#include "machines.hh"
#include "store-api.hh"
+#include "temporary-dir.hh"
#include "worker.hh"
#include "builtins.hh"
#include "builtins/buildenv.hh"
diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc
index cf72cfcbc..bd7e7abb5 100644
--- a/src/libstore/globals.cc
+++ b/src/libstore/globals.cc
@@ -468,11 +468,6 @@ void initLibStore() {
[1] https://github.com/apple-oss-distributions/objc4/blob/01edf1705fbc3ff78a423cd21e03dfc21eb4d780/runtime/objc-initialize.mm#L614-L636
*/
curl_global_init(CURL_GLOBAL_ALL);
- /* On macOS, don't use the per-session TMPDIR (as set e.g. by
- sshd). This breaks build users because they don't have access
- to the TMPDIR, in particular in ‘nix-store --serve’. */
- if (defaultTempDir().starts_with("/var/folders/"))
- unsetenv("TMPDIR");
#endif
registerStoreImplementations();
diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh
index dfb90cbe6..5fadc21e6 100644
--- a/src/libstore/globals.hh
+++ b/src/libstore/globals.hh
@@ -615,11 +615,11 @@ public:
)"};
#endif
- Setting> buildDir{this, std::nullopt, "build-dir",
+ PathsSetting> buildDir{this, std::nullopt, "build-dir",
R"(
The directory on the host, in which derivations' temporary build directories are created.
- If not set, Nix will use the system temporary directory indicated by the `TMPDIR` environment variable.
+ If not set, Nix will use the [`temp-dir`](#conf-temp-dir) setting if set, otherwise the system temporary directory indicated by the `TMPDIR` environment variable.
Note that builds are often performed by the Nix daemon, so its `TMPDIR` is used, and not that of the Nix command line interface.
This is also the location where [`--keep-failed`](@docroot@/command-ref/opt-common.md#opt-keep-failed) leaves its files.
@@ -627,6 +627,18 @@ public:
If Nix runs without sandbox, or if the platform does not support sandboxing with bind mounts (e.g. macOS), then the [`builder`](@docroot@/language/derivations.md#attr-builder)'s environment will contain this directory, instead of the virtual location [`sandbox-build-dir`](#conf-sandbox-build-dir).
)"};
+ PathsSetting> tempDir{this, std::nullopt, "temp-dir",
+ R"(
+ The directory on the host used as the default temporary directory.
+
+ If not set, Nix will use the system temporary directory indicated by the `TMPDIR` environment variable.
+
+ This will be used for anything that would otherwise fall back to `TMPDIR`, and the inherited `TMPDIR` value will be preserved for child processes to use.
+ If [`build-dir`](#conf-build-dir) is set, that takes precedence over this where it applies.
+
+ If set, the value must be a path that exists and is accessible to all users.
+ )"};
+
Setting allowedImpureHostPrefixes{this, {}, "allowed-impure-host-deps",
"Which prefixes to allow derivations to ask for access to (primarily for Darwin)."};
diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
index 0bea40bd9..cb7f97841 100644
--- a/src/libstore/local-store.cc
+++ b/src/libstore/local-store.cc
@@ -2,6 +2,7 @@
#include "globals.hh"
#include "archive.hh"
#include "pathlocks.hh"
+#include "temporary-dir.hh"
#include "worker-protocol.hh"
#include "derivations.hh"
#include "nar-info.hh"
diff --git a/src/libstore/meson.build b/src/libstore/meson.build
index 8d8b3422c..bde8792aa 100644
--- a/src/libstore/meson.build
+++ b/src/libstore/meson.build
@@ -77,6 +77,7 @@ libstore_sources = files(
'ssh-store.cc',
'ssh.cc',
'store-api.cc',
+ 'temporary-dir.cc',
'uds-remote-store.cc',
'worker-protocol.cc',
'build/child.cc',
@@ -160,6 +161,7 @@ libstore_headers = files(
'ssh-store.hh',
'store-api.hh',
'store-cast.hh',
+ 'temporary-dir.hh',
'uds-remote-store.hh',
'worker-protocol-impl.hh',
'worker-protocol.hh',
diff --git a/src/libstore/ssh.cc b/src/libstore/ssh.cc
index 8386b0e0a..c161c60ab 100644
--- a/src/libstore/ssh.cc
+++ b/src/libstore/ssh.cc
@@ -4,6 +4,7 @@
#include "finally.hh"
#include "logging.hh"
#include "strings.hh"
+#include "temporary-dir.hh"
namespace nix {
diff --git a/src/libstore/temporary-dir.cc b/src/libstore/temporary-dir.cc
new file mode 100644
index 000000000..ef639bda9
--- /dev/null
+++ b/src/libstore/temporary-dir.cc
@@ -0,0 +1,42 @@
+#include "temporary-dir.hh"
+
+#include "file-system.hh"
+#include "globals.hh"
+
+namespace nix {
+
+Path createTempDir(const Path & tmpRoot, const Path & prefix,
+ bool includePid, bool useGlobalCounter, mode_t mode)
+{
+ return createTempSubdir(tmpRoot.empty() ? defaultTempDir() : tmpRoot, prefix, includePid, useGlobalCounter, mode);
+}
+
+std::pair createTempFile(const Path & prefix)
+{
+ Path tmpl(defaultTempDir() + "/" + prefix + ".XXXXXX");
+ // FIXME: use O_TMPFILE.
+ AutoCloseFD fd(mkstemp(tmpl.data()));
+ if (!fd)
+ throw SysError("creating temporary file '%s'", tmpl);
+ closeOnExec(fd.get());
+ return {std::move(fd), tmpl};
+}
+
+Path defaultTempDir()
+{
+ return settings.tempDir.get().or_else([] {
+ return getEnvNonEmpty("TMPDIR").and_then([](auto val) -> std::optional {
+#if __APPLE__
+ /* On macOS, don't use the per-session TMPDIR (as set e.g. by
+ sshd). This breaks build users because they don't have access
+ to the TMPDIR, in particular in ‘nix-store --serve’. */
+ if (val.starts_with("/var/folders/")) {
+ return std::nullopt;
+ }
+#endif
+ return val;
+ });
+ }).value_or("/tmp");
+}
+
+}
diff --git a/src/libstore/temporary-dir.hh b/src/libstore/temporary-dir.hh
new file mode 100644
index 000000000..59f79d0b9
--- /dev/null
+++ b/src/libstore/temporary-dir.hh
@@ -0,0 +1,24 @@
+#pragma once
+///@file
+
+#include "file-system.hh"
+
+namespace nix {
+
+/**
+ * Create a temporary directory.
+ */
+Path createTempDir(const Path & tmpRoot = "", const Path & prefix = "nix",
+ bool includePid = true, bool useGlobalCounter = true, mode_t mode = 0755);
+
+/**
+ * Create a temporary file, returning a file handle and its path.
+ */
+std::pair createTempFile(const Path & prefix = "nix");
+
+/**
+ * Return settings.tempDir, `TMPDIR`, or the default temporary directory if unset or empty.
+ */
+Path defaultTempDir();
+
+}
diff --git a/src/libutil/file-system.cc b/src/libutil/file-system.cc
index 9e12f1230..36d44f924 100644
--- a/src/libutil/file-system.cc
+++ b/src/libutil/file-system.cc
@@ -562,21 +562,17 @@ void AutoDelete::reset(const Path & p, bool recursive) {
//////////////////////////////////////////////////////////////////////
-std::string defaultTempDir() {
- return getEnvNonEmpty("TMPDIR").value_or("/tmp");
-}
-
-static Path tempName(Path tmpRoot, const Path & prefix, bool includePid,
+static Path tempName(PathView parent, const Path & prefix, bool includePid,
std::atomic & counter)
{
- tmpRoot = canonPath(tmpRoot.empty() ? defaultTempDir() : tmpRoot, true);
+ auto tmpRoot = canonPath(parent, true);
if (includePid)
return fmt("%1%/%2%-%3%-%4%", tmpRoot, prefix, getpid(), counter++);
else
return fmt("%1%/%2%-%3%", tmpRoot, prefix, counter++);
}
-Path createTempDir(const Path & tmpRoot, const Path & prefix,
+Path createTempSubdir(const Path & parent, const Path & prefix,
bool includePid, bool useGlobalCounter, mode_t mode)
{
static std::atomic globalCounter = 0;
@@ -585,7 +581,7 @@ Path createTempDir(const Path & tmpRoot, const Path & prefix,
while (1) {
checkInterrupt();
- Path tmpDir = tempName(tmpRoot, prefix, includePid, counter);
+ Path tmpDir = tempName(parent, prefix, includePid, counter);
if (mkdir(tmpDir.c_str(), mode) == 0) {
#if __FreeBSD__
/* Explicitly set the group of the directory. This is to
@@ -606,18 +602,6 @@ Path createTempDir(const Path & tmpRoot, const Path & prefix,
}
}
-
-std::pair createTempFile(const Path & prefix)
-{
- Path tmpl(defaultTempDir() + "/" + prefix + ".XXXXXX");
- // FIXME: use O_TMPFILE.
- AutoCloseFD fd(mkstemp(tmpl.data()));
- if (!fd)
- throw SysError("creating temporary file '%s'", tmpl);
- closeOnExec(fd.get());
- return {std::move(fd), tmpl};
-}
-
Path makeTempPath(const Path & root, const Path & suffix)
{
// start the counter at a random value to minimize issues with preexisting temp paths
@@ -721,7 +705,7 @@ void moveFile(const Path & oldName, const Path & newName)
auto newPath = fs::path(newName);
// For the move to be as atomic as possible, copy to a temporary
// directory
- fs::path temp = createTempDir(newPath.parent_path(), "rename-tmp");
+ fs::path temp = createTempSubdir(newPath.parent_path(), "rename-tmp");
Finally removeTemp = [&]() { fs::remove(temp); };
auto tempCopyTarget = temp / "copy-target";
if (e.code().value() == EXDEV) {
diff --git a/src/libutil/file-system.hh b/src/libutil/file-system.hh
index 1c8d36a3a..f8265ff87 100644
--- a/src/libutil/file-system.hh
+++ b/src/libutil/file-system.hh
@@ -288,21 +288,11 @@ struct DIRDeleter
typedef std::unique_ptr AutoCloseDir;
/**
- * Create a temporary directory.
+ * Create a temporary directory in a given parent directory.
*/
-Path createTempDir(const Path & tmpRoot = "", const Path & prefix = "nix",
+Path createTempSubdir(const Path & parent, const Path & prefix = "nix",
bool includePid = true, bool useGlobalCounter = true, mode_t mode = 0755);
-/**
- * Create a temporary file, returning a file handle and its path.
- */
-std::pair createTempFile(const Path & prefix = "nix");
-
-/**
- * Return `TMPDIR`, or the default temporary directory if unset or empty.
- */
-Path defaultTempDir();
-
/**
* Return temporary path constructed by appending a suffix to a root path.
*
diff --git a/src/nix/develop.cc b/src/nix/develop.cc
index 81bc73e12..87c31ef35 100644
--- a/src/nix/develop.cc
+++ b/src/nix/develop.cc
@@ -7,6 +7,7 @@
#include "outputs-spec.hh"
#include "derivations.hh"
#include "run.hh"
+#include "temporary-dir.hh"
#include
#include
diff --git a/src/nix/prefetch.cc b/src/nix/prefetch.cc
index 686df2caa..ca1241d2e 100644
--- a/src/nix/prefetch.cc
+++ b/src/nix/prefetch.cc
@@ -8,6 +8,7 @@
#include "attr-path.hh"
#include "eval-inline.hh" // IWYU pragma: keep
#include "legacy.hh"
+#include "temporary-dir.hh"
#include "terminal.hh"
#include "prefetch-command.hh"
diff --git a/src/nix/run.cc b/src/nix/run.cc
index 824201fdf..b7449e6c4 100644
--- a/src/nix/run.cc
+++ b/src/nix/run.cc
@@ -13,6 +13,7 @@
#include "current-process.hh"
#if __linux__
+#include "temporary-dir.hh"
#include
#endif
diff --git a/tests/functional/check.sh b/tests/functional/check.sh
index 90ab61968..653563971 100644
--- a/tests/functional/check.sh
+++ b/tests/functional/check.sh
@@ -49,6 +49,50 @@ test_custom_build_dir() {
}
test_custom_build_dir
+test_custom_temp_dir() {
+ # like test_custom_build_dir(), but uses the temp-dir setting instead
+ # build-dir inherits from temp-dir when build-dir is unset
+ local customTempDir="$TEST_ROOT/custom-temp-dir"
+
+ mkdir "$customTempDir"
+ nix-build check.nix -A failed --argstr checkBuildId $checkBuildId \
+ --no-out-link --keep-failed --option temp-dir "$customTempDir" 2> $TEST_ROOT/log || status=$?
+ [ "$status" = "100" ]
+ [[ 1 == "$(count "$customTempDir/nix-build-"*)" ]]
+ local buildDir="$customTempDir/nix-build-"*
+ grep $checkBuildId $buildDir/checkBuildId
+
+ # also check a separate code path that doesn't involve build-dir
+ # nix-shell uses temp-dir for its rcfile path
+ rcpath=$(NIX_BUILD_SHELL=$SHELL nix-shell check.nix -A deterministic --option temp-dir "$customTempDir" --run 'echo $0' 2> $TEST_ROOT/log)
+ # rcpath is /nix-shell-*/rc
+ [[ $rcpath = "$customTempDir"/* ]]
+}
+test_custom_temp_dir
+
+test_shell_preserves_tmpdir() {
+ # ensure commands that spawn interactive shells don't overwrite TMPDIR with temp-dir
+ local envTempDir=$TEST_ROOT/shell-temp-dir-env
+ mkdir $envTempDir
+ local settingTempDir=$TEST_ROOT/shell-temp-dir-setting
+ mkdir $settingTempDir
+
+ # FIXME: switch to check.nix's deterministic once `nix develop` doesn't need `outputs`
+ # https://git.lix.systems/lix-project/lix/issues/556
+ local expr='with import ./config.nix; mkDerivation { name = "foo"; buildCommand = "echo foo > $out"; outputs = [ "out" ]; }'
+
+ local output
+ output=$(TMPDIR=$envTempDir NIX_BUILD_SHELL=$SHELL nix-shell -E "$expr" --option temp-dir "$settingTempDir" --command 'echo $TMPDIR' 2> $TEST_ROOT/log)
+ [[ $output = "$envTempDir" ]]
+
+ output=$(TMPDIR=$envTempDir nix develop --impure -E "$expr" --option temp-dir "$settingTempDir" --command bash -c 'echo $TMPDIR' 2> $TEST_ROOT/log)
+ [[ $output = "$envTempDir"/nix-shell.* ]]
+
+ output=$(TMPDIR=$envTempDir nix shell --impure -E "$expr" --option temp-dir "$settingTempDir" --command bash -c 'echo $TMPDIR' 2> $TEST_ROOT/log)
+ [[ $output = "$envTempDir" ]]
+}
+test_shell_preserves_tmpdir
+
nix-build check.nix -A deterministic --argstr checkBuildId $checkBuildId \
--no-out-link 2> $TEST_ROOT/log
checkBuildTempDirRemoved $TEST_ROOT/log
diff --git a/tests/unit/libstore/nar-info-disk-cache.cc b/tests/unit/libstore/nar-info-disk-cache.cc
index b4bdb8329..5273487c5 100644
--- a/tests/unit/libstore/nar-info-disk-cache.cc
+++ b/tests/unit/libstore/nar-info-disk-cache.cc
@@ -3,6 +3,7 @@
#include
#include
#include "sqlite.hh"
+#include "temporary-dir.hh"
#include