# HG changeset patch # User cin # Date 2025-01-08 08:12:15 # Node ID bed0567066c1455f8bad4edcfb13fe3420056faf # Parent 59dae0731c45cdeb24e6b06a5d49b7391e1ea851 WIP diff --git a/common/build.gradle b/common/build.gradle --- a/common/build.gradle +++ b/common/build.gradle @@ -12,7 +12,8 @@ java { } dependencies { - compileOnly "org.eclipse.jdt:org.eclipse.jdt.annotation:2.3.0" + compileOnly libs.jdt.annotations + api gradleApi() } 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,5 +1,7 @@ package org.implab.gradle.common.exec; +import java.util.function.Consumer; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.implab.gradle.common.utils.Values; @@ -42,8 +44,22 @@ public interface CommandBuilder { return this; } + default Consumer flag(String name) { + return f -> { + if (f) + addArguments(name); + }; + } + + default Consumer param(String name) { + return v -> { + if (v != null && v.length() > 0) + addArguments(name, v); + }; + } + /** Adds the specified arguments to this builder */ - CommandBuilder addArguments(String arg0, String... args); + CommandBuilder addArguments(String... args); default CommandBuilder addArguments(Iterable args) { for (String arg : args) 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 @@ -9,13 +9,15 @@ import org.eclipse.jdt.annotation.NonNul @NonNullByDefault public interface EnvironmentBuilder { /** Sets the specified environment variable */ - EnvironmentBuilder setVariable(String envVar, String value); + EnvironmentBuilder putEnvironment(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); + /** + * Enables or disables environment inheritance for the child process + */ + EnvironmentBuilder inheritEnvironment(boolean inherit); /** Specifies the working directory */ EnvironmentBuilder workingDirectory(File directory); diff --git a/common/src/main/java/org/implab/gradle/common/exec/EnvironmentSpec.java b/common/src/main/java/org/implab/gradle/common/exec/EnvironmentSpec.java --- a/common/src/main/java/org/implab/gradle/common/exec/EnvironmentSpec.java +++ b/common/src/main/java/org/implab/gradle/common/exec/EnvironmentSpec.java @@ -5,6 +5,9 @@ import java.util.Map; import java.util.Optional; public interface EnvironmentSpec { + + boolean inheritEnvironment(); + Map environment(); Optional 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 @@ -18,15 +18,16 @@ import static java.util.Objects.requireN /** Command line builder */ @NonNullByDefault public abstract class ExecBuilder implements - CommandBuilder, - PipeBuilder, - EnvironmentBuilder, - Runnable -{ + CommandBuilder, + PipeBuilder, + EnvironmentBuilder, + Executable { private String executable; private final List arguments = new ArrayList<>(); + private boolean inheritEnvironment = true; + private final Map environment = new HashMap<>(); private @Nullable File directory; @@ -48,18 +49,15 @@ public abstract class ExecBuilder implem public ExecBuilder arguments(Iterable args) { requireNonNull(args, "Args must not be null"); arguments.clear(); - for(var arg: args) + for (var arg : args) arguments.add(arg); return this; } @Override - public ExecBuilder addArguments(String arg0, String... args) { - requireNonNull(arg0, "arg0 parameter can't be null"); - arguments.add(arg0); - + public ExecBuilder addArguments(String... args) { for (var arg : args) - arguments.add(arg); + arguments.add(requireNonNull(arg, "arguments element shouldn't be null")); return this; } @@ -68,7 +66,7 @@ public abstract class ExecBuilder implem @Override public ExecBuilder workingDirectory(File directory) { requireNonNull(directory, "directory parameter can't be null"); - + this.directory = directory; return this; } @@ -81,6 +79,12 @@ public abstract class ExecBuilder implem return this; } + @Override + public ExecBuilder inheritEnvironment(boolean inherit) { + this.inheritEnvironment = inherit; + return this; + } + /** * Sets the environment value. The value cannot be null. * @@ -88,7 +92,7 @@ public abstract class ExecBuilder implem * @param value The value to set, */ @Override - public ExecBuilder setVariable(String envVar, String value) { + public ExecBuilder putEnvironment(String envVar, String value) { requireNonNull(value, "Value can't be null"); requireNonNull(envVar, "envVar parameter can't be null"); @@ -97,15 +101,7 @@ 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) { + public ExecBuilder environment(Map env) { requireNonNull(env, "env parameter can't be null"); environment.clear(); @@ -123,7 +119,7 @@ public abstract class ExecBuilder implem @Override public ExecBuilder stdin(RedirectFrom from) { requireNonNull(from, "from parameter can't be null"); - + inputRedirect = from; return this; } @@ -202,7 +198,7 @@ public abstract class ExecBuilder implem * @return * @throws IOException */ - public CompletableFuture run() throws IOException { + public CompletableFuture exec() throws IOException { if (executable == null || executable.isEmpty()) throw new IllegalStateException("The executable isn't set"); @@ -211,10 +207,9 @@ public abstract class ExecBuilder implem commandLine.addAll(arguments); return startInternal( - new SelfCommandSpec(), - new SelfEnvironmentSpec(), - new SelfPipeSpec() - ); + new SelfCommandSpec(), + new SelfEnvironmentSpec(), + new SelfPipeSpec()); } private class SelfCommandSpec implements CommandSpec { @@ -231,6 +226,10 @@ public abstract class ExecBuilder implem } private class SelfEnvironmentSpec implements EnvironmentSpec { + @Override + public boolean inheritEnvironment() { + return inheritEnvironment; + } @Override public Map environment() { @@ -239,7 +238,7 @@ public abstract class ExecBuilder implem @Override public Optional workingDirectory() { - return Optional.ofNullable( directory); + return Optional.ofNullable(directory); } } diff --git a/common/src/main/java/org/implab/gradle/common/exec/Runnable.java b/common/src/main/java/org/implab/gradle/common/exec/Executable.java rename from common/src/main/java/org/implab/gradle/common/exec/Runnable.java rename to common/src/main/java/org/implab/gradle/common/exec/Executable.java --- a/common/src/main/java/org/implab/gradle/common/exec/Runnable.java +++ b/common/src/main/java/org/implab/gradle/common/exec/Executable.java @@ -4,16 +4,15 @@ import java.io.IOException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; -public interface Runnable { - CompletableFuture run() throws IOException; +public interface Executable { + CompletableFuture exec() throws IOException; /** Starts process and will throw error if error code is non-zero */ - default CompletableFuture run(boolean throwOnError) throws IOException { - return run().thenApply(code -> { - if (!throwOnError && code != 0) + default CompletableFuture execChecked() throws IOException { + return exec().thenAccept(code -> { + if (code != 0) throw new CompletionException(new IOException( String.format("The process is terminated with code %d", code))); - return code; }); } } diff --git a/common/src/main/java/org/implab/gradle/common/exec/RedirectFrom.java b/common/src/main/java/org/implab/gradle/common/exec/RedirectFrom.java --- a/common/src/main/java/org/implab/gradle/common/exec/RedirectFrom.java +++ b/common/src/main/java/org/implab/gradle/common/exec/RedirectFrom.java @@ -6,13 +6,18 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.concurrent.CompletableFuture; -/** Describes how to redirect input streams. This interface is used to configure +/** + * Describes how to redirect input streams. This interface is used to configure * lazy redirection. {@link #redirect(OutputStream)} is called when the process - * is started. + * is started. Before the process is started the redirection isn't invoked and + * no resources are allocated or used. */ public interface RedirectFrom { CompletableFuture redirect(OutputStream to); + /** + * Read file contents and redirect it to the output stream. + */ public static RedirectFrom file(final File file) { return to -> CompletableFuture.runAsync(() -> { try (var from = new FileInputStream(file); to) { @@ -45,5 +50,4 @@ public interface RedirectFrom { } } - } diff --git a/common/src/main/java/org/implab/gradle/common/exec/RedirectTo.java b/common/src/main/java/org/implab/gradle/common/exec/RedirectTo.java --- a/common/src/main/java/org/implab/gradle/common/exec/RedirectTo.java +++ b/common/src/main/java/org/implab/gradle/common/exec/RedirectTo.java @@ -11,7 +11,10 @@ import java.util.function.Consumer; import org.eclipse.jdt.annotation.NonNullByDefault; /** - * RedirectSpec + * Redirection specification for the {@link InputStream}. Redirection is invoked + * when the {@link InputStream} becomes available, for example, on process + * start. Before the process is started the redirection isn't invoked and no + * resources are allocated or used. */ @NonNullByDefault public interface RedirectTo { @@ -20,6 +23,7 @@ public interface RedirectTo { public interface StringConsumer extends Consumer { } + /** Creates a redirect to the specified consumer */ public static RedirectTo consumer(final Consumer consumer) { return consumer(new StringConsumer() { @Override @@ -29,6 +33,7 @@ public interface RedirectTo { }); } + /** Creates a redirect to the specified consumer */ public static RedirectTo consumer(final StringConsumer consumer) { return (src) -> CompletableFuture.runAsync(() -> { try (Scanner sc = new Scanner(src)) { @@ -39,6 +44,11 @@ public interface RedirectTo { }); } + /** + * Creates a redirect to the specified file. There will be opened the output + * stream in this redirection and original stream will be transferred to this + * this output stream. + */ public static RedirectTo file(final File file) { return src -> CompletableFuture.runAsync(() -> { try (OutputStream out = new FileOutputStream(file)) { @@ -49,6 +59,7 @@ public interface RedirectTo { }); } + /** Creates a redirect to the specified output stream. */ public static RedirectTo stream(final OutputStream dest) { return src -> CompletableFuture.runAsync(() -> { try (dest; src) { @@ -59,6 +70,10 @@ public interface RedirectTo { }); } + /** + * Creates the redirection to the specified destination, actual type of + * redirection will be determined from the type of the output object. + */ public static RedirectTo any(final Object output) { if (output instanceof StringConsumer fn) { return consumer(s -> fn.accept(s)); 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 @@ -8,7 +8,7 @@ import java.util.concurrent.CompletableF class SystemExecBuilder extends ExecBuilder { @Override protected CompletableFuture startInternal( - CommandSpec command, + CommandSpec command, EnvironmentSpec environment, PipeSpec redirect) throws IOException { @@ -16,6 +16,10 @@ class SystemExecBuilder extends ExecBuil environment.workingDirectory().ifPresent(builder::directory); + // if the env isn't inherited we need to clear it + if (!environment.inheritEnvironment()) + builder.environment().clear(); + builder.environment().putAll(environment.environment()); var tasks = new ArrayList>(); 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 @@ -100,7 +100,7 @@ public abstract class ShellExecTask getStderr().getRedirection().ifPresent(execBuilder::stderr); getStdin().getRedirection().ifPresent(execBuilder::stdin); - execBuilder.run().thenAccept(ThrowingConsumer.guard(this::checkRetCode)).join(); + execBuilder.exec().thenAccept(ThrowingConsumer.guard(this::checkRetCode)).join(); } protected void checkRetCode(Integer code) throws IOException { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,7 @@ +[versions] +immutables = "2.10.1" +jdt = "2.3.0" + +[libraries] +immutables = { module = "org.immutables:value", version.ref = "immutables" } +jdt-annotations = { module = "org.eclipse.jdt:org.eclipse.jdt.annotation", version.ref = "jdt" }