| @@ -0,0 +1,7 | |||||
|
|
1 | [versions] | |||
|
|
2 | immutables = "2.10.1" | |||
|
|
3 | jdt = "2.3.0" | |||
|
|
4 | ||||
|
|
5 | [libraries] | |||
|
|
6 | immutables = { module = "org.immutables:value", version.ref = "immutables" } | |||
|
|
7 | jdt-annotations = { module = "org.eclipse.jdt:org.eclipse.jdt.annotation", version.ref = "jdt" } | |||
| @@ -12,7 +12,8 java { | |||||
| 12 | } |
|
12 | } | |
| 13 |
|
13 | |||
| 14 | dependencies { |
|
14 | dependencies { | |
| 15 |
compileOnly |
|
15 | compileOnly libs.jdt.annotations | |
|
|
16 | ||||
| 16 | api gradleApi() |
|
17 | api gradleApi() | |
| 17 | } |
|
18 | } | |
| 18 |
|
19 | |||
| @@ -1,5 +1,7 | |||||
| 1 | package org.implab.gradle.common.exec; |
|
1 | package org.implab.gradle.common.exec; | |
| 2 |
|
2 | |||
|
|
3 | import java.util.function.Consumer; | |||
|
|
4 | ||||
| 3 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
5 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
| 4 | import org.implab.gradle.common.utils.Values; |
|
6 | import org.implab.gradle.common.utils.Values; | |
| 5 |
|
7 | |||
| @@ -42,8 +44,22 public interface CommandBuilder { | |||||
| 42 | return this; |
|
44 | return this; | |
| 43 | } |
|
45 | } | |
| 44 |
|
46 | |||
|
|
47 | default Consumer<Boolean> flag(String name) { | |||
|
|
48 | return f -> { | |||
|
|
49 | if (f) | |||
|
|
50 | addArguments(name); | |||
|
|
51 | }; | |||
|
|
52 | } | |||
|
|
53 | ||||
|
|
54 | default Consumer<String> param(String name) { | |||
|
|
55 | return v -> { | |||
|
|
56 | if (v != null && v.length() > 0) | |||
|
|
57 | addArguments(name, v); | |||
|
|
58 | }; | |||
|
|
59 | } | |||
|
|
60 | ||||
| 45 | /** Adds the specified arguments to this builder */ |
|
61 | /** Adds the specified arguments to this builder */ | |
| 46 |
CommandBuilder addArguments( |
|
62 | CommandBuilder addArguments(String... args); | |
| 47 |
|
63 | |||
| 48 | default CommandBuilder addArguments(Iterable<String> args) { |
|
64 | default CommandBuilder addArguments(Iterable<String> args) { | |
| 49 | for (String arg : args) |
|
65 | for (String arg : args) | |
| @@ -9,13 +9,15 import org.eclipse.jdt.annotation.NonNul | |||||
| 9 | @NonNullByDefault |
|
9 | @NonNullByDefault | |
| 10 | public interface EnvironmentBuilder { |
|
10 | public interface EnvironmentBuilder { | |
| 11 | /** Sets the specified environment variable */ |
|
11 | /** Sets the specified environment variable */ | |
| 12 |
EnvironmentBuilder |
|
12 | EnvironmentBuilder putEnvironment(String envVar, String value); | |
| 13 |
|
13 | |||
| 14 | /** Replaces environment with the supplied one */ |
|
14 | /** Replaces environment with the supplied one */ | |
| 15 | EnvironmentBuilder environment(Map<String, ? extends String> env); |
|
15 | EnvironmentBuilder environment(Map<String, ? extends String> env); | |
| 16 |
|
16 | |||
| 17 | /** Unsets the specified variable. If the variable is inherited it removed anyway. */ |
|
17 | /** | |
| 18 | EnvironmentBuilder unsetVariable(String envVar); |
|
18 | * Enables or disables environment inheritance for the child process | |
|
|
19 | */ | |||
|
|
20 | EnvironmentBuilder inheritEnvironment(boolean inherit); | |||
| 19 |
|
21 | |||
| 20 | /** Specifies the working directory */ |
|
22 | /** Specifies the working directory */ | |
| 21 | EnvironmentBuilder workingDirectory(File directory); |
|
23 | EnvironmentBuilder workingDirectory(File directory); | |
| @@ -5,6 +5,9 import java.util.Map; | |||||
| 5 | import java.util.Optional; |
|
5 | import java.util.Optional; | |
| 6 |
|
6 | |||
| 7 | public interface EnvironmentSpec { |
|
7 | public interface EnvironmentSpec { | |
|
|
8 | ||||
|
|
9 | boolean inheritEnvironment(); | |||
|
|
10 | ||||
| 8 | Map<String,String> environment(); |
|
11 | Map<String,String> environment(); | |
| 9 |
|
12 | |||
| 10 | Optional<File> workingDirectory(); |
|
13 | Optional<File> workingDirectory(); | |
| @@ -18,15 +18,16 import static java.util.Objects.requireN | |||||
| 18 | /** Command line builder */ |
|
18 | /** Command line builder */ | |
| 19 | @NonNullByDefault |
|
19 | @NonNullByDefault | |
| 20 | public abstract class ExecBuilder implements |
|
20 | public abstract class ExecBuilder implements | |
| 21 | CommandBuilder, |
|
21 | CommandBuilder, | |
| 22 | PipeBuilder, |
|
22 | PipeBuilder, | |
| 23 | EnvironmentBuilder, |
|
23 | EnvironmentBuilder, | |
| 24 | Runnable |
|
24 | Executable { | |
| 25 | { |
|
|||
| 26 | private String executable; |
|
25 | private String executable; | |
| 27 |
|
26 | |||
| 28 | private final List<String> arguments = new ArrayList<>(); |
|
27 | private final List<String> arguments = new ArrayList<>(); | |
| 29 |
|
28 | |||
|
|
29 | private boolean inheritEnvironment = true; | |||
|
|
30 | ||||
| 30 | private final Map<String, String> environment = new HashMap<>(); |
|
31 | private final Map<String, String> environment = new HashMap<>(); | |
| 31 |
|
32 | |||
| 32 | private @Nullable File directory; |
|
33 | private @Nullable File directory; | |
| @@ -48,18 +49,15 public abstract class ExecBuilder implem | |||||
| 48 | public ExecBuilder arguments(Iterable<String> args) { |
|
49 | public ExecBuilder arguments(Iterable<String> args) { | |
| 49 | requireNonNull(args, "Args must not be null"); |
|
50 | requireNonNull(args, "Args must not be null"); | |
| 50 | arguments.clear(); |
|
51 | arguments.clear(); | |
| 51 | for(var arg: args) |
|
52 | for (var arg : args) | |
| 52 | arguments.add(arg); |
|
53 | arguments.add(arg); | |
| 53 | return this; |
|
54 | return this; | |
| 54 | } |
|
55 | } | |
| 55 |
|
56 | |||
| 56 | @Override |
|
57 | @Override | |
| 57 |
public ExecBuilder addArguments( |
|
58 | public ExecBuilder addArguments(String... args) { | |
| 58 | requireNonNull(arg0, "arg0 parameter can't be null"); |
|
|||
| 59 | arguments.add(arg0); |
|
|||
| 60 |
|
||||
| 61 | for (var arg : args) |
|
59 | for (var arg : args) | |
| 62 | arguments.add(arg); |
|
60 | arguments.add(requireNonNull(arg, "arguments element shouldn't be null")); | |
| 63 |
|
61 | |||
| 64 | return this; |
|
62 | return this; | |
| 65 | } |
|
63 | } | |
| @@ -68,7 +66,7 public abstract class ExecBuilder implem | |||||
| 68 | @Override |
|
66 | @Override | |
| 69 | public ExecBuilder workingDirectory(File directory) { |
|
67 | public ExecBuilder workingDirectory(File directory) { | |
| 70 | requireNonNull(directory, "directory parameter can't be null"); |
|
68 | requireNonNull(directory, "directory parameter can't be null"); | |
| 71 |
|
69 | |||
| 72 | this.directory = directory; |
|
70 | this.directory = directory; | |
| 73 | return this; |
|
71 | return this; | |
| 74 | } |
|
72 | } | |
| @@ -81,6 +79,12 public abstract class ExecBuilder implem | |||||
| 81 | return this; |
|
79 | return this; | |
| 82 | } |
|
80 | } | |
| 83 |
|
81 | |||
|
|
82 | @Override | |||
|
|
83 | public ExecBuilder inheritEnvironment(boolean inherit) { | |||
|
|
84 | this.inheritEnvironment = inherit; | |||
|
|
85 | return this; | |||
|
|
86 | } | |||
|
|
87 | ||||
| 84 | /** |
|
88 | /** | |
| 85 | * Sets the environment value. The value cannot be null. |
|
89 | * Sets the environment value. The value cannot be null. | |
| 86 | * |
|
90 | * | |
| @@ -88,7 +92,7 public abstract class ExecBuilder implem | |||||
| 88 | * @param value The value to set, |
|
92 | * @param value The value to set, | |
| 89 | */ |
|
93 | */ | |
| 90 | @Override |
|
94 | @Override | |
| 91 |
public ExecBuilder |
|
95 | public ExecBuilder putEnvironment(String envVar, String value) { | |
| 92 | requireNonNull(value, "Value can't be null"); |
|
96 | requireNonNull(value, "Value can't be null"); | |
| 93 | requireNonNull(envVar, "envVar parameter can't be null"); |
|
97 | requireNonNull(envVar, "envVar parameter can't be null"); | |
| 94 |
|
98 | |||
| @@ -97,15 +101,7 public abstract class ExecBuilder implem | |||||
| 97 | } |
|
101 | } | |
| 98 |
|
102 | |||
| 99 | @Override |
|
103 | @Override | |
| 100 |
public ExecBuilder |
|
104 | public ExecBuilder environment(Map<String, ? extends String> env) { | |
| 101 | requireNonNull(envVar, "envVar parameter can't be null"); |
|
|||
| 102 |
|
||||
| 103 | environment.remove(envVar); |
|
|||
| 104 | return this; |
|
|||
| 105 | } |
|
|||
| 106 |
|
||||
| 107 | @Override |
|
|||
| 108 | public ExecBuilder environment(Map<String,? extends String> env) { |
|
|||
| 109 | requireNonNull(env, "env parameter can't be null"); |
|
105 | requireNonNull(env, "env parameter can't be null"); | |
| 110 |
|
106 | |||
| 111 | environment.clear(); |
|
107 | environment.clear(); | |
| @@ -123,7 +119,7 public abstract class ExecBuilder implem | |||||
| 123 | @Override |
|
119 | @Override | |
| 124 | public ExecBuilder stdin(RedirectFrom from) { |
|
120 | public ExecBuilder stdin(RedirectFrom from) { | |
| 125 | requireNonNull(from, "from parameter can't be null"); |
|
121 | requireNonNull(from, "from parameter can't be null"); | |
| 126 |
|
122 | |||
| 127 | inputRedirect = from; |
|
123 | inputRedirect = from; | |
| 128 | return this; |
|
124 | return this; | |
| 129 | } |
|
125 | } | |
| @@ -202,7 +198,7 public abstract class ExecBuilder implem | |||||
| 202 | * @return |
|
198 | * @return | |
| 203 | * @throws IOException |
|
199 | * @throws IOException | |
| 204 | */ |
|
200 | */ | |
| 205 |
public CompletableFuture<Integer> |
|
201 | public CompletableFuture<Integer> exec() throws IOException { | |
| 206 | if (executable == null || executable.isEmpty()) |
|
202 | if (executable == null || executable.isEmpty()) | |
| 207 | throw new IllegalStateException("The executable isn't set"); |
|
203 | throw new IllegalStateException("The executable isn't set"); | |
| 208 |
|
204 | |||
| @@ -211,10 +207,9 public abstract class ExecBuilder implem | |||||
| 211 | commandLine.addAll(arguments); |
|
207 | commandLine.addAll(arguments); | |
| 212 |
|
208 | |||
| 213 | return startInternal( |
|
209 | return startInternal( | |
| 214 | new SelfCommandSpec(), |
|
210 | new SelfCommandSpec(), | |
| 215 | new SelfEnvironmentSpec(), |
|
211 | new SelfEnvironmentSpec(), | |
| 216 | new SelfPipeSpec() |
|
212 | new SelfPipeSpec()); | |
| 217 | ); |
|
|||
| 218 | } |
|
213 | } | |
| 219 |
|
214 | |||
| 220 | private class SelfCommandSpec implements CommandSpec { |
|
215 | private class SelfCommandSpec implements CommandSpec { | |
| @@ -231,6 +226,10 public abstract class ExecBuilder implem | |||||
| 231 | } |
|
226 | } | |
| 232 |
|
227 | |||
| 233 | private class SelfEnvironmentSpec implements EnvironmentSpec { |
|
228 | private class SelfEnvironmentSpec implements EnvironmentSpec { | |
|
|
229 | @Override | |||
|
|
230 | public boolean inheritEnvironment() { | |||
|
|
231 | return inheritEnvironment; | |||
|
|
232 | } | |||
| 234 |
|
233 | |||
| 235 | @Override |
|
234 | @Override | |
| 236 | public Map<String, String> environment() { |
|
235 | public Map<String, String> environment() { | |
| @@ -239,7 +238,7 public abstract class ExecBuilder implem | |||||
| 239 |
|
238 | |||
| 240 | @Override |
|
239 | @Override | |
| 241 | public Optional<File> workingDirectory() { |
|
240 | public Optional<File> workingDirectory() { | |
| 242 |
return Optional.ofNullable( |
|
241 | return Optional.ofNullable(directory); | |
| 243 | } |
|
242 | } | |
| 244 | } |
|
243 | } | |
| 245 |
|
244 | |||
| @@ -4,16 +4,15 import java.io.IOException; | |||||
| 4 | import java.util.concurrent.CompletableFuture; |
|
4 | import java.util.concurrent.CompletableFuture; | |
| 5 | import java.util.concurrent.CompletionException; |
|
5 | import java.util.concurrent.CompletionException; | |
| 6 |
|
6 | |||
| 7 |
public interface |
|
7 | public interface Executable { | |
| 8 |
CompletableFuture<Integer> |
|
8 | CompletableFuture<Integer> exec() throws IOException; | |
| 9 |
|
9 | |||
| 10 | /** Starts process and will throw error if error code is non-zero */ |
|
10 | /** Starts process and will throw error if error code is non-zero */ | |
| 11 |
default CompletableFuture< |
|
11 | default CompletableFuture<Void> execChecked() throws IOException { | |
| 12 |
return |
|
12 | return exec().thenAccept(code -> { | |
| 13 |
if ( |
|
13 | if (code != 0) | |
| 14 | throw new CompletionException(new IOException( |
|
14 | throw new CompletionException(new IOException( | |
| 15 | String.format("The process is terminated with code %d", code))); |
|
15 | String.format("The process is terminated with code %d", code))); | |
| 16 | return code; |
|
|||
| 17 | }); |
|
16 | }); | |
| 18 | } |
|
17 | } | |
| 19 | } |
|
18 | } | |
| @@ -6,13 +6,18 import java.io.InputStream; | |||||
| 6 | import java.io.OutputStream; |
|
6 | import java.io.OutputStream; | |
| 7 | import java.util.concurrent.CompletableFuture; |
|
7 | import java.util.concurrent.CompletableFuture; | |
| 8 |
|
8 | |||
| 9 | /** Describes how to redirect input streams. This interface is used to configure |
|
9 | /** | |
|
|
10 | * Describes how to redirect input streams. This interface is used to configure | |||
| 10 | * lazy redirection. {@link #redirect(OutputStream)} is called when the process |
|
11 | * lazy redirection. {@link #redirect(OutputStream)} is called when the process | |
| 11 | * is started. |
|
12 | * is started. Before the process is started the redirection isn't invoked and | |
|
|
13 | * no resources are allocated or used. | |||
| 12 | */ |
|
14 | */ | |
| 13 | public interface RedirectFrom { |
|
15 | public interface RedirectFrom { | |
| 14 | CompletableFuture<Void> redirect(OutputStream to); |
|
16 | CompletableFuture<Void> redirect(OutputStream to); | |
| 15 |
|
17 | |||
|
|
18 | /** | |||
|
|
19 | * Read file contents and redirect it to the output stream. | |||
|
|
20 | */ | |||
| 16 | public static RedirectFrom file(final File file) { |
|
21 | public static RedirectFrom file(final File file) { | |
| 17 | return to -> CompletableFuture.runAsync(() -> { |
|
22 | return to -> CompletableFuture.runAsync(() -> { | |
| 18 | try (var from = new FileInputStream(file); to) { |
|
23 | try (var from = new FileInputStream(file); to) { | |
| @@ -45,5 +50,4 public interface RedirectFrom { | |||||
| 45 | } |
|
50 | } | |
| 46 | } |
|
51 | } | |
| 47 |
|
52 | |||
| 48 |
|
||||
| 49 | } |
|
53 | } | |
| @@ -11,7 +11,10 import java.util.function.Consumer; | |||||
| 11 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
11 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
| 12 |
|
12 | |||
| 13 | /** |
|
13 | /** | |
| 14 | * RedirectSpec |
|
14 | * Redirection specification for the {@link InputStream}. Redirection is invoked | |
|
|
15 | * when the {@link InputStream} becomes available, for example, on process | |||
|
|
16 | * start. Before the process is started the redirection isn't invoked and no | |||
|
|
17 | * resources are allocated or used. | |||
| 15 | */ |
|
18 | */ | |
| 16 | @NonNullByDefault |
|
19 | @NonNullByDefault | |
| 17 | public interface RedirectTo { |
|
20 | public interface RedirectTo { | |
| @@ -20,6 +23,7 public interface RedirectTo { | |||||
| 20 | public interface StringConsumer extends Consumer<String> { |
|
23 | public interface StringConsumer extends Consumer<String> { | |
| 21 | } |
|
24 | } | |
| 22 |
|
25 | |||
|
|
26 | /** Creates a redirect to the specified consumer */ | |||
| 23 | public static RedirectTo consumer(final Consumer<String> consumer) { |
|
27 | public static RedirectTo consumer(final Consumer<String> consumer) { | |
| 24 | return consumer(new StringConsumer() { |
|
28 | return consumer(new StringConsumer() { | |
| 25 | @Override |
|
29 | @Override | |
| @@ -29,6 +33,7 public interface RedirectTo { | |||||
| 29 | }); |
|
33 | }); | |
| 30 | } |
|
34 | } | |
| 31 |
|
35 | |||
|
|
36 | /** Creates a redirect to the specified consumer */ | |||
| 32 | public static RedirectTo consumer(final StringConsumer consumer) { |
|
37 | public static RedirectTo consumer(final StringConsumer consumer) { | |
| 33 | return (src) -> CompletableFuture.runAsync(() -> { |
|
38 | return (src) -> CompletableFuture.runAsync(() -> { | |
| 34 | try (Scanner sc = new Scanner(src)) { |
|
39 | try (Scanner sc = new Scanner(src)) { | |
| @@ -39,6 +44,11 public interface RedirectTo { | |||||
| 39 | }); |
|
44 | }); | |
| 40 | } |
|
45 | } | |
| 41 |
|
46 | |||
|
|
47 | /** | |||
|
|
48 | * Creates a redirect to the specified file. There will be opened the output | |||
|
|
49 | * stream in this redirection and original stream will be transferred to this | |||
|
|
50 | * this output stream. | |||
|
|
51 | */ | |||
| 42 | public static RedirectTo file(final File file) { |
|
52 | public static RedirectTo file(final File file) { | |
| 43 | return src -> CompletableFuture.runAsync(() -> { |
|
53 | return src -> CompletableFuture.runAsync(() -> { | |
| 44 | try (OutputStream out = new FileOutputStream(file)) { |
|
54 | try (OutputStream out = new FileOutputStream(file)) { | |
| @@ -49,6 +59,7 public interface RedirectTo { | |||||
| 49 | }); |
|
59 | }); | |
| 50 | } |
|
60 | } | |
| 51 |
|
61 | |||
|
|
62 | /** Creates a redirect to the specified output stream. */ | |||
| 52 | public static RedirectTo stream(final OutputStream dest) { |
|
63 | public static RedirectTo stream(final OutputStream dest) { | |
| 53 | return src -> CompletableFuture.runAsync(() -> { |
|
64 | return src -> CompletableFuture.runAsync(() -> { | |
| 54 | try (dest; src) { |
|
65 | try (dest; src) { | |
| @@ -59,6 +70,10 public interface RedirectTo { | |||||
| 59 | }); |
|
70 | }); | |
| 60 | } |
|
71 | } | |
| 61 |
|
72 | |||
|
|
73 | /** | |||
|
|
74 | * Creates the redirection to the specified destination, actual type of | |||
|
|
75 | * redirection will be determined from the type of the output object. | |||
|
|
76 | */ | |||
| 62 | public static RedirectTo any(final Object output) { |
|
77 | public static RedirectTo any(final Object output) { | |
| 63 | if (output instanceof StringConsumer fn) { |
|
78 | if (output instanceof StringConsumer fn) { | |
| 64 | return consumer(s -> fn.accept(s)); |
|
79 | return consumer(s -> fn.accept(s)); | |
| @@ -8,7 +8,7 import java.util.concurrent.CompletableF | |||||
| 8 | class SystemExecBuilder extends ExecBuilder { |
|
8 | class SystemExecBuilder extends ExecBuilder { | |
| 9 | @Override |
|
9 | @Override | |
| 10 | protected CompletableFuture<Integer> startInternal( |
|
10 | protected CompletableFuture<Integer> startInternal( | |
| 11 | CommandSpec command, |
|
11 | CommandSpec command, | |
| 12 | EnvironmentSpec environment, |
|
12 | EnvironmentSpec environment, | |
| 13 | PipeSpec redirect) throws IOException { |
|
13 | PipeSpec redirect) throws IOException { | |
| 14 |
|
14 | |||
| @@ -16,6 +16,10 class SystemExecBuilder extends ExecBuil | |||||
| 16 |
|
16 | |||
| 17 | environment.workingDirectory().ifPresent(builder::directory); |
|
17 | environment.workingDirectory().ifPresent(builder::directory); | |
| 18 |
|
18 | |||
|
|
19 | // if the env isn't inherited we need to clear it | |||
|
|
20 | if (!environment.inheritEnvironment()) | |||
|
|
21 | builder.environment().clear(); | |||
|
|
22 | ||||
| 19 | builder.environment().putAll(environment.environment()); |
|
23 | builder.environment().putAll(environment.environment()); | |
| 20 |
|
24 | |||
| 21 | var tasks = new ArrayList<CompletableFuture<?>>(); |
|
25 | var tasks = new ArrayList<CompletableFuture<?>>(); | |
| @@ -100,7 +100,7 public abstract class ShellExecTask | |||||
| 100 | getStderr().getRedirection().ifPresent(execBuilder::stderr); |
|
100 | getStderr().getRedirection().ifPresent(execBuilder::stderr); | |
| 101 | getStdin().getRedirection().ifPresent(execBuilder::stdin); |
|
101 | getStdin().getRedirection().ifPresent(execBuilder::stdin); | |
| 102 |
|
102 | |||
| 103 |
execBuilder. |
|
103 | execBuilder.exec().thenAccept(ThrowingConsumer.guard(this::checkRetCode)).join(); | |
| 104 | } |
|
104 | } | |
| 105 |
|
105 | |||
| 106 | protected void checkRetCode(Integer code) throws IOException { |
|
106 | protected void checkRetCode(Integer code) throws IOException { | |
General Comments 0
You need to be logged in to leave comments.
Login now
