diff --git a/doc/manual/src/advanced-topics/distributed-builds.md b/doc/manual/src/advanced-topics/distributed-builds.md
index c4c60db15..b0d7fbf1a 100644
--- a/doc/manual/src/advanced-topics/distributed-builds.md
+++ b/doc/manual/src/advanced-topics/distributed-builds.md
@@ -110,7 +110,7 @@ default, set it to `-`.
7. A comma-separated list of *mandatory features*. A machine will only
be used to build a derivation if all of the machine’s mandatory
features appear in the derivation’s `requiredSystemFeatures`
- attribute..
+ attribute.
8. The (base64-encoded) public host key of the remote machine. If omitted, SSH
will use its regular known-hosts file. Specifically, the field is calculated
diff --git a/doc/manual/src/installation/installing-binary.md b/doc/manual/src/installation/installing-binary.md
index 4367654a2..e5fb50088 100644
--- a/doc/manual/src/installation/installing-binary.md
+++ b/doc/manual/src/installation/installing-binary.md
@@ -84,7 +84,9 @@ The installer will modify `/etc/bashrc`, and `/etc/zshrc` if they exist.
The installer will first back up these files with a `.backup-before-nix`
extension. The installer will also create `/etc/profile.d/nix.sh`.
-You can uninstall Nix with the following commands:
+## Uninstalling
+
+### Linux
```console
sudo rm -rf /etc/profile/nix.sh /etc/nix /nix ~root/.nix-profile ~root/.nix-defexpr ~root/.nix-channels ~/.nix-profile ~/.nix-defexpr ~/.nix-channels
@@ -95,15 +97,95 @@ sudo systemctl stop nix-daemon.service
sudo systemctl disable nix-daemon.socket
sudo systemctl disable nix-daemon.service
sudo systemctl daemon-reload
-
-# If you are on macOS, you will need to run:
-sudo launchctl unload /Library/LaunchDaemons/org.nixos.nix-daemon.plist
-sudo rm /Library/LaunchDaemons/org.nixos.nix-daemon.plist
```
There may also be references to Nix in `/etc/profile`, `/etc/bashrc`,
and `/etc/zshrc` which you may remove.
+### macOS
+
+1. Edit `/etc/zshrc` and `/etc/bashrc` to remove the lines sourcing
+ `nix-daemon.sh`, which should look like this:
+
+ ```bash
+ # Nix
+ if [ -e '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' ]; then
+ . '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh'
+ fi
+ # End Nix
+ ```
+
+ If these files haven't been altered since installing Nix you can simply put
+ the backups back in place:
+
+ ```console
+ sudo mv /etc/zshrc.backup-before-nix /etc/zshrc
+ sudo mv /etc/bashrc.backup-before-nix /etc/bashrc
+ ```
+
+ This will stop shells from sourcing the file and bringing everything you
+ installed using Nix in scope.
+
+2. Stop and remove the Nix daemon services:
+
+ ```console
+ sudo launchctl unload /Library/LaunchDaemons/org.nixos.nix-daemon.plist
+ sudo rm /Library/LaunchDaemons/org.nixos.nix-daemon.plist
+ sudo launchctl unload /Library/LaunchDaemons/org.nixos.darwin-store.plist
+ sudo rm /Library/LaunchDaemons/org.nixos.darwin-store.plist
+ ```
+
+ This stops the Nix daemon and prevents it from being started next time you
+ boot the system.
+
+3. Remove the `nixbld` group and the `_nixbuildN` users:
+
+ ```console
+ sudo dscl . -delete /Groups/nixbld
+ for u in $(sudo dscl . -list /Users | grep _nixbld); do sudo dscl . -delete /Users/$u; done
+ ```
+
+ This will remove all the build users that no longer serve a purpose.
+
+4. Edit fstab using `sudo vifs` to remove the line mounting the Nix Store
+ volume on `/nix`, which looks like this,
+ `LABEL=Nix\040Store /nix apfs rw,nobrowse`. This will prevent automatic
+ mounting of the Nix Store volume.
+
+5. Edit `/etc/synthetic.conf` to remove the `nix` line. If this is the only
+ line in the file you can remove it entirely, `sudo rm /etc/synthetic.conf`.
+ This will prevent the creation of the empty `/nix` directory to provide a
+ mountpoint for the Nix Store volume.
+
+6. Remove the files Nix added to your system:
+
+ ```console
+ sudo rm -rf /etc/nix /var/root/.nix-profile /var/root/.nix-defexpr /var/root/.nix-channels ~/.nix-profile ~/.nix-defexpr ~/.nix-channels
+ ```
+
+ This gets rid of any data Nix may have created except for the store which is
+ removed next.
+
+7. Remove the Nix Store volume:
+
+ ```console
+ sudo diskutil apfs deleteVolume /nix
+ ```
+
+ This will remove the Nix Store volume and everything that was added to the
+ store.
+
+> **Note**
+>
+> After you complete the steps here, you will still have an empty `/nix`
+> directory. This is an expected sign of a successful uninstall. The empty
+> `/nix` directory will disappear the next time you reboot.
+>
+> You do not have to reboot to finish uninstalling Nix. The uninstall is
+> complete. macOS (Catalina+) directly controls root directories and its
+> read-only root will prevent you from manually deleting the empty `/nix`
+> mountpoint.
+
# macOS Installation
diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md
index c869b5e2f..2ec864ee4 100644
--- a/doc/manual/src/release-notes/rl-next.md
+++ b/doc/manual/src/release-notes/rl-next.md
@@ -1 +1,16 @@
# Release X.Y (202?-??-??)
+
+* Various nix commands can now read expressions from stdin with `--file -`.
+
+* `nix store make-content-addressable` has been renamed to `nix store
+ make-content-addressed`.
+
+* New experimental builtin function `builtins.fetchClosure` that
+ copies a closure from a binary cache at evaluation time and rewrites
+ it to content-addressed form (if it isn't already). Like
+ `builtins.storePath`, this allows importing pre-built store paths;
+ the difference is that it doesn't require the user to configure
+ binary caches and trusted public keys.
+
+ This function is only available if you enable the experimental
+ feature `fetch-closure`.
diff --git a/misc/systemd/local.mk b/misc/systemd/local.mk
index 1fa037485..76121a0f9 100644
--- a/misc/systemd/local.mk
+++ b/misc/systemd/local.mk
@@ -1,7 +1,8 @@
ifdef HOST_LINUX
$(foreach n, nix-daemon.socket nix-daemon.service, $(eval $(call install-file-in, $(d)/$(n), $(prefix)/lib/systemd/system, 0644)))
+ $(foreach n, nix-daemon.conf, $(eval $(call install-file-in, $(d)/$(n), $(prefix)/lib/tmpfiles.d, 0644)))
- clean-files += $(d)/nix-daemon.socket $(d)/nix-daemon.service
+ clean-files += $(d)/nix-daemon.socket $(d)/nix-daemon.service $(d)/nix-daemon.conf
endif
diff --git a/misc/systemd/nix-daemon.conf.in b/misc/systemd/nix-daemon.conf.in
new file mode 100644
index 000000000..e7b264234
--- /dev/null
+++ b/misc/systemd/nix-daemon.conf.in
@@ -0,0 +1 @@
+d @localstatedir@/nix/daemon-socket 0755 root root - -
diff --git a/misc/systemd/nix-daemon.service.in b/misc/systemd/nix-daemon.service.in
index 25655204d..b4badf2ba 100644
--- a/misc/systemd/nix-daemon.service.in
+++ b/misc/systemd/nix-daemon.service.in
@@ -1,5 +1,6 @@
[Unit]
Description=Nix Daemon
+Documentation=man:nix-daemon https://nixos.org/manual
RequiresMountsFor=@storedir@
RequiresMountsFor=@localstatedir@
ConditionPathIsReadWrite=@localstatedir@/nix/daemon-socket
diff --git a/scripts/install-multi-user.sh b/scripts/install-multi-user.sh
index 5f2c93b7f..69b6676ea 100644
--- a/scripts/install-multi-user.sh
+++ b/scripts/install-multi-user.sh
@@ -739,7 +739,7 @@ install_from_extracted_nix() {
cd "$EXTRACTED_NIX_PATH"
_sudo "to copy the basic Nix files to the new store at $NIX_ROOT/store" \
- cp -RLp ./store/* "$NIX_ROOT/store/"
+ cp -RPp ./store/* "$NIX_ROOT/store/"
_sudo "to make the new store non-writable at $NIX_ROOT/store" \
chmod -R ugo-w "$NIX_ROOT/store/"
diff --git a/scripts/install-systemd-multi-user.sh b/scripts/install-systemd-multi-user.sh
index f4a2dfc5d..24884a023 100755
--- a/scripts/install-systemd-multi-user.sh
+++ b/scripts/install-systemd-multi-user.sh
@@ -9,6 +9,8 @@ readonly SERVICE_DEST=/etc/systemd/system/nix-daemon.service
readonly SOCKET_SRC=/lib/systemd/system/nix-daemon.socket
readonly SOCKET_DEST=/etc/systemd/system/nix-daemon.socket
+readonly TMPFILES_SRC=/lib/tmpfiles.d/nix-daemon.conf
+readonly TMPFILES_DEST=/etc/tmpfiles.d/nix-daemon.conf
# Path for the systemd override unit file to contain the proxy settings
readonly SERVICE_OVERRIDE=${SERVICE_DEST}.d/override.conf
@@ -83,6 +85,13 @@ EOF
poly_configure_nix_daemon_service() {
if [ -e /run/systemd/system ]; then
task "Setting up the nix-daemon systemd service"
+
+ _sudo "to create the nix-daemon tmpfiles config" \
+ ln -sfn /nix/var/nix/profiles/default/$TMPFILES_SRC $TMPFILES_DEST
+
+ _sudo "to run systemd-tmpfiles once to pick that path up" \
+ sytemd-tmpfiles create --prefix=/nix/var/nix
+
_sudo "to set up the nix-daemon service" \
systemctl link "/nix/var/nix/profiles/default$SERVICE_SRC"
diff --git a/src/build-remote/build-remote.cc b/src/build-remote/build-remote.cc
index 8c9133c17..ff8ba2724 100644
--- a/src/build-remote/build-remote.cc
+++ b/src/build-remote/build-remote.cc
@@ -300,7 +300,7 @@ connected:
std::set missingRealisations;
StorePathSet missingPaths;
- if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations) && !derivationHasKnownOutputPaths(drv.type())) {
+ if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations) && !drv.type().hasKnownOutputPaths()) {
for (auto & outputName : wantedOutputs) {
auto thisOutputHash = outputHashes.at(outputName);
auto thisOutputId = DrvOutput{ thisOutputHash, outputName };
diff --git a/src/libcmd/command.cc b/src/libcmd/command.cc
index dc8fa9e5a..a53b029b7 100644
--- a/src/libcmd/command.cc
+++ b/src/libcmd/command.cc
@@ -204,7 +204,8 @@ Strings editorFor(const Pos & pos)
if (pos.line > 0 && (
editor.find("emacs") != std::string::npos ||
editor.find("nano") != std::string::npos ||
- editor.find("vim") != std::string::npos))
+ editor.find("vim") != std::string::npos ||
+ editor.find("kak") != std::string::npos))
args.push_back(fmt("+%d", pos.line));
args.push_back(pos.file);
return args;
diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc
index b7623d4ba..784117569 100644
--- a/src/libcmd/installables.cc
+++ b/src/libcmd/installables.cc
@@ -134,7 +134,9 @@ SourceExprCommand::SourceExprCommand()
addFlag({
.longName = "file",
.shortName = 'f',
- .description = "Interpret installables as attribute paths relative to the Nix expression stored in *file*.",
+ .description =
+ "Interpret installables as attribute paths relative to the Nix expression stored in *file*. "
+ "If *file* is the character -, then a Nix expression will be read from standard input.",
.category = installablesCategory,
.labels = {"file"},
.handler = {&file},
@@ -695,7 +697,10 @@ std::vector> SourceExprCommand::parseInstallables(
auto state = getEvalState();
auto vFile = state->allocValue();
- if (file)
+ if (file == "-") {
+ auto e = state->parseStdin();
+ state->eval(e, *vFile);
+ } else if (file)
state->evalFile(lookupFileArg(*state, *file), *vFile);
else {
auto e = state->parseExprFromString(*expr, absPath("."));
diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc
index 188223957..54fa9b741 100644
--- a/src/libexpr/eval-cache.cc
+++ b/src/libexpr/eval-cache.cc
@@ -21,6 +21,8 @@ struct AttrDb
{
std::atomic_bool failed{false};
+ const Store & cfg;
+
struct State
{
SQLite db;
@@ -33,8 +35,9 @@ struct AttrDb
std::unique_ptr> _state;
- AttrDb(const Hash & fingerprint)
- : _state(std::make_unique>())
+ AttrDb(const Store & cfg, const Hash & fingerprint)
+ : cfg(cfg)
+ , _state(std::make_unique>())
{
auto state(_state->lock());
@@ -254,10 +257,10 @@ struct AttrDb
return {{rowId, attrs}};
}
case AttrType::String: {
- std::vector> context;
+ NixStringContext context;
if (!queryAttribute.isNull(3))
for (auto & s : tokenizeString>(queryAttribute.getStr(3), ";"))
- context.push_back(decodeContext(s));
+ context.push_back(decodeContext(cfg, s));
return {{rowId, string_t{queryAttribute.getStr(2), context}}};
}
case AttrType::Bool:
@@ -274,10 +277,10 @@ struct AttrDb
}
};
-static std::shared_ptr makeAttrDb(const Hash & fingerprint)
+static std::shared_ptr makeAttrDb(const Store & cfg, const Hash & fingerprint)
{
try {
- return std::make_shared(fingerprint);
+ return std::make_shared(cfg, fingerprint);
} catch (SQLiteError &) {
ignoreException();
return nullptr;
@@ -288,7 +291,7 @@ EvalCache::EvalCache(
std::optional> useCache,
EvalState & state,
RootLoader rootLoader)
- : db(useCache ? makeAttrDb(*useCache) : nullptr)
+ : db(useCache ? makeAttrDb(*state.store, *useCache) : nullptr)
, state(state)
, rootLoader(rootLoader)
{
@@ -546,7 +549,7 @@ string_t AttrCursor::getStringWithContext()
if (auto s = std::get_if(&cachedValue->second)) {
bool valid = true;
for (auto & c : s->second) {
- if (!root->state.store->isValidPath(root->state.store->parseStorePath(c.first))) {
+ if (!root->state.store->isValidPath(c.first)) {
valid = false;
break;
}
@@ -563,7 +566,7 @@ string_t AttrCursor::getStringWithContext()
auto & v = forceValue();
if (v.type() == nString)
- return {v.string.s, v.getContext()};
+ return {v.string.s, v.getContext(*root->state.store)};
else if (v.type() == nPath)
return {v.path, {}};
else
diff --git a/src/libexpr/eval-cache.hh b/src/libexpr/eval-cache.hh
index 40f1d4ffc..c9a9bf471 100644
--- a/src/libexpr/eval-cache.hh
+++ b/src/libexpr/eval-cache.hh
@@ -52,7 +52,7 @@ struct misc_t {};
struct failed_t {};
typedef uint64_t AttrId;
typedef std::pair AttrKey;
-typedef std::pair>> string_t;
+typedef std::pair string_t;
typedef std::variant<
std::vector,
diff --git a/src/libexpr/eval-inline.hh b/src/libexpr/eval-inline.hh
index aef1f6351..08a419923 100644
--- a/src/libexpr/eval-inline.hh
+++ b/src/libexpr/eval-inline.hh
@@ -24,6 +24,81 @@ LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const
}
+/* Note: Various places expect the allocated memory to be zeroed. */
+[[gnu::always_inline]]
+inline void * allocBytes(size_t n)
+{
+ void * p;
+#if HAVE_BOEHMGC
+ p = GC_MALLOC(n);
+#else
+ p = calloc(n, 1);
+#endif
+ if (!p) throw std::bad_alloc();
+ return p;
+}
+
+
+[[gnu::always_inline]]
+Value * EvalState::allocValue()
+{
+#if HAVE_BOEHMGC
+ /* We use the boehm batch allocator to speed up allocations of Values (of which there are many).
+ GC_malloc_many returns a linked list of objects of the given size, where the first word
+ of each object is also the pointer to the next object in the list. This also means that we
+ have to explicitly clear the first word of every object we take. */
+ if (!*valueAllocCache) {
+ *valueAllocCache = GC_malloc_many(sizeof(Value));
+ if (!*valueAllocCache) throw std::bad_alloc();
+ }
+
+ /* GC_NEXT is a convenience macro for accessing the first word of an object.
+ Take the first list item, advance the list to the next item, and clear the next pointer. */
+ void * p = *valueAllocCache;
+ *valueAllocCache = GC_NEXT(p);
+ GC_NEXT(p) = nullptr;
+#else
+ void * p = allocBytes(sizeof(Value));
+#endif
+
+ nrValues++;
+ return (Value *) p;
+}
+
+
+[[gnu::always_inline]]
+Env & EvalState::allocEnv(size_t size)
+{
+ nrEnvs++;
+ nrValuesInEnvs += size;
+
+ Env * env;
+
+#if HAVE_BOEHMGC
+ if (size == 1) {
+ /* see allocValue for explanations. */
+ if (!*env1AllocCache) {
+ *env1AllocCache = GC_malloc_many(sizeof(Env) + sizeof(Value *));
+ if (!*env1AllocCache) throw std::bad_alloc();
+ }
+
+ void * p = *env1AllocCache;
+ *env1AllocCache = GC_NEXT(p);
+ GC_NEXT(p) = nullptr;
+ env = (Env *) p;
+ } else
+#endif
+ env = (Env *) allocBytes(sizeof(Env) + size * sizeof(Value *));
+
+ env->type = Env::Plain;
+
+ /* We assume that env->values has been cleared by the allocator; maybeThunk() and lookupVar fromWith expect this. */
+
+ return *env;
+}
+
+
+[[gnu::always_inline]]
void EvalState::forceValue(Value & v, const Pos & pos)
{
forceValue(v, [&]() { return pos; });
@@ -52,6 +127,7 @@ void EvalState::forceValue(Value & v, Callable getPos)
}
+[[gnu::always_inline]]
inline void EvalState::forceAttrs(Value & v, const Pos & pos)
{
forceAttrs(v, [&]() { return pos; });
@@ -59,6 +135,7 @@ inline void EvalState::forceAttrs(Value & v, const Pos & pos)
template
+[[gnu::always_inline]]
inline void EvalState::forceAttrs(Value & v, Callable getPos)
{
forceValue(v, getPos);
@@ -67,6 +144,7 @@ inline void EvalState::forceAttrs(Value & v, Callable getPos)
}
+[[gnu::always_inline]]
inline void EvalState::forceList(Value & v, const Pos & pos)
{
forceValue(v, pos);
@@ -74,18 +152,5 @@ inline void EvalState::forceList(Value & v, const Pos & pos)
throwTypeError(pos, "value is %1% while a list was expected", v);
}
-/* Note: Various places expect the allocated memory to be zeroed. */
-inline void * allocBytes(size_t n)
-{
- void * p;
-#if HAVE_BOEHMGC
- p = GC_MALLOC(n);
-#else
- p = calloc(n, 1);
-#endif
- if (!p) throw std::bad_alloc();
- return p;
-}
-
}
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 5bf161cc0..437c7fc53 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -96,20 +96,20 @@ RootValue allocRootValue(Value * v)
}
-void printValue(std::ostream & str, std::set & seen, const Value & v)
+void Value::print(std::ostream & str, std::set * seen) const
{
checkInterrupt();
- switch (v.internalType) {
+ switch (internalType) {
case tInt:
- str << v.integer;
+ str << integer;
break;
case tBool:
- str << (v.boolean ? "true" : "false");
+ str << (boolean ? "true" : "false");
break;
case tString:
str << "\"";
- for (const char * i = v.string.s; *i; i++)
+ for (const char * i = string.s; *i; i++)
if (*i == '\"' || *i == '\\') str << "\\" << *i;
else if (*i == '\n') str << "\\n";
else if (*i == '\r') str << "\\r";
@@ -119,19 +119,19 @@ void printValue(std::ostream & str, std::set & seen, const Value &
str << "\"";
break;
case tPath:
- str << v.path; // !!! escaping?
+ str << path; // !!! escaping?
break;
case tNull:
str << "null";
break;
case tAttrs: {
- if (!v.attrs->empty() && !seen.insert(v.attrs).second)
- str << "";
+ if (seen && !attrs->empty() && !seen->insert(attrs).second)
+ str << "«repeated»";
else {
str << "{ ";
- for (auto & i : v.attrs->lexicographicOrder()) {
+ for (auto & i : attrs->lexicographicOrder()) {
str << i->name << " = ";
- printValue(str, seen, *i->value);
+ i->value->print(str, seen);
str << "; ";
}
str << "}";
@@ -141,12 +141,12 @@ void printValue(std::ostream & str, std::set & seen, const Value &
case tList1:
case tList2:
case tListN:
- if (v.listSize() && !seen.insert(v.listElems()).second)
- str << "";
+ if (seen && listSize() && !seen->insert(listElems()).second)
+ str << "«repeated»";
else {
str << "[ ";
- for (auto v2 : v.listItems()) {
- printValue(str, seen, *v2);
+ for (auto v2 : listItems()) {
+ v2->print(str, seen);
str << " ";
}
str << "]";
@@ -166,10 +166,10 @@ void printValue(std::ostream & str, std::set & seen, const Value &
str << "";
break;
case tExternal:
- str << *v.external;
+ str << *external;
break;
case tFloat:
- str << v.fpoint;
+ str << fpoint;
break;
default:
abort();
@@ -177,10 +177,16 @@ void printValue(std::ostream & str, std::set & seen, const Value &
}
-std::ostream & operator << (std::ostream & str, const Value & v)
+void Value::print(std::ostream & str, bool showRepeated) const
{
std::set seen;
- printValue(str, seen, v);
+ print(str, showRepeated ? nullptr : &seen);
+}
+
+
+std::ostream & operator << (std::ostream & str, const Value & v)
+{
+ v.print(str, false);
return str;
}
@@ -449,8 +455,10 @@ EvalState::EvalState(
, regexCache(makeRegexCache())
#if HAVE_BOEHMGC
, valueAllocCache(std::allocate_shared(traceable_allocator(), nullptr))
+ , env1AllocCache(std::allocate_shared(traceable_allocator(), nullptr))
#else
, valueAllocCache(std::make_shared(nullptr))
+ , env1AllocCache(std::make_shared(nullptr))
#endif
, baseEnv(allocEnv(128))
, staticBaseEnv(false, 0)
@@ -499,23 +507,6 @@ EvalState::~EvalState()
}
-void EvalState::requireExperimentalFeatureOnEvaluation(
- const ExperimentalFeature & feature,
- const std::string_view fName,
- const Pos & pos)
-{
- if (!settings.isExperimentalFeatureEnabled(feature)) {
- throw EvalError({
- .msg = hintfmt(
- "Cannot call '%2%' because experimental Nix feature '%1%' is disabled. You can enable it via '--extra-experimental-features %1%'.",
- feature,
- fName
- ),
- .errPos = pos
- });
- }
-}
-
void EvalState::allowPath(const Path & path)
{
if (allowedPaths)
@@ -727,9 +718,18 @@ LocalNoInlineNoReturn(void throwEvalError(const char * s, const std::string & s2
throw EvalError(s, s2);
}
+LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const Suggestions & suggestions, const char * s, const std::string & s2))
+{
+ throw EvalError(ErrorInfo {
+ .msg = hintfmt(s, s2),
+ .errPos = pos,
+ .suggestions = suggestions,
+ });
+}
+
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const std::string & s2))
{
- throw EvalError({
+ throw EvalError(ErrorInfo {
.msg = hintfmt(s, s2),
.errPos = pos
});
@@ -773,6 +773,16 @@ LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const
});
}
+LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const Suggestions & suggestions, const char * s, const ExprLambda & fun, const Symbol & s2))
+{
+ throw TypeError(ErrorInfo {
+ .msg = hintfmt(s, fun.showNamePos(), s2),
+ .errPos = pos,
+ .suggestions = suggestions,
+ });
+}
+
+
LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v))
{
throw TypeError(s, showType(v));
@@ -876,42 +886,6 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
}
-Value * EvalState::allocValue()
-{
- /* We use the boehm batch allocator to speed up allocations of Values (of which there are many).
- GC_malloc_many returns a linked list of objects of the given size, where the first word
- of each object is also the pointer to the next object in the list. This also means that we
- have to explicitly clear the first word of every object we take. */
- if (!*valueAllocCache) {
- *valueAllocCache = GC_malloc_many(sizeof(Value));
- if (!*valueAllocCache) throw std::bad_alloc();
- }
-
- /* GC_NEXT is a convenience macro for accessing the first word of an object.
- Take the first list item, advance the list to the next item, and clear the next pointer. */
- void * p = *valueAllocCache;
- GC_PTR_STORE_AND_DIRTY(&*valueAllocCache, GC_NEXT(p));
- GC_NEXT(p) = nullptr;
-
- nrValues++;
- auto v = (Value *) p;
- return v;
-}
-
-
-Env & EvalState::allocEnv(size_t size)
-{
- nrEnvs++;
- nrValuesInEnvs += size;
- Env * env = (Env *) allocBytes(sizeof(Env) + size * sizeof(Value *));
- env->type = Env::Plain;
-
- /* We assume that env->values has been cleared by the allocator; maybeThunk() and lookupVar fromWith expect this. */
-
- return *env;
-}
-
-
void EvalState::mkList(Value & v, size_t size)
{
v.mkList(size);
@@ -1281,8 +1255,15 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
}
} else {
state.forceAttrs(*vAttrs, pos);
- if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end())
- throwEvalError(pos, "attribute '%1%' missing", name);
+ if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) {
+ std::set allAttrNames;
+ for (auto & attr : *vAttrs->attrs)
+ allAttrNames.insert(attr.name);
+ throwEvalError(
+ pos,
+ Suggestions::bestMatches(allAttrNames, name),
+ "attribute '%1%' missing", name);
+ }
}
vAttrs = j->value;
pos2 = j->pos;
@@ -1398,8 +1379,17 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
/* Nope, so show the first unexpected argument to the
user. */
for (auto & i : *args[0]->attrs)
- if (!lambda.formals->has(i.name))
- throwTypeError(pos, "%1% called with unexpected argument '%2%'", lambda, i.name);
+ if (!lambda.formals->has(i.name)) {
+ std::set formalNames;
+ for (auto & formal : lambda.formals->formals)
+ formalNames.insert(formal.name);
+ throwTypeError(
+ pos,
+ Suggestions::bestMatches(formalNames, i.name),
+ "%1% called with unexpected argument '%2%'",
+ lambda,
+ i.name);
+ }
abort(); // can't happen
}
}
@@ -1902,13 +1892,22 @@ std::string_view EvalState::forceString(Value & v, const Pos & pos)
/* Decode a context string ‘!!’ into a pair . */
-std::pair decodeContext(std::string_view s)
+NixStringContextElem decodeContext(const Store & store, std::string_view s)
{
if (s.at(0) == '!') {
size_t index = s.find("!", 1);
- return {std::string(s.substr(index + 1)), std::string(s.substr(1, index - 1))};
+ return {
+ store.parseStorePath(s.substr(index + 1)),
+ std::string(s.substr(1, index - 1)),
+ };
} else
- return {s.at(0) == '/' ? std::string(s) : std::string(s.substr(1)), ""};
+ return {
+ store.parseStorePath(
+ s.at(0) == '/'
+ ? s
+ : s.substr(1)),
+ "",
+ };
}
@@ -1920,13 +1919,13 @@ void copyContext(const Value & v, PathSet & context)
}
-std::vector> Value::getContext()
+NixStringContext Value::getContext(const Store & store)
{
- std::vector> res;
+ NixStringContext res;
assert(internalType == tString);
if (string.context)
for (const char * * p = string.context; *p; ++p)
- res.push_back(decodeContext(*p));
+ res.push_back(decodeContext(store, *p));
return res;
}
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index 800b00eef..e7915dd99 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -133,9 +133,14 @@ private:
/* Cache used by prim_match(). */
std::shared_ptr regexCache;
+#if HAVE_BOEHMGC
/* Allocation cache for GC'd Value objects. */
std::shared_ptr valueAllocCache;
+ /* Allocation cache for size-1 Env objects. */
+ std::shared_ptr env1AllocCache;
+#endif
+
public:
EvalState(
@@ -144,12 +149,6 @@ public:
std::shared_ptr buildStore = nullptr);
~EvalState();
- void requireExperimentalFeatureOnEvaluation(
- const ExperimentalFeature &,
- const std::string_view fName,
- const Pos & pos
- );
-
void addToSearchPath(const std::string & s);
SearchPath getSearchPath() { return searchPath; }
@@ -347,8 +346,8 @@ public:
void autoCallFunction(Bindings & args, Value & fun, Value & res);
/* Allocation primitives. */
- Value * allocValue();
- Env & allocEnv(size_t size);
+ inline Value * allocValue();
+ inline Env & allocEnv(size_t size);
Value * allocAttr(Value & vAttrs, const Symbol & name);
Value * allocAttr(Value & vAttrs, std::string_view name);
@@ -425,7 +424,7 @@ std::string showType(const Value & v);
/* Decode a context string ‘!!’ into a pair . */
-std::pair decodeContext(std::string_view s);
+NixStringContextElem decodeContext(const Store & store, std::string_view s);
/* If `path' refers to a directory, then append "/default.nix". */
Path resolveExprPath(Path path);
@@ -509,3 +508,5 @@ extern EvalSettings evalSettings;
static const std::string corepkgsPrefix{"/__corepkgs__/"};
}
+
+#include "eval-inline.hh"
diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc
index 6a1aca40d..22257c6b3 100644
--- a/src/libexpr/flake/flake.cc
+++ b/src/libexpr/flake/flake.cc
@@ -706,8 +706,6 @@ void callFlake(EvalState & state,
static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
- state.requireExperimentalFeatureOnEvaluation(Xp::Flakes, "builtins.getFlake", pos);
-
std::string flakeRefS(state.forceStringNoCtx(*args[0], pos));
auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
if (evalSettings.pureEval && !flakeRef.input.isLocked())
@@ -723,7 +721,30 @@ static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Va
v);
}
-static RegisterPrimOp r2("__getFlake", 1, prim_getFlake);
+static RegisterPrimOp r2({
+ .name = "__getFlake",
+ .args = {"args"},
+ .doc = R"(
+ Fetch a flake from a flake reference, and return its output attributes and some metadata. For example:
+
+ ```nix
+ (builtins.getFlake "nix/55bc52401966fbffa525c574c14f67b00bc4fb3a").packages.x86_64-linux.nix
+ ```
+
+ Unless impure evaluation is allowed (`--impure`), the flake reference
+ must be "locked", e.g. contain a Git revision or content hash. An
+ example of an unlocked usage is:
+
+ ```nix
+ (builtins.getFlake "github:edolstra/dwarffs").rev
+ ```
+
+ This function is only available if you enable the experimental feature
+ `flakes`.
+ )",
+ .fun = prim_getFlake,
+ .experimentalFeature = Xp::Flakes,
+});
}
diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc
index 7630c5ff4..bb7e77b61 100644
--- a/src/libexpr/get-drvs.cc
+++ b/src/libexpr/get-drvs.cc
@@ -1,6 +1,7 @@
#include "get-drvs.hh"
#include "util.hh"
#include "eval-inline.hh"
+#include "derivations.hh"
#include "store-api.hh"
#include "path-with-outputs.hh"
@@ -102,7 +103,7 @@ StorePath DrvInfo::queryOutPath() const
}
-DrvInfo::Outputs DrvInfo::queryOutputs(bool onlyOutputsToInstall)
+DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall)
{
if (outputs.empty()) {
/* Get the ‘outputs’ list. */
@@ -112,20 +113,24 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool onlyOutputsToInstall)
/* For each output... */
for (auto elem : i->value->listItems()) {
- /* Evaluate the corresponding set. */
- std::string name(state->forceStringNoCtx(*elem, *i->pos));
- Bindings::iterator out = attrs->find(state->symbols.create(name));
- if (out == attrs->end()) continue; // FIXME: throw error?
- state->forceAttrs(*out->value, *i->pos);
+ std::string output(state->forceStringNoCtx(*elem, *i->pos));
- /* And evaluate its ‘outPath’ attribute. */
- Bindings::iterator outPath = out->value->attrs->find(state->sOutPath);
- if (outPath == out->value->attrs->end()) continue; // FIXME: throw error?
- PathSet context;
- outputs.emplace(name, state->coerceToStorePath(*outPath->pos, *outPath->value, context));
+ if (withPaths) {
+ /* Evaluate the corresponding set. */
+ Bindings::iterator out = attrs->find(state->symbols.create(output));
+ if (out == attrs->end()) continue; // FIXME: throw error?
+ state->forceAttrs(*out->value, *i->pos);
+
+ /* And evaluate its ‘outPath’ attribute. */
+ Bindings::iterator outPath = out->value->attrs->find(state->sOutPath);
+ if (outPath == out->value->attrs->end()) continue; // FIXME: throw error?
+ PathSet context;
+ outputs.emplace(output, state->coerceToStorePath(*outPath->pos, *outPath->value, context));
+ } else
+ outputs.emplace(output, std::nullopt);
}
} else
- outputs.emplace("out", queryOutPath());
+ outputs.emplace("out", withPaths ? std::optional{queryOutPath()} : std::nullopt);
}
if (!onlyOutputsToInstall || !attrs)
return outputs;
diff --git a/src/libexpr/get-drvs.hh b/src/libexpr/get-drvs.hh
index 3ca6f1fca..7cc1abef2 100644
--- a/src/libexpr/get-drvs.hh
+++ b/src/libexpr/get-drvs.hh
@@ -13,7 +13,7 @@ namespace nix {
struct DrvInfo
{
public:
- typedef std::map Outputs;
+ typedef std::map> Outputs;
private:
EvalState * state;
@@ -46,8 +46,9 @@ public:
StorePath requireDrvPath() const;
StorePath queryOutPath() const;
std::string queryOutputName() const;
- /** Return the list of outputs. The "outputs to install" are determined by `meta.outputsToInstall`. */
- Outputs queryOutputs(bool onlyOutputsToInstall = false);
+ /** Return the unordered map of output names to (optional) output paths.
+ * The "outputs to install" are determined by `meta.outputsToInstall`. */
+ Outputs queryOutputs(bool withPaths = true, bool onlyOutputsToInstall = false);
StringSet queryMetaNames();
Value * queryMeta(const std::string & name);
diff --git a/src/libexpr/lexer.l b/src/libexpr/lexer.l
index e276b0467..d574121b0 100644
--- a/src/libexpr/lexer.l
+++ b/src/libexpr/lexer.l
@@ -28,6 +28,13 @@ using namespace nix;
namespace nix {
+static inline Pos makeCurPos(const YYLTYPE & loc, ParseData * data)
+{
+ return Pos(data->origin, data->file, loc.first_line, loc.first_column);
+}
+
+#define CUR_POS makeCurPos(*yylloc, data)
+
// backup to recover from yyless(0)
YYLTYPE prev_yylloc;
@@ -37,7 +44,6 @@ static void initLoc(YYLTYPE * loc)
loc->first_column = loc->last_column = 1;
}
-
static void adjustLoc(YYLTYPE * loc, const char * s, size_t len)
{
prev_yylloc = *loc;
@@ -147,14 +153,20 @@ or { return OR_KW; }
try {
yylval->n = boost::lexical_cast(yytext);
} catch (const boost::bad_lexical_cast &) {
- throw ParseError("invalid integer '%1%'", yytext);
+ throw ParseError({
+ .msg = hintfmt("invalid integer '%1%'", yytext),
+ .errPos = CUR_POS,
+ });
}
return INT;
}
{FLOAT} { errno = 0;
yylval->nf = strtod(yytext, 0);
if (errno != 0)
- throw ParseError("invalid float '%1%'", yytext);
+ throw ParseError({
+ .msg = hintfmt("invalid float '%1%'", yytext),
+ .errPos = CUR_POS,
+ });
return FLOAT;
}
@@ -280,7 +292,10 @@ or { return OR_KW; }
{ANY} |
<> {
- throw ParseError("path has a trailing slash");
+ throw ParseError({
+ .msg = hintfmt("path has a trailing slash"),
+ .errPos = CUR_POS,
+ });
}
{SPATH} { yylval->path = {yytext, (size_t) yyleng}; return SPATH; }
diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh
index 12b54b8eb..4dbe31510 100644
--- a/src/libexpr/nixexpr.hh
+++ b/src/libexpr/nixexpr.hh
@@ -23,14 +23,13 @@ MakeError(RestrictedPathError, Error);
struct Pos
{
- FileOrigin origin;
Symbol file;
- unsigned int line, column;
-
- Pos() : origin(foString), line(0), column(0) { }
- Pos(FileOrigin origin, const Symbol & file, unsigned int line, unsigned int column)
- : origin(origin), file(file), line(line), column(column) { }
-
+ uint32_t line;
+ FileOrigin origin:2;
+ uint32_t column:30;
+ Pos() : line(0), origin(foString), column(0) { };
+ Pos(FileOrigin origin, const Symbol & file, uint32_t line, uint32_t column)
+ : file(file), line(line), origin(origin), column(column) { };
operator bool() const
{
return line != 0;
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 77f4224c2..eca9d6358 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -43,8 +43,8 @@ StringMap EvalState::realiseContext(const PathSet & context)
StringMap res;
for (auto & i : context) {
- auto [ctxS, outputName] = decodeContext(i);
- auto ctx = store->parseStorePath(ctxS);
+ auto [ctx, outputName] = decodeContext(*store, i);
+ auto ctxS = store->printStorePath(ctx);
if (!store->isValidPath(ctx))
throw InvalidPathError(store->printStorePath(ctx));
if (!outputName.empty() && ctx.isDerivation()) {
@@ -694,7 +694,32 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar
static RegisterPrimOp primop_genericClosure(RegisterPrimOp::Info {
.name = "__genericClosure",
+ .args = {"attrset"},
.arity = 1,
+ .doc = R"(
+ Take an *attrset* with values named `startSet` and `operator` in order to
+ return a *list of attrsets* by starting with the `startSet`, recursively
+ applying the `operator` function to each element. The *attrsets* in the
+ `startSet` and produced by the `operator` must each contain value named
+ `key` which are comparable to each other. The result is produced by
+ repeatedly calling the operator for each element encountered with a
+ unique key, terminating when no new elements are produced. For example,
+
+ ```
+ builtins.genericClosure {
+ startSet = [ {key = 5;} ];
+ operator = item: [{
+ key = if (item.key / 2 ) * 2 == item.key
+ then item.key / 2
+ else 3 * item.key + 1;
+ }];
+ }
+ ```
+ evaluates to
+ ```
+ [ { key = 5; } { key = 16; } { key = 8; } { key = 4; } { key = 2; } { key = 1; } ]
+ ```
+ )",
.fun = prim_genericClosure,
});
@@ -1118,8 +1143,8 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
/* Handle derivation outputs of the form ‘!!’. */
else if (path.at(0) == '!') {
- auto ctx = decodeContext(path);
- drv.inputDrvs[state.store->parseStorePath(ctx.first)].insert(ctx.second);
+ auto ctx = decodeContext(*state.store, path);
+ drv.inputDrvs[ctx.first].insert(ctx.second);
}
/* Otherwise it's a source file. */
@@ -1169,26 +1194,24 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
{},
});
drv.env["out"] = state.store->printStorePath(outPath);
- drv.outputs.insert_or_assign("out", DerivationOutput {
- .output = DerivationOutputCAFixed {
- .hash = FixedOutputHash {
- .method = ingestionMethod,
- .hash = std::move(h),
- },
+ drv.outputs.insert_or_assign("out",
+ DerivationOutput::CAFixed {
+ .hash = FixedOutputHash {
+ .method = ingestionMethod,
+ .hash = std::move(h),
},
- });
+ });
}
else if (contentAddressed) {
HashType ht = parseHashType(outputHashAlgo);
for (auto & i : outputs) {
drv.env[i] = hashPlaceholder(i);
- drv.outputs.insert_or_assign(i, DerivationOutput {
- .output = DerivationOutputCAFloating {
+ drv.outputs.insert_or_assign(i,
+ DerivationOutput::CAFloating {
.method = ingestionMethod,
.hashType = ht,
- },
- });
+ });
}
}
@@ -1202,43 +1225,36 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
for (auto & i : outputs) {
drv.env[i] = "";
drv.outputs.insert_or_assign(i,
- DerivationOutput {
- .output = DerivationOutputInputAddressed {
- .path = StorePath::dummy,
- },
- });
+ DerivationOutput::Deferred { });
}
// Regular, non-CA derivation should always return a single hash and not
// hash per output.
- auto hashModulo = hashDerivationModulo(*state.store, Derivation(drv), true);
+ auto hashModulo = hashDerivationModulo(*state.store, drv, true);
std::visit(overloaded {
- [&](Hash & h) {
- for (auto & i : outputs) {
- auto outPath = state.store->makeOutputPath(i, h, drvName);
- drv.env[i] = state.store->printStorePath(outPath);
- drv.outputs.insert_or_assign(i,
- DerivationOutput {
- .output = DerivationOutputInputAddressed {
- .path = std::move(outPath),
- },
- });
+ [&](const DrvHash & drvHash) {
+ auto & h = drvHash.hash;
+ switch (drvHash.kind) {
+ case DrvHash::Kind::Deferred:
+ /* Outputs already deferred, nothing to do */
+ break;
+ case DrvHash::Kind::Regular:
+ for (auto & [outputName, output] : drv.outputs) {
+ auto outPath = state.store->makeOutputPath(outputName, h, drvName);
+ drv.env[outputName] = state.store->printStorePath(outPath);
+ output = DerivationOutput::InputAddressed {
+ .path = std::move(outPath),
+ };
+ }
+ break;
}
},
- [&](CaOutputHashes &) {
+ [&](const CaOutputHashes &) {
// Shouldn't happen as the toplevel derivation is not CA.
assert(false);
},
- [&](DeferredHash &) {
- for (auto & i : outputs) {
- drv.outputs.insert_or_assign(i,
- DerivationOutput {
- .output = DerivationOutputDeferred{},
- });
- }
- },
},
- hashModulo);
+ hashModulo.raw());
}
@@ -1250,12 +1266,9 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
/* Optimisation, but required in read-only mode! because in that
case we don't actually write store derivations, so we can't
- read them later.
-
- However, we don't bother doing this for floating CA derivations because
- their "hash modulo" is indeterminate until built. */
- if (drv.type() != DerivationType::CAFloating) {
- auto h = hashDerivationModulo(*state.store, Derivation(drv), false);
+ read them later. */
+ {
+ auto h = hashDerivationModulo(*state.store, drv, false);
drvHashes.lock()->insert_or_assign(drvPath, h);
}
@@ -3813,7 +3826,7 @@ RegisterPrimOp::RegisterPrimOp(std::string name, size_t arity, PrimOpFun fun)
.name = name,
.args = {},
.arity = arity,
- .fun = fun
+ .fun = fun,
});
}
@@ -3885,13 +3898,17 @@ void EvalState::createBaseEnv()
if (RegisterPrimOp::primOps)
for (auto & primOp : *RegisterPrimOp::primOps)
- addPrimOp({
- .fun = primOp.fun,
- .arity = std::max(primOp.args.size(), primOp.arity),
- .name = symbols.create(primOp.name),
- .args = primOp.args,
- .doc = primOp.doc,
- });
+ if (!primOp.experimentalFeature
+ || settings.isExperimentalFeatureEnabled(*primOp.experimentalFeature))
+ {
+ addPrimOp({
+ .fun = primOp.fun,
+ .arity = std::max(primOp.args.size(), primOp.arity),
+ .name = symbols.create(primOp.name),
+ .args = primOp.args,
+ .doc = primOp.doc,
+ });
+ }
/* Add a wrapper around the derivation primop that computes the
`drvPath' and `outPath' attributes lazily. */
diff --git a/src/libexpr/primops.hh b/src/libexpr/primops.hh
index 5b16e075f..905bd0366 100644
--- a/src/libexpr/primops.hh
+++ b/src/libexpr/primops.hh
@@ -16,6 +16,7 @@ struct RegisterPrimOp
size_t arity = 0;
const char * doc;
PrimOpFun fun;
+ std::optional experimentalFeature;
};
typedef std::vector PrimOps;
@@ -35,6 +36,7 @@ struct RegisterPrimOp
/* These primops are disabled without enableNativeCode, but plugins
may wish to use them in limited contexts without globally enabling
them. */
+
/* Load a ValueInitializer from a DSO and return whatever it initializes */
void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value & v);
diff --git a/src/libexpr/primops/context.cc b/src/libexpr/primops/context.cc
index 3701bd442..cc74c7f58 100644
--- a/src/libexpr/primops/context.cc
+++ b/src/libexpr/primops/context.cc
@@ -1,5 +1,6 @@
#include "primops.hh"
#include "eval-inline.hh"
+#include "derivations.hh"
#include "store-api.hh"
namespace nix {
@@ -82,8 +83,8 @@ static void prim_getContext(EvalState & state, const Pos & pos, Value * * args,
drv = std::string(p, 1);
path = &drv;
} else if (p.at(0) == '!') {
- std::pair ctx = decodeContext(p);
- drv = ctx.first;
+ NixStringContextElem ctx = decodeContext(*state.store, p);
+ drv = state.store->printStorePath(ctx.first);
output = ctx.second;
path = &drv;
}
diff --git a/src/libexpr/primops/fetchClosure.cc b/src/libexpr/primops/fetchClosure.cc
new file mode 100644
index 000000000..efeb93daf
--- /dev/null
+++ b/src/libexpr/primops/fetchClosure.cc
@@ -0,0 +1,154 @@
+#include "primops.hh"
+#include "store-api.hh"
+#include "make-content-addressed.hh"
+#include "url.hh"
+
+namespace nix {
+
+static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args, Value & v)
+{
+ state.forceAttrs(*args[0], pos);
+
+ std::optional fromStoreUrl;
+ std::optional fromPath;
+ bool toCA = false;
+ std::optional toPath;
+
+ for (auto & attr : *args[0]->attrs) {
+ if (attr.name == "fromPath") {
+ PathSet context;
+ fromPath = state.coerceToStorePath(*attr.pos, *attr.value, context);
+ }
+
+ else if (attr.name == "toPath") {
+ state.forceValue(*attr.value, *attr.pos);
+ toCA = true;
+ if (attr.value->type() != nString || attr.value->string.s != std::string("")) {
+ PathSet context;
+ toPath = state.coerceToStorePath(*attr.pos, *attr.value, context);
+ }
+ }
+
+ else if (attr.name == "fromStore")
+ fromStoreUrl = state.forceStringNoCtx(*attr.value, *attr.pos);
+
+ else
+ throw Error({
+ .msg = hintfmt("attribute '%s' isn't supported in call to 'fetchClosure'", attr.name),
+ .errPos = pos
+ });
+ }
+
+ if (!fromPath)
+ throw Error({
+ .msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"),
+ .errPos = pos
+ });
+
+ if (!fromStoreUrl)
+ throw Error({
+ .msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"),
+ .errPos = pos
+ });
+
+ auto parsedURL = parseURL(*fromStoreUrl);
+
+ if (parsedURL.scheme != "http" &&
+ parsedURL.scheme != "https" &&
+ !(getEnv("_NIX_IN_TEST").has_value() && parsedURL.scheme == "file"))
+ throw Error({
+ .msg = hintfmt("'fetchClosure' only supports http:// and https:// stores"),
+ .errPos = pos
+ });
+
+ auto fromStore = openStore(parsedURL.to_string());
+
+ if (toCA) {
+ if (!toPath || !state.store->isValidPath(*toPath)) {
+ auto remappings = makeContentAddressed(*fromStore, *state.store, { *fromPath });
+ auto i = remappings.find(*fromPath);
+ assert(i != remappings.end());
+ if (toPath && *toPath != i->second)
+ throw Error({
+ .msg = hintfmt("rewriting '%s' to content-addressed form yielded '%s', while '%s' was expected",
+ state.store->printStorePath(*fromPath),
+ state.store->printStorePath(i->second),
+ state.store->printStorePath(*toPath)),
+ .errPos = pos
+ });
+ if (!toPath)
+ throw Error({
+ .msg = hintfmt(
+ "rewriting '%s' to content-addressed form yielded '%s'; "
+ "please set this in the 'toPath' attribute passed to 'fetchClosure'",
+ state.store->printStorePath(*fromPath),
+ state.store->printStorePath(i->second)),
+ .errPos = pos
+ });
+ }
+ } else {
+ copyClosure(*fromStore, *state.store, RealisedPath::Set { *fromPath });
+ toPath = fromPath;
+ }
+
+ /* In pure mode, require a CA path. */
+ if (evalSettings.pureEval) {
+ auto info = state.store->queryPathInfo(*toPath);
+ if (!info->isContentAddressed(*state.store))
+ throw Error({
+ .msg = hintfmt("in pure mode, 'fetchClosure' requires a content-addressed path, which '%s' isn't",
+ state.store->printStorePath(*toPath)),
+ .errPos = pos
+ });
+ }
+
+ auto toPathS = state.store->printStorePath(*toPath);
+ v.mkString(toPathS, {toPathS});
+}
+
+static RegisterPrimOp primop_fetchClosure({
+ .name = "__fetchClosure",
+ .args = {"args"},
+ .doc = R"(
+ Fetch a Nix store closure from a binary cache, rewriting it into
+ content-addressed form. For example,
+
+ ```nix
+ builtins.fetchClosure {
+ fromStore = "https://cache.nixos.org";
+ fromPath = /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1;
+ toPath = /nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33.1;
+ }
+ ```
+
+ fetches `/nix/store/r2jd...` from the specified binary cache,
+ and rewrites it into the content-addressed store path
+ `/nix/store/ldbh...`.
+
+ If `fromPath` is already content-addressed, or if you are
+ allowing impure evaluation (`--impure`), then `toPath` may be
+ omitted.
+
+ To find out the correct value for `toPath` given a `fromPath`,
+ you can use `nix store make-content-addressed`:
+
+ ```console
+ # nix store make-content-addressed --from https://cache.nixos.org /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1
+ rewrote '/nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1' to '/nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33.1'
+ ```
+
+ This function is similar to `builtins.storePath` in that it
+ allows you to use a previously built store path in a Nix
+ expression. However, it is more reproducible because it requires
+ specifying a binary cache from which the path can be fetched.
+ Also, requiring a content-addressed final store path avoids the
+ need for users to configure binary cache public keys.
+
+ This function is only available if you enable the experimental
+ feature `fetch-closure`.
+ )",
+ .fun = prim_fetchClosure,
+ .experimentalFeature = Xp::FetchClosure,
+});
+
+}
diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc
index 281f9bc2c..c12872084 100644
--- a/src/libexpr/primops/fetchTree.cc
+++ b/src/libexpr/primops/fetchTree.cc
@@ -145,7 +145,7 @@ static void fetchTree(
if (!params.allowNameArgument)
if (auto nameIter = attrs.find("name"); nameIter != attrs.end())
throw Error({
- .msg = hintfmt("attribute 'name' isn’t supported in call to 'fetchTree'"),
+ .msg = hintfmt("attribute 'name' isn't supported in call to 'fetchTree'"),
.errPos = pos
});
@@ -334,7 +334,7 @@ static RegisterPrimOp primop_fetchTarball({
.fun = prim_fetchTarball,
});
-static void prim_fetchGit(EvalState &state, const Pos &pos, Value **args, Value &v)
+static void prim_fetchGit(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
fetchTree(state, pos, args, v, "git", FetchTreeParams { .emptyRevFallback = true, .allowNameArgument = true });
}
diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh
index d0fa93e92..3d07c3198 100644
--- a/src/libexpr/value.hh
+++ b/src/libexpr/value.hh
@@ -57,6 +57,8 @@ struct ExprLambda;
struct PrimOp;
class Symbol;
struct Pos;
+class StorePath;
+class Store;
class EvalState;
class XMLWriter;
class JSONPlaceholder;
@@ -64,6 +66,8 @@ class JSONPlaceholder;
typedef int64_t NixInt;
typedef double NixFloat;
+typedef std::pair NixStringContextElem;
+typedef std::vector NixStringContext;
/* External values must descend from ExternalValueBase, so that
* type-agnostic nix functions (e.g. showType) can be implemented
@@ -115,10 +119,13 @@ private:
InternalType internalType;
friend std::string showType(const Value & v);
- friend void printValue(std::ostream & str, std::set & seen, const Value & v);
+
+ void print(std::ostream & str, std::set * seen) const;
public:
+ void print(std::ostream & str, bool showRepeated = false) const;
+
// Functions needed to distinguish the type
// These should be removed eventually, by putting the functionality that's
// needed by callers into methods of this type
@@ -368,7 +375,7 @@ public:
non-trivial. */
bool isTrivial() const;
- std::vector> getContext();
+ NixStringContext getContext(const Store &);
auto listItems()
{
diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc
index c0beca2f2..d75c5d3ae 100644
--- a/src/libfetchers/git.cc
+++ b/src/libfetchers/git.cc
@@ -222,22 +222,46 @@ struct GitInputScheme : InputScheme
if (!input.getRef() && !input.getRev() && isLocal) {
bool clean = false;
- /* Check whether this repo has any commits. There are
- probably better ways to do this. */
- auto gitDir = actualUrl + "/.git";
- auto commonGitDir = chomp(runProgram(
- "git",
- true,
- { "-C", actualUrl, "rev-parse", "--git-common-dir" }
- ));
- if (commonGitDir != ".git")
- gitDir = commonGitDir;
+ auto env = getEnv();
+ // Set LC_ALL to C: because we rely on the error messages from git rev-parse to determine what went wrong
+ // that way unknown errors can lead to a failure instead of continuing through the wrong code path
+ env["LC_ALL"] = "C";
- bool haveCommits = !readDirectory(gitDir + "/refs/heads").empty();
+ /* Check whether HEAD points to something that looks like a commit,
+ since that is the refrence we want to use later on. */
+ auto result = runProgram(RunOptions {
+ .program = "git",
+ .args = { "-C", actualUrl, "--git-dir=.git", "rev-parse", "--verify", "--no-revs", "HEAD^{commit}" },
+ .environment = env,
+ .mergeStderrToStdout = true
+ });
+ auto exitCode = WEXITSTATUS(result.first);
+ auto errorMessage = result.second;
+ if (errorMessage.find("fatal: not a git repository") != std::string::npos) {
+ throw Error("'%s' is not a Git repository", actualUrl);
+ } else if (errorMessage.find("fatal: Needed a single revision") != std::string::npos) {
+ // indicates that the repo does not have any commits
+ // we want to proceed and will consider it dirty later
+ } else if (exitCode != 0) {
+ // any other errors should lead to a failure
+ throw Error("getting the HEAD of the Git tree '%s' failed with exit code %d:\n%s", actualUrl, exitCode, errorMessage);
+ }
+
+ bool hasHead = exitCode == 0;
try {
- if (haveCommits) {
- runProgram("git", true, { "-C", actualUrl, "diff-index", "--quiet", "HEAD", "--" });
+ if (hasHead) {
+ // Using git diff is preferrable over lower-level operations here,
+ // because its conceptually simpler and we only need the exit code anyways.
+ auto gitDiffOpts = Strings({ "-C", actualUrl, "diff", "HEAD", "--quiet"});
+ if (!submodules) {
+ // Changes in submodules should only make the tree dirty
+ // when those submodules will be copied as well.
+ gitDiffOpts.emplace_back("--ignore-submodules");
+ }
+ gitDiffOpts.emplace_back("--");
+ runProgram("git", true, gitDiffOpts);
+
clean = true;
}
} catch (ExecError & e) {
@@ -282,7 +306,7 @@ struct GitInputScheme : InputScheme
// modified dirty file?
input.attrs.insert_or_assign(
"lastModified",
- haveCommits ? std::stoull(runProgram("git", true, { "-C", actualUrl, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0);
+ hasHead ? std::stoull(runProgram("git", true, { "-C", actualUrl, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0);
return {std::move(storePath), input};
}
diff --git a/src/libfetchers/github.cc b/src/libfetchers/github.cc
index a1430f087..58b6e7c04 100644
--- a/src/libfetchers/github.cc
+++ b/src/libfetchers/github.cc
@@ -390,7 +390,7 @@ struct SourceHutInputScheme : GitArchiveInputScheme
ref_uri = line.substr(ref_index+5, line.length()-1);
} else
- ref_uri = fmt("refs/heads/%s", ref);
+ ref_uri = fmt("refs/(heads|tags)/%s", ref);
auto file = store->toRealPath(
downloadFile(store, fmt("%s/info/refs", base_url), "source", false, headers).storePath);
@@ -399,9 +399,11 @@ struct SourceHutInputScheme : GitArchiveInputScheme
std::string line;
std::string id;
while(getline(is, line)) {
- auto index = line.find(ref_uri);
- if (index != std::string::npos) {
- id = line.substr(0, index-1);
+ // Append $ to avoid partial name matches
+ std::regex pattern(fmt("%s$", ref_uri));
+
+ if (std::regex_search(line, pattern)) {
+ id = line.substr(0, line.find('\t'));
break;
}
}
diff --git a/src/libfetchers/path.cc b/src/libfetchers/path.cc
index 59e228e97..f0ef97da5 100644
--- a/src/libfetchers/path.cc
+++ b/src/libfetchers/path.cc
@@ -1,5 +1,6 @@
#include "fetchers.hh"
#include "store-api.hh"
+#include "archive.hh"
namespace nix::fetchers {
@@ -80,8 +81,9 @@ struct PathInputScheme : InputScheme
// nothing to do
}
- std::pair fetch(ref store, const Input & input) override
+ std::pair fetch(ref store, const Input & _input) override
{
+ Input input(_input);
std::string absPath;
auto path = getStrAttr(input.attrs, "path");
@@ -111,9 +113,15 @@ struct PathInputScheme : InputScheme
if (storePath)
store->addTempRoot(*storePath);
- if (!storePath || storePath->name() != "source" || !store->isValidPath(*storePath))
+ time_t mtime = 0;
+ if (!storePath || storePath->name() != "source" || !store->isValidPath(*storePath)) {
// FIXME: try to substitute storePath.
- storePath = store->addToStore("source", absPath);
+ auto src = sinkToSource([&](Sink & sink) {
+ mtime = dumpPathAndGetMtime(absPath, sink, defaultPathFilter);
+ });
+ storePath = store->addToStoreFromDump(*src, "source");
+ }
+ input.attrs.insert_or_assign("lastModified", uint64_t(mtime));
return {std::move(*storePath), input};
}
diff --git a/src/libstore/binary-cache-store.hh b/src/libstore/binary-cache-store.hh
index 9603a8caa..ca538b3cb 100644
--- a/src/libstore/binary-cache-store.hh
+++ b/src/libstore/binary-cache-store.hh
@@ -2,6 +2,7 @@
#include "crypto.hh"
#include "store-api.hh"
+#include "log-store.hh"
#include "pool.hh"
@@ -28,7 +29,9 @@ struct BinaryCacheStoreConfig : virtual StoreConfig
"other than -1 which we reserve to indicate Nix defaults should be used"};
};
-class BinaryCacheStore : public virtual BinaryCacheStoreConfig, public virtual Store
+class BinaryCacheStore : public virtual BinaryCacheStoreConfig,
+ public virtual Store,
+ public virtual LogStore
{
private:
diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc
index afed9bf16..3d1c4fbc1 100644
--- a/src/libstore/build/derivation-goal.cc
+++ b/src/libstore/build/derivation-goal.cc
@@ -204,11 +204,9 @@ void DerivationGoal::haveDerivation()
{
trace("have derivation");
- if (drv->type() == DerivationType::CAFloating)
+ if (!drv->type().hasKnownOutputPaths())
settings.requireExperimentalFeature(Xp::CaDerivations);
- retrySubstitution = false;
-
for (auto & i : drv->outputsAndOptPaths(worker.store))
if (i.second.second)
worker.store.addTempRoot(*i.second.second);
@@ -311,14 +309,11 @@ void DerivationGoal::outputsSubstitutionTried()
gaveUpOnSubstitution();
}
+
/* At least one of the output paths could not be
produced using a substitute. So we have to build instead. */
void DerivationGoal::gaveUpOnSubstitution()
{
- /* Make sure checkPathValidity() from now on checks all
- outputs. */
- wantedOutputs.clear();
-
/* The inputs must be built before we can build this goal. */
if (useDerivation)
for (auto & i : dynamic_cast(drv.get())->inputDrvs)
@@ -426,7 +421,8 @@ void DerivationGoal::inputsRealised()
return;
}
- if (retrySubstitution) {
+ if (retrySubstitution && !retriedSubstitution) {
+ retriedSubstitution = true;
haveDerivation();
return;
}
@@ -440,9 +436,28 @@ void DerivationGoal::inputsRealised()
if (useDerivation) {
auto & fullDrv = *dynamic_cast(drv.get());
- if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations) &&
- ((!fullDrv.inputDrvs.empty() && derivationIsCA(fullDrv.type()))
- || fullDrv.type() == DerivationType::DeferredInputAddressed)) {
+ auto drvType = fullDrv.type();
+ bool resolveDrv = std::visit(overloaded {
+ [&](const DerivationType::InputAddressed & ia) {
+ /* must resolve if deferred. */
+ return ia.deferred;
+ },
+ [&](const DerivationType::ContentAddressed & ca) {
+ return !fullDrv.inputDrvs.empty() && (
+ ca.fixed
+ /* Can optionally resolve if fixed, which is good
+ for avoiding unnecessary rebuilds. */
+ ? settings.isExperimentalFeatureEnabled(Xp::CaDerivations)
+ /* Must resolve if floating and there are any inputs
+ drvs. */
+ : true);
+ },
+ }, drvType.raw());
+
+ if (resolveDrv)
+ {
+ settings.requireExperimentalFeature(Xp::CaDerivations);
+
/* We are be able to resolve this derivation based on the
now-known results of dependencies. If so, we become a stub goal
aliasing that resolved derivation goal */
@@ -501,7 +516,7 @@ void DerivationGoal::inputsRealised()
/* Don't repeat fixed-output derivations since they're already
verified by their output hash.*/
- nrRounds = derivationIsFixed(derivationType) ? 1 : settings.buildRepeat + 1;
+ nrRounds = derivationType.isFixed() ? 1 : settings.buildRepeat + 1;
/* Okay, try to build. Note that here we don't wait for a build
slot to become available, since we don't need one if there is a
@@ -908,7 +923,7 @@ void DerivationGoal::buildDone()
st =
dynamic_cast(&e) ? BuildResult::NotDeterministic :
statusOk(status) ? BuildResult::OutputRejected :
- derivationIsImpure(derivationType) || diskFull ? BuildResult::TransientFailure :
+ derivationType.isImpure() || diskFull ? BuildResult::TransientFailure :
BuildResult::PermanentFailure;
}
@@ -1221,7 +1236,7 @@ void DerivationGoal::flushLine()
std::map> DerivationGoal::queryPartialDerivationOutputMap()
{
- if (!useDerivation || drv->type() != DerivationType::CAFloating) {
+ if (!useDerivation || drv->type().hasKnownOutputPaths()) {
std::map> res;
for (auto & [name, output] : drv->outputs)
res.insert_or_assign(name, output.path(worker.store, drv->name, name));
@@ -1233,7 +1248,7 @@ std::map> DerivationGoal::queryPartialDeri
OutputPathMap DerivationGoal::queryDerivationOutputMap()
{
- if (!useDerivation || drv->type() != DerivationType::CAFloating) {
+ if (!useDerivation || drv->type().hasKnownOutputPaths()) {
OutputPathMap res;
for (auto & [name, output] : drv->outputsAndOptPaths(worker.store))
res.insert_or_assign(name, *output.second);
diff --git a/src/libstore/build/derivation-goal.hh b/src/libstore/build/derivation-goal.hh
index ea2db89b2..f556b6f25 100644
--- a/src/libstore/build/derivation-goal.hh
+++ b/src/libstore/build/derivation-goal.hh
@@ -61,8 +61,12 @@ struct DerivationGoal : public Goal
bool needRestart = false;
/* Whether to retry substituting the outputs after building the
- inputs. */
- bool retrySubstitution;
+ inputs. This is done in case of an incomplete closure. */
+ bool retrySubstitution = false;
+
+ /* Whether we've retried substitution, in which case we won't try
+ again. */
+ bool retriedSubstitution = false;
/* The derivation stored at drvPath. */
std::unique_ptr drv;
diff --git a/src/libstore/build/goal.cc b/src/libstore/build/goal.cc
index d2420b107..58e805f55 100644
--- a/src/libstore/build/goal.cc
+++ b/src/libstore/build/goal.cc
@@ -28,7 +28,7 @@ void Goal::addWaitee(GoalPtr waitee)
void Goal::waiteeDone(GoalPtr waitee, ExitCode result)
{
- assert(waitees.find(waitee) != waitees.end());
+ assert(waitees.count(waitee));
waitees.erase(waitee);
trace(fmt("waitee '%s' done; %d left", waitee->name, waitees.size()));
diff --git a/src/libstore/build/goal.hh b/src/libstore/build/goal.hh
index 07c752bb9..35121c5d9 100644
--- a/src/libstore/build/goal.hh
+++ b/src/libstore/build/goal.hh
@@ -40,21 +40,21 @@ struct Goal : public std::enable_shared_from_this
WeakGoals waiters;
/* Number of goals we are/were waiting for that have failed. */
- unsigned int nrFailed;
+ size_t nrFailed = 0;
/* Number of substitution goals we are/were waiting for that
failed because there are no substituters. */
- unsigned int nrNoSubstituters;
+ size_t nrNoSubstituters = 0;
/* Number of substitution goals we are/were waiting for that
failed because they had unsubstitutable references. */
- unsigned int nrIncompleteClosure;
+ size_t nrIncompleteClosure = 0;
/* Name of this goal for debugging purposes. */
std::string name;
/* Whether the goal is finished. */
- ExitCode exitCode;
+ ExitCode exitCode = ecBusy;
/* Build result. */
BuildResult buildResult;
@@ -65,10 +65,7 @@ struct Goal : public std::enable_shared_from_this
Goal(Worker & worker, DerivedPath path)
: worker(worker)
, buildResult { .path = std::move(path) }
- {
- nrFailed = nrNoSubstituters = nrIncompleteClosure = 0;
- exitCode = ecBusy;
- }
+ { }
virtual ~Goal()
{
diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc
index 890a159ac..008ce0050 100644
--- a/src/libstore/build/local-derivation-goal.cc
+++ b/src/libstore/build/local-derivation-goal.cc
@@ -395,7 +395,7 @@ void LocalDerivationGoal::startBuilder()
else if (settings.sandboxMode == smDisabled)
useChroot = false;
else if (settings.sandboxMode == smRelaxed)
- useChroot = !(derivationIsImpure(derivationType)) && !noChroot;
+ useChroot = !(derivationType.isImpure()) && !noChroot;
}
auto & localStore = getLocalStore();
@@ -608,7 +608,7 @@ void LocalDerivationGoal::startBuilder()
"nogroup:x:65534:\n", sandboxGid()));
/* Create /etc/hosts with localhost entry. */
- if (!(derivationIsImpure(derivationType)))
+ if (!(derivationType.isImpure()))
writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n::1 localhost\n");
/* Make the closure of the inputs available in the chroot,
@@ -796,7 +796,7 @@ void LocalDerivationGoal::startBuilder()
us.
*/
- if (!(derivationIsImpure(derivationType)))
+ if (!(derivationType.isImpure()))
privateNetwork = true;
userNamespaceSync.create();
@@ -1049,7 +1049,7 @@ void LocalDerivationGoal::initEnv()
derivation, tell the builder, so that for instance `fetchurl'
can skip checking the output. On older Nixes, this environment
variable won't be set, so `fetchurl' will do the check. */
- if (derivationIsFixed(derivationType)) env["NIX_OUTPUT_CHECKED"] = "1";
+ if (derivationType.isFixed()) env["NIX_OUTPUT_CHECKED"] = "1";
/* *Only* if this is a fixed-output derivation, propagate the
values of the environment variables specified in the
@@ -1060,7 +1060,7 @@ void LocalDerivationGoal::initEnv()
to the builder is generally impure, but the output of
fixed-output derivations is by definition pure (since we
already know the cryptographic hash of the output). */
- if (derivationIsImpure(derivationType)) {
+ if (derivationType.isImpure()) {
for (auto & i : parsedDrv->getStringsAttr("impureEnvVars").value_or(Strings()))
env[i] = getEnv(i).value_or("");
}
@@ -1340,6 +1340,12 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
next->queryMissing(allowed, willBuild, willSubstitute,
unknown, downloadSize, narSize);
}
+
+ virtual std::optional getBuildLog(const StorePath & path) override
+ { return std::nullopt; }
+
+ virtual void addBuildLog(const StorePath & path, std::string_view log) override
+ { unsupported("addBuildLog"); }
};
@@ -1668,7 +1674,7 @@ void LocalDerivationGoal::runChild()
/* Fixed-output derivations typically need to access the
network, so give them access to /etc/resolv.conf and so
on. */
- if (derivationIsImpure(derivationType)) {
+ if (derivationType.isImpure()) {
// Only use nss functions to resolve hosts and
// services. Don’t use it for anything else that may
// be configured for this system. This limits the
@@ -1912,7 +1918,7 @@ void LocalDerivationGoal::runChild()
sandboxProfile += "(import \"sandbox-defaults.sb\")\n";
- if (derivationIsImpure(derivationType))
+ if (derivationType.isImpure())
sandboxProfile += "(import \"sandbox-network.sb\")\n";
/* Add the output paths we'll use at build-time to the chroot */
@@ -2272,7 +2278,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
return res;
};
- auto newInfoFromCA = [&](const DerivationOutputCAFloating outputHash) -> ValidPathInfo {
+ auto newInfoFromCA = [&](const DerivationOutput::CAFloating outputHash) -> ValidPathInfo {
auto & st = outputStats.at(outputName);
if (outputHash.method == FileIngestionMethod::Flat) {
/* The output path should be a regular file without execute permission. */
@@ -2334,7 +2340,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
ValidPathInfo newInfo = std::visit(overloaded {
- [&](const DerivationOutputInputAddressed & output) {
+ [&](const DerivationOutput::InputAddressed & output) {
/* input-addressed case */
auto requiredFinalPath = output.path;
/* Preemptively add rewrite rule for final hash, as that is
@@ -2351,8 +2357,8 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
return newInfo0;
},
- [&](const DerivationOutputCAFixed & dof) {
- auto newInfo0 = newInfoFromCA(DerivationOutputCAFloating {
+ [&](const DerivationOutput::CAFixed & dof) {
+ auto newInfo0 = newInfoFromCA(DerivationOutput::CAFloating {
.method = dof.hash.method,
.hashType = dof.hash.hash.type,
});
@@ -2374,17 +2380,17 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
return newInfo0;
},
- [&](DerivationOutputCAFloating & dof) {
+ [&](const DerivationOutput::CAFloating & dof) {
return newInfoFromCA(dof);
},
- [&](DerivationOutputDeferred) -> ValidPathInfo {
+ [&](const DerivationOutput::Deferred &) -> ValidPathInfo {
// No derivation should reach that point without having been
// rewritten first
assert(false);
},
- }, output.output);
+ }, output.raw());
/* FIXME: set proper permissions in restorePath() so
we don't have to do another traversal. */
@@ -2598,7 +2604,8 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
signRealisation(thisRealisation);
worker.store.registerDrvOutput(thisRealisation);
}
- builtOutputs.emplace(thisRealisation.id, thisRealisation);
+ if (wantOutput(outputName, wantedOutputs))
+ builtOutputs.emplace(thisRealisation.id, thisRealisation);
}
return builtOutputs;
diff --git a/src/libstore/builtins/buildenv.cc b/src/libstore/builtins/buildenv.cc
index 25d015cb9..6f6ad57cb 100644
--- a/src/libstore/builtins/buildenv.cc
+++ b/src/libstore/builtins/buildenv.cc
@@ -47,9 +47,9 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir,
throw;
}
- /* The files below are special-cased to that they don't show up
- * in user profiles, either because they are useless, or
- * because they would cauase pointless collisions (e.g., each
+ /* The files below are special-cased to that they don't show
+ * up in user profiles, either because they are useless, or
+ * because they would cause pointless collisions (e.g., each
* Python package brings its own
* `$out/lib/pythonX.Y/site-packages/easy-install.pth'.)
*/
@@ -57,7 +57,9 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir,
hasSuffix(srcFile, "/nix-support") ||
hasSuffix(srcFile, "/perllocal.pod") ||
hasSuffix(srcFile, "/info/dir") ||
- hasSuffix(srcFile, "/log"))
+ hasSuffix(srcFile, "/log") ||
+ hasSuffix(srcFile, "/manifest.nix") ||
+ hasSuffix(srcFile, "/manifest.json"))
continue;
else if (S_ISDIR(srcSt.st_mode)) {
diff --git a/src/libstore/daemon.cc b/src/libstore/daemon.cc
index 1bb445f93..a59fbae3c 100644
--- a/src/libstore/daemon.cc
+++ b/src/libstore/daemon.cc
@@ -3,7 +3,9 @@
#include "worker-protocol.hh"
#include "build-result.hh"
#include "store-api.hh"
+#include "store-cast.hh"
#include "gc-store.hh"
+#include "log-store.hh"
#include "path-with-outputs.hh"
#include "finally.hh"
#include "archive.hh"
@@ -558,6 +560,8 @@ static void performOp(TunnelLogger * logger, ref store,
BuildMode buildMode = (BuildMode) readInt(from);
logger->startWork();
+ auto drvType = drv.type();
+
/* Content-addressed derivations are trustless because their output paths
are verified by their content alone, so any derivation is free to
try to produce such a path.
@@ -590,12 +594,12 @@ static void performOp(TunnelLogger * logger, ref store,
derivations, we throw out the precomputed output paths and just
store the hashes, so there aren't two competing sources of truth an
attacker could exploit. */
- if (drv.type() == DerivationType::InputAddressed && !trusted)
+ if (!(drvType.isCA() || trusted))
throw Error("you are not privileged to build input-addressed derivations");
/* Make sure that the non-input-addressed derivations that got this far
are in fact content-addressed if we don't trust them. */
- assert(derivationIsCA(drv.type()) || trusted);
+ assert(drvType.isCA() || trusted);
/* Recompute the derivation path when we cannot trust the original. */
if (!trusted) {
@@ -604,7 +608,7 @@ static void performOp(TunnelLogger * logger, ref store,
original not-necessarily-resolved derivation to verify the drv
derivation as adequate claim to the input-addressed output
paths. */
- assert(derivationIsCA(drv.type()));
+ assert(drvType.isCA());
Derivation drv2;
static_cast(drv2) = drv;
@@ -645,7 +649,7 @@ static void performOp(TunnelLogger * logger, ref store,
Path path = absPath(readString(from));
logger->startWork();
- auto & gcStore = requireGcStore(*store);
+ auto & gcStore = require(*store);
gcStore.addIndirectRoot(path);
logger->stopWork();
@@ -663,7 +667,7 @@ static void performOp(TunnelLogger * logger, ref store,
case wopFindRoots: {
logger->startWork();
- auto & gcStore = requireGcStore(*store);
+ auto & gcStore = require(*store);
Roots roots = gcStore.findRoots(!trusted);
logger->stopWork();
@@ -695,7 +699,7 @@ static void performOp(TunnelLogger * logger, ref store,
logger->startWork();
if (options.ignoreLiveness)
throw Error("you are not allowed to ignore liveness");
- auto & gcStore = requireGcStore(*store);
+ auto & gcStore = require(*store);
gcStore.collectGarbage(options, results);
logger->stopWork();
@@ -953,11 +957,12 @@ static void performOp(TunnelLogger * logger, ref store,
logger->startWork();
if (!trusted)
throw Error("you are not privileged to add logs");
+ auto & logStore = require(*store);
{
FramedSource source(from);
StringSink sink;
source.drainInto(sink);
- store->addBuildLog(path, sink.s);
+ logStore.addBuildLog(path, sink.s);
}
logger->stopWork();
to << 1;
diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc
index 0eacff3c5..1b64819f6 100644
--- a/src/libstore/derivations.cc
+++ b/src/libstore/derivations.cc
@@ -11,72 +11,71 @@ namespace nix {
std::optional DerivationOutput::path(const Store & store, std::string_view drvName, std::string_view outputName) const
{
return std::visit(overloaded {
- [](const DerivationOutputInputAddressed & doi) -> std::optional {
+ [](const DerivationOutput::InputAddressed & doi) -> std::optional {
return { doi.path };
},
- [&](const DerivationOutputCAFixed & dof) -> std::optional {
+ [&](const DerivationOutput::CAFixed & dof) -> std::optional {
return {
dof.path(store, drvName, outputName)
};
},
- [](const DerivationOutputCAFloating & dof) -> std::optional {
+ [](const DerivationOutput::CAFloating & dof) -> std::optional {
return std::nullopt;
},
- [](const DerivationOutputDeferred &) -> std::optional {
+ [](const DerivationOutput::Deferred &) -> std::optional {
return std::nullopt;
},
- }, output);
+ }, raw());
}
-StorePath DerivationOutputCAFixed::path(const Store & store, std::string_view drvName, std::string_view outputName) const {
+StorePath DerivationOutput::CAFixed::path(const Store & store, std::string_view drvName, std::string_view outputName) const {
return store.makeFixedOutputPath(
outputPathName(drvName, outputName),
{ hash, {} });
}
-bool derivationIsCA(DerivationType dt) {
- switch (dt) {
- case DerivationType::InputAddressed: return false;
- case DerivationType::CAFixed: return true;
- case DerivationType::CAFloating: return true;
- case DerivationType::DeferredInputAddressed: return false;
- };
- // Since enums can have non-variant values, but making a `default:` would
- // disable exhaustiveness warnings.
- assert(false);
+bool DerivationType::isCA() const {
+ /* Normally we do the full `std::visit` to make sure we have
+ exhaustively handled all variants, but so long as there is a
+ variant called `ContentAddressed`, it must be the only one for
+ which `isCA` is true for this to make sense!. */
+ return std::holds_alternative(raw());
}
-bool derivationIsFixed(DerivationType dt) {
- switch (dt) {
- case DerivationType::InputAddressed: return false;
- case DerivationType::CAFixed: return true;
- case DerivationType::CAFloating: return false;
- case DerivationType::DeferredInputAddressed: return false;
- };
- assert(false);
+bool DerivationType::isFixed() const {
+ return std::visit(overloaded {
+ [](const InputAddressed & ia) {
+ return false;
+ },
+ [](const ContentAddressed & ca) {
+ return ca.fixed;
+ },
+ }, raw());
}
-bool derivationHasKnownOutputPaths(DerivationType dt) {
- switch (dt) {
- case DerivationType::InputAddressed: return true;
- case DerivationType::CAFixed: return true;
- case DerivationType::CAFloating: return false;
- case DerivationType::DeferredInputAddressed: return false;
- };
- assert(false);
+bool DerivationType::hasKnownOutputPaths() const {
+ return std::visit(overloaded {
+ [](const InputAddressed & ia) {
+ return !ia.deferred;
+ },
+ [](const ContentAddressed & ca) {
+ return ca.fixed;
+ },
+ }, raw());
}
-bool derivationIsImpure(DerivationType dt) {
- switch (dt) {
- case DerivationType::InputAddressed: return false;
- case DerivationType::CAFixed: return true;
- case DerivationType::CAFloating: return false;
- case DerivationType::DeferredInputAddressed: return false;
- };
- assert(false);
+bool DerivationType::isImpure() const {
+ return std::visit(overloaded {
+ [](const InputAddressed & ia) {
+ return false;
+ },
+ [](const ContentAddressed & ca) {
+ return !ca.pure;
+ },
+ }, raw());
}
@@ -179,35 +178,27 @@ static DerivationOutput parseDerivationOutput(const Store & store,
const auto hashType = parseHashType(hashAlgo);
if (hash != "") {
validatePath(pathS);
- return DerivationOutput {
- .output = DerivationOutputCAFixed {
- .hash = FixedOutputHash {
- .method = std::move(method),
- .hash = Hash::parseNonSRIUnprefixed(hash, hashType),
- },
+ return DerivationOutput::CAFixed {
+ .hash = FixedOutputHash {
+ .method = std::move(method),
+ .hash = Hash::parseNonSRIUnprefixed(hash, hashType),
},
};
} else {
settings.requireExperimentalFeature(Xp::CaDerivations);
assert(pathS == "");
- return DerivationOutput {
- .output = DerivationOutputCAFloating {
- .method = std::move(method),
- .hashType = std::move(hashType),
- },
+ return DerivationOutput::CAFloating {
+ .method = std::move(method),
+ .hashType = std::move(hashType),
};
}
} else {
if (pathS == "") {
- return DerivationOutput {
- .output = DerivationOutputDeferred { }
- };
+ return DerivationOutput::Deferred { };
}
validatePath(pathS);
- return DerivationOutput {
- .output = DerivationOutputInputAddressed {
- .path = store.parseStorePath(pathS),
- }
+ return DerivationOutput::InputAddressed {
+ .path = store.parseStorePath(pathS),
};
}
}
@@ -335,27 +326,27 @@ std::string Derivation::unparse(const Store & store, bool maskOutputs,
if (first) first = false; else s += ',';
s += '('; printUnquotedString(s, i.first);
std::visit(overloaded {
- [&](const DerivationOutputInputAddressed & doi) {
+ [&](const DerivationOutput::InputAddressed & doi) {
s += ','; printUnquotedString(s, maskOutputs ? "" : store.printStorePath(doi.path));
s += ','; printUnquotedString(s, "");
s += ','; printUnquotedString(s, "");
},
- [&](const DerivationOutputCAFixed & dof) {
+ [&](const DerivationOutput::CAFixed & dof) {
s += ','; printUnquotedString(s, maskOutputs ? "" : store.printStorePath(dof.path(store, name, i.first)));
s += ','; printUnquotedString(s, dof.hash.printMethodAlgo());
s += ','; printUnquotedString(s, dof.hash.hash.to_string(Base16, false));
},
- [&](const DerivationOutputCAFloating & dof) {
+ [&](const DerivationOutput::CAFloating & dof) {
s += ','; printUnquotedString(s, "");
s += ','; printUnquotedString(s, makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType));
s += ','; printUnquotedString(s, "");
},
- [&](const DerivationOutputDeferred &) {
+ [&](const DerivationOutput::Deferred &) {
s += ','; printUnquotedString(s, "");
s += ','; printUnquotedString(s, "");
s += ','; printUnquotedString(s, "");
}
- }, i.second.output);
+ }, i.second.raw());
s += ')';
}
@@ -423,13 +414,13 @@ DerivationType BasicDerivation::type() const
std::optional floatingHashType;
for (auto & i : outputs) {
std::visit(overloaded {
- [&](const DerivationOutputInputAddressed &) {
+ [&](const DerivationOutput::InputAddressed &) {
inputAddressedOutputs.insert(i.first);
},
- [&](const DerivationOutputCAFixed &) {
+ [&](const DerivationOutput::CAFixed &) {
fixedCAOutputs.insert(i.first);
},
- [&](const DerivationOutputCAFloating & dof) {
+ [&](const DerivationOutput::CAFloating & dof) {
floatingCAOutputs.insert(i.first);
if (!floatingHashType) {
floatingHashType = dof.hashType;
@@ -438,27 +429,37 @@ DerivationType BasicDerivation::type() const
throw Error("All floating outputs must use the same hash type");
}
},
- [&](const DerivationOutputDeferred &) {
+ [&](const DerivationOutput::Deferred &) {
deferredIAOutputs.insert(i.first);
},
- }, i.second.output);
+ }, i.second.raw());
}
if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
throw Error("Must have at least one output");
} else if (! inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
- return DerivationType::InputAddressed;
+ return DerivationType::InputAddressed {
+ .deferred = false,
+ };
} else if (inputAddressedOutputs.empty() && ! fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
if (fixedCAOutputs.size() > 1)
// FIXME: Experimental feature?
throw Error("Only one fixed output is allowed for now");
if (*fixedCAOutputs.begin() != "out")
throw Error("Single fixed output must be named \"out\"");
- return DerivationType::CAFixed;
+ return DerivationType::ContentAddressed {
+ .pure = false,
+ .fixed = true,
+ };
} else if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && ! floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
- return DerivationType::CAFloating;
+ return DerivationType::ContentAddressed {
+ .pure = true,
+ .fixed = false,
+ };
} else if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && !deferredIAOutputs.empty()) {
- return DerivationType::DeferredInputAddressed;
+ return DerivationType::InputAddressed {
+ .deferred = true,
+ };
} else {
throw Error("Can't mix derivation output types");
}
@@ -510,13 +511,13 @@ static const DrvHashModulo pathDerivationModulo(Store & store, const StorePath &
*/
DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool maskOutputs)
{
- bool isDeferred = false;
+ auto type = drv.type();
+
/* Return a fixed hash for fixed-output derivations. */
- switch (drv.type()) {
- case DerivationType::CAFixed: {
+ if (type.isFixed()) {
std::map outputHashes;
for (const auto & i : drv.outputs) {
- auto & dof = std::get(i.second.output);
+ auto & dof = std::get(i.second.raw());
auto hash = hashString(htSHA256, "fixed:out:"
+ dof.hash.printMethodAlgo() + ":"
+ dof.hash.hash.to_string(Base16, false) + ":"
@@ -525,33 +526,37 @@ DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool m
}
return outputHashes;
}
- case DerivationType::CAFloating:
- isDeferred = true;
- break;
- case DerivationType::InputAddressed:
- break;
- case DerivationType::DeferredInputAddressed:
- break;
- }
+
+ auto kind = std::visit(overloaded {
+ [](const DerivationType::InputAddressed & ia) {
+ /* This might be a "pesimistically" deferred output, so we don't
+ "taint" the kind yet. */
+ return DrvHash::Kind::Regular;
+ },
+ [](const DerivationType::ContentAddressed & ca) {
+ return ca.fixed
+ ? DrvHash::Kind::Regular
+ : DrvHash::Kind::Deferred;
+ },
+ }, drv.type().raw());
/* For other derivations, replace the inputs paths with recursive
calls to this function. */
std::map inputs2;
- for (auto & i : drv.inputDrvs) {
- const auto & res = pathDerivationModulo(store, i.first);
+ for (auto & [drvPath, inputOutputs0] : drv.inputDrvs) {
+ // Avoid lambda capture restriction with standard / Clang
+ auto & inputOutputs = inputOutputs0;
+ const auto & res = pathDerivationModulo(store, drvPath);
std::visit(overloaded {
// Regular non-CA derivation, replace derivation
- [&](const Hash & drvHash) {
- inputs2.insert_or_assign(drvHash.to_string(Base16, false), i.second);
- },
- [&](const DeferredHash & deferredHash) {
- isDeferred = true;
- inputs2.insert_or_assign(deferredHash.hash.to_string(Base16, false), i.second);
+ [&](const DrvHash & drvHash) {
+ kind |= drvHash.kind;
+ inputs2.insert_or_assign(drvHash.hash.to_string(Base16, false), inputOutputs);
},
// CA derivation's output hashes
[&](const CaOutputHashes & outputHashes) {
std::set justOut = { "out" };
- for (auto & output : i.second) {
+ for (auto & output : inputOutputs) {
/* Put each one in with a single "out" output.. */
const auto h = outputHashes.at(output);
inputs2.insert_or_assign(
@@ -559,15 +564,24 @@ DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool m
justOut);
}
},
- }, res);
+ }, res.raw());
}
auto hash = hashString(htSHA256, drv.unparse(store, maskOutputs, &inputs2));
- if (isDeferred)
- return DeferredHash { hash };
- else
- return hash;
+ return DrvHash { .hash = hash, .kind = kind };
+}
+
+
+void operator |= (DrvHash::Kind & self, const DrvHash::Kind & other) noexcept
+{
+ switch (other) {
+ case DrvHash::Kind::Regular:
+ break;
+ case DrvHash::Kind::Deferred:
+ self = other;
+ break;
+ }
}
@@ -575,20 +589,15 @@ std::map staticOutputHashes(Store & store, const Derivation &
{
std::map res;
std::visit(overloaded {
- [&](const Hash & drvHash) {
+ [&](const DrvHash & drvHash) {
for (auto & outputName : drv.outputNames()) {
- res.insert({outputName, drvHash});
- }
- },
- [&](const DeferredHash & deferredHash) {
- for (auto & outputName : drv.outputNames()) {
- res.insert({outputName, deferredHash.hash});
+ res.insert({outputName, drvHash.hash});
}
},
[&](const CaOutputHashes & outputHashes) {
res = outputHashes;
},
- }, hashDerivationModulo(store, drv, true));
+ }, hashDerivationModulo(store, drv, true).raw());
return res;
}
@@ -669,27 +678,27 @@ void writeDerivation(Sink & out, const Store & store, const BasicDerivation & dr
for (auto & i : drv.outputs) {
out << i.first;
std::visit(overloaded {
- [&](const DerivationOutputInputAddressed & doi) {
+ [&](const DerivationOutput::InputAddressed & doi) {
out << store.printStorePath(doi.path)
<< ""
<< "";
},
- [&](const DerivationOutputCAFixed & dof) {
+ [&](const DerivationOutput::CAFixed & dof) {
out << store.printStorePath(dof.path(store, drv.name, i.first))
<< dof.hash.printMethodAlgo()
<< dof.hash.hash.to_string(Base16, false);
},
- [&](const DerivationOutputCAFloating & dof) {
+ [&](const DerivationOutput::CAFloating & dof) {
out << ""
<< (makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType))
<< "";
},
- [&](const DerivationOutputDeferred &) {
+ [&](const DerivationOutput::Deferred &) {
out << ""
<< ""
<< "";
},
- }, i.second.output);
+ }, i.second.raw());
}
worker_proto::write(store, out, drv.inputSrcs);
out << drv.platform << drv.builder << drv.args;
@@ -737,45 +746,63 @@ static void rewriteDerivation(Store & store, BasicDerivation & drv, const String
auto hashModulo = hashDerivationModulo(store, Derivation(drv), true);
for (auto & [outputName, output] : drv.outputs) {
- if (std::holds_alternative(output.output)) {
- Hash h = std::get(hashModulo);
+ if (std::holds_alternative(output.raw())) {
+ auto & h = hashModulo.requireNoFixedNonDeferred();
auto outPath = store.makeOutputPath(outputName, h, drv.name);
drv.env[outputName] = store.printStorePath(outPath);
- output = DerivationOutput {
- .output = DerivationOutputInputAddressed {
- .path = std::move(outPath),
- },
+ output = DerivationOutput::InputAddressed {
+ .path = std::move(outPath),
};
}
}
}
+const Hash & DrvHashModulo::requireNoFixedNonDeferred() const {
+ auto * drvHashOpt = std::get_if(&raw());
+ assert(drvHashOpt);
+ assert(drvHashOpt->kind == DrvHash::Kind::Regular);
+ return drvHashOpt->hash;
+}
+
+static bool tryResolveInput(
+ Store & store, StorePathSet & inputSrcs, StringMap & inputRewrites,
+ const StorePath & inputDrv, const StringSet & inputOutputs)
+{
+ auto inputDrvOutputs = store.queryPartialDerivationOutputMap(inputDrv);
+
+ auto getOutput = [&](const std::string & outputName) {
+ auto & actualPathOpt = inputDrvOutputs.at(outputName);
+ if (!actualPathOpt)
+ warn("output %s of input %s missing, aborting the resolving",
+ outputName,
+ store.printStorePath(inputDrv)
+ );
+ return actualPathOpt;
+ };
+
+ for (auto & outputName : inputOutputs) {
+ auto actualPathOpt = getOutput(outputName);
+ if (!actualPathOpt) return false;
+ auto actualPath = *actualPathOpt;
+ inputRewrites.emplace(
+ downstreamPlaceholder(store, inputDrv, outputName),
+ store.printStorePath(actualPath));
+ inputSrcs.insert(std::move(actualPath));
+ }
+
+ return true;
+}
+
std::optional Derivation::tryResolve(Store & store) {
BasicDerivation resolved { *this };
// Input paths that we'll want to rewrite in the derivation
StringMap inputRewrites;
- for (auto & input : inputDrvs) {
- auto inputDrvOutputs = store.queryPartialDerivationOutputMap(input.first);
- StringSet newOutputNames;
- for (auto & outputName : input.second) {
- auto actualPathOpt = inputDrvOutputs.at(outputName);
- if (!actualPathOpt) {
- warn("output %s of input %s missing, aborting the resolving",
- outputName,
- store.printStorePath(input.first)
- );
- return std::nullopt;
- }
- auto actualPath = *actualPathOpt;
- inputRewrites.emplace(
- downstreamPlaceholder(store, input.first, outputName),
- store.printStorePath(actualPath));
- resolved.inputSrcs.insert(std::move(actualPath));
- }
- }
+ for (auto & [inputDrv, inputOutputs] : inputDrvs)
+ if (!tryResolveInput(store, resolved.inputSrcs, inputRewrites, inputDrv, inputOutputs))
+ return std::nullopt;
rewriteDerivation(store, resolved, inputRewrites);
diff --git a/src/libstore/derivations.hh b/src/libstore/derivations.hh
index 132de82b6..8dea90abf 100644
--- a/src/libstore/derivations.hh
+++ b/src/libstore/derivations.hh
@@ -4,6 +4,7 @@
#include "types.hh"
#include "hash.hh"
#include "content-address.hh"
+#include "repair-flag.hh"
#include "sync.hh"
#include