130 lines
4.1 KiB
C++
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();
|
|
}
|
|
}
|
|
|
|
}
|