Split {,local-}derivation-goal.{cc,hh}

This separates the scheduling logic (including simple hook pathway) from
the local-store needing code.

This should be the final split for now. I'm reasonably happy with how
it's turning out, even before I'm done moving code into
`local-derivation-goal`. Benefits:

1. This will help "witness" that the hook case is indeed a lot simpler,
   and also compensate for the increased complexity that comes from
   content-addressed derivation outputs.

2. It also moves us ever so slightly towards a world where we could use
   off-the-shelf storage or sandboxing, since `local-derivation-goal`
   would be gutted in those cases, but `derivation-goal` should remain
   nearly the same.

The new `#if 0` in the new files will be deleted in the following
commit. I keep it here so if it turns out more stuff can be moved over,
it's easy to do so in a way that preserves ordering --- and thus
prevents conflicts.

N.B.
```sh
git diff HEAD^^ --color-moved --find-copies-harder --patience --stat
```
makes nicer output.
This commit is contained in:
John Ericson 2021-02-26 15:20:33 +00:00
parent 05cc5a8587
commit 68f4c728ec
7 changed files with 329 additions and 3045 deletions

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,8 @@
#include "parsed-derivations.hh" #include "parsed-derivations.hh"
#include "lock.hh" #include "lock.hh"
#include "local-store.hh" #include "store-api.hh"
#include "pathlocks.hh"
#include "goal.hh" #include "goal.hh"
namespace nix { namespace nix {
@ -79,18 +80,6 @@ struct DerivationGoal : public Goal
std::map<std::string, InitialOutput> initialOutputs; std::map<std::string, InitialOutput> initialOutputs;
/* User selected for running the builder. */
std::unique_ptr<UserLock> buildUser;
/* The process ID of the builder. */
Pid pid;
/* The temporary directory. */
Path tmpDir;
/* The path of the temporary directory in the sandbox. */
Path tmpDirInSandbox;
/* File descriptor for the log file. */ /* File descriptor for the log file. */
AutoCloseFD fdLogFile; AutoCloseFD fdLogFile;
std::shared_ptr<BufferedSink> logFileSink, logSink; std::shared_ptr<BufferedSink> logFileSink, logSink;
@ -106,79 +95,15 @@ struct DerivationGoal : public Goal
std::string currentHookLine; std::string currentHookLine;
/* Pipe for the builder's standard output/error. */
Pipe builderOut;
/* Pipe for synchronising updates to the builder namespaces. */
Pipe userNamespaceSync;
/* The mount namespace of the builder, used to add additional
paths to the sandbox as a result of recursive Nix calls. */
AutoCloseFD sandboxMountNamespace;
/* On Linux, whether we're doing the build in its own user
namespace. */
bool usingUserNamespace = true;
/* The build hook. */ /* The build hook. */
std::unique_ptr<HookInstance> hook; std::unique_ptr<HookInstance> hook;
/* Whether we're currently doing a chroot build. */
bool useChroot = false;
Path chrootRootDir;
/* RAII object to delete the chroot directory. */
std::shared_ptr<AutoDelete> autoDelChroot;
/* The sort of derivation we are building. */ /* The sort of derivation we are building. */
DerivationType derivationType; DerivationType derivationType;
/* Whether to run the build in a private network namespace. */
bool privateNetwork = false;
typedef void (DerivationGoal::*GoalState)(); typedef void (DerivationGoal::*GoalState)();
GoalState state; GoalState state;
/* Stuff we need to pass to initChild(). */
struct ChrootPath {
Path source;
bool optional;
ChrootPath(Path source = "", bool optional = false)
: source(source), optional(optional)
{ }
};
typedef map<Path, ChrootPath> DirsInChroot; // maps target path to source path
DirsInChroot dirsInChroot;
typedef map<string, string> Environment;
Environment env;
#if __APPLE__
typedef string SandboxProfile;
SandboxProfile additionalSandboxProfile;
#endif
/* Hash rewriting. */
StringMap inputRewrites, outputRewrites;
typedef map<StorePath, StorePath> RedirectedOutputs;
RedirectedOutputs redirectedOutputs;
/* The outputs paths used during the build.
- Input-addressed derivations or fixed content-addressed outputs are
sometimes built when some of their outputs already exist, and can not
be hidden via sandboxing. We use temporary locations instead and
rewrite after the build. Otherwise the regular predetermined paths are
put here.
- Floating content-addressed derivations do not know their final build
output paths until the outputs are hashed, so random locations are
used, and then renamed. The randomness helps guard against hidden
self-references.
*/
OutputPathMap scratchOutputs;
/* The final output paths of the build. /* The final output paths of the build.
- For input-addressed derivations, always the precomputed paths - For input-addressed derivations, always the precomputed paths
@ -191,11 +116,6 @@ struct DerivationGoal : public Goal
BuildMode buildMode; BuildMode buildMode;
/* If we're repairing without a chroot, there may be outputs that
are valid but corrupt. So we redirect these outputs to
temporary paths. */
StorePathSet redirectedBadOutputs;
BuildResult result; BuildResult result;
/* The current round, if we're building multiple times. */ /* The current round, if we're building multiple times. */
@ -203,17 +123,6 @@ struct DerivationGoal : public Goal
size_t nrRounds; size_t nrRounds;
/* Path registration info from the previous round, if we're
building multiple times. Since this contains the hash, it
allows us to compare whether two rounds produced the same
result. */
std::map<Path, ValidPathInfo> prevInfos;
uid_t sandboxUid() { return usingUserNamespace ? 1000 : buildUser->getUID(); }
gid_t sandboxGid() { return usingUserNamespace ? 100 : buildUser->getGID(); }
const static Path homeDir;
std::unique_ptr<MaintainCount<uint64_t>> mcExpectedBuilds, mcRunningBuilds; std::unique_ptr<MaintainCount<uint64_t>> mcExpectedBuilds, mcRunningBuilds;
std::unique_ptr<Activity> act; std::unique_ptr<Activity> act;
@ -226,39 +135,13 @@ struct DerivationGoal : public Goal
/* The remote machine on which we're building. */ /* The remote machine on which we're building. */
std::string machineName; std::string machineName;
/* The recursive Nix daemon socket. */
AutoCloseFD daemonSocket;
/* The daemon main thread. */
std::thread daemonThread;
/* The daemon worker threads. */
std::vector<std::thread> daemonWorkerThreads;
/* Paths that were added via recursive Nix calls. */
StorePathSet addedPaths;
/* Recursive Nix calls are only allowed to build or realize paths
in the original input closure or added via a recursive Nix call
(so e.g. you can't do 'nix-store -r /nix/store/<bla>' where
/nix/store/<bla> is some arbitrary path in a binary cache). */
bool isAllowed(const StorePath & path)
{
return inputPaths.count(path) || addedPaths.count(path);
}
friend struct RestrictedStore;
DerivationGoal(const StorePath & drvPath, DerivationGoal(const StorePath & drvPath,
const StringSet & wantedOutputs, Worker & worker, const StringSet & wantedOutputs, Worker & worker,
BuildMode buildMode = bmNormal); BuildMode buildMode = bmNormal);
DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv, DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
const StringSet & wantedOutputs, Worker & worker, const StringSet & wantedOutputs, Worker & worker,
BuildMode buildMode = bmNormal); BuildMode buildMode = bmNormal);
~DerivationGoal(); virtual ~DerivationGoal();
/* Whether we need to perform hash rewriting if there are valid output paths. */
bool needsHashRewrite();
void timedOut(Error && ex) override; void timedOut(Error && ex) override;
@ -280,7 +163,7 @@ struct DerivationGoal : public Goal
void closureRepaired(); void closureRepaired();
void inputsRealised(); void inputsRealised();
void tryToBuild(); void tryToBuild();
void tryLocalBuild(); virtual void tryLocalBuild();
void buildDone(); void buildDone();
void resolvedFinished(); void resolvedFinished();
@ -288,40 +171,11 @@ struct DerivationGoal : public Goal
/* Is the build hook willing to perform the build? */ /* Is the build hook willing to perform the build? */
HookReply tryBuildHook(); HookReply tryBuildHook();
/* Start building a derivation. */ virtual int getChildStatus();
void startBuilder();
/* Fill in the environment for the builder. */
void initEnv();
/* Setup tmp dir location. */
void initTmpDir();
/* Write a JSON file containing the derivation attributes. */
void writeStructuredAttrs();
void startDaemon();
void stopDaemon();
/* Add 'path' to the set of paths that may be referenced by the
outputs, and make it appear in the sandbox. */
void addDependency(const StorePath & path);
/* Make a file owned by the builder. */
void chownToBuilder(const Path & path);
/* Run the builder's process. */
void runChild();
/* Check that the derivation outputs all exist and register them /* Check that the derivation outputs all exist and register them
as valid. */ as valid. */
void registerOutputs(); virtual void registerOutputs();
/* Check that an output meets the requirements specified by the
'outputChecks' attribute (or the legacy
'{allowed,disallowed}{References,Requisites}' attributes). */
void checkOutputs(const std::map<std::string, ValidPathInfo> & outputs);
/* Open a log file and a pipe to it. */ /* Open a log file and a pipe to it. */
Path openLogFile(); Path openLogFile();
@ -329,8 +183,18 @@ struct DerivationGoal : public Goal
/* Close the log file. */ /* Close the log file. */
void closeLogFile(); void closeLogFile();
/* Delete the temporary directory, if we have one. */ /* Close the read side of the logger pipe. */
void deleteTmpDir(bool force); virtual void closeReadPipes();
/* Cleanup hooks for buildDone() */
virtual void cleanupHookFinally();
virtual void cleanupPreChildKill();
virtual void cleanupPostChildKill();
virtual bool cleanupDecideWhetherDiskFull();
virtual void cleanupPostOutputsRegisteredModeCheck();
virtual void cleanupPostOutputsRegisteredModeNonCheck();
virtual bool isReadDesc(int fd);
/* Callback used by the worker to write to the log. */ /* Callback used by the worker to write to the log. */
void handleChildOutput(int fd, const string & data) override; void handleChildOutput(int fd, const string & data) override;
@ -347,17 +211,7 @@ struct DerivationGoal : public Goal
void checkPathValidity(); void checkPathValidity();
/* Forcibly kill the child process, if any. */ /* Forcibly kill the child process, if any. */
void killChild(); virtual void killChild();
/* Create alternative path calculated from but distinct from the
input, so we can avoid overwriting outputs (or other store paths)
that already exist. */
StorePath makeFallbackPath(const StorePath & path);
/* Make a path to another based on the output name along with the
derivation hash. */
/* FIXME add option to randomize, so we can audit whether our
rewrites caught everything */
StorePath makeFallbackPath(std::string_view outputName);
void repairClosure(); void repairClosure();
@ -370,4 +224,6 @@ struct DerivationGoal : public Goal
StorePathSet exportReferences(const StorePathSet & storePaths); StorePathSet exportReferences(const StorePathSet & storePaths);
}; };
MakeError(NotDeterministic, BuildError);
} }

