Files
lix/lix/libstore/build/hook-instance.cc
T
eldritch horrors c20aaf6ca7 libstore: inline commonExecveingChildInit into single user
Change-Id: Ia389ec8c5e375b8386949c5fb031a52f27e76d37
2026-02-02 14:20:54 +00:00

130 lines
4.1 KiB
C++

#include "lix/libutil/c-calls.hh"
#include "lix/libutil/error.hh"
#include "lix/libutil/file-descriptor.hh"
#include "lix/libutil/file-system.hh"
#include "lix/libstore/globals.hh"
#include "lix/libstore/build/hook-instance.hh"
#include "lix/libutil/json.hh"
#include "lix/libutil/logging.hh"
#include "lix/libutil/processes.hh"
#include "lix/libutil/result.hh"
#include "lix/libutil/rpc.hh"
#include "lix/libutil/serialise.hh"
#include "lix/libutil/strings.hh"
#include "lix/libutil/logging-rpc.hh" // IWYU pragma: keep
#include "lix/libutil/types-rpc.hh" // IWYU pragma: keep
#include <fcntl.h>
#include <kj/common.h>
#include <kj/memory.h>
#include <memory>
#include <string_view>
#include <unistd.h>
namespace nix {
void HookInstance::HookLogger::emitLog(rpc::log::Event::Result::Reader r)
{
auto type = rpc::log::from(r.getType());
auto fields = r.getFields();
if (!type) {
return;
}
// ensure that logs from a builder using `ssh-ng://` as protocol
// are also available to `nix log`.
if (type == resBuildLogLine) {
if (fields.size() > 0 && fields[0].isS()) {
(*logSink)(fmt("%s\n", rpc::to<std::string_view>(fields[0].getS())));
} else {
(*logSink)("\n");
}
} else if (type == resSetPhase && fields.size() > 0 && fields[0].isS()) {
// nixpkgs' stdenv produces lines in the log to signal phase changes.
// We want to get the same lines in case of remote builds.
// The format is:
// @nix { "action": "setPhase", "phase": "$curPhase" }
const auto phase = rpc::to<std::string_view>(fields[0].getS());
const auto logLine = JSON::object({{"action", "setPhase"}, {"phase", phase}});
(*logSink)("@nix " + logLine.dump(-1, ' ', false, JSON::error_handler_t::replace) + "\n");
}
}
kj::Promise<void> HookInstance::HookLogger::push(PushContext context)
{
try {
auto e = context.getParams().getE();
if (logSink && e.isResult()) {
emitLog(e.getResult());
}
} catch (std::exception & e) { // NOLINT(lix-foreign-exceptions)
printError("error in log processor: %s", e.what());
throw; // NOLINT(lix-foreign-exceptions)
}
return RpcLoggerServer::push(context);
}
kj::Promise<Result<std::unique_ptr<HookInstance>>> HookInstance::create(const Activity & act)
try {
debug("starting build hook '%s'", concatStringsSep(" ", settings.buildHook.get()));
auto buildHookArgs = settings.buildHook.get();
if (buildHookArgs.empty())
throw Error("'build-hook' setting is empty");
auto buildHook = canonPath(buildHookArgs.front());
buildHookArgs.emplace(std::next(buildHookArgs.begin()), baseNameOf(buildHook));
buildHookArgs.push_back(std::to_string(verbosity));
/* Create the communication pipes. */
auto [selfRPC, hookRPC] = SocketPair::stream();
/* Fork the hook. */
auto pid = runHelper(
"run-build-hook",
{
.args = buildHookArgs,
.redirections = {{.dup = STDOUT_FILENO, .from = hookRPC.get()}},
}
);
KJ_DEFER({
// kill the hook if the promise is cancelled. the hook helper creates
// a session, so we'll kill the entire process group just to be safe.
if (pid) {
pid.killProcessGroup();
}
});
std::map<std::string, Config::SettingInfo> settings;
globalConfig.getSettings(settings, true);
auto conn = AIO().lowLevelProvider.wrapUnixSocketFd(kj::AutoCloseFd(selfRPC.release()));
auto client = std::make_unique<capnp::TwoPartyClient>(*conn, 1);
auto rpc = client->bootstrap().castAs<rpc::build_remote::HookInstance>();
{
auto initReq = rpc.initRequest();
initReq.setLogger(kj::heap<HookLogger>(act, nullptr));
RPC_FILL(initReq, initSettings, settings);
TRY_AWAIT_RPC(initReq.send());
}
co_return std::make_unique<HookInstance>(
kj::heap(std::move(rpc)).attach(std::move(conn), std::move(client)), std::move(pid)
);
} catch (...) {
co_return result::current_exception();
}
HookInstance::~HookInstance()
{
try {
kill();
} catch (...) {
ignoreExceptionInDestructor();
}
}
}