aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorrebecca “wiggles” turner <rbt@sent.as>2024-09-01 22:06:36 +0000
committerGerrit Code Review <gerrit@localhost>2024-09-01 22:06:36 +0000
commit02eb07cfd539c34c080cb1baf042e5e780c1fcc2 (patch)
tree0397ab434cce8b284a8d447594e2e1f3f35a1470 /src
parentd75df91f74b6819f674f0733143fdf32580af183 (diff)
parent690f07272e58bfe86d12adb0bd6c81c031f930fd (diff)
Merge changes I5566a985,I88cf53d3 into main
* changes: Support relative and `~/` paths in config settings Thread `ApplyConfigOptions` through config parsing
Diffstat (limited to 'src')
-rw-r--r--src/libfetchers/fetch-settings.cc2
-rw-r--r--src/libstore/globals.cc27
-rw-r--r--src/libstore/globals.hh5
-rw-r--r--src/libutil/config-impl.hh22
-rw-r--r--src/libutil/config.cc97
-rw-r--r--src/libutil/config.hh19
-rw-r--r--src/libutil/file-system.cc15
-rw-r--r--src/libutil/file-system.hh10
8 files changed, 120 insertions, 77 deletions
diff --git a/src/libfetchers/fetch-settings.cc b/src/libfetchers/fetch-settings.cc
index aeb3c542b..007f2725f 100644
--- a/src/libfetchers/fetch-settings.cc
+++ b/src/libfetchers/fetch-settings.cc
@@ -7,7 +7,7 @@
namespace nix {
-template<> AcceptFlakeConfig BaseSetting<AcceptFlakeConfig>::parse(const std::string & str) const
+template<> AcceptFlakeConfig BaseSetting<AcceptFlakeConfig>::parse(const std::string & str, const ApplyConfigOptions & options) const
{
if (str == "true") return AcceptFlakeConfig::True;
else if (str == "ask") return AcceptFlakeConfig::Ask;
diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc
index b534882de..c114e22dc 100644
--- a/src/libstore/globals.cc
+++ b/src/libstore/globals.cc
@@ -126,29 +126,30 @@ Settings::Settings()
void loadConfFile()
{
- auto applyConfigFile = [&](const Path & path) {
+ auto applyConfigFile = [&](const ApplyConfigOptions & options) {
try {
- std::string contents = readFile(path);
- globalConfig.applyConfig(contents, path);
- } catch (SysError &) { }
+ std::string contents = readFile(*options.path);
+ globalConfig.applyConfig(contents, options);
+ } catch (SysError &) {
+ }
};
- applyConfigFile(settings.nixConfDir + "/nix.conf");
+ applyConfigFile(ApplyConfigOptions{.path = settings.nixConfDir + "/nix.conf"});
/* We only want to send overrides to the daemon, i.e. stuff from
~/.nix/nix.conf or the command line. */
globalConfig.resetOverridden();
auto files = settings.nixUserConfFiles;
+ auto home = getHome();
for (auto file = files.rbegin(); file != files.rend(); file++) {
- applyConfigFile(*file);
+ applyConfigFile(ApplyConfigOptions{.path = *file, .home = home});
}
auto nixConfEnv = getEnv("NIX_CONFIG");
if (nixConfEnv.has_value()) {
- globalConfig.applyConfig(nixConfEnv.value(), "NIX_CONFIG");
+ globalConfig.applyConfig(nixConfEnv.value(), ApplyConfigOptions{.fromEnvVar = true});
}
-
}
std::vector<Path> getUserConfigFiles()
@@ -274,7 +275,7 @@ NLOHMANN_JSON_SERIALIZE_ENUM(SandboxMode, {
{SandboxMode::smDisabled, false},
});
-template<> SandboxMode BaseSetting<SandboxMode>::parse(const std::string & str) const
+template<> SandboxMode BaseSetting<SandboxMode>::parse(const std::string & str, const ApplyConfigOptions & options) const
{
if (str == "true") return smEnabled;
else if (str == "relaxed") return smRelaxed;
@@ -317,7 +318,7 @@ template<> void BaseSetting<SandboxMode>::convertToArg(Args & args, const std::s
});
}
-unsigned int MaxBuildJobsSetting::parse(const std::string & str) const
+unsigned int MaxBuildJobsSetting::parse(const std::string & str, const ApplyConfigOptions & options) const
{
if (str == "auto") return std::max(1U, std::thread::hardware_concurrency());
else {
@@ -325,15 +326,15 @@ unsigned int MaxBuildJobsSetting::parse(const std::string & str) const
return *n;
else
throw UsageError("configuration setting '%s' should be 'auto' or an integer", name);
+ }
}
-}
-Paths PluginFilesSetting::parse(const std::string & str) const
+Paths PluginFilesSetting::parse(const std::string & str, const ApplyConfigOptions & options) const
{
if (pluginsLoaded)
throw UsageError("plugin-files set after plugins were loaded, you may need to move the flag before the subcommand");
- return BaseSetting<Paths>::parse(str);
+ return BaseSetting<Paths>::parse(str, options);
}
diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh
index aba99d969..51550b2c3 100644
--- a/src/libstore/globals.hh
+++ b/src/libstore/globals.hh
@@ -26,7 +26,7 @@ struct MaxBuildJobsSetting : public BaseSetting<unsigned int>
options->addSetting(this);
}
- unsigned int parse(const std::string & str) const override;
+ unsigned int parse(const std::string & str, const ApplyConfigOptions & options) const override;
};
struct PluginFilesSetting : public BaseSetting<Paths>
@@ -43,7 +43,7 @@ struct PluginFilesSetting : public BaseSetting<Paths>
options->addSetting(this);
}
- Paths parse(const std::string & str) const override;
+ Paths parse(const std::string & str, const ApplyConfigOptions & options) const override;
};
const uint32_t maxIdsPerBuild =
@@ -1088,6 +1088,7 @@ void loadConfFile();
// Used by the Settings constructor
std::vector<Path> getUserConfigFiles();
+std::vector<Path> getHomeConfigFile();
extern const std::string nixVersion;
diff --git a/src/libutil/config-impl.hh b/src/libutil/config-impl.hh
index 024018e00..748107b6e 100644
--- a/src/libutil/config-impl.hh
+++ b/src/libutil/config-impl.hh
@@ -51,14 +51,14 @@ bool BaseSetting<T>::isAppendable()
return trait::appendable;
}
-template<> void BaseSetting<Strings>::appendOrSet(Strings newValue, bool append);
-template<> void BaseSetting<StringSet>::appendOrSet(StringSet newValue, bool append);
-template<> void BaseSetting<StringMap>::appendOrSet(StringMap newValue, bool append);
-template<> void BaseSetting<ExperimentalFeatures>::appendOrSet(ExperimentalFeatures newValue, bool append);
-template<> void BaseSetting<DeprecatedFeatures>::appendOrSet(DeprecatedFeatures newValue, bool append);
+template<> void BaseSetting<Strings>::appendOrSet(Strings newValue, bool append, const ApplyConfigOptions & options);
+template<> void BaseSetting<StringSet>::appendOrSet(StringSet newValue, bool append, const ApplyConfigOptions & options);
+template<> void BaseSetting<StringMap>::appendOrSet(StringMap newValue, bool append, const ApplyConfigOptions & options);
+template<> void BaseSetting<ExperimentalFeatures>::appendOrSet(ExperimentalFeatures newValue, bool append, const ApplyConfigOptions & options);
+template<> void BaseSetting<DeprecatedFeatures>::appendOrSet(DeprecatedFeatures newValue, bool append, const ApplyConfigOptions & options);
template<typename T>
-void BaseSetting<T>::appendOrSet(T newValue, bool append)
+void BaseSetting<T>::appendOrSet(T newValue, bool append, const ApplyConfigOptions & options)
{
static_assert(
!trait::appendable,
@@ -69,14 +69,14 @@ void BaseSetting<T>::appendOrSet(T newValue, bool append)
}
template<typename T>
-void BaseSetting<T>::set(const std::string & str, bool append)
+void BaseSetting<T>::set(const std::string & str, bool append, const ApplyConfigOptions & options)
{
if (experimentalFeatureSettings.isEnabled(experimentalFeature)) {
- auto parsed = parse(str);
+ auto parsed = parse(str, options);
if (deprecated && (append || parsed != value)) {
warn("deprecated setting '%s' found (set to '%s')", name, str);
}
- appendOrSet(std::move(parsed), append);
+ appendOrSet(std::move(parsed), append, options);
} else {
assert(experimentalFeature);
warn("Ignoring setting '%s' because experimental feature '%s' is not enabled",
@@ -111,7 +111,7 @@ void BaseSetting<T>::convertToArg(Args & args, const std::string & category)
}
#define DECLARE_CONFIG_SERIALISER(TY) \
- template<> TY BaseSetting< TY >::parse(const std::string & str) const; \
+ template<> TY BaseSetting< TY >::parse(const std::string & str, const ApplyConfigOptions & options) const; \
template<> std::string BaseSetting< TY >::to_string() const;
DECLARE_CONFIG_SERIALISER(std::string)
@@ -124,7 +124,7 @@ DECLARE_CONFIG_SERIALISER(ExperimentalFeatures)
DECLARE_CONFIG_SERIALISER(DeprecatedFeatures)
template<typename T>
-T BaseSetting<T>::parse(const std::string & str) const
+T BaseSetting<T>::parse(const std::string & str, const ApplyConfigOptions & options) const
{
static_assert(std::is_integral<T>::value, "Integer required.");
diff --git a/src/libutil/config.cc b/src/libutil/config.cc
index 8e20f1321..778da1413 100644
--- a/src/libutil/config.cc
+++ b/src/libutil/config.cc
@@ -1,4 +1,5 @@
#include "config.hh"
+#include "apply-config-options.hh"
#include "args.hh"
#include "abstract-setting-to-json.hh"
#include "experimental-features.hh"
@@ -17,7 +18,7 @@ Config::Config(StringMap initials)
: AbstractConfig(std::move(initials))
{ }
-bool Config::set(const std::string & name, const std::string & value)
+bool Config::set(const std::string & name, const std::string & value, const ApplyConfigOptions & options)
{
bool append = false;
auto i = _settings.find(name);
@@ -30,7 +31,7 @@ bool Config::set(const std::string & name, const std::string & value)
} else
return false;
}
- i->second.setting->set(value, append);
+ i->second.setting->set(value, append, options);
i->second.setting->overridden = true;
return true;
}
@@ -91,7 +92,7 @@ void Config::getSettings(std::map<std::string, SettingInfo> & res, bool overridd
}
-static void applyConfigInner(const std::string & contents, const std::string & path, std::vector<std::pair<std::string, std::string>> & parsedContents) {
+static void applyConfigInner(const std::string & contents, const ApplyConfigOptions & options, std::vector<std::pair<std::string, std::string>> & parsedContents) {
unsigned int pos = 0;
while (pos < contents.size()) {
@@ -107,7 +108,7 @@ static void applyConfigInner(const std::string & contents, const std::string & p
if (tokens.empty()) continue;
if (tokens.size() < 2)
- throw UsageError("illegal configuration line '%1%' in '%2%'", line, path);
+ throw UsageError("illegal configuration line '%1%' in '%2%'", line, options.relativeDisplay());
auto include = false;
auto ignoreMissing = false;
@@ -119,24 +120,32 @@ static void applyConfigInner(const std::string & contents, const std::string & p
}
if (include) {
- if (tokens.size() != 2)
- throw UsageError("illegal configuration line '%1%' in '%2%'", line, path);
- auto p = absPath(tokens[1], dirOf(path));
- if (pathExists(p)) {
+ if (tokens.size() != 2) {
+ throw UsageError("illegal configuration line '%1%' in '%2%'", line, options.relativeDisplay());
+ }
+ if (!options.path) {
+ throw UsageError("can only include configuration '%1%' from files", tokens[1]);
+ }
+ auto pathToInclude = absPath(tildePath(tokens[1], options.home), dirOf(*options.path));
+ if (pathExists(pathToInclude)) {
+ auto includeOptions = ApplyConfigOptions {
+ .path = pathToInclude,
+ .home = options.home,
+ };
try {
- std::string includedContents = readFile(path);
- applyConfigInner(includedContents, p, parsedContents);
+ std::string includedContents = readFile(pathToInclude);
+ applyConfigInner(includedContents, includeOptions, parsedContents);
} catch (SysError &) {
// TODO: Do we actually want to ignore this? Or is it better to fail?
}
} else if (!ignoreMissing) {
- throw Error("file '%1%' included from '%2%' not found", p, path);
+ throw Error("file '%1%' included from '%2%' not found", pathToInclude, *options.path);
}
continue;
}
if (tokens[1] != "=")
- throw UsageError("illegal configuration line '%1%' in '%2%'", line, path);
+ throw UsageError("illegal configuration line '%1%' in '%2%'", line, options.relativeDisplay());
std::string name = std::move(tokens[0]);
@@ -150,20 +159,20 @@ static void applyConfigInner(const std::string & contents, const std::string & p
};
}
-void AbstractConfig::applyConfig(const std::string & contents, const std::string & path) {
+void AbstractConfig::applyConfig(const std::string & contents, const ApplyConfigOptions & options) {
std::vector<std::pair<std::string, std::string>> parsedContents;
- applyConfigInner(contents, path, parsedContents);
+ applyConfigInner(contents, options, parsedContents);
// First apply experimental-feature related settings
for (const auto & [name, value] : parsedContents)
if (name == "experimental-features" || name == "extra-experimental-features")
- set(name, value);
+ set(name, value, options);
// Then apply other settings
for (const auto & [name, value] : parsedContents)
if (name != "experimental-features" && name != "extra-experimental-features")
- set(name, value);
+ set(name, value, options);
}
void Config::resetOverridden()
@@ -241,7 +250,7 @@ void AbstractSetting::convertToArg(Args & args, const std::string & category)
bool AbstractSetting::isOverridden() const { return overridden; }
-template<> std::string BaseSetting<std::string>::parse(const std::string & str) const
+template<> std::string BaseSetting<std::string>::parse(const std::string & str, const ApplyConfigOptions & options) const
{
return str;
}
@@ -251,7 +260,7 @@ template<> std::string BaseSetting<std::string>::to_string() const
return value;
}
-template<> std::optional<std::string> BaseSetting<std::optional<std::string>>::parse(const std::string & str) const
+template<> std::optional<std::string> BaseSetting<std::optional<std::string>>::parse(const std::string & str, const ApplyConfigOptions & options) const
{
if (str == "")
return std::nullopt;
@@ -264,7 +273,7 @@ template<> std::string BaseSetting<std::optional<std::string>>::to_string() cons
return value ? *value : "";
}
-template<> bool BaseSetting<bool>::parse(const std::string & str) const
+template<> bool BaseSetting<bool>::parse(const std::string & str, const ApplyConfigOptions & options) const
{
if (str == "true" || str == "yes" || str == "1")
return true;
@@ -297,12 +306,12 @@ template<> void BaseSetting<bool>::convertToArg(Args & args, const std::string &
});
}
-template<> Strings BaseSetting<Strings>::parse(const std::string & str) const
+template<> Strings BaseSetting<Strings>::parse(const std::string & str, const ApplyConfigOptions & options) const
{
return tokenizeString<Strings>(str);
}
-template<> void BaseSetting<Strings>::appendOrSet(Strings newValue, bool append)
+template<> void BaseSetting<Strings>::appendOrSet(Strings newValue, bool append, const ApplyConfigOptions & options)
{
if (!append) value.clear();
value.insert(value.end(), std::make_move_iterator(newValue.begin()),
@@ -314,12 +323,12 @@ template<> std::string BaseSetting<Strings>::to_string() const
return concatStringsSep(" ", value);
}
-template<> StringSet BaseSetting<StringSet>::parse(const std::string & str) const
+template<> StringSet BaseSetting<StringSet>::parse(const std::string & str, const ApplyConfigOptions & options) const
{
return tokenizeString<StringSet>(str);
}
-template<> void BaseSetting<StringSet>::appendOrSet(StringSet newValue, bool append)
+template<> void BaseSetting<StringSet>::appendOrSet(StringSet newValue, bool append, const ApplyConfigOptions & options)
{
if (!append) value.clear();
value.insert(std::make_move_iterator(newValue.begin()), std::make_move_iterator(newValue.end()));
@@ -330,7 +339,7 @@ template<> std::string BaseSetting<StringSet>::to_string() const
return concatStringsSep(" ", value);
}
-template<> ExperimentalFeatures BaseSetting<ExperimentalFeatures>::parse(const std::string & str) const
+template<> ExperimentalFeatures BaseSetting<ExperimentalFeatures>::parse(const std::string & str, const ApplyConfigOptions & options) const
{
ExperimentalFeatures res{};
for (auto & s : tokenizeString<StringSet>(str)) {
@@ -342,7 +351,7 @@ template<> ExperimentalFeatures BaseSetting<ExperimentalFeatures>::parse(const s
return res;
}
-template<> void BaseSetting<ExperimentalFeatures>::appendOrSet(ExperimentalFeatures newValue, bool append)
+template<> void BaseSetting<ExperimentalFeatures>::appendOrSet(ExperimentalFeatures newValue, bool append, const ApplyConfigOptions & options)
{
if (append)
value = value | newValue;
@@ -359,7 +368,7 @@ template<> std::string BaseSetting<ExperimentalFeatures>::to_string() const
return concatStringsSep(" ", stringifiedXpFeatures);
}
-template<> DeprecatedFeatures BaseSetting<DeprecatedFeatures>::parse(const std::string & str) const
+template<> DeprecatedFeatures BaseSetting<DeprecatedFeatures>::parse(const std::string & str, const ApplyConfigOptions & options) const
{
DeprecatedFeatures res{};
for (auto & s : tokenizeString<StringSet>(str)) {
@@ -371,7 +380,7 @@ template<> DeprecatedFeatures BaseSetting<DeprecatedFeatures>::parse(const std::
return res;
}
-template<> void BaseSetting<DeprecatedFeatures>::appendOrSet(DeprecatedFeatures newValue, bool append)
+template<> void BaseSetting<DeprecatedFeatures>::appendOrSet(DeprecatedFeatures newValue, bool append, const ApplyConfigOptions & options)
{
if (append)
value = value | newValue;
@@ -388,7 +397,7 @@ template<> std::string BaseSetting<DeprecatedFeatures>::to_string() const
return concatStringsSep(" ", stringifiedDpFeatures);
}
-template<> StringMap BaseSetting<StringMap>::parse(const std::string & str) const
+template<> StringMap BaseSetting<StringMap>::parse(const std::string & str, const ApplyConfigOptions & options) const
{
StringMap res;
for (const auto & s : tokenizeString<Strings>(str)) {
@@ -399,7 +408,7 @@ template<> StringMap BaseSetting<StringMap>::parse(const std::string & str) cons
return res;
}
-template<> void BaseSetting<StringMap>::appendOrSet(StringMap newValue, bool append)
+template<> void BaseSetting<StringMap>::appendOrSet(StringMap newValue, bool append, const ApplyConfigOptions & options)
{
if (!append) value.clear();
value.insert(std::make_move_iterator(newValue.begin()), std::make_move_iterator(newValue.end()));
@@ -426,34 +435,40 @@ template class BaseSetting<StringMap>;
template class BaseSetting<ExperimentalFeatures>;
template class BaseSetting<DeprecatedFeatures>;
-static Path parsePath(const AbstractSetting & s, const std::string & str)
+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
+template<> Path PathsSetting<Path>::parse(const std::string & str, const ApplyConfigOptions & options) const
{
- return parsePath(*this, str);
+ return parsePath(*this, str, options);
}
-template<> std::optional<Path> PathsSetting<std::optional<Path>>::parse(const std::string & str) const
+template<> std::optional<Path> PathsSetting<std::optional<Path>>::parse(const std::string & str, const ApplyConfigOptions & options) const
{
if (str == "")
return std::nullopt;
else
- return parsePath(*this, str);
+ return parsePath(*this, str, options);
}
-template<> Paths PathsSetting<Paths>::parse(const std::string & str) const
+template<> Paths PathsSetting<Paths>::parse(const std::string & str, const ApplyConfigOptions & options) const
{
auto strings = tokenizeString<Strings>(str);
Paths parsed;
for (auto str : strings) {
- parsed.push_back(canonPath(str));
+ parsed.push_back(parsePath(*this, str, options));
}
return parsed;
@@ -464,10 +479,10 @@ template class PathsSetting<std::optional<Path>>;
template class PathsSetting<Paths>;
-bool GlobalConfig::set(const std::string & name, const std::string & value)
+bool GlobalConfig::set(const std::string & name, const std::string & value, const ApplyConfigOptions & options)
{
for (auto & config : *configRegistrations)
- if (config->set(name, value)) return true;
+ if (config->set(name, value, options)) return true;
unknownSettings.emplace(name, value);
diff --git a/src/libutil/config.hh b/src/libutil/config.hh
index dbca4b406..59cc281c5 100644
--- a/src/libutil/config.hh
+++ b/src/libutil/config.hh
@@ -10,6 +10,7 @@
#include "types.hh"
#include "experimental-features.hh"
#include "deprecated-features.hh"
+#include "apply-config-options.hh"
namespace nix {
@@ -61,7 +62,7 @@ public:
* Sets the value referenced by `name` to `value`. Returns true if the
* setting is known, false otherwise.
*/
- virtual bool set(const std::string & name, const std::string & value) = 0;
+ virtual bool set(const std::string & name, const std::string & value, const ApplyConfigOptions & options = {}) = 0;
struct SettingInfo
{
@@ -81,7 +82,7 @@ public:
* - contents: configuration contents to be parsed and applied
* - path: location of the configuration file
*/
- void applyConfig(const std::string & contents, const std::string & path = "<unknown>");
+ void applyConfig(const std::string & contents, const ApplyConfigOptions & options = {});
/**
* Resets the `overridden` flag of all Settings
@@ -155,7 +156,7 @@ public:
Config(StringMap initials = {});
- bool set(const std::string & name, const std::string & value) override;
+ bool set(const std::string & name, const std::string & value, const ApplyConfigOptions & options = {}) override;
void addSetting(AbstractSetting * setting);
@@ -200,7 +201,7 @@ protected:
virtual ~AbstractSetting();
- virtual void set(const std::string & value, bool append = false) = 0;
+ virtual void set(const std::string & value, bool append = false, const ApplyConfigOptions & options = {}) = 0;
/**
* Whether the type is appendable; i.e. whether the `append`
@@ -237,7 +238,7 @@ protected:
*
* Used by `set()`.
*/
- virtual T parse(const std::string & str) const;
+ virtual T parse(const std::string & str, const ApplyConfigOptions & options) const;
/**
* Append or overwrite `value` with `newValue`.
@@ -247,7 +248,7 @@ protected:
*
* @param append Whether to append or overwrite.
*/
- virtual void appendOrSet(T newValue, bool append);
+ virtual void appendOrSet(T newValue, bool append, const ApplyConfigOptions & options);
public:
@@ -284,7 +285,7 @@ public:
* Uses `parse()` to get the value from `str`, and `appendOrSet()`
* to set it.
*/
- void set(const std::string & str, bool append = false) override final;
+ void set(const std::string & str, bool append = false, const ApplyConfigOptions & options = {}) override final;
/**
* C++ trick; This is template-specialized to compile-time indicate whether
@@ -373,7 +374,7 @@ public:
options->addSetting(this);
}
- T parse(const std::string & str) const override;
+ T parse(const std::string & str, const ApplyConfigOptions & options) const override;
void operator =(const T & v) { this->assign(v); }
};
@@ -384,7 +385,7 @@ struct GlobalConfig : public AbstractConfig
typedef std::vector<Config*> ConfigRegistrations;
static ConfigRegistrations * configRegistrations;
- bool set(const std::string & name, const std::string & value) override;
+ bool set(const std::string & name, const std::string & value, const ApplyConfigOptions & options = {}) override;
void getSettings(std::map<std::string, SettingInfo> & res, bool overriddenOnly = false) override;
diff --git a/src/libutil/file-system.cc b/src/libutil/file-system.cc
index 1d266067e..8c69c9864 100644
--- a/src/libutil/file-system.cc
+++ b/src/libutil/file-system.cc
@@ -118,6 +118,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