Start building the scheduler for Windows

Building derivations is a lot harder, but the downloading goals is
portable enough.

The "common channel" code is due to Volth. I wonder if there is a way we
can factor it out into separate functions / files to avoid some
within-function CPP.

Co-authored-by: volth <volth@volth.com>
This commit is contained in:
John Ericson 2024-05-10 13:03:05 -04:00
parent 87ab3c0ea4
commit 39b2a399ad
25 changed files with 285 additions and 94 deletions

View file

@ -227,18 +227,18 @@
''^src/libstore/store-dir-config\.hh$'' ''^src/libstore/store-dir-config\.hh$''
''^src/libstore/unix/build/derivation-goal\.cc$'' ''^src/libstore/unix/build/derivation-goal\.cc$''
''^src/libstore/unix/build/derivation-goal\.hh$'' ''^src/libstore/unix/build/derivation-goal\.hh$''
''^src/libstore/unix/build/drv-output-substitution-goal\.cc$'' ''^src/libstore/build/drv-output-substitution-goal\.cc$''
''^src/libstore/unix/build/drv-output-substitution-goal\.hh$'' ''^src/libstore/build/drv-output-substitution-goal\.hh$''
''^src/libstore/unix/build/entry-points\.cc$'' ''^src/libstore/build/entry-points\.cc$''
''^src/libstore/unix/build/goal\.cc$'' ''^src/libstore/build/goal\.cc$''
''^src/libstore/unix/build/goal\.hh$'' ''^src/libstore/build/goal\.hh$''
''^src/libstore/unix/build/hook-instance\.cc$'' ''^src/libstore/unix/build/hook-instance\.cc$''
''^src/libstore/unix/build/local-derivation-goal\.cc$'' ''^src/libstore/unix/build/local-derivation-goal\.cc$''
''^src/libstore/unix/build/local-derivation-goal\.hh$'' ''^src/libstore/unix/build/local-derivation-goal\.hh$''
''^src/libstore/unix/build/substitution-goal\.cc$'' ''^src/libstore/build/substitution-goal\.cc$''
''^src/libstore/unix/build/substitution-goal\.hh$'' ''^src/libstore/build/substitution-goal\.hh$''
''^src/libstore/unix/build/worker\.cc$'' ''^src/libstore/build/worker\.cc$''
''^src/libstore/unix/build/worker\.hh$'' ''^src/libstore/build/worker\.hh$''
''^src/libstore/unix/builtins/fetchurl\.cc$'' ''^src/libstore/unix/builtins/fetchurl\.cc$''
''^src/libstore/unix/builtins/unpack-channel\.cc$'' ''^src/libstore/unix/builtins/unpack-channel\.cc$''
''^src/libstore/gc\.cc$'' ''^src/libstore/gc\.cc$''
@ -246,8 +246,8 @@
''^src/libstore/unix/local-overlay-store\.hh$'' ''^src/libstore/unix/local-overlay-store\.hh$''
''^src/libstore/local-store\.cc$'' ''^src/libstore/local-store\.cc$''
''^src/libstore/local-store\.hh$'' ''^src/libstore/local-store\.hh$''
''^src/libstore/unix/lock\.cc$'' ''^src/libstore/unix/user-lock\.cc$''
''^src/libstore/unix/lock\.hh$'' ''^src/libstore/unix/user-lock\.hh$''
''^src/libstore/optimise-store\.cc$'' ''^src/libstore/optimise-store\.cc$''
''^src/libstore/unix/pathlocks\.cc$'' ''^src/libstore/unix/pathlocks\.cc$''
''^src/libstore/posix-fs-canonicalise\.cc$'' ''^src/libstore/posix-fs-canonicalise\.cc$''

View file

@ -66,7 +66,11 @@ void DrvOutputSubstitutionGoal::tryNext()
some other error occurs), so it must not touch `this`. So put some other error occurs), so it must not touch `this`. So put
the shared state in a separate refcounted object. */ the shared state in a separate refcounted object. */
downloadState = std::make_shared<DownloadState>(); downloadState = std::make_shared<DownloadState>();
#ifndef _WIN32
downloadState->outPipe.create(); downloadState->outPipe.create();
#else
downloadState->outPipe.createAsyncPipe(worker.ioport.get());
#endif
sub->queryRealisation( sub->queryRealisation(
id, id,
@ -79,7 +83,13 @@ void DrvOutputSubstitutionGoal::tryNext()
} }
} }); } });
worker.childStarted(shared_from_this(), {downloadState->outPipe.readSide.get()}, true, false); worker.childStarted(shared_from_this(), {
#ifndef _WIN32
downloadState->outPipe.readSide.get()
#else
&downloadState->outPipe
#endif
}, true, false);
state = &DrvOutputSubstitutionGoal::realisationFetched; state = &DrvOutputSubstitutionGoal::realisationFetched;
} }
@ -158,7 +168,7 @@ void DrvOutputSubstitutionGoal::work()
(this->*state)(); (this->*state)();
} }
void DrvOutputSubstitutionGoal::handleEOF(int fd) void DrvOutputSubstitutionGoal::handleEOF(Descriptor fd)
{ {
if (fd == downloadState->outPipe.readSide.get()) worker.wakeUp(shared_from_this()); if (fd == downloadState->outPipe.readSide.get()) worker.wakeUp(shared_from_this());
} }