View file

@ -2,6 +2,7 @@
#include "worker.hh" #include "worker.hh"
#include "substitution-goal.hh" #include "substitution-goal.hh"
#include "derivation-goal.hh" #include "derivation-goal.hh"
#include "local-store.hh"
namespace nix { namespace nix {

View file

@ -1,4 +1,4 @@
#include "derivation-goal.hh" #include "local-derivation-goal.hh"
#include "hook-instance.hh" #include "hook-instance.hh"
#include "worker.hh" #include "worker.hh"
#include "builtins.hh" #include "builtins.hh"
@ -75,7 +75,6 @@ void handleDiffHook(
diffHookOptions.uid = uid; diffHookOptions.uid = uid;
diffHookOptions.gid = gid; diffHookOptions.gid = gid;
diffHookOptions.chdir = "/"; diffHookOptions.chdir = "/";
auto diffRes = runProgram(diffHookOptions); auto diffRes = runProgram(diffHookOptions);
if (!statusOk(diffRes.first)) if (!statusOk(diffRes.first))
throw ExecError(diffRes.first, throw ExecError(diffRes.first,
@ -94,8 +93,9 @@ void handleDiffHook(
} }
} }
const Path DerivationGoal::homeDir = "/homeless-shelter"; const Path LocalDerivationGoal::homeDir = "/homeless-shelter";
#if 0
DerivationGoal::DerivationGoal(const StorePath & drvPath, DerivationGoal::DerivationGoal(const StorePath & drvPath,
const StringSet & wantedOutputs, Worker & worker, BuildMode buildMode) const StringSet & wantedOutputs, Worker & worker, BuildMode buildMode)
: Goal(worker) : Goal(worker)
@ -138,19 +138,20 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath, const BasicDerivation
garbage-collected. (See isActiveTempFile() in gc.cc.) */ garbage-collected. (See isActiveTempFile() in gc.cc.) */
worker.store.addTempRoot(this->drvPath); worker.store.addTempRoot(this->drvPath);
} }
#endif
DerivationGoal::~DerivationGoal() LocalDerivationGoal::~LocalDerivationGoal()
{ {
/* Careful: we should never ever throw an exception from a /* Careful: we should never ever throw an exception from a
destructor. */ destructor. */
try { deleteTmpDir(false); } catch (...) { ignoreException(); }
try { killChild(); } catch (...) { ignoreException(); } try { killChild(); } catch (...) { ignoreException(); }
try { stopDaemon(); } catch (...) { ignoreException(); } try { stopDaemon(); } catch (...) { ignoreException(); }
try { deleteTmpDir(false); } catch (...) { ignoreException(); }
try { closeLogFile(); } catch (...) { ignoreException(); }
} }
#if 0
string DerivationGoal::key() string DerivationGoal::key()
{ {
/* Ensure that derivations get built in order of their name, /* Ensure that derivations get built in order of their name,
@ -159,9 +160,10 @@ string DerivationGoal::key()
derivation goals (due to "b$"). */ derivation goals (due to "b$"). */
return "b$" + std::string(drvPath.name()) + "$" + worker.store.printStorePath(drvPath); return "b$" + std::string(drvPath.name()) + "$" + worker.store.printStorePath(drvPath);
} }
#endif
inline bool DerivationGoal::needsHashRewrite() inline bool LocalDerivationGoal::needsHashRewrite()
{ {
#if __linux__ #if __linux__
return !useChroot; return !useChroot;
@ -172,7 +174,15 @@ inline bool DerivationGoal::needsHashRewrite()
} }
void DerivationGoal::killChild() LocalStore & LocalDerivationGoal::getLocalStore()
{
auto p = dynamic_cast<LocalStore *>(&worker.store);
assert(p);
return *p;
}
void LocalDerivationGoal::killChild()
{ {
if (pid != -1) { if (pid != -1) {
worker.childTerminated(this); worker.childTerminated(this);
@ -193,17 +203,11 @@ void DerivationGoal::killChild()
assert(pid == -1); assert(pid == -1);
} }
hook.reset(); DerivationGoal::killChild();
}
void DerivationGoal::timedOut(Error && ex)
{
killChild();
done(BuildResult::TimedOut, ex);
} }
#if 0
void DerivationGoal::work() void DerivationGoal::work()
{ {
(this->*state)(); (this->*state)();
@ -695,15 +699,9 @@ void DerivationGoal::tryToBuild()
state = &DerivationGoal::tryLocalBuild; state = &DerivationGoal::tryLocalBuild;
worker.wakeUp(shared_from_this()); worker.wakeUp(shared_from_this());
} }
#endif
void DerivationGoal::tryLocalBuild() { void LocalDerivationGoal::tryLocalBuild() {
/* Make sure that we are allowed to start a build. */
if (!dynamic_cast<LocalStore *>(&worker.store)) {
throw Error(
"unable to build with a primary store that isn't a local store; "
"either pass a different '--store' or enable remote builds."
"\nhttps://nixos.org/nix/manual/#chap-distributed-builds");
}
unsigned int curBuilds = worker.getNrLocalBuilds(); unsigned int curBuilds = worker.getNrLocalBuilds();
if (curBuilds >= settings.maxBuildJobs) { if (curBuilds >= settings.maxBuildJobs) {
worker.waitForBuildSlot(shared_from_this()); worker.waitForBuildSlot(shared_from_this());
@ -757,7 +755,6 @@ void DerivationGoal::tryLocalBuild() {
started(); started();
} }
static void chmod_(const Path & path, mode_t mode) static void chmod_(const Path & path, mode_t mode)
{ {
if (chmod(path.c_str(), mode) == -1) if (chmod(path.c_str(), mode) == -1)
@ -785,51 +782,125 @@ static void movePath(const Path & src, const Path & dst)
} }
void replaceValidPath(const Path & storePath, const Path & tmpPath) extern void replaceValidPath(const Path & storePath, const Path & tmpPath);
int LocalDerivationGoal::getChildStatus()
{ {
/* We can't atomically replace storePath (the original) with return hook ? DerivationGoal::getChildStatus() : pid.kill();
tmpPath (the replacement), so we have to move it out of the }
way first. We'd better not be interrupted here, because if
we're repairing (say) Glibc, we end up with a broken system. */
Path oldPath = (format("%1%.old-%2%-%3%") % storePath % getpid() % random()).str();
if (pathExists(storePath))
movePath(storePath, oldPath);
try { void LocalDerivationGoal::closeReadPipes()
movePath(tmpPath, storePath); {
} catch (...) { if (hook) {
try { DerivationGoal::closeReadPipes();
// attempt to recover } else
movePath(oldPath, storePath); builderOut.readSide = -1;
} catch (...) {
ignoreException();
}
throw;
}
deletePath(oldPath);
} }
MakeError(NotDeterministic, BuildError); void LocalDerivationGoal::cleanupHookFinally()
{
/* Release the build user at the end of this function. We don't do
it right away because we don't want another build grabbing this
uid and then messing around with our output. */
buildUser.reset();
}
void LocalDerivationGoal::cleanupPreChildKill()
{
sandboxMountNamespace = -1;
}
void LocalDerivationGoal::cleanupPostChildKill()
{
/* When running under a build user, make sure that all processes
running under that uid are gone. This is to prevent a
malicious user from leaving behind a process that keeps files
open and modifies them after they have been chown'ed to
root. */
if (buildUser) buildUser->kill();
/* Terminate the recursive Nix daemon. */
stopDaemon();
}
bool LocalDerivationGoal::cleanupDecideWhetherDiskFull()
{
bool diskFull = false;
/* Heuristically check whether the build failure may have
been caused by a disk full condition. We have no way
of knowing whether the build actually got an ENOSPC.
So instead, check if the disk is (nearly) full now. If
so, we don't mark this build as a permanent failure. */
#if HAVE_STATVFS
{
auto & localStore = getLocalStore();
uint64_t required = 8ULL * 1024 * 1024; // FIXME: make configurable
struct statvfs st;
if (statvfs(localStore.realStoreDir.c_str(), &st) == 0 &&
(uint64_t) st.f_bavail * st.f_bsize < required)
diskFull = true;
if (statvfs(tmpDir.c_str(), &st) == 0 &&
(uint64_t) st.f_bavail * st.f_bsize < required)
diskFull = true;
}
#endif
deleteTmpDir(false);
/* Move paths out of the chroot for easier debugging of
build failures. */
if (useChroot && buildMode == bmNormal)
for (auto & [_, status] : initialOutputs) {
if (!status.known) continue;
if (buildMode != bmCheck && status.known->isValid()) continue;
auto p = worker.store.printStorePath(status.known->path);
if (pathExists(chrootRootDir + p))
rename((chrootRootDir + p).c_str(), p.c_str());
}
return diskFull;
}
void LocalDerivationGoal::cleanupPostOutputsRegisteredModeCheck()
{
deleteTmpDir(true);
}
void LocalDerivationGoal::cleanupPostOutputsRegisteredModeNonCheck()
{
/* Delete unused redirected outputs (when doing hash rewriting). */
for (auto & i : redirectedOutputs)
deletePath(worker.store.Store::toRealPath(i.second));
/* Delete the chroot (if we were using one). */
autoDelChroot.reset(); /* this runs the destructor */
cleanupPostOutputsRegisteredModeCheck();
}
#if 0
void DerivationGoal::buildDone() void DerivationGoal::buildDone()
{ {
trace("build done"); trace("build done");
/* Release the build user at the end of this function. We don't do Finally releaseBuildUser([&](){ this->cleanupHookFinally(); });
it right away because we don't want another build grabbing this
uid and then messing around with our output. */
Finally releaseBuildUser([&]() { buildUser.reset(); });
sandboxMountNamespace = -1; cleanupPreChildKill();
/* Since we got an EOF on the logger pipe, the builder is presumed /* Since we got an EOF on the logger pipe, the builder is presumed
to have terminated. In fact, the builder could also have to have terminated. In fact, the builder could also have
simply have closed its end of the pipe, so just to be sure, simply have closed its end of the pipe, so just to be sure,
kill it. */ kill it. */
int status = hook ? hook->pid.kill() : pid.kill(); int status = getChildStatus();
debug("builder process for '%s' finished", worker.store.printStorePath(drvPath)); debug("builder process for '%s' finished", worker.store.printStorePath(drvPath));
@ -840,24 +911,12 @@ void DerivationGoal::buildDone()
worker.childTerminated(this); worker.childTerminated(this);
/* Close the read side of the logger pipe. */ /* Close the read side of the logger pipe. */
if (hook) { closeReadPipes();
hook->builderOut.readSide = -1;
hook->fromHook.readSide = -1;
} else
builderOut.readSide = -1;
/* Close the log file. */ /* Close the log file. */
closeLogFile(); closeLogFile();
/* When running under a build user, make sure that all processes cleanupPostChildKill();
running under that uid are gone. This is to prevent a
malicious user from leaving behind a process that keeps files
open and modifies them after they have been chown'ed to
root. */
if (buildUser) buildUser->kill();
/* Terminate the recursive Nix daemon. */
stopDaemon();
bool diskFull = false; bool diskFull = false;
@ -866,36 +925,7 @@ void DerivationGoal::buildDone()
/* Check the exit status. */ /* Check the exit status. */
if (!statusOk(status)) { if (!statusOk(status)) {
/* Heuristically check whether the build failure may have diskFull |= cleanupDecideWhetherDiskFull();
been caused by a disk full condition. We have no way
of knowing whether the build actually got an ENOSPC.
So instead, check if the disk is (nearly) full now. If
so, we don't mark this build as a permanent failure. */
#if HAVE_STATVFS
if (auto localStore = dynamic_cast<LocalStore *>(&worker.store)) {
uint64_t required = 8ULL * 1024 * 1024; // FIXME: make configurable
struct statvfs st;
if (statvfs(localStore->realStoreDir.c_str(), &st) == 0 &&
(uint64_t) st.f_bavail * st.f_bsize < required)
diskFull = true;
if (statvfs(tmpDir.c_str(), &st) == 0 &&
(uint64_t) st.f_bavail * st.f_bsize < required)
diskFull = true;
}
#endif
deleteTmpDir(false);
/* Move paths out of the chroot for easier debugging of
build failures. */
if (useChroot && buildMode == bmNormal)
for (auto & [_, status] : initialOutputs) {
if (!status.known) continue;
if (buildMode != bmCheck && status.known->isValid()) continue;
auto p = worker.store.printStorePath(status.known->path);
if (pathExists(chrootRootDir + p))
rename((chrootRootDir + p).c_str(), p.c_str());
}
auto msg = fmt("builder for '%s' %s", auto msg = fmt("builder for '%s' %s",
yellowtxt(worker.store.printStorePath(drvPath)), yellowtxt(worker.store.printStorePath(drvPath)),
@ -975,19 +1005,12 @@ void DerivationGoal::buildDone()
} }
if (buildMode == bmCheck) { if (buildMode == bmCheck) {
deleteTmpDir(true); cleanupPostOutputsRegisteredModeCheck();
done(BuildResult::Built); done(BuildResult::Built);
return; return;
} }
/* Delete unused redirected outputs (when doing hash rewriting). */ cleanupPostOutputsRegisteredModeNonCheck();
for (auto & i : redirectedOutputs)
deletePath(worker.store.Store::toRealPath(i.second));
/* Delete the chroot (if we were using one). */
autoDelChroot.reset(); /* this runs the destructor */
deleteTmpDir(true);
/* Repeat the build if necessary. */ /* Repeat the build if necessary. */
if (curRound++ < nrRounds) { if (curRound++ < nrRounds) {
@ -1169,15 +1192,17 @@ HookReply DerivationGoal::tryBuildHook()
return rpAccept; return rpAccept;
} }
#endif
int childEntry(void * arg) int childEntry(void * arg)
{ {
((DerivationGoal *) arg)->runChild(); ((LocalDerivationGoal *) arg)->runChild();
return 1; return 1;
} }
#if 0
StorePathSet DerivationGoal::exportReferences(const StorePathSet & storePaths) StorePathSet DerivationGoal::exportReferences(const StorePathSet & storePaths)
{ {
StorePathSet paths; StorePathSet paths;
@ -1212,6 +1237,7 @@ StorePathSet DerivationGoal::exportReferences(const StorePathSet & storePaths)
return paths; return paths;
} }
#endif
static std::once_flag dns_resolve_flag; static std::once_flag dns_resolve_flag;
@ -1230,7 +1256,7 @@ static void preloadNSS() {
} }
void linkOrCopy(const Path & from, const Path & to) static void linkOrCopy(const Path & from, const Path & to)
{ {
if (link(from.c_str(), to.c_str()) == -1) { if (link(from.c_str(), to.c_str()) == -1) {
/* Hard-linking fails if we exceed the maximum link count on a /* Hard-linking fails if we exceed the maximum link count on a
@ -1247,7 +1273,7 @@ void linkOrCopy(const Path & from, const Path & to)
} }
void DerivationGoal::startBuilder() void LocalDerivationGoal::startBuilder()
{ {
/* Right platform? */ /* Right platform? */
if (!parsedDrv->canBuildLocally(worker.store)) if (!parsedDrv->canBuildLocally(worker.store))
@ -1285,15 +1311,13 @@ void DerivationGoal::startBuilder()
useChroot = !(derivationIsImpure(derivationType)) && !noChroot; useChroot = !(derivationIsImpure(derivationType)) && !noChroot;
} }
if (auto localStoreP = dynamic_cast<LocalStore *>(&worker.store)) { auto & localStore = getLocalStore();
auto & localStore = *localStoreP; if (localStore.storeDir != localStore.realStoreDir) {
if (localStore.storeDir != localStore.realStoreDir) { #if __linux__
#if __linux__ useChroot = true;
useChroot = true; #else
#else throw Error("building using a diverted store is not supported on this platform");
throw Error("building using a diverted store is not supported on this platform"); #endif
#endif
}
} }
/* Create a temporary directory where the build will take /* Create a temporary directory where the build will take
@ -1850,7 +1874,7 @@ void DerivationGoal::startBuilder()
} }
void DerivationGoal::initTmpDir() { void LocalDerivationGoal::initTmpDir() {
/* In a sandbox, for determinism, always use the same temporary /* In a sandbox, for determinism, always use the same temporary
directory. */ directory. */
#if __linux__ #if __linux__
@ -1899,7 +1923,7 @@ void DerivationGoal::initTmpDir() {
} }
void DerivationGoal::initEnv() void LocalDerivationGoal::initEnv()
{ {
env.clear(); env.clear();
@ -1960,7 +1984,7 @@ void DerivationGoal::initEnv()
static std::regex shVarName("[A-Za-z_][A-Za-z0-9_]*"); static std::regex shVarName("[A-Za-z_][A-Za-z0-9_]*");
void DerivationGoal::writeStructuredAttrs() void LocalDerivationGoal::writeStructuredAttrs()
{ {
auto structuredAttrs = parsedDrv->getStructuredAttrs(); auto structuredAttrs = parsedDrv->getStructuredAttrs();
if (!structuredAttrs) return; if (!structuredAttrs) return;
@ -2079,9 +2103,9 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
{ {
ref<LocalStore> next; ref<LocalStore> next;
DerivationGoal & goal; LocalDerivationGoal & goal;
RestrictedStore(const Params & params, ref<LocalStore> next, DerivationGoal & goal) RestrictedStore(const Params & params, ref<LocalStore> next, LocalDerivationGoal & goal)
: StoreConfig(params) : StoreConfig(params)
, LocalFSStoreConfig(params) , LocalFSStoreConfig(params)
, RestrictedStoreConfig(params) , RestrictedStoreConfig(params)
@ -2256,15 +2280,14 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
}; };
void DerivationGoal::startDaemon() void LocalDerivationGoal::startDaemon()
{ {
settings.requireExperimentalFeature("recursive-nix"); settings.requireExperimentalFeature("recursive-nix");
Store::Params params; Store::Params params;
params["path-info-cache-size"] = "0"; params["path-info-cache-size"] = "0";
params["store"] = worker.store.storeDir; params["store"] = worker.store.storeDir;
if (auto localStore = dynamic_cast<LocalStore *>(&worker.store)) params["root"] = getLocalStore().rootDir;
params["root"] = localStore->rootDir;
params["state"] = "/no-such-path"; params["state"] = "/no-such-path";
params["log"] = "/no-such-path"; params["log"] = "/no-such-path";
auto store = make_ref<RestrictedStore>(params, auto store = make_ref<RestrictedStore>(params,
@ -2322,7 +2345,7 @@ void DerivationGoal::startDaemon()
} }
void DerivationGoal::stopDaemon() void LocalDerivationGoal::stopDaemon()
{ {
if (daemonSocket && shutdown(daemonSocket.get(), SHUT_RDWR) == -1) if (daemonSocket && shutdown(daemonSocket.get(), SHUT_RDWR) == -1)
throw SysError("shutting down daemon socket"); throw SysError("shutting down daemon socket");
@ -2340,7 +2363,7 @@ void DerivationGoal::stopDaemon()
} }
void DerivationGoal::addDependency(const StorePath & path) void LocalDerivationGoal::addDependency(const StorePath & path)
{ {
if (isAllowed(path)) return; if (isAllowed(path)) return;
@ -2397,8 +2420,7 @@ void DerivationGoal::addDependency(const StorePath & path)
} }
} }
void LocalDerivationGoal::chownToBuilder(const Path & path)
void DerivationGoal::chownToBuilder(const Path & path)
{ {
if (!buildUser) return; if (!buildUser) return;
if (chown(path.c_str(), buildUser->getUID(), buildUser->getGID()) == -1) if (chown(path.c_str(), buildUser->getUID(), buildUser->getGID()) == -1)
@ -2469,7 +2491,7 @@ void setupSeccomp()
} }
void DerivationGoal::runChild() void LocalDerivationGoal::runChild()
{ {
/* Warning: in the child we should absolutely not make any SQLite /* Warning: in the child we should absolutely not make any SQLite
calls! */ calls! */
@ -2977,7 +2999,7 @@ void DerivationGoal::runChild()
} }
void DerivationGoal::registerOutputs() void LocalDerivationGoal::registerOutputs()
{ {
/* When using a build hook, the build hook can register the output /* When using a build hook, the build hook can register the output
as valid (by doing `nix-store --import'). If so we don't have as valid (by doing `nix-store --import'). If so we don't have
@ -2987,14 +3009,8 @@ void DerivationGoal::registerOutputs()
floating content-addressed derivations this isn't the case. floating content-addressed derivations this isn't the case.
*/ */
if (hook) { if (hook) {
bool allValid = true; DerivationGoal::registerOutputs();
for (auto & [outputName, outputPath] : worker.store.queryPartialDerivationOutputMap(drvPath)) { return;
if (!outputPath || !worker.store.isValidPath(*outputPath))
allValid = false;
else
finalOutputs.insert_or_assign(outputName, *outputPath);
}
if (allValid) return;
} }
std::map<std::string, ValidPathInfo> infos; std::map<std::string, ValidPathInfo> infos;
@ -3349,10 +3365,7 @@ void DerivationGoal::registerOutputs()
} }
} }
auto localStoreP = dynamic_cast<LocalStore *>(&worker.store); auto & localStore = getLocalStore();
if (!localStoreP)
throw Unsupported("can only register outputs with local store, but this is %s", worker.store.getUri());
auto & localStore = *localStoreP;
if (buildMode == bmCheck) { if (buildMode == bmCheck) {
@ -3481,10 +3494,7 @@ void DerivationGoal::registerOutputs()
paths referenced by each of them. If there are cycles in the paths referenced by each of them. If there are cycles in the
outputs, this will fail. */ outputs, this will fail. */
{ {
auto localStoreP = dynamic_cast<LocalStore *>(&worker.store); auto & localStore = getLocalStore();
if (!localStoreP)
throw Unsupported("can only register outputs with local store, but this is %s", worker.store.getUri());
auto & localStore = *localStoreP;
ValidPathInfos infos2; ValidPathInfos infos2;
for (auto & [outputName, newInfo] : infos) { for (auto & [outputName, newInfo] : infos) {
@ -3513,7 +3523,7 @@ void DerivationGoal::registerOutputs()
} }
void DerivationGoal::checkOutputs(const std::map<Path, ValidPathInfo> & outputs) void LocalDerivationGoal::checkOutputs(const std::map<Path, ValidPathInfo> & outputs)
{ {
std::map<Path, const ValidPathInfo &> outputsByPath; std::map<Path, const ValidPathInfo &> outputsByPath;
for (auto & output : outputs) for (auto & output : outputs)
@ -3678,6 +3688,7 @@ void DerivationGoal::checkOutputs(const std::map<Path, ValidPathInfo> & outputs)
} }
#if 0
Path DerivationGoal::openLogFile() Path DerivationGoal::openLogFile()
{ {
logSize = 0; logSize = 0;
@ -3720,9 +3731,10 @@ void DerivationGoal::closeLogFile()
logSink = logFileSink = 0; logSink = logFileSink = 0;
fdLogFile = -1; fdLogFile = -1;
} }
#endif
void DerivationGoal::deleteTmpDir(bool force) void LocalDerivationGoal::deleteTmpDir(bool force)
{ {
if (tmpDir != "") { if (tmpDir != "") {
/* Don't keep temporary directories for builtins because they /* Don't keep temporary directories for builtins because they
@ -3738,10 +3750,17 @@ void DerivationGoal::deleteTmpDir(bool force)
} }
bool LocalDerivationGoal::isReadDesc(int fd)
{
return (hook && DerivationGoal::isReadDesc(fd)) ||
(!hook && fd == builderOut.readSide.get());
}
#if 0
void DerivationGoal::handleChildOutput(int fd, const string & data) void DerivationGoal::handleChildOutput(int fd, const string & data)
{ {
if ((hook && fd == hook->builderOut.readSide.get()) || if (isReadDesc(fd))
(!hook && fd == builderOut.readSide.get()))
{ {
logSize += data.size(); logSize += data.size();
if (settings.maxLogSize && logSize > settings.maxLogSize) { if (settings.maxLogSize && logSize > settings.maxLogSize) {
@ -3855,9 +3874,10 @@ void DerivationGoal::checkPathValidity()
} }
} }
} }
#endif
StorePath DerivationGoal::makeFallbackPath(std::string_view outputName) StorePath LocalDerivationGoal::makeFallbackPath(std::string_view outputName)
{ {
return worker.store.makeStorePath( return worker.store.makeStorePath(
"rewrite:" + std::string(drvPath.to_string()) + ":name:" + std::string(outputName), "rewrite:" + std::string(drvPath.to_string()) + ":name:" + std::string(outputName),
@ -3865,7 +3885,7 @@ StorePath DerivationGoal::makeFallbackPath(std::string_view outputName)
} }
StorePath DerivationGoal::makeFallbackPath(const StorePath & path) StorePath LocalDerivationGoal::makeFallbackPath(const StorePath & path)
{ {
return worker.store.makeStorePath( return worker.store.makeStorePath(
"rewrite:" + std::string(drvPath.to_string()) + ":" + std::string(path.to_string()), "rewrite:" + std::string(drvPath.to_string()) + ":" + std::string(path.to_string()),
@ -3873,6 +3893,7 @@ StorePath DerivationGoal::makeFallbackPath(const StorePath & path)
} }
#if 0
void DerivationGoal::done(BuildResult::Status status, std::optional<Error> ex) void DerivationGoal::done(BuildResult::Status status, std::optional<Error> ex)
{ {
result.status = status; result.status = status;
@ -3897,6 +3918,7 @@ void DerivationGoal::done(BuildResult::Status status, std::optional<Error> ex)
worker.updateProgress(); worker.updateProgress();
} }
#endif
} }

View file

@ -1,48 +1,15 @@
#pragma once #pragma once
#include "parsed-derivations.hh" #include "derivation-goal.hh"
#include "lock.hh"
#include "local-store.hh" #include "local-store.hh"
#include "goal.hh"
namespace nix { namespace nix {
using std::map; struct LocalDerivationGoal : public DerivationGoal
struct HookInstance;
typedef enum {rpAccept, rpDecline, rpPostpone} HookReply;
/* Unless we are repairing, we don't both to test validity and just assume it,
so the choices are `Absent` or `Valid`. */
enum struct PathStatus {
Corrupt,
Absent,
Valid,
};
struct InitialOutputStatus {
StorePath path;
PathStatus status;
/* Valid in the store, and additionally non-corrupt if we are repairing */
bool isValid() const {
return status == PathStatus::Valid;
}
/* Merely present, allowed to be corrupt */
bool isPresent() const {
return status == PathStatus::Corrupt
|| status == PathStatus::Valid;
}
};
struct InitialOutput {
bool wanted;
Hash outputHash;
std::optional<InitialOutputStatus> known;
};
struct DerivationGoal : public Goal
{ {
LocalStore & getLocalStore();
#if 0
/* Whether to use an on-disk .drv file. */ /* Whether to use an on-disk .drv file. */
bool useDerivation; bool useDerivation;
@ -78,6 +45,7 @@ struct DerivationGoal : public Goal
StorePathSet inputPaths; StorePathSet inputPaths;
std::map<std::string, InitialOutput> initialOutputs; std::map<std::string, InitialOutput> initialOutputs;
#endif
/* User selected for running the builder. */ /* User selected for running the builder. */
std::unique_ptr<UserLock> buildUser; std::unique_ptr<UserLock> buildUser;
@ -91,6 +59,7 @@ struct DerivationGoal : public Goal
/* The path of the temporary directory in the sandbox. */ /* The path of the temporary directory in the sandbox. */
Path tmpDirInSandbox; Path tmpDirInSandbox;
#if 0
/* File descriptor for the log file. */ /* File descriptor for the log file. */
AutoCloseFD fdLogFile; AutoCloseFD fdLogFile;
std::shared_ptr<BufferedSink> logFileSink, logSink; std::shared_ptr<BufferedSink> logFileSink, logSink;
@ -105,6 +74,7 @@ struct DerivationGoal : public Goal
size_t currentLogLinePos = 0; // to handle carriage return size_t currentLogLinePos = 0; // to handle carriage return
std::string currentHookLine; std::string currentHookLine;
#endif
/* Pipe for the builder's standard output/error. */ /* Pipe for the builder's standard output/error. */
Pipe builderOut; Pipe builderOut;
@ -120,8 +90,10 @@ struct DerivationGoal : public Goal
namespace. */ namespace. */
bool usingUserNamespace = true; bool usingUserNamespace = true;
#if 0
/* The build hook. */ /* The build hook. */
std::unique_ptr<HookInstance> hook; std::unique_ptr<HookInstance> hook;
#endif
/* Whether we're currently doing a chroot build. */ /* Whether we're currently doing a chroot build. */
bool useChroot = false; bool useChroot = false;
@ -131,14 +103,18 @@ struct DerivationGoal : public Goal
/* RAII object to delete the chroot directory. */ /* RAII object to delete the chroot directory. */
std::shared_ptr<AutoDelete> autoDelChroot; std::shared_ptr<AutoDelete> autoDelChroot;
#if 0
/* The sort of derivation we are building. */ /* The sort of derivation we are building. */
DerivationType derivationType; DerivationType derivationType;
#endif
/* Whether to run the build in a private network namespace. */ /* Whether to run the build in a private network namespace. */
bool privateNetwork = false; bool privateNetwork = false;
#if 0
typedef void (DerivationGoal::*GoalState)(); typedef void (DerivationGoal::*GoalState)();
GoalState state; GoalState state;
#endif
/* Stuff we need to pass to initChild(). */ /* Stuff we need to pass to initChild(). */
struct ChrootPath { struct ChrootPath {
@ -179,6 +155,7 @@ struct DerivationGoal : public Goal
*/ */
OutputPathMap scratchOutputs; OutputPathMap scratchOutputs;
#if 0
/* The final output paths of the build. /* The final output paths of the build.
- For input-addressed derivations, always the precomputed paths - For input-addressed derivations, always the precomputed paths
@ -190,18 +167,21 @@ struct DerivationGoal : public Goal
OutputPathMap finalOutputs; OutputPathMap finalOutputs;
BuildMode buildMode; BuildMode buildMode;
#endif
/* If we're repairing without a chroot, there may be outputs that /* If we're repairing without a chroot, there may be outputs that
are valid but corrupt. So we redirect these outputs to are valid but corrupt. So we redirect these outputs to
temporary paths. */ temporary paths. */
StorePathSet redirectedBadOutputs; StorePathSet redirectedBadOutputs;
#if 0
BuildResult result; BuildResult result;
/* The current round, if we're building multiple times. */ /* The current round, if we're building multiple times. */
size_t curRound = 1; size_t curRound = 1;
size_t nrRounds; size_t nrRounds;
#endif
/* Path registration info from the previous round, if we're /* Path registration info from the previous round, if we're
building multiple times. Since this contains the hash, it building multiple times. Since this contains the hash, it
@ -214,6 +194,7 @@ struct DerivationGoal : public Goal
const static Path homeDir; const static Path homeDir;
#if 0
std::unique_ptr<MaintainCount<uint64_t>> mcExpectedBuilds, mcRunningBuilds; std::unique_ptr<MaintainCount<uint64_t>> mcExpectedBuilds, mcRunningBuilds;
std::unique_ptr<Activity> act; std::unique_ptr<Activity> act;
@ -225,6 +206,7 @@ struct DerivationGoal : public Goal
/* The remote machine on which we're building. */ /* The remote machine on which we're building. */
std::string machineName; std::string machineName;
#endif
/* The recursive Nix daemon socket. */ /* The recursive Nix daemon socket. */
AutoCloseFD daemonSocket; AutoCloseFD daemonSocket;
@ -249,17 +231,14 @@ struct DerivationGoal : public Goal
friend struct RestrictedStore; friend struct RestrictedStore;
DerivationGoal(const StorePath & drvPath, using DerivationGoal::DerivationGoal;
const StringSet & wantedOutputs, Worker & worker,
BuildMode buildMode = bmNormal); virtual ~LocalDerivationGoal() override;
DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
const StringSet & wantedOutputs, Worker & worker,
BuildMode buildMode = bmNormal);
~DerivationGoal();
/* Whether we need to perform hash rewriting if there are valid output paths. */ /* Whether we need to perform hash rewriting if there are valid output paths. */
bool needsHashRewrite(); bool needsHashRewrite();
#if 0
void timedOut(Error && ex) override; void timedOut(Error && ex) override;
string key() override; string key() override;
@ -280,13 +259,16 @@ struct DerivationGoal : public Goal
void closureRepaired(); void closureRepaired();
void inputsRealised(); void inputsRealised();
void tryToBuild(); void tryToBuild();
void tryLocalBuild(); #endif
void tryLocalBuild() override;
#if 0
void buildDone(); void buildDone();
void resolvedFinished(); void resolvedFinished();
/* Is the build hook willing to perform the build? */ /* Is the build hook willing to perform the build? */
HookReply tryBuildHook(); HookReply tryBuildHook();
#endif
/* Start building a derivation. */ /* Start building a derivation. */
void startBuilder(); void startBuilder();
@ -311,27 +293,46 @@ struct DerivationGoal : public Goal
/* Make a file owned by the builder. */ /* Make a file owned by the builder. */
void chownToBuilder(const Path & path); void chownToBuilder(const Path & path);
int getChildStatus() override;
/* Run the builder's process. */ /* Run the builder's process. */
void runChild(); void runChild();
/* Check that the derivation outputs all exist and register them /* Check that the derivation outputs all exist and register them
as valid. */ as valid. */
void registerOutputs(); void registerOutputs() override;
/* Check that an output meets the requirements specified by the /* Check that an output meets the requirements specified by the
'outputChecks' attribute (or the legacy 'outputChecks' attribute (or the legacy
'{allowed,disallowed}{References,Requisites}' attributes). */ '{allowed,disallowed}{References,Requisites}' attributes). */
void checkOutputs(const std::map<std::string, ValidPathInfo> & outputs); void checkOutputs(const std::map<std::string, ValidPathInfo> & outputs);
#if 0
/* Open a log file and a pipe to it. */ /* Open a log file and a pipe to it. */
Path openLogFile(); Path openLogFile();
/* Close the log file. */ /* Close the log file. */
void closeLogFile(); void closeLogFile();
#endif
/* Close the read side of the logger pipe. */
void closeReadPipes() override;
/* Cleanup hooks for buildDone() */
void cleanupHookFinally() override;
void cleanupPreChildKill() override;
void cleanupPostChildKill() override;
bool cleanupDecideWhetherDiskFull() override;
void cleanupPostOutputsRegisteredModeCheck() override;
void cleanupPostOutputsRegisteredModeNonCheck() override;
bool isReadDesc(int fd) override;
/* Delete the temporary directory, if we have one. */ /* Delete the temporary directory, if we have one. */
void deleteTmpDir(bool force); void deleteTmpDir(bool force);
#if 0
/* Callback used by the worker to write to the log. */ /* Callback used by the worker to write to the log. */
void handleChildOutput(int fd, const string & data) override; void handleChildOutput(int fd, const string & data) override;
void handleEOF(int fd) override; void handleEOF(int fd) override;
@ -345,9 +346,10 @@ struct DerivationGoal : public Goal
/* Return the set of (in)valid paths. */ /* Return the set of (in)valid paths. */
void checkPathValidity(); void checkPathValidity();
#endif
/* Forcibly kill the child process, if any. */ /* Forcibly kill the child process, if any. */
void killChild(); void killChild() override;
/* Create alternative path calculated from but distinct from the /* Create alternative path calculated from but distinct from the
input, so we can avoid overwriting outputs (or other store paths) input, so we can avoid overwriting outputs (or other store paths)
@ -359,6 +361,7 @@ struct DerivationGoal : public Goal
rewrites caught everything */ rewrites caught everything */
StorePath makeFallbackPath(std::string_view outputName); StorePath makeFallbackPath(std::string_view outputName);
#if 0
void repairClosure(); void repairClosure();
void started(); void started();
@ -368,6 +371,7 @@ struct DerivationGoal : public Goal
std::optional<Error> ex = {}); std::optional<Error> ex = {});
StorePathSet exportReferences(const StorePathSet & storePaths); StorePathSet exportReferences(const StorePathSet & storePaths);
#endif
}; };
} }

View file

@ -1,7 +1,7 @@
#include "machines.hh" #include "machines.hh"
#include "worker.hh" #include "worker.hh"
#include "substitution-goal.hh" #include "substitution-goal.hh"
#include "derivation-goal.hh" #include "local-derivation-goal.hh"
#include "hook-instance.hh" #include "hook-instance.hh"
#include <poll.h> #include <poll.h>
@ -59,8 +59,10 @@ std::shared_ptr<DerivationGoal> Worker::makeDerivationGoalCommon(
std::shared_ptr<DerivationGoal> Worker::makeDerivationGoal(const StorePath & drvPath, std::shared_ptr<DerivationGoal> Worker::makeDerivationGoal(const StorePath & drvPath,
const StringSet & wantedOutputs, BuildMode buildMode) const StringSet & wantedOutputs, BuildMode buildMode)
{ {
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() { return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> {
return std::make_shared<DerivationGoal>(drvPath, wantedOutputs, *this, buildMode); return !dynamic_cast<LocalStore *>(&store)
? std::make_shared</* */DerivationGoal>(drvPath, wantedOutputs, *this, buildMode)
: std::make_shared<LocalDerivationGoal>(drvPath, wantedOutputs, *this, buildMode);
}); });
} }
@ -68,8 +70,10 @@ std::shared_ptr<DerivationGoal> Worker::makeDerivationGoal(const StorePath & drv
std::shared_ptr<DerivationGoal> Worker::makeBasicDerivationGoal(const StorePath & drvPath, std::shared_ptr<DerivationGoal> Worker::makeBasicDerivationGoal(const StorePath & drvPath,
const BasicDerivation & drv, const StringSet & wantedOutputs, BuildMode buildMode) const BasicDerivation & drv, const StringSet & wantedOutputs, BuildMode buildMode)
{ {
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() { return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> {
return std::make_shared<DerivationGoal>(drvPath, drv, wantedOutputs, *this, buildMode); return !dynamic_cast<LocalStore *>(&store)
? std::make_shared</* */DerivationGoal>(drvPath, drv, wantedOutputs, *this, buildMode)
: std::make_shared<LocalDerivationGoal>(drvPath, drv, wantedOutputs, *this, buildMode);
}); });
} }

View file

@ -280,7 +280,7 @@ private:
void createUser(const std::string & userName, uid_t userId) override; void createUser(const std::string & userName, uid_t userId) override;
friend struct DerivationGoal; friend struct LocalDerivationGoal;
friend struct SubstitutionGoal; friend struct SubstitutionGoal;
}; };