aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/manual/rl-next/relative-and-tilde-paths-in-config.md30
-rw-r--r--src/libstore/globals.cc3
-rw-r--r--src/libutil/config.cc14
-rw-r--r--src/libutil/file-system.cc15
-rw-r--r--src/libutil/file-system.hh10
-rw-r--r--tests/unit/libutil/paths-setting.cc55
6 files changed, 121 insertions, 6 deletions
diff --git a/doc/manual/rl-next/relative-and-tilde-paths-in-config.md b/doc/manual/rl-next/relative-and-tilde-paths-in-config.md
new file mode 100644
index 000000000..6645496a2
--- /dev/null
+++ b/doc/manual/rl-next/relative-and-tilde-paths-in-config.md
@@ -0,0 +1,30 @@
+---
+synopsis: Relative and tilde paths in configuration
+issues: [fj#482]
+cls: [1851, 1863, 1864]
+category: Features
+credits: [9999years]
+---
+
+[Configuration settings](@docroot@/command-ref/conf-file.md) can now refer to
+files with paths relative to the file they're written in or relative to your
+home directory (with `~/`).
+
+This makes settings like
+[`repl-overlays`](@docroot@/command-ref/conf-file.md#conf-repl-overlays) and
+[`secret-key-files`](@docroot@/command-ref/conf-file.md#conf-repl-overlays)
+much easier to set, especially if you'd like to refer to files in an existing
+dotfiles repo cloned into your home directory.
+
+If you put `repl-overlays = repl.nix` in your `~/.config/nix/nix.conf`, it'll
+load `~/.config/nix/repl.nix`. Similarly, you can set `repl-overlays =
+~/.dotfiles/repl.nix` to load a file relative to your home directory.
+
+Configuration files can also
+[`include`](@docroot@/command-ref/conf-file.md#file-format) paths relative to
+your home directory.
+
+Only user configuration files (like `$XDG_CONFIG_HOME/nix/nix.conf` or the
+files listed in `$NIX_USER_CONF_FILES`) can use tilde paths relative to your
+home directory. Configuration listed in the `$NIX_CONFIG` environment variable
+may not use relative paths.
diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc
index ab461e739..29ec60105 100644
--- a/src/libstore/globals.cc
+++ b/src/libstore/globals.cc
@@ -131,8 +131,9 @@ void loadConfFile()
globalConfig.resetOverridden();
auto files = settings.nixUserConfFiles;
+ auto home = getHome();
for (auto file = files.rbegin(); file != files.rend(); file++) {
- applyConfigFile(ApplyConfigOptions{.path = *file});
+ applyConfigFile(ApplyConfigOptions{.path = *file, .home = home});
}
auto nixConfEnv = getEnv("NIX_CONFIG");
diff --git a/src/libutil/config.cc b/src/libutil/config.cc
index 333deb388..778da1413 100644
--- a/src/libutil/config.cc
+++ b/src/libutil/config.cc
@@ -126,7 +126,7 @@ static void applyConfigInner(const std::string & contents, const ApplyConfigOpti
if (!options.path) {
throw UsageError("can only include configuration '%1%' from files", tokens[1]);
}
- auto pathToInclude = absPath(tokens[1], dirOf(*options.path));
+ auto pathToInclude = absPath(tildePath(tokens[1], options.home), dirOf(*options.path));
if (pathExists(pathToInclude)) {
auto includeOptions = ApplyConfigOptions {
.path = pathToInclude,
@@ -437,10 +437,16 @@ template class BaseSetting<DeprecatedFeatures>;
static Path parsePath(const AbstractSetting & s, const std::string & str, const ApplyConfigOptions & options)
{
- if (str == "")
+ if (str == "") {
throw UsageError("setting '%s' is a path and paths cannot be empty", s.name);
- else
- return canonPath(str);
+ } else {
+ auto tildeResolvedPath = tildePath(str, options.home);
+ if (options.path) {
+ return absPath(tildeResolvedPath, dirOf(*options.path));
+ } else {
+ return canonPath(tildeResolvedPath);
+ }
+ }
}
template<> Path PathsSetting<Path>::parse(const std::string & str, const ApplyConfigOptions & options) const
diff --git a/src/libutil/file-system.cc b/src/libutil/file-system.cc
index 631cf076b..234c73163 100644
--- a/src/libutil/file-system.cc
+++ b/src/libutil/file-system.cc
@@ -117,6 +117,21 @@ Path realPath(Path const & path)
return ret;
}
+Path tildePath(Path const & path, const std::optional<Path> & home)
+{
+ if (path.starts_with("~/")) {
+ if (home) {
+ return *home + "/" + path.substr(2);
+ } else {
+ throw UsageError("`~` path not allowed: %1%", path);
+ }
+ } else if (path.starts_with('~')) {
+ throw UsageError("`~` paths must start with `~/`: %1%", path);
+ } else {
+ return path;
+ }
+}
+
void chmodPath(const Path & path, mode_t mode)
{
if (chmod(path.c_str(), mode) == -1)
diff --git a/src/libutil/file-system.hh b/src/libutil/file-system.hh
index e49323e84..0a54d1a3b 100644
--- a/src/libutil/file-system.hh
+++ b/src/libutil/file-system.hh
@@ -63,6 +63,16 @@ Path canonPath(PathView path, bool resolveSymlinks = false);
Path realPath(Path const & path);
/**
+ * Resolve a tilde path like `~/puppy.nix` into an absolute path.
+ *
+ * If `home` is given, it's substituted for `~/` at the start of the input
+ * `path`. Otherwise, an error is thrown.
+ *
+ * If the path starts with `~` but not `~/`, an error is thrown.
+ */
+Path tildePath(Path const & path, const std::optional<Path> & home = std::nullopt);
+
+/**
* Change the permissions of a path
* Not called `chmod` as it shadows and could be confused with
* `int chmod(char *, mode_t)`, which does not handle errors
diff --git a/tests/unit/libutil/paths-setting.cc b/tests/unit/libutil/paths-setting.cc
index c198b25e0..2d37ad525 100644
--- a/tests/unit/libutil/paths-setting.cc
+++ b/tests/unit/libutil/paths-setting.cc
@@ -48,7 +48,60 @@ TEST_F(PathsSettingTest, parse)
ASSERT_THAT(config.paths.parse("/puppy/../doggy.nix", {}), Eq<Paths>({"/doggy.nix"}));
}
-TEST_F(PathsSettingTest, append) {
+TEST_F(PathsSettingTest, parseRelative)
+{
+ auto options = ApplyConfigOptions{.path = "/doggy/kinds/config.nix"};
+ auto config = mkConfig();
+ ASSERT_THAT(
+ config.paths.parse("puppy.nix", options),
+ Eq<Paths>({"/doggy/kinds/puppy.nix"})
+ );
+
+ // Splits on whitespace:
+ ASSERT_THAT(
+ config.paths.parse("puppy.nix /doggy.nix", options), Eq<Paths>({"/doggy/kinds/puppy.nix", "/doggy.nix"})
+ );
+
+ // Canonicizes paths:
+ ASSERT_THAT(config.paths.parse("../soft.nix", options), Eq<Paths>({"/doggy/soft.nix"}));
+
+ // Canonicizes paths:
+ ASSERT_THAT(config.paths.parse("./soft.nix", options), Eq<Paths>({"/doggy/kinds/soft.nix"}));
+}
+
+TEST_F(PathsSettingTest, parseHome)
+{
+ auto options = ApplyConfigOptions{
+ .path = "/doggy/kinds/config.nix",
+ .home = "/home/puppy"
+ };
+ auto config = mkConfig();
+
+ ASSERT_THAT(
+ config.paths.parse("puppy.nix", options),
+ Eq<Paths>({"/doggy/kinds/puppy.nix"})
+ );
+
+ ASSERT_THAT(
+ config.paths.parse("~/.config/nix/puppy.nix", options),
+ Eq<Paths>({"/home/puppy/.config/nix/puppy.nix"})
+ );
+
+ // Splits on whitespace:
+ ASSERT_THAT(
+ config.paths.parse("~/puppy.nix ~/doggy.nix", options),
+ Eq<Paths>({"/home/puppy/puppy.nix", "/home/puppy/doggy.nix"})
+ );
+
+ // Canonicizes paths:
+ ASSERT_THAT(config.paths.parse("~/../why.nix", options), Eq<Paths>({"/home/why.nix"}));
+
+ // Home paths for other users not allowed. Needs to start with `~/`.
+ ASSERT_THROW(config.paths.parse("~root/config.nix", options), Error);
+}
+
+TEST_F(PathsSettingTest, append)
+{
auto config = mkConfig();
ASSERT_TRUE(config.paths.isAppendable());