From 3c8096e5cb94fcb0a007d3f5af94addb6ffc3e5d Mon Sep 17 00:00:00 2001 From: Lily Ballard Date: Mon, 21 Oct 2024 00:01:44 -0700 Subject: [PATCH] feat: Add temp-dir setting This adds a new temp-dir setting for controlling the temporary directory without having to change the TMPDIR env var. This can be used to e.g. use a path on a case-sensitive store on macOS for temporary files without changing the TMPDIR var used by interactive shells or commands invoked with `nix run`. This also stops unsetting `TMPDIR` on darwin when the env var value starts with `/var/folders/`, preferring instead to just do the check when reading `TMPDIR`. This way the inherited `TMPDIR` env var is preserved for child processes (such as interactive shells). As a side effect this changes the behavior of `nix-build -o ''` to act like `nix-build --no-out-link` instead of failing with an error caused by trying to create a symlink at the cwd. Fixes: https://git.lix.systems/lix-project/lix/issues/253 Fixes: https://git.lix.systems/lix-project/lix/issues/112 Change-Id: I9ee826323f2deca62854715a77ca7a373a948a29 --- doc/manual/rl-next/nix-build-out-link.md | 13 ++++++ doc/manual/rl-next/temp-dir.md | 15 +++++++ src/legacy/nix-build.cc | 15 +++---- src/legacy/nix-channel.cc | 1 + src/libcmd/cmd-profiles.cc | 1 + src/libfetchers/git.cc | 1 + src/libfetchers/mercurial.cc | 1 + src/libfetchers/tarball.cc | 1 + src/libstore/binary-cache-store.cc | 1 + src/libstore/build/local-derivation-goal.cc | 1 + src/libstore/globals.cc | 5 --- src/libstore/globals.hh | 16 +++++++- src/libstore/local-store.cc | 1 + src/libstore/meson.build | 2 + src/libstore/ssh.cc | 1 + src/libstore/temporary-dir.cc | 42 ++++++++++++++++++++ src/libstore/temporary-dir.hh | 24 +++++++++++ src/libutil/file-system.cc | 26 +++--------- src/libutil/file-system.hh | 14 +------ src/nix/develop.cc | 1 + src/nix/prefetch.cc | 1 + src/nix/run.cc | 1 + tests/functional/check.sh | 44 +++++++++++++++++++++ tests/unit/libstore/nar-info-disk-cache.cc | 1 + 24 files changed, 182 insertions(+), 47 deletions(-) create mode 100644 doc/manual/rl-next/nix-build-out-link.md create mode 100644 doc/manual/rl-next/temp-dir.md create mode 100644 src/libstore/temporary-dir.cc create mode 100644 src/libstore/temporary-dir.hh 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