diff --git a/common/src/main/java/org/implab/gradle/common/dsl/RedirectFromSpec.java b/common/src/main/java/org/implab/gradle/common/dsl/RedirectFromSpec.java --- a/common/src/main/java/org/implab/gradle/common/dsl/RedirectFromSpec.java +++ b/common/src/main/java/org/implab/gradle/common/dsl/RedirectFromSpec.java @@ -1,5 +1,7 @@ package org.implab.gradle.common.dsl; +import java.io.File; +import java.io.InputStream; import java.util.Optional; import java.util.function.Supplier; @@ -8,7 +10,7 @@ import org.implab.gradle.common.exec.Red public class RedirectFromSpec { private Supplier streamRedirect; - + public boolean isRedirected() { return streamRedirect != null; } @@ -17,6 +19,22 @@ public class RedirectFromSpec { return streamRedirect != null ? Optional.ofNullable(streamRedirect.get()) : Optional.empty(); } + public void fromFile(File file) { + this.streamRedirect = () -> RedirectFrom.file(file); + } + + public void fromFile(Provider fileProvider) { + this.streamRedirect = fileProvider.map(RedirectFrom::file)::getOrNull; + } + + public void fromStream(InputStream stream) { + this.streamRedirect = () -> RedirectFrom.stream(stream); + } + + public void fromStream(Provider streamProvider) { + this.streamRedirect = streamProvider.map(RedirectFrom::stream)::getOrNull; + } + public void from(Object input) { if (input instanceof Provider inputProvider) { this.streamRedirect = inputProvider.map(RedirectFrom::any)::get; diff --git a/common/src/main/java/org/implab/gradle/common/exec/CommandBuilder.java b/common/src/main/java/org/implab/gradle/common/exec/CommandBuilder.java --- a/common/src/main/java/org/implab/gradle/common/exec/CommandBuilder.java +++ b/common/src/main/java/org/implab/gradle/common/exec/CommandBuilder.java @@ -1,7 +1,5 @@ package org.implab.gradle.common.exec; -import java.util.stream.Stream; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.implab.gradle.common.utils.Values; @@ -12,18 +10,21 @@ public interface CommandBuilder { /** Sets the executable, the parameters are left intact. */ CommandBuilder executable(String executable); - /** Sets the specified executable and parameters, old executable and parameters + /** + * Sets the specified executable and parameters, old executable and parameters * are discarded. */ default CommandBuilder commandLine(String executable, String... args) { return executable(executable) - .arguments(args); + .arguments(args); } - /** Sets the specified executable and parameters, old executable and parameters + /** + * Sets the specified executable and parameters, old executable and parameters * are discarded. * - * @param command The command line. Must contain at least one element (executable). + * @param command The command line. Must contain at least one element + * (executable). * */ default CommandBuilder commandLine(Iterable command) { @@ -33,6 +34,7 @@ public interface CommandBuilder { executable(Values.take(iterator).orElseThrow()); // cleat arguments arguments(); + // set new arguments while (iterator.hasNext()) addArguments(iterator.next()); @@ -41,10 +43,13 @@ public interface CommandBuilder { } /** Adds the specified arguments to this builder */ - default CommandBuilder addArguments(String arg0, String... args) { - return arguments(() -> Stream - .concat(Stream.of(arg0), Stream.of(args)) - .iterator()); + CommandBuilder addArguments(String arg0, String... args); + + default CommandBuilder addArguments(Iterable args) { + for (String arg : args) + addArguments(arg); + + return this; } /** Replaces arguments in the builder with the specified one. */ 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 --- a/common/src/main/java/org/implab/gradle/common/exec/EchoExecBuilder.java +++ b/common/src/main/java/org/implab/gradle/common/exec/EchoExecBuilder.java @@ -1,13 +1,10 @@ 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; +import java.util.stream.Collectors; class EchoExecBuilder extends ExecBuilder { @@ -19,18 +16,18 @@ class EchoExecBuilder extends ExecBuilde @Override protected CompletableFuture startInternal( - Map environment, - Optional directory, - List commandLine, - Optional stdinRedirect, - Optional stdoutRedirect, - Optional stderrRedirect) throws IOException { + CommandSpec command, + EnvironmentSpec environment, + PipeSpec redirect) throws IOException { - var outputRedirect = echoToStderr ? stderrRedirect : stdoutRedirect; + var outputRedirect = echoToStderr ? redirect.stderr() : redirect.stdout(); return outputRedirect .map(to -> { - var bytes = String.format("exec: %s", commandLine) + var bytes = String + .format( + "exec: %s", + command.commandLine().stream().collect(Collectors.joining(" "))) .getBytes(StandardCharsets.UTF_8); return to.redirect(new ByteArrayInputStream(bytes)) diff --git a/common/src/main/java/org/implab/gradle/common/exec/EnvironmentBuilder.java b/common/src/main/java/org/implab/gradle/common/exec/EnvironmentBuilder.java --- a/common/src/main/java/org/implab/gradle/common/exec/EnvironmentBuilder.java +++ b/common/src/main/java/org/implab/gradle/common/exec/EnvironmentBuilder.java @@ -8,16 +8,22 @@ import org.eclipse.jdt.annotation.NonNul @NonNullByDefault public interface EnvironmentBuilder { + /** Sets the specified environment variable */ EnvironmentBuilder setVariable(String envVar, String value); + /** Replaces environment with the supplied one */ EnvironmentBuilder environment(Map env); + /** Unsets the specified variable. If the variable is inherited it removed anyway. */ EnvironmentBuilder unsetVariable(String envVar); + /** Specifies the working directory */ EnvironmentBuilder workingDirectory(File directory); + /** Specifies the working directory */ EnvironmentBuilder workingDirectory(Optional directory); + /** Copies the supplied environment to this one */ default EnvironmentBuilder from(EnvironmentSpec environmentSpec) { return environment(environmentSpec.environment()) .workingDirectory(environmentSpec.workingDirectory()); 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 @@ -97,6 +97,14 @@ public abstract class ExecBuilder implem } @Override + public ExecBuilder unsetVariable(String envVar) { + requireNonNull(envVar, "envVar parameter can't be null"); + + environment.remove(envVar); + return this; + } + + @Override public ExecBuilder environment(Map env) { requireNonNull(env, "env parameter can't be null"); @@ -121,7 +129,7 @@ public abstract class ExecBuilder implem } @Override - public PipeBuilder stdin(Optional from) { + public ExecBuilder stdin(Optional from) { requireNonNull(from, "from parameter can't be null"); inputRedirect = from.orElse(null); return this; @@ -139,7 +147,7 @@ public abstract class ExecBuilder implem } @Override - public PipeBuilder stdout(Optional to) { + public ExecBuilder stdout(Optional to) { requireNonNull(to, "from parameter can't be null"); outputRedirect = to.orElse(null); return this; @@ -157,20 +165,35 @@ public abstract class ExecBuilder implem } @Override - public PipeBuilder stderr(Optional to) { + public ExecBuilder stderr(Optional to) { requireNonNull(to, "from parameter can't be null"); errorRedirect = to.orElse(null); return this; } + @Override + public ExecBuilder from(CommandSpec commandSpec) { + CommandBuilder.super.from(commandSpec); + return this; + } + + @Override + public ExecBuilder from(PipeSpec pipeSpec) { + PipeBuilder.super.from(pipeSpec); + return this; + } + + @Override + public ExecBuilder from(EnvironmentSpec environmentSpec) { + EnvironmentBuilder.super.from(environmentSpec); + return this; + } + /** Implement this function to */ protected abstract CompletableFuture startInternal( - Map environment, - Optional directory, - List commandLine, - Optional stdinRedirect, - Optional stdoutRedirect, - Optional stderrRedirect) throws IOException; + CommandSpec command, + EnvironmentSpec environment, + PipeSpec redirect) throws IOException; /** * Creates and starts new process and returns {@link CompletableFuture}. The @@ -188,12 +211,55 @@ public abstract class ExecBuilder implem commandLine.addAll(arguments); return startInternal( - environment, - Optional.ofNullable(directory), - commandLine, - Optional.ofNullable(inputRedirect), - Optional.ofNullable(outputRedirect), - Optional.ofNullable(errorRedirect)); + new SelfCommandSpec(), + new SelfEnvironmentSpec(), + new SelfPipeSpec() + ); + } + + private class SelfCommandSpec implements CommandSpec { + + @Override + public String executable() { + return executable; + } + + @Override + public List arguments() { + return arguments; + } + } + + private class SelfEnvironmentSpec implements EnvironmentSpec { + + @Override + public Map environment() { + return environment; + } + + @Override + public Optional workingDirectory() { + return Optional.ofNullable( directory); + } + } + + private class SelfPipeSpec implements PipeSpec { + + @Override + public Optional stdout() { + return Optional.ofNullable(outputRedirect); + } + + @Override + public Optional stderr() { + return Optional.ofNullable(errorRedirect); + } + + @Override + public Optional stdin() { + return Optional.ofNullable(inputRedirect); + } + } } 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,10 +1,7 @@ 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; @@ -39,18 +36,22 @@ public interface ExecShell { return () -> new EchoExecBuilder(false); } - /** Creates a stub shell which will call the specified consumer and will supply a command line to it. + /** + * 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 + * + * @param consumer The consumer fot the command line * @return */ static ExecShell echo(Consumer> consumer) { return () -> new ExecBuilder() { @Override - protected CompletableFuture startInternal(Map environment, Optional directory, - List commandLine, Optional stdinRedirect, Optional stdoutRedirect, - Optional stderrRedirect) throws IOException { - consumer.accept(commandLine); + protected CompletableFuture startInternal( + CommandSpec command, + EnvironmentSpec environment, + PipeSpec redirect) throws IOException { + consumer.accept(command.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 @@ -1,36 +1,28 @@ package org.implab.gradle.common.exec; -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 extends ExecBuilder { @Override protected CompletableFuture startInternal( - Map environment, - Optional directory, - List commandLine, - Optional stdinRedirect, - Optional stdoutRedirect, - Optional stderrRedirect) throws IOException { + CommandSpec command, + EnvironmentSpec environment, + PipeSpec redirect) throws IOException { - var builder = new ProcessBuilder(commandLine); - - directory.ifPresent(builder::directory); + var builder = new ProcessBuilder(command.commandLine()); - builder.environment().putAll(environment); + environment.workingDirectory().ifPresent(builder::directory); - // TODO Auto-generated method stub + builder.environment().putAll(environment.environment()); + var tasks = new ArrayList>(); - if (!stdoutRedirect.isPresent()) + if (!redirect.stdout().isPresent()) builder.redirectOutput(Redirect.DISCARD); - if (!stderrRedirect.isPresent()) + if (!redirect.stderr().isPresent()) builder.redirectError(Redirect.DISCARD); // run process @@ -38,9 +30,9 @@ class SystemExecBuilder extends ExecBuil tasks.add(proc.onExit()); - 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); + redirect.stdin().map(from -> from.redirect(proc.getOutputStream())).ifPresent(tasks::add); + redirect.stdout().map(to -> to.redirect(proc.getInputStream())).ifPresent(tasks::add); + redirect.stderr().map(to -> to.redirect(proc.getErrorStream())).ifPresent(tasks::add); return CompletableFuture .allOf(tasks.toArray(new CompletableFuture[0])) diff --git a/common/src/main/java/org/implab/gradle/common/tasks/ShellExecTask.java b/common/src/main/java/org/implab/gradle/common/tasks/ShellExecTask.java --- a/common/src/main/java/org/implab/gradle/common/tasks/ShellExecTask.java +++ b/common/src/main/java/org/implab/gradle/common/tasks/ShellExecTask.java @@ -91,10 +91,10 @@ public abstract class ShellExecTask @TaskAction public final void run() throws IOException, InterruptedException, ExecutionException { - var execBuilder = execBuilder() - .workingDirectory(getWorkingDirectory().get().getAsFile()) - .environment(getEnvironment().getOrElse(Map.of())) - .commandLine(getCommandLine().get()); + var execBuilder = execBuilder(); + execBuilder.workingDirectory(getWorkingDirectory().get().getAsFile()); + execBuilder.environment(getEnvironment().getOrElse(Map.of())); + execBuilder.commandLine(getCommandLine().get()); getStdout().getRedirection().ifPresent(execBuilder::stdout); getStderr().getRedirection().ifPresent(execBuilder::stderr); @@ -103,7 +103,7 @@ public abstract class ShellExecTask execBuilder.run().thenAccept(ThrowingConsumer.guard(this::checkRetCode)).join(); } - protected void checkRetCode(Integer code) throws IOException{ + protected void checkRetCode(Integer code) throws IOException { throw new IOException(String.format("The process is terminated with code %s", code)); } diff --git a/common/src/main/java/org/implab/gradle/common/utils/Values.java b/common/src/main/java/org/implab/gradle/common/utils/Values.java --- a/common/src/main/java/org/implab/gradle/common/utils/Values.java +++ b/common/src/main/java/org/implab/gradle/common/utils/Values.java @@ -2,7 +2,6 @@ package org.implab.gradle.common.utils; import java.util.Iterator; import java.util.Map; -import java.util.Spliterator; import java.util.Spliterators; import java.util.Map.Entry; import java.util.Optional; @@ -21,6 +20,7 @@ public final class Values { /** * Converts values in the specified map + * * @param * @param * @param @@ -35,12 +35,13 @@ public final class Values { .collect(Collectors.toMap(Entry::getKey, getter.andThen(mapper))); } - /** Converts the supplied value to a string. + /** + * Converts the supplied value to a string. */ public static String toString(Object value) { if (value == null) { - return null; - } else if (value instanceof String string) { + return null; + } else if (value instanceof String string) { return string; } else if (value instanceof Provider provider) { return toString(provider.getOrNull()); @@ -52,9 +53,9 @@ public final class Values { } public static Stream stream(Iterator remaining) { - var spliterator = Spliterators.spliteratorUnknownSize(remaining, 0); - - return StreamSupport.stream(spliterator, false); + return StreamSupport.stream( + Spliterators.spliteratorUnknownSize(remaining, 0), + false); } public static Optional take(Iterator iterator) {