All checks were successful
Build, test and publish / verify (push) Successful in 8m54s
97 lines
3.6 KiB
Java
97 lines
3.6 KiB
Java
import java.io.*;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.nio.file.*;
|
|
import java.time.*;
|
|
import java.util.*;
|
|
import java.util.concurrent.*;
|
|
|
|
/** Executes argument lists directly, without shell interpolation or credential logging. */
|
|
class Commands {
|
|
record Result(int code, String output) {
|
|
String checked() {
|
|
if (code != 0) throw new Failure(code, "Command failed with exit status " + code);
|
|
return output.strip();
|
|
}
|
|
}
|
|
|
|
static class Failure extends RuntimeException {
|
|
final int exitCode;
|
|
|
|
Failure(int exitCode, String message) {
|
|
super(message);
|
|
this.exitCode = exitCode;
|
|
}
|
|
}
|
|
|
|
Instant deadline;
|
|
volatile Process active;
|
|
Path log;
|
|
|
|
void renew(Duration timeout) {
|
|
deadline = Instant.now().plus(timeout);
|
|
}
|
|
|
|
void cancel() {
|
|
Process process = active;
|
|
if (process != null) stop(process);
|
|
}
|
|
|
|
Commands(Duration timeout) {
|
|
deadline = Instant.now().plus(timeout);
|
|
}
|
|
|
|
Result execute(List<String> args, Path cwd, Map<String, String> env, String input, boolean echo) throws Exception {
|
|
if (!Instant.now().isBefore(deadline)) throw new Failure(124, "CI time limit exceeded");
|
|
ProcessBuilder builder =
|
|
new ProcessBuilder(args).directory(cwd.toFile()).redirectErrorStream(true);
|
|
builder.environment().clear();
|
|
builder.environment().putAll(env);
|
|
Process process = builder.start();
|
|
active = process;
|
|
ByteArrayOutputStream captured = new ByteArrayOutputStream();
|
|
Path destination = log;
|
|
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
|
|
Future<?> reader = executor.submit(() -> {
|
|
try (InputStream stream = process.getInputStream();
|
|
OutputStream file = destination == null
|
|
? OutputStream.nullOutputStream()
|
|
: Files.newOutputStream(
|
|
destination, StandardOpenOption.CREATE, StandardOpenOption.APPEND)) {
|
|
byte[] buffer = new byte[8192];
|
|
int count;
|
|
while ((count = stream.read(buffer)) != -1) {
|
|
captured.write(buffer, 0, count);
|
|
if (echo) {
|
|
System.out.write(buffer, 0, count);
|
|
System.out.flush();
|
|
}
|
|
file.write(buffer, 0, count);
|
|
}
|
|
} catch (IOException e) {
|
|
throw new UncheckedIOException(e);
|
|
}
|
|
});
|
|
try {
|
|
try (OutputStream stdin = process.getOutputStream()) {
|
|
if (input != null) stdin.write(input.getBytes(StandardCharsets.UTF_8));
|
|
}
|
|
if (!process.waitFor(
|
|
Math.max(1, Duration.between(Instant.now(), deadline).toMillis()), TimeUnit.MILLISECONDS)) {
|
|
stop(process);
|
|
throw new Failure(124, "CI time limit exceeded");
|
|
}
|
|
reader.get(10, TimeUnit.SECONDS);
|
|
return new Result(process.exitValue(), captured.toString(StandardCharsets.UTF_8));
|
|
} finally {
|
|
if (process.isAlive()) stop(process);
|
|
active = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void stop(Process process) {
|
|
process.descendants().forEach(ProcessHandle::destroyForcibly);
|
|
process.destroyForcibly();
|
|
}
|
|
}
|