View file

@ -1,11 +1,16 @@
#pragma once #pragma once
///@file ///@file
#include <thread>
#include <future>
#include "store-api.hh" #include "store-api.hh"
#include "goal.hh" #include "goal.hh"
#include "realisation.hh" #include "realisation.hh"
#include <thread>
#include <future> #ifdef _WIN32
# include "windows-async-pipe.hh"
#endif
namespace nix { namespace nix {
@ -43,7 +48,11 @@ class DrvOutputSubstitutionGoal : public Goal {
struct DownloadState struct DownloadState
{ {
#ifndef _WIN32
Pipe outPipe; Pipe outPipe;
#else
windows::AsyncPipe outPipe;
#endif
std::promise<std::shared_ptr<const Realisation>> promise; std::promise<std::shared_ptr<const Realisation>> promise;
}; };
@ -71,7 +80,7 @@ public:
std::string key() override; std::string key() override;
void work() override; void work() override;
void handleEOF(int fd) override; void handleEOF(Descriptor fd) override;
JobCategory jobCategory() const override { JobCategory jobCategory() const override {
return JobCategory::Substitution; return JobCategory::Substitution;

View file

@ -1,6 +1,8 @@
#include "worker.hh" #include "worker.hh"
#include "substitution-goal.hh" #include "substitution-goal.hh"
#include "derivation-goal.hh" #ifndef _WIN32 // TODO Enable building on Windows
# include "derivation-goal.hh"
#endif
#include "local-store.hh" #include "local-store.hh"
namespace nix { namespace nix {
@ -25,9 +27,12 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod
ex = std::move(i->ex); ex = std::move(i->ex);
} }
if (i->exitCode != Goal::ecSuccess) { if (i->exitCode != Goal::ecSuccess) {
#ifndef _WIN32 // TODO Enable building on Windows
if (auto i2 = dynamic_cast<DerivationGoal *>(i.get())) if (auto i2 = dynamic_cast<DerivationGoal *>(i.get()))
failed.insert(printStorePath(i2->drvPath)); failed.insert(printStorePath(i2->drvPath));
else if (auto i2 = dynamic_cast<PathSubstitutionGoal *>(i.get())) else
#endif
if (auto i2 = dynamic_cast<PathSubstitutionGoal *>(i.get()))
failed.insert(printStorePath(i2->storePath)); failed.insert(printStorePath(i2->storePath));
} }
} }
@ -74,7 +79,12 @@ BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivat
BuildMode buildMode) BuildMode buildMode)
{ {
Worker worker(*this, *this); Worker worker(*this, *this);
#ifndef _WIN32 // TODO Enable building on Windows
auto goal = worker.makeBasicDerivationGoal(drvPath, drv, OutputsSpec::All {}, buildMode); auto goal = worker.makeBasicDerivationGoal(drvPath, drv, OutputsSpec::All {}, buildMode);
#else
std::shared_ptr<Goal> goal;
throw UnimplementedError("Building derivations not yet implemented on windows.");
#endif
try { try {
worker.run(Goals{goal}); worker.run(Goals{goal});

View file

@ -138,12 +138,12 @@ public:
virtual void waiteeDone(GoalPtr waitee, ExitCode result); virtual void waiteeDone(GoalPtr waitee, ExitCode result);
virtual void handleChildOutput(int fd, std::string_view data) virtual void handleChildOutput(Descriptor fd, std::string_view data)
{ {
abort(); abort();
} }
virtual void handleEOF(int fd) virtual void handleEOF(Descriptor fd)
{ {
abort(); abort();
} }

View file

@ -212,7 +212,11 @@ void PathSubstitutionGoal::tryToRun()
maintainRunningSubstitutions = std::make_unique<MaintainCount<uint64_t>>(worker.runningSubstitutions); maintainRunningSubstitutions = std::make_unique<MaintainCount<uint64_t>>(worker.runningSubstitutions);
worker.updateProgress(); worker.updateProgress();
#ifndef _WIN32
outPipe.create(); outPipe.create();
#else
outPipe.createAsyncPipe(worker.ioport.get());
#endif
promise = std::promise<void>(); promise = std::promise<void>();
@ -235,7 +239,13 @@ void PathSubstitutionGoal::tryToRun()
} }
}); });
worker.childStarted(shared_from_this(), {outPipe.readSide.get()}, true, false); worker.childStarted(shared_from_this(), {
#ifndef _WIN32
outPipe.readSide.get()
#else
&outPipe
#endif
}, true, false);
state = &PathSubstitutionGoal::finished; state = &PathSubstitutionGoal::finished;
} }
@ -294,12 +304,12 @@ void PathSubstitutionGoal::finished()
} }
void PathSubstitutionGoal::handleChildOutput(int fd, std::string_view data) void PathSubstitutionGoal::handleChildOutput(Descriptor fd, std::string_view data)
{ {
} }
void PathSubstitutionGoal::handleEOF(int fd) void PathSubstitutionGoal::handleEOF(Descriptor fd)
{ {
if (fd == outPipe.readSide.get()) worker.wakeUp(shared_from_this()); if (fd == outPipe.readSide.get()) worker.wakeUp(shared_from_this());
} }

