|
|
@@ -8,12 +8,19 @@
|
|
|
package org.elasticsearch.gradle;
|
|
|
|
|
|
import org.gradle.api.Action;
|
|
|
+import org.gradle.api.DefaultTask;
|
|
|
import org.gradle.api.GradleException;
|
|
|
-import org.gradle.api.Task;
|
|
|
import org.gradle.api.file.FileSystemOperations;
|
|
|
+import org.gradle.api.file.ProjectLayout;
|
|
|
import org.gradle.api.logging.Logger;
|
|
|
import org.gradle.api.logging.Logging;
|
|
|
-import org.gradle.api.tasks.Exec;
|
|
|
+import org.gradle.api.provider.ListProperty;
|
|
|
+import org.gradle.api.provider.MapProperty;
|
|
|
+import org.gradle.api.provider.Property;
|
|
|
+import org.gradle.api.tasks.Input;
|
|
|
+import org.gradle.api.tasks.Internal;
|
|
|
+import org.gradle.api.tasks.Optional;
|
|
|
+import org.gradle.api.tasks.TaskAction;
|
|
|
import org.gradle.api.tasks.WorkResult;
|
|
|
import org.gradle.process.BaseExecSpec;
|
|
|
import org.gradle.process.ExecOperations;
|
|
|
@@ -21,13 +28,16 @@ import org.gradle.process.ExecResult;
|
|
|
import org.gradle.process.ExecSpec;
|
|
|
import org.gradle.process.JavaExecSpec;
|
|
|
|
|
|
+import java.io.ByteArrayInputStream;
|
|
|
import java.io.ByteArrayOutputStream;
|
|
|
import java.io.File;
|
|
|
import java.io.IOException;
|
|
|
import java.io.OutputStream;
|
|
|
import java.io.UncheckedIOException;
|
|
|
+import java.io.UnsupportedEncodingException;
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
import java.nio.file.Files;
|
|
|
+import java.util.List;
|
|
|
import java.util.function.Consumer;
|
|
|
import java.util.function.Function;
|
|
|
import java.util.regex.Pattern;
|
|
|
@@ -35,52 +45,70 @@ import java.util.regex.Pattern;
|
|
|
import javax.inject.Inject;
|
|
|
|
|
|
/**
|
|
|
- * A wrapper around gradle's Exec task to capture output and log on error.
|
|
|
+ * A wrapper around gradle's exec functionality to capture output and log on error.
|
|
|
+ * This Task is configuration cache-compatible in contrast to Gradle's built-in
|
|
|
+ * Exec task implementation.
|
|
|
*/
|
|
|
@SuppressWarnings("unchecked")
|
|
|
-public class LoggedExec extends Exec implements FileSystemOperationsAware {
|
|
|
+public abstract class LoggedExec extends DefaultTask implements FileSystemOperationsAware {
|
|
|
|
|
|
private static final Logger LOGGER = Logging.getLogger(LoggedExec.class);
|
|
|
- private Consumer<Logger> outputLogger;
|
|
|
- private FileSystemOperations fileSystemOperations;
|
|
|
+ protected FileSystemOperations fileSystemOperations;
|
|
|
+ private ProjectLayout projectLayout;
|
|
|
+ private ExecOperations execOperations;
|
|
|
+ private boolean spoolOutput;
|
|
|
+
|
|
|
+ @Input
|
|
|
+ @Optional
|
|
|
+ abstract public ListProperty<Object> getArgs();
|
|
|
+
|
|
|
+ @Input
|
|
|
+ @Optional
|
|
|
+ abstract public MapProperty<String, String> getEnvironment();
|
|
|
+
|
|
|
+ @Input
|
|
|
+ abstract public Property<String> getExecutable();
|
|
|
+
|
|
|
+ @Input
|
|
|
+ @Optional
|
|
|
+ abstract public Property<String> getStandardInput();
|
|
|
+
|
|
|
+ @Input
|
|
|
+ @Optional
|
|
|
+ abstract public Property<String> getIndentingConsoleOutput();
|
|
|
+
|
|
|
+ @Input
|
|
|
+ @Optional
|
|
|
+ abstract public Property<Boolean> getCaptureOutput();
|
|
|
+
|
|
|
+ @Input
|
|
|
+ abstract public Property<File> getWorkingDir();
|
|
|
+
|
|
|
+ private String output;
|
|
|
|
|
|
@Inject
|
|
|
- public LoggedExec(FileSystemOperations fileSystemOperations) {
|
|
|
+ public LoggedExec(ProjectLayout projectLayout, ExecOperations execOperations, FileSystemOperations fileSystemOperations) {
|
|
|
+ this.projectLayout = projectLayout;
|
|
|
+ this.execOperations = execOperations;
|
|
|
this.fileSystemOperations = fileSystemOperations;
|
|
|
- if (getLogger().isInfoEnabled() == false) {
|
|
|
- setIgnoreExitValue(true);
|
|
|
- setSpoolOutput(false);
|
|
|
- // We use an anonymous inner class here because Gradle cannot properly snapshot this input for the purposes of
|
|
|
- // incremental build if we use a lambda. This ensures LoggedExec tasks that declare output can be UP-TO-DATE.
|
|
|
- doLast(new Action<Task>() {
|
|
|
- @Override
|
|
|
- public void execute(Task task) {
|
|
|
- int exitValue = LoggedExec.this.getExecutionResult().get().getExitValue();
|
|
|
- if (exitValue != 0) {
|
|
|
- try {
|
|
|
- LoggedExec.this.getLogger().error("Output for " + LoggedExec.this.getExecutable() + ":");
|
|
|
- outputLogger.accept(LoggedExec.this.getLogger());
|
|
|
- } catch (Exception e) {
|
|
|
- throw new GradleException("Failed to read exec output", e);
|
|
|
- }
|
|
|
- throw new GradleException(
|
|
|
- String.format(
|
|
|
- "Process '%s %s' finished with non-zero exit value %d",
|
|
|
- LoggedExec.this.getExecutable(),
|
|
|
- LoggedExec.this.getArgs(),
|
|
|
- exitValue
|
|
|
- )
|
|
|
- );
|
|
|
- }
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
+ getWorkingDir().convention(projectLayout.getProjectDirectory().getAsFile());
|
|
|
+ // For now mimic default behaviour of Gradle Exec task here
|
|
|
+ getEnvironment().putAll(System.getenv());
|
|
|
+ getCaptureOutput().convention(false);
|
|
|
}
|
|
|
|
|
|
- public void setSpoolOutput(boolean spoolOutput) {
|
|
|
- final OutputStream out;
|
|
|
+ @TaskAction
|
|
|
+ public void run() {
|
|
|
+ if (spoolOutput && getCaptureOutput().get()) {
|
|
|
+ throw new GradleException("Capturing output is not supported when spoolOutput is true.");
|
|
|
+ }
|
|
|
+ if (getCaptureOutput().getOrElse(false) && getIndentingConsoleOutput().isPresent()) {
|
|
|
+ throw new GradleException("Capturing output is not supported when indentingConsoleOutput is configured.");
|
|
|
+ }
|
|
|
+ Consumer<Logger> outputLogger;
|
|
|
+ OutputStream out;
|
|
|
if (spoolOutput) {
|
|
|
- File spoolFile = new File(getProject().getBuildDir() + "/buffered-output/" + this.getName());
|
|
|
+ File spoolFile = new File(projectLayout.getBuildDirectory().dir("buffered-output").get().getAsFile(), this.getName());
|
|
|
out = new LazyFileOutputStream(spoolFile);
|
|
|
outputLogger = logger -> {
|
|
|
try {
|
|
|
@@ -94,10 +122,59 @@ public class LoggedExec extends Exec implements FileSystemOperationsAware {
|
|
|
};
|
|
|
} else {
|
|
|
out = new ByteArrayOutputStream();
|
|
|
- outputLogger = logger -> { logger.error(((ByteArrayOutputStream) out).toString(StandardCharsets.UTF_8)); };
|
|
|
+ outputLogger = getIndentingConsoleOutput().isPresent() ? logger -> {} : logger -> logger.error(byteStreamToString(out));
|
|
|
+ }
|
|
|
+
|
|
|
+ OutputStream finalOutputStream = getIndentingConsoleOutput().isPresent()
|
|
|
+ ? new IndentingOutputStream(System.out, getIndentingConsoleOutput().get())
|
|
|
+ : out;
|
|
|
+ ExecResult execResult = execOperations.exec(execSpec -> {
|
|
|
+ execSpec.setIgnoreExitValue(true);
|
|
|
+ execSpec.setStandardOutput(finalOutputStream);
|
|
|
+ execSpec.setErrorOutput(finalOutputStream);
|
|
|
+ execSpec.setExecutable(getExecutable().get());
|
|
|
+ execSpec.setEnvironment(getEnvironment().get());
|
|
|
+ if (getArgs().isPresent()) {
|
|
|
+ execSpec.setArgs(getArgs().get());
|
|
|
+ }
|
|
|
+ if (getWorkingDir().isPresent()) {
|
|
|
+ execSpec.setWorkingDir(getWorkingDir().get());
|
|
|
+ }
|
|
|
+ if (getStandardInput().isPresent()) {
|
|
|
+ try {
|
|
|
+ execSpec.setStandardInput(new ByteArrayInputStream(getStandardInput().get().getBytes("UTF-8")));
|
|
|
+ } catch (UnsupportedEncodingException e) {
|
|
|
+ throw new GradleException("Cannot set standard input", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ int exitValue = execResult.getExitValue();
|
|
|
+
|
|
|
+ if (exitValue == 0 && getCaptureOutput().get()) {
|
|
|
+ output = byteStreamToString(out);
|
|
|
+ }
|
|
|
+ if (getLogger().isInfoEnabled() == false) {
|
|
|
+ if (exitValue != 0) {
|
|
|
+ try {
|
|
|
+ getLogger().error("Output for " + getExecutable().get() + ":");
|
|
|
+ outputLogger.accept(getLogger());
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new GradleException("Failed to read exec output", e);
|
|
|
+ }
|
|
|
+ throw new GradleException(
|
|
|
+ String.format("Process '%s %s' finished with non-zero exit value %d", getExecutable().get(), getArgs().get(), exitValue)
|
|
|
+ );
|
|
|
+ }
|
|
|
}
|
|
|
- setStandardOutput(out);
|
|
|
- setErrorOutput(out);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private String byteStreamToString(OutputStream out) {
|
|
|
+ return ((ByteArrayOutputStream) out).toString(StandardCharsets.UTF_8);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setSpoolOutput(boolean spoolOutput) {
|
|
|
+ this.spoolOutput = spoolOutput;
|
|
|
}
|
|
|
|
|
|
public static ExecResult exec(ExecOperations execOperations, Action<ExecSpec> action) {
|
|
|
@@ -139,4 +216,60 @@ public class LoggedExec extends Exec implements FileSystemOperationsAware {
|
|
|
public WorkResult delete(Object... objects) {
|
|
|
return fileSystemOperations.delete(d -> d.delete(objects));
|
|
|
}
|
|
|
+
|
|
|
+ @Internal
|
|
|
+ public String getOutput() {
|
|
|
+ if (getCaptureOutput().get() == false) {
|
|
|
+ throw new GradleException(
|
|
|
+ "Capturing output was not enabled. Use " + getName() + ".getCapturedOutput.set(true) to enable output capturing."
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return output;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class IndentingOutputStream extends OutputStream {
|
|
|
+
|
|
|
+ public final byte[] indent;
|
|
|
+ private final OutputStream delegate;
|
|
|
+
|
|
|
+ IndentingOutputStream(OutputStream delegate, Object version) {
|
|
|
+ this.delegate = delegate;
|
|
|
+ indent = (" [" + version + "] ").getBytes(StandardCharsets.UTF_8);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void write(int b) throws IOException {
|
|
|
+ int[] arr = { b };
|
|
|
+ write(arr, 0, 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void write(int[] bytes, int offset, int length) throws IOException {
|
|
|
+ for (int i = 0; i < bytes.length; i++) {
|
|
|
+ delegate.write(bytes[i]);
|
|
|
+ if (bytes[i] == '\n') {
|
|
|
+ delegate.write(indent);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void args(Object... args) {
|
|
|
+ args(List.of(args));
|
|
|
+ }
|
|
|
+
|
|
|
+ public void args(List<Object> args) {
|
|
|
+ getArgs().addAll(args);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void commandLine(Object... args) {
|
|
|
+ commandLine(List.of(args));
|
|
|
+ }
|
|
|
+
|
|
|
+ public void commandLine(List<Object> args) {
|
|
|
+ if (args.isEmpty()) {
|
|
|
+ throw new IllegalArgumentException("Cannot set commandline with empty list.");
|
|
|
+ }
|
|
|
+ getExecutable().set(args.get(0).toString());
|
|
|
+ getArgs().set(args.subList(1, args.size()));
|
|
|
+ }
|
|
|
}
|