diff --git a/common/src/main/java/org/implab/gradle/common/exec/EchoExecBuilder.java b/common/src/main/java/org/implab/gradle/common/exec/EchoExecBuilder.java new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/exec/EchoExecBuilder.java @@ -0,0 +1,32 @@ +package org.implab.gradle.common.exec; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +class EchoExecBuilder extends ExecBuilder { + + private final boolean echoToStderr; + + public EchoExecBuilder(boolean echoToStderr) { + this.echoToStderr = echoToStderr; + } + + @Override + protected CompletableFuture startInternal(Map environment, File directory, + List commandLine, Optional stdinRedirect, Optional stdoutRedirect, + Optional stderrRedirect) throws IOException { + var outputRedirect = echoToStderr ? stderrRedirect : stdoutRedirect; + + return outputRedirect.map(to -> { + var bytes = String.format("exec: %s", commandLine).getBytes(StandardCharsets.UTF_8); + return to.redirect(new ByteArrayInputStream(bytes)).thenApply((x) -> 0); + }).orElse(CompletableFuture.completedFuture(0)); + } + +} diff --git a/common/src/main/java/org/implab/gradle/common/exec/ExecBuilder.java b/common/src/main/java/org/implab/gradle/common/exec/ExecBuilder.java --- a/common/src/main/java/org/implab/gradle/common/exec/ExecBuilder.java +++ b/common/src/main/java/org/implab/gradle/common/exec/ExecBuilder.java @@ -1,6 +1,13 @@ package org.implab.gradle.common.exec; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -9,53 +16,109 @@ import java.io.IOException; /** Command line builder */ @NonNullByDefault -public interface ExecBuilder { +public abstract class ExecBuilder { + + private final List commandLine = new ArrayList<>(); + + private final Map environment = new HashMap<>(); + + private File directory; + + private RedirectFrom inputRedirect; + + private RedirectTo outputRedirect; + + private RedirectTo errorRedirect; /** Sets command line, clears previous one if any */ - void command(String cmd, String... args); + public void command(String cmd, String... args) { + commandLine.clear(); + commandLine.add(cmd); + Stream.of(args).forEach(commandLine::add); + } /** Adds an argument to the command line */ - void argument(String arg); + public void argument(String arg) { + Objects.requireNonNull(arg, "arg parameter can't be null"); + commandLine.add(arg); + } /** Sets the working directory */ - void directory(File directory); + public void directory(File directory) { + Objects.requireNonNull(directory, "directory parameter can't be null"); + this.directory = directory; + } /** * Sets the environment value. The value cannot be null. * * @param envVar The name of the environment variable - * @param value The value to set, + * @param value The value to set, */ - void setEnvironment(String envVar, String value); + public void setEnvironment(String envVar, String value) { + Objects.requireNonNull(value, "Value can't be null"); + Objects.requireNonNull(envVar, "envVar parameter can't be null"); + environment.put(envVar, value); + } - void unsetEnvironment(String envVar); + public void unsetEnvironment(String envVar) { + environment.remove(envVar); + } /** * Sets redirection for the stdin, {@link RedirectFrom} will be applied * every time the process is started. * - *

If redirection + *

+ * If redirection */ - void stdin(RedirectFrom from); + public void stdin(RedirectFrom from) { + Objects.requireNonNull(from, "from parameter can't be null"); + inputRedirect = from; + } /** * Sets redirection for the stdout, {@link RedirectTo} will be applied * every time the process is started. */ - void stdout(RedirectTo out); + public void stdout(RedirectTo out) { + Objects.requireNonNull(out, "out parameter can't be null"); + outputRedirect = out; + } /** * Sets redirection for the stderr, {@link RedirectTo} will be applied * every time the process is started. */ - void stderr(RedirectTo err); + public void stderr(RedirectTo err) { + Objects.requireNonNull(err, "err parameter can't be null"); + errorRedirect = err; + } + + /** Implement this function to */ + protected abstract CompletableFuture startInternal( + Map environment, + File directory, + List commandLine, + Optional stdinRedirect, + Optional stdoutRedirect, + Optional stderrRedirect) throws IOException; /** * Creates and starts new process and returns {@link CompletableFuture}. The * process may be a remote process, or a shell process or etc. + * * @return * @throws IOException */ - CompletableFuture start() throws IOException; + public CompletableFuture start() throws IOException { + return startInternal( + environment, + directory, + commandLine, + Optional.ofNullable(inputRedirect), + Optional.ofNullable(outputRedirect), + Optional.ofNullable(errorRedirect)); + } } diff --git a/common/src/main/java/org/implab/gradle/common/exec/ExecShell.java b/common/src/main/java/org/implab/gradle/common/exec/ExecShell.java --- a/common/src/main/java/org/implab/gradle/common/exec/ExecShell.java +++ b/common/src/main/java/org/implab/gradle/common/exec/ExecShell.java @@ -1,5 +1,13 @@ package org.implab.gradle.common.exec; +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + /** Shell used to start processes */ public interface ExecShell { @@ -13,4 +21,38 @@ public interface ExecShell { static ExecShell system() { return () -> new SystemExecBuilder(); } + + /** + * Creates a stub shell which will print command to STDERR and return 0 (no + * error). + */ + static ExecShell echoStderr() { + return () -> new EchoExecBuilder(true); + } + + /** + * Creates a stub shell which will print command to STDOUT and return 0 (no + * error). + */ + + static ExecShell echo() { + return () -> new EchoExecBuilder(false); + } + + /** Creates a stub shell which will call the specified consumer and will supply a command line to it. + * The command line will be passed as a list of strings. + * @param consumer The consumer fot the command line + * @return + */ + static ExecShell echo(Consumer> consumer) { + return () -> new ExecBuilder() { + @Override + protected CompletableFuture startInternal(Map environment, File directory, + List commandLine, Optional stdinRedirect, Optional stdoutRedirect, + Optional stderrRedirect) throws IOException { + consumer.accept(commandLine); + return CompletableFuture.completedFuture(0); + } + }; + } } diff --git a/common/src/main/java/org/implab/gradle/common/exec/SystemExecBuilder.java b/common/src/main/java/org/implab/gradle/common/exec/SystemExecBuilder.java --- a/common/src/main/java/org/implab/gradle/common/exec/SystemExecBuilder.java +++ b/common/src/main/java/org/implab/gradle/common/exec/SystemExecBuilder.java @@ -4,29 +4,28 @@ import java.io.File; import java.io.IOException; import java.lang.ProcessBuilder.Redirect; import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.concurrent.CompletableFuture; -class SystemExecBuilder implements ExecBuilder { - - private final ProcessBuilder builder = new ProcessBuilder(); - - private RedirectFrom inputRedirect; +class SystemExecBuilder extends ExecBuilder { + @Override + protected CompletableFuture startInternal(Map environment, File directory, + List commandLine, Optional stdinRedirect, Optional stdoutRedirect, + Optional stderrRedirect) throws IOException { - private RedirectTo outputRedirect; + var builder = new ProcessBuilder(commandLine) + .directory(directory); - private RedirectTo errorRedirect; + builder.environment().putAll(environment); - @Override - public CompletableFuture start() throws IOException { // TODO Auto-generated method stub var tasks = new ArrayList>(); - // discard stdout if not redirected - if (outputRedirect == null) + if (!stdoutRedirect.isPresent()) builder.redirectOutput(Redirect.DISCARD); - - // discard stderr if not redirected - if (errorRedirect == null) + if (!stderrRedirect.isPresent()) builder.redirectError(Redirect.DISCARD); // run process @@ -34,65 +33,12 @@ class SystemExecBuilder implements ExecB tasks.add(proc.onExit()); - if (inputRedirect != null) - tasks.add(inputRedirect.redirect(proc.getOutputStream())); - - if (outputRedirect != null) - tasks.add(outputRedirect.redirect(proc.getInputStream())); - - if (errorRedirect != null) - tasks.add(errorRedirect.redirect(proc.getErrorStream())); + stdinRedirect.map(from -> from.redirect(proc.getOutputStream())).ifPresent(tasks::add); + stdoutRedirect.map(to -> to.redirect(proc.getInputStream())).ifPresent(tasks::add); + stderrRedirect.map(to -> to.redirect(proc.getErrorStream())).ifPresent(tasks::add); return CompletableFuture .allOf(tasks.toArray(new CompletableFuture[0])) .thenApply(t -> proc.exitValue()); } - - @Override - public void directory(File workingDir) { - builder.directory(workingDir); - } - - @Override - public void stderr(RedirectTo to) { - errorRedirect = to; - } - - @Override - public void stdin(RedirectFrom from) { - inputRedirect = from; - } - - @Override - public void stdout(RedirectTo to) { - outputRedirect = to; - } - - @Override - public void argument(String arg) { - builder.command().add(arg); - } - - @Override - public void setEnvironment(String envVar, String value) { - builder.environment().put(envVar, value); - - } - - @Override - public void unsetEnvironment(String envVar) { - builder.environment().remove(envVar); - } - - @Override - public void command(String cmd, String... args) { - var commandLine = new ArrayList(); - - commandLine.add(cmd); - for (var arg : args) - commandLine.add(arg); - - builder.command(commandLine); - } - } diff --git a/common/src/main/java/org/implab/gradle/common/utils/ObjectsMixin.java b/common/src/main/java/org/implab/gradle/common/utils/ObjectsMixin.java --- a/common/src/main/java/org/implab/gradle/common/utils/ObjectsMixin.java +++ b/common/src/main/java/org/implab/gradle/common/utils/ObjectsMixin.java @@ -4,6 +4,7 @@ import java.util.function.Supplier; import javax.inject.Inject; +import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.Provider; import org.gradle.api.provider.ProviderFactory; @@ -24,4 +25,16 @@ public interface ObjectsMixin { default Provider provider(Closure closure) { return getProviders().provider(closure); } + + default T newInstance(Class clazz) { + return getObjects().newInstance(clazz); + } + + default T newInstance(Class clazz, Object... params) { + return getObjects().newInstance(clazz, params); + } + + default NamedDomainObjectContainer newContainer(Class clazz) { + return getObjects().domainObjectContainer(clazz); + } }