libutil: return a program handle from runProgram2

this will let us also return a source for the program output later,
which will in turn make sinkToSource unnecessary for program output
processing. this may also reopen a path for provigin program input,
but that still needs a proper async io framework to avoid problems.

Change-Id: Iaf93f47db99c38cfaf134bd60ed6a804d7ddf688
This commit is contained in:
eldritch horrors 2024-04-05 22:22:44 +02:00
parent f4f6d1d8e2
commit b6a08a2fed
5 changed files with 43 additions and 12 deletions

View file

@ -251,7 +251,7 @@ void runNix(Path program, const Strings & args)
.program = settings.nixBinDir+ "/" + program, .program = settings.nixBinDir+ "/" + program,
.args = args, .args = args,
.environment = subprocessEnv, .environment = subprocessEnv,
}); }).wait();
return; return;
} }
@ -650,7 +650,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
// runProgram redirects stdout to a StringSink, // runProgram redirects stdout to a StringSink,
// using runProgram2 to allow editors to display their UI // using runProgram2 to allow editors to display their UI
runProgram2(RunOptions { .program = editor, .searchPath = true, .args = args }); runProgram2(RunOptions { .program = editor, .searchPath = true, .args = args }).wait();
// Reload right after exiting the editor // Reload right after exiting the editor
state->resetFileCache(); state->resetFileCache();

View file

@ -695,7 +695,7 @@ struct GitInputScheme : InputScheme
.program = "git", .program = "git",
.args = { "-C", repoDir, "--git-dir", gitDir, "archive", input.getRev()->gitRev() }, .args = { "-C", repoDir, "--git-dir", gitDir, "archive", input.getRev()->gitRev() },
.standardOut = &sink .standardOut = &sink
}); }).wait();
}); });
unpackTarfile(*source, tmpDir); unpackTarfile(*source, tmpDir);

View file

@ -928,7 +928,7 @@ void runPostBuildHook(
.environment = hookEnvironment, .environment = hookEnvironment,
.standardOut = &sink, .standardOut = &sink,
.mergeStderrToStdout = true, .mergeStderrToStdout = true,
}); }).wait();
} }
void DerivationGoal::buildDone() void DerivationGoal::buildDone()

View file

@ -249,7 +249,7 @@ std::pair<int, std::string> runProgram(RunOptions && options)
int status = 0; int status = 0;
try { try {
runProgram2(options); runProgram2(options).wait();
} catch (ExecError & e) { } catch (ExecError & e) {
status = e.status; status = e.status;
} }
@ -257,7 +257,25 @@ std::pair<int, std::string> runProgram(RunOptions && options)
return {status, std::move(sink.s)}; return {status, std::move(sink.s)};
} }
void runProgram2(const RunOptions & options) RunningProgram::~RunningProgram()
{
if (pid) {
// we will not kill a subprocess because we *can't* kill a subprocess
// reliably without placing it in its own process group, and cleaning
// up a subprocess only when `separatePG` is set is a loaded footgun.
assert(false && "destroying un-wait()ed running process");
std::terminate();
}
}
void RunningProgram::wait()
{
int status = pid.wait();
if (status)
throw ExecError(status, "program '%1%' %2%", program, statusToString(status));
}
RunningProgram runProgram2(const RunOptions & options)
{ {
checkInterrupt(); checkInterrupt();
@ -317,11 +335,7 @@ void runProgram2(const RunOptions & options)
if (options.standardOut) if (options.standardOut)
*options.standardOut << drainFDSource(out.readSide.get()); *options.standardOut << drainFDSource(out.readSide.get());
/* Wait for the child to finish. */ return RunningProgram{options.program, std::move(pid)};
int status = pid.wait();
if (status)
throw ExecError(status, "program '%1%' %2%", options.program, statusToString(status));
} }
std::string statusToString(int status) std::string statusToString(int status)

View file

@ -87,9 +87,26 @@ struct RunOptions
bool isInteractive = false; bool isInteractive = false;
}; };
struct [[nodiscard("you must call RunningProgram::wait()")]] RunningProgram
{
friend RunningProgram runProgram2(const RunOptions & options);
private:
Path program;
Pid pid;
RunningProgram(Path program, Pid pid) : program(std::move(program)), pid(std::move(pid)) {}
public:
RunningProgram() = default;
~RunningProgram();
void wait();
};
std::pair<int, std::string> runProgram(RunOptions && options); std::pair<int, std::string> runProgram(RunOptions && options);
void runProgram2(const RunOptions & options); RunningProgram runProgram2(const RunOptions & options);
class ExecError : public Error class ExecError : public Error
{ {