View file

@ -1,10 +1,13 @@
#pragma once #pragma once
///@file ///@file
#include "lock.hh"
#include "store-api.hh" #include "store-api.hh"
#include "goal.hh" #include "goal.hh"
#ifdef _WIN32
# include "windows-async-pipe.hh"
#endif
namespace nix { namespace nix {
class Worker; class Worker;
@ -45,7 +48,11 @@ struct PathSubstitutionGoal : public Goal
/** /**
* Pipe for the substituter's standard output. * Pipe for the substituter's standard output.
*/ */
#ifndef _WIN32
Pipe outPipe; Pipe outPipe;
#else
windows::AsyncPipe outPipe;
#endif
/** /**
* The substituter thread. * The substituter thread.
@ -111,8 +118,8 @@ public:
/** /**
* Callback used by the worker to write to the log. * Callback used by the worker to write to the log.
*/ */
void handleChildOutput(int fd, std::string_view data) override; void handleChildOutput(Descriptor fd, std::string_view data) override;
void handleEOF(int fd) override; void handleEOF(Descriptor fd) override;
/* Called by destructor, can't be overridden */ /* Called by destructor, can't be overridden */
void cleanup() override final; void cleanup() override final;

View file

@ -1,12 +1,20 @@
#include "local-store.hh"
#include "machines.hh" #include "machines.hh"
#include "worker.hh" #include "worker.hh"
#include "substitution-goal.hh" #include "substitution-goal.hh"
#include "drv-output-substitution-goal.hh" #include "drv-output-substitution-goal.hh"
#include "local-derivation-goal.hh" #ifndef _WIN32 // TODO Enable building on Windows
#include "hook-instance.hh" # include "local-derivation-goal.hh"
# include "hook-instance.hh"
#endif
#include "signals.hh" #include "signals.hh"
#include <poll.h> #ifndef _WIN32
# include <poll.h>
#else
# include <ioapiset.h>
# include "windows-error.hh"
#endif
namespace nix { namespace nix {
@ -41,6 +49,7 @@ Worker::~Worker()
assert(expectedNarSize == 0); assert(expectedNarSize == 0);
} }
#ifndef _WIN32 // TODO Enable building on Windows
std::shared_ptr<DerivationGoal> Worker::makeDerivationGoalCommon( std::shared_ptr<DerivationGoal> Worker::makeDerivationGoalCommon(
const StorePath & drvPath, const StorePath & drvPath,
@ -70,7 +79,6 @@ 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 OutputsSpec & wantedOutputs, BuildMode buildMode) const BasicDerivation & drv, const OutputsSpec & wantedOutputs, BuildMode buildMode)
{ {
@ -81,6 +89,8 @@ std::shared_ptr<DerivationGoal> Worker::makeBasicDerivationGoal(const StorePath
}); });
} }
#endif
std::shared_ptr<PathSubstitutionGoal> Worker::makePathSubstitutionGoal(const StorePath & path, RepairFlag repair, std::optional<ContentAddress> ca) std::shared_ptr<PathSubstitutionGoal> Worker::makePathSubstitutionGoal(const StorePath & path, RepairFlag repair, std::optional<ContentAddress> ca)
{ {
@ -112,10 +122,14 @@ GoalPtr Worker::makeGoal(const DerivedPath & req, BuildMode buildMode)
{ {
return std::visit(overloaded { return std::visit(overloaded {
[&](const DerivedPath::Built & bfd) -> GoalPtr { [&](const DerivedPath::Built & bfd) -> GoalPtr {
#ifndef _WIN32 // TODO Enable building on Windows
if (auto bop = std::get_if<DerivedPath::Opaque>(&*bfd.drvPath)) if (auto bop = std::get_if<DerivedPath::Opaque>(&*bfd.drvPath))
return makeDerivationGoal(bop->path, bfd.outputs, buildMode); return makeDerivationGoal(bop->path, bfd.outputs, buildMode);
else else
throw UnimplementedError("Building dynamic derivations in one shot is not yet implemented."); throw UnimplementedError("Building dynamic derivations in one shot is not yet implemented.");
#else
throw UnimplementedError("Building derivations not yet implemented on Windows");
#endif
}, },
[&](const DerivedPath::Opaque & bo) -> GoalPtr { [&](const DerivedPath::Opaque & bo) -> GoalPtr {
return makePathSubstitutionGoal(bo.path, buildMode == bmRepair ? Repair : NoRepair); return makePathSubstitutionGoal(bo.path, buildMode == bmRepair ? Repair : NoRepair);
@ -141,9 +155,12 @@ static void removeGoal(std::shared_ptr<G> goal, std::map<K, std::weak_ptr<G>> &
void Worker::removeGoal(GoalPtr goal) void Worker::removeGoal(GoalPtr goal)
{ {
#ifndef _WIN32 // TODO Enable building on Windows
if (auto drvGoal = std::dynamic_pointer_cast<DerivationGoal>(goal)) if (auto drvGoal = std::dynamic_pointer_cast<DerivationGoal>(goal))
nix::removeGoal(drvGoal, derivationGoals); nix::removeGoal(drvGoal, derivationGoals);
else if (auto subGoal = std::dynamic_pointer_cast<PathSubstitutionGoal>(goal)) else
#endif
if (auto subGoal = std::dynamic_pointer_cast<PathSubstitutionGoal>(goal))
nix::removeGoal(subGoal, substitutionGoals); nix::removeGoal(subGoal, substitutionGoals);
else if (auto subGoal = std::dynamic_pointer_cast<DrvOutputSubstitutionGoal>(goal)) else if (auto subGoal = std::dynamic_pointer_cast<DrvOutputSubstitutionGoal>(goal))
nix::removeGoal(subGoal, drvOutputSubstitutionGoals); nix::removeGoal(subGoal, drvOutputSubstitutionGoals);
@ -187,13 +204,13 @@ unsigned Worker::getNrSubstitutions()
} }
void Worker::childStarted(GoalPtr goal, const std::set<int> & fds, void Worker::childStarted(GoalPtr goal, const std::set<Child::CommChannel> & channels,
bool inBuildSlot, bool respectTimeouts) bool inBuildSlot, bool respectTimeouts)
{ {
Child child; Child child;
child.goal = goal; child.goal = goal;
child.goal2 = goal.get(); child.goal2 = goal.get();
child.fds = fds; child.channels = channels;
child.timeStarted = child.lastOutput = steady_time_point::clock::now(); child.timeStarted = child.lastOutput = steady_time_point::clock::now();
child.inBuildSlot = inBuildSlot; child.inBuildSlot = inBuildSlot;
child.respectTimeouts = respectTimeouts; child.respectTimeouts = respectTimeouts;
@ -281,12 +298,15 @@ void Worker::run(const Goals & _topGoals)
for (auto & i : _topGoals) { for (auto & i : _topGoals) {
topGoals.insert(i); topGoals.insert(i);
#ifndef _WIN32 // TODO Enable building on Windows
if (auto goal = dynamic_cast<DerivationGoal *>(i.get())) { if (auto goal = dynamic_cast<DerivationGoal *>(i.get())) {
topPaths.push_back(DerivedPath::Built { topPaths.push_back(DerivedPath::Built {
.drvPath = makeConstantStorePathRef(goal->drvPath), .drvPath = makeConstantStorePathRef(goal->drvPath),
.outputs = goal->wantedOutputs, .outputs = goal->wantedOutputs,
}); });
} else if (auto goal = dynamic_cast<PathSubstitutionGoal *>(i.get())) { } else
#endif
if (auto goal = dynamic_cast<PathSubstitutionGoal *>(i.get())) {
topPaths.push_back(DerivedPath::Opaque{goal->storePath}); topPaths.push_back(DerivedPath::Opaque{goal->storePath});
} }
} }
@ -408,13 +428,14 @@ void Worker::waitForInput()
if (useTimeout) if (useTimeout)
vomit("sleeping %d seconds", timeout); vomit("sleeping %d seconds", timeout);
#ifndef _WIN32
/* Use select() to wait for the input side of any logger pipe to /* Use select() to wait for the input side of any logger pipe to
become `available'. Note that `available' (i.e., non-blocking) become `available'. Note that `available' (i.e., non-blocking)
includes EOF. */ includes EOF. */
std::vector<struct pollfd> pollStatus; std::vector<struct pollfd> pollStatus;
std::map<int, size_t> fdToPollStatus; std::map<int, size_t> fdToPollStatus;
for (auto & i : children) { for (auto & i : children) {
for (auto & j : i.fds) { for (auto & j : i.channels) {
pollStatus.push_back((struct pollfd) { .fd = j, .events = POLLIN }); pollStatus.push_back((struct pollfd) { .fd = j, .events = POLLIN });
fdToPollStatus[j] = pollStatus.size() - 1; fdToPollStatus[j] = pollStatus.size() - 1;
} }
@ -425,6 +446,28 @@ void Worker::waitForInput()
if (errno == EINTR) return; if (errno == EINTR) return;
throw SysError("waiting for input"); throw SysError("waiting for input");
} }
#else
OVERLAPPED_ENTRY oentries[0x20] = {0};
ULONG removed;
bool gotEOF = false;
// we are on at least Windows Vista / Server 2008 and can get many (countof(oentries)) statuses in one API call
if (!GetQueuedCompletionStatusEx(
ioport.get(),
oentries,
sizeof(oentries) / sizeof(*oentries),
&removed,
useTimeout ? timeout * 1000 : INFINITE,
false))
{
windows::WinError winError("GetQueuedCompletionStatusEx");
if (winError.lastError != WAIT_TIMEOUT)
throw winError;
assert(removed == 0);
} else {
assert(0 < removed && removed <= sizeof(oentries)/sizeof(*oentries));
}
#endif
auto after = steady_time_point::clock::now(); auto after = steady_time_point::clock::now();
@ -439,20 +482,21 @@ void Worker::waitForInput()
GoalPtr goal = j->goal.lock(); GoalPtr goal = j->goal.lock();
assert(goal); assert(goal);
std::set<int> fds2(j->fds); #ifndef _WIN32
std::set<Descriptor> fds2(j->channels);
std::vector<unsigned char> buffer(4096); std::vector<unsigned char> buffer(4096);
for (auto & k : fds2) { for (auto & k : fds2) {
const auto fdPollStatusId = get(fdToPollStatus, k); const auto fdPollStatusId = get(fdToPollStatus, k);
assert(fdPollStatusId); assert(fdPollStatusId);
assert(*fdPollStatusId < pollStatus.size()); assert(*fdPollStatusId < pollStatus.size());
if (pollStatus.at(*fdPollStatusId).revents) { if (pollStatus.at(*fdPollStatusId).revents) {
ssize_t rd = ::read(k, buffer.data(), buffer.size()); ssize_t rd = ::read(fromDescriptorReadOnly(k), buffer.data(), buffer.size());
// FIXME: is there a cleaner way to handle pt close // FIXME: is there a cleaner way to handle pt close
// than EIO? Is this even standard? // than EIO? Is this even standard?
if (rd == 0 || (rd == -1 && errno == EIO)) { if (rd == 0 || (rd == -1 && errno == EIO)) {
debug("%1%: got EOF", goal->getName()); debug("%1%: got EOF", goal->getName());
goal->handleEOF(k); goal->handleEOF(k);
j->fds.erase(k); j->channels.erase(k);
} else if (rd == -1) { } else if (rd == -1) {
if (errno != EINTR) if (errno != EINTR)
throw SysError("%s: read failed", goal->getName()); throw SysError("%s: read failed", goal->getName());
@ -465,6 +509,48 @@ void Worker::waitForInput()
} }
} }
} }
#else
decltype(j->channels)::iterator p = j->channels.begin();
while (p != j->channels.end()) {
decltype(p) nextp = p;
++nextp;
for (ULONG i = 0; i < removed; i++) {
if (oentries[i].lpCompletionKey == ((ULONG_PTR)((*p)->readSide.get()) ^ 0x5555)) {
printMsg(lvlVomit, "%s: read %s bytes", goal->getName(), oentries[i].dwNumberOfBytesTransferred);
if (oentries[i].dwNumberOfBytesTransferred > 0) {
std::string data {
(char *) (*p)->buffer.data(),
oentries[i].dwNumberOfBytesTransferred,
};
//std::cerr << "read [" << data << "]" << std::endl;
j->lastOutput = after;
goal->handleChildOutput((*p)->readSide.get(), data);
}
if (gotEOF) {
debug("%s: got EOF", goal->getName());
goal->handleEOF((*p)->readSide.get());
nextp = j->channels.erase(p); // no need to maintain `j->channels` ?
} else {
BOOL rc = ReadFile((*p)->readSide.get(), (*p)->buffer.data(), (*p)->buffer.size(), &(*p)->got, &(*p)->overlapped);
if (rc) {
// here is possible (but not obligatory) to call `goal->handleChildOutput` and repeat ReadFile immediately
} else {
windows::WinError winError("ReadFile(%s, ..)", (*p)->readSide.get());
if (winError.lastError == ERROR_BROKEN_PIPE) {
debug("%s: got EOF", goal->getName());
goal->handleEOF((*p)->readSide.get());
nextp = j->channels.erase(p); // no need to maintain `j->channels` ?
} else if (winError.lastError != ERROR_IO_PENDING)
throw winError;
}
}
break;
}
}
p = nextp;
}
#endif
if (goal->exitCode == Goal::ecBusy && if (goal->exitCode == Goal::ecBusy &&
0 != settings.maxSilentTime && 0 != settings.maxSilentTime &&

View file

@ -2,18 +2,23 @@
///@file ///@file
#include "types.hh" #include "types.hh"
#include "lock.hh"
#include "store-api.hh" #include "store-api.hh"
#include "goal.hh" #include "goal.hh"
#include "realisation.hh" #include "realisation.hh"
#ifdef _WIN32
# include "windows-async-pipe.hh"
#endif
#include <future> #include <future>
#include <thread> #include <thread>
namespace nix { namespace nix {
/* Forward definition. */ /* Forward definition. */
#ifndef _WIN32 // TODO Enable building on Windows
struct DerivationGoal; struct DerivationGoal;
#endif
struct PathSubstitutionGoal; struct PathSubstitutionGoal;
class DrvOutputSubstitutionGoal; class DrvOutputSubstitutionGoal;
@ -36,14 +41,22 @@ typedef std::chrono::time_point<std::chrono::steady_clock> steady_time_point;
/** /**
* A mapping used to remember for each child process to what goal it * A mapping used to remember for each child process to what goal it
* belongs, and file descriptors for receiving log data and output * belongs, and comm channels for receiving log data and output
* path creation commands. * path creation commands.
*/ */
struct Child struct Child
{ {
using CommChannel =
#ifndef _WIN32
Descriptor
#else
windows::AsyncPipe *
#endif
;
WeakGoalPtr goal; WeakGoalPtr goal;
Goal * goal2; // ugly hackery Goal * goal2; // ugly hackery
std::set<int> fds; std::set<CommChannel> channels;
bool respectTimeouts; bool respectTimeouts;
bool inBuildSlot; bool inBuildSlot;
/** /**
@ -53,8 +66,10 @@ struct Child
steady_time_point timeStarted; steady_time_point timeStarted;
}; };
#ifndef _WIN32 // TODO Enable building on Windows
/* Forward definition. */ /* Forward definition. */
struct HookInstance; struct HookInstance;
#endif
/** /**
* The worker class. * The worker class.
@ -101,7 +116,9 @@ private:
* Maps used to prevent multiple instantiations of a goal for the * Maps used to prevent multiple instantiations of a goal for the
* same derivation / path. * same derivation / path.
*/ */
#ifndef _WIN32 // TODO Enable building on Windows
std::map<StorePath, std::weak_ptr<DerivationGoal>> derivationGoals; std::map<StorePath, std::weak_ptr<DerivationGoal>> derivationGoals;
#endif
std::map<StorePath, std::weak_ptr<PathSubstitutionGoal>> substitutionGoals; std::map<StorePath, std::weak_ptr<PathSubstitutionGoal>> substitutionGoals;
std::map<DrvOutput, std::weak_ptr<DrvOutputSubstitutionGoal>> drvOutputSubstitutionGoals; std::map<DrvOutput, std::weak_ptr<DrvOutputSubstitutionGoal>> drvOutputSubstitutionGoals;
@ -152,10 +169,16 @@ public:
*/ */
bool checkMismatch; bool checkMismatch;
#ifdef _WIN32
AutoCloseFD ioport;
#endif
Store & store; Store & store;
Store & evalStore; Store & evalStore;
#ifndef _WIN32 // TODO Enable building on Windows
std::unique_ptr<HookInstance> hook; std::unique_ptr<HookInstance> hook;
#endif
uint64_t expectedBuilds = 0; uint64_t expectedBuilds = 0;
uint64_t doneBuilds = 0; uint64_t doneBuilds = 0;
@ -184,6 +207,7 @@ public:
* Make a goal (with caching). * Make a goal (with caching).
*/ */
#ifndef _WIN32 // TODO Enable building on Windows
/** /**
* @ref DerivationGoal "derivation goal" * @ref DerivationGoal "derivation goal"
*/ */
@ -198,6 +222,7 @@ public:
std::shared_ptr<DerivationGoal> makeBasicDerivationGoal( std::shared_ptr<DerivationGoal> makeBasicDerivationGoal(
const StorePath & drvPath, const BasicDerivation & drv, const StorePath & drvPath, const BasicDerivation & drv,
const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal); const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal);
#endif
/** /**
* @ref SubstitutionGoal "substitution goal" * @ref SubstitutionGoal "substitution goal"
@ -238,7 +263,7 @@ public:
* Registers a running child process. `inBuildSlot` means that * Registers a running child process. `inBuildSlot` means that
* the process counts towards the jobs limit. * the process counts towards the jobs limit.
*/ */
void childStarted(GoalPtr goal, const std::set<int> & fds, void childStarted(GoalPtr goal, const std::set<Child::CommChannel> & channels,
bool inBuildSlot, bool respectTimeouts); bool inBuildSlot, bool respectTimeouts);
/** /**

View file

@ -4,7 +4,7 @@ libstore_NAME = libnixstore
libstore_DIR := $(d) libstore_DIR := $(d)
libstore_SOURCES := $(wildcard $(d)/*.cc $(d)/builtins/*.cc) libstore_SOURCES := $(wildcard $(d)/*.cc $(d)/builtins/*.cc $(d)/build/*.cc)
ifdef HOST_UNIX ifdef HOST_UNIX
libstore_SOURCES += $(wildcard $(d)/unix/*.cc $(d)/unix/builtins/*.cc $(d)/unix/build/*.cc) libstore_SOURCES += $(wildcard $(d)/unix/*.cc $(d)/unix/builtins/*.cc $(d)/unix/build/*.cc)
endif endif
@ -43,7 +43,7 @@ endif
INCLUDE_libstore := -I $(d) -I $(d)/build INCLUDE_libstore := -I $(d) -I $(d)/build
ifdef HOST_UNIX ifdef HOST_UNIX
INCLUDE_libstore += -I $(d)/unix INCLUDE_libstore += -I $(d)/unix -I $(d)/unix/build
endif endif
ifdef HOST_LINUX ifdef HOST_LINUX
INCLUDE_libstore += -I $(d)/linux INCLUDE_libstore += -I $(d)/linux

View file

@ -1280,7 +1280,7 @@ bool DerivationGoal::isReadDesc(int fd)
return fd == hook->builderOut.readSide.get(); return fd == hook->builderOut.readSide.get();
} }
void DerivationGoal::handleChildOutput(int fd, std::string_view data) void DerivationGoal::handleChildOutput(Descriptor fd, std::string_view data)
{ {
// local & `ssh://`-builds are dealt with here. // local & `ssh://`-builds are dealt with here.
auto isWrittenToLog = isReadDesc(fd); auto isWrittenToLog = isReadDesc(fd);
@ -1347,7 +1347,7 @@ void DerivationGoal::handleChildOutput(int fd, std::string_view data)
} }
void DerivationGoal::handleEOF(int fd) void DerivationGoal::handleEOF(Descriptor fd)
{ {
if (!currentLogLine.empty()) flushLine(); if (!currentLogLine.empty()) flushLine();
worker.wakeUp(shared_from_this()); worker.wakeUp(shared_from_this());

View file

@ -2,7 +2,7 @@
///@file ///@file
#include "parsed-derivations.hh" #include "parsed-derivations.hh"
#include "lock.hh" #include "user-lock.hh"
#include "outputs-spec.hh" #include "outputs-spec.hh"
#include "store-api.hh" #include "store-api.hh"
#include "pathlocks.hh" #include "pathlocks.hh"
@ -292,8 +292,8 @@ struct DerivationGoal : public Goal
/** /**
* Callback used by the worker to write to the log. * Callback used by the worker to write to the log.
*/ */
void handleChildOutput(int fd, std::string_view data) override; void handleChildOutput(Descriptor fd, std::string_view data) override;
void handleEOF(int fd) override; void handleEOF(Descriptor fd) override;
void flushLine(); void flushLine();
/** /**

View file

@ -1,4 +1,4 @@
#include "lock.hh" #include "user-lock.hh"
#include "file-system.hh" #include "file-system.hh"
#include "globals.hh" #include "globals.hh"
#include "pathlocks.hh" #include "pathlocks.hh"

View file

@ -1,37 +0,0 @@
#include "store-api.hh"
#include "build-result.hh"
namespace nix {
void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMode, std::shared_ptr<Store> evalStore)
{
unsupported("buildPaths");
}
std::vector<KeyedBuildResult> Store::buildPathsWithResults(
const std::vector<DerivedPath> & reqs,
BuildMode buildMode,
std::shared_ptr<Store> evalStore)
{
unsupported("buildPathsWithResults");
}
BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
BuildMode buildMode)
{
unsupported("buildDerivation");
}
void Store::ensurePath(const StorePath & path)
{
unsupported("ensurePath");
}
void Store::repairPath(const StorePath & path)
{
unsupported("repairPath");
}
}

View file

@ -9,6 +9,8 @@
namespace nix { namespace nix {
using namespace nix::windows;
void deleteLockFile(const Path & path, Descriptor desc) void deleteLockFile(const Path & path, Descriptor desc)
{ {

View file

@ -206,11 +206,11 @@ MakeError(SystemError, Error);
* *
* Throw this, but prefer not to catch this, and catch `SystemError` * Throw this, but prefer not to catch this, and catch `SystemError`
* instead. This allows implementations to freely switch between this * instead. This allows implementations to freely switch between this
* and `WinError` without breaking catch blocks. * and `windows::WinError` without breaking catch blocks.
* *
* However, it is permissible to catch this and rethrow so long as * However, it is permissible to catch this and rethrow so long as
* certain conditions are not met (e.g. to catch only if `errNo = * certain conditions are not met (e.g. to catch only if `errNo =
* EFooBar`). In that case, try to also catch the equivalent `WinError` * EFooBar`). In that case, try to also catch the equivalent `windows::WinError`
* code. * code.
* *
* @todo Rename this to `PosixError` or similar. At this point Windows * @todo Rename this to `PosixError` or similar. At this point Windows
@ -248,7 +248,9 @@ public:
}; };
#ifdef _WIN32 #ifdef _WIN32
class WinError; namespace windows {
class WinError;
}
#endif #endif
/** /**
@ -258,7 +260,7 @@ class WinError;
*/ */
using NativeSysError = using NativeSysError =
#ifdef _WIN32 #ifdef _WIN32
WinError windows::WinError
#else #else
SysError SysError
#endif #endif

View file

@ -136,7 +136,7 @@ size_t FdSource::readUnbuffered(char * data, size_t len)
checkInterrupt(); checkInterrupt();
if (!::ReadFile(fd, data, len, &n, NULL)) { if (!::ReadFile(fd, data, len, &n, NULL)) {
_good = false; _good = false;
throw WinError("ReadFile when FdSource::readUnbuffered"); throw windows::WinError("ReadFile when FdSource::readUnbuffered");
} }
#else #else
ssize_t n; ssize_t n;

View file

@ -14,6 +14,8 @@
namespace nix { namespace nix {
using namespace nix::windows;
std::string readFile(HANDLE handle) std::string readFile(HANDLE handle)
{ {
LARGE_INTEGER li; LARGE_INTEGER li;

View file

@ -9,6 +9,8 @@
namespace nix { namespace nix {
using namespace nix::windows;
std::string getUserName() std::string getUserName()
{ {
// Get the required buffer size // Get the required buffer size

View file

@ -0,0 +1,43 @@
#include "windows-async-pipe.hh"
#include "windows-error.hh"
namespace nix::windows {
void AsyncPipe::createAsyncPipe(HANDLE iocp)
{
// std::cerr << (format("-----AsyncPipe::createAsyncPipe(%x)") % iocp) << std::endl;
buffer.resize(0x1000);
memset(&overlapped, 0, sizeof(overlapped));
std::string pipeName = fmt("\\\\.\\pipe\\nix-%d-%p", GetCurrentProcessId(), (void *) this);
readSide = CreateNamedPipeA(
pipeName.c_str(), PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE, PIPE_UNLIMITED_INSTANCES, 0, 0,
INFINITE, NULL);
if (!readSide)
throw WinError("CreateNamedPipeA(%s)", pipeName);
HANDLE hIocp = CreateIoCompletionPort(readSide.get(), iocp, (ULONG_PTR) (readSide.get()) ^ 0x5555, 0);
if (hIocp != iocp)
throw WinError("CreateIoCompletionPort(%x[%s], %x, ...) returned %x", readSide.get(), pipeName, iocp, hIocp);
if (!ConnectNamedPipe(readSide.get(), &overlapped) && GetLastError() != ERROR_IO_PENDING)
throw WinError("ConnectNamedPipe(%s)", pipeName);
SECURITY_ATTRIBUTES psa2 = {0};
psa2.nLength = sizeof(SECURITY_ATTRIBUTES);
psa2.bInheritHandle = TRUE;
writeSide = CreateFileA(pipeName.c_str(), GENERIC_WRITE, 0, &psa2, OPEN_EXISTING, 0, NULL);
if (!readSide)
throw WinError("CreateFileA(%s)", pipeName);
}
void AsyncPipe::close()
{
readSide.close();
writeSide.close();
}
}

View file

@ -0,0 +1,20 @@
#pragma once
///@file
#include "file-descriptor.hh"
namespace nix::windows {
class AsyncPipe
{
public:
AutoCloseFD writeSide, readSide;
OVERLAPPED overlapped;
DWORD got;
std::vector<unsigned char> buffer;
void createAsyncPipe(HANDLE iocp);
void close();
};
}

View file

@ -4,7 +4,7 @@
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#include <windows.h> #include <windows.h>
namespace nix { namespace nix::windows {
std::string WinError::renderError(DWORD lastError) std::string WinError::renderError(DWORD lastError)
{ {

View file

@ -5,7 +5,7 @@
#include "error.hh" #include "error.hh"
namespace nix { namespace nix::windows {
/** /**
* Windows Error type. * Windows Error type.