##// END OF EJS Templates
Reworked shell execution traits, removed useless wrappers, added JsonDelegate for using with closures to construct json-like objects.
cin -
r18:97a649d829ac default
parent child
Show More
@@ -0,0 +1,15
1 package org.implab.gradle.common.dsl;
2
3 import org.implab.gradle.common.exec.Shell;
4
5 public interface ShellSpecMixin {
6 void setShell(Shell shell);
7
8 default void useEchoShell() {
9 setShell(Shell.echo());
10 }
11
12 default void useSystemShell() {
13 setShell(Shell.system());
14 }
15 }
@@ -0,0 +1,212
1 package org.implab.gradle.common.exec;
2
3 import java.util.HashMap;
4 import java.util.Map;
5 import java.util.Optional;
6 import java.util.concurrent.CompletableFuture;
7
8 import org.eclipse.jdt.annotation.NonNullByDefault;
9 import org.eclipse.jdt.annotation.Nullable;
10
11 import java.io.File;
12 import java.io.IOException;
13
14 import static java.util.Objects.requireNonNull;
15
16 /** Command line builder */
17 @NonNullByDefault
18 public abstract class AbstractExecBuilder<CS> implements ShellExec {
19
20 private boolean inheritEnvironment = true;
21
22 private final Map<String, String> environment = new HashMap<>();
23
24 private @Nullable File directory;
25
26 private RedirectFrom inputRedirect;
27
28 private RedirectTo outputRedirect;
29
30 private RedirectTo errorRedirect;
31
32 private final CS command;
33
34 protected AbstractExecBuilder(CS command) {
35 this.command = command;
36 }
37
38
39 /** Sets the working directory */
40 @Override
41 public ShellExec workingDirectory(File directory) {
42 requireNonNull(directory, "directory parameter can't be null");
43
44 this.directory = directory;
45 return this;
46 }
47
48 @Override
49 public ShellExec workingDirectory(Optional<? extends File> directory) {
50 requireNonNull(directory, "directory parameter can't be null");
51
52 this.directory = directory.orElse(null);
53 return this;
54 }
55
56 @Override
57 public ShellExec inheritEnvironment(boolean inherit) {
58 this.inheritEnvironment = inherit;
59 return this;
60 }
61
62 /**
63 * Sets the environment value. The value cannot be null.
64 *
65 * @param envVar The name of the environment variable
66 * @param value The value to set,
67 */
68 @Override
69 public ShellExec putEnvironment(String envVar, String value) {
70 requireNonNull(value, "Value can't be null");
71 requireNonNull(envVar, "envVar parameter can't be null");
72
73 environment.put(envVar, value);
74 return this;
75 }
76
77 @Override
78 public ShellExec environment(Map<String, ? extends String> env) {
79 requireNonNull(env, "env parameter can't be null");
80
81 environment.clear();
82 environment.putAll(env);
83 return this;
84 }
85
86 /**
87 * Sets redirection for the stdin, {@link RedirectFrom} will be applied
88 * every time the process is started.
89 *
90 * <p>
91 * If redirection
92 */
93 @Override
94 public ShellExec stdin(RedirectFrom from) {
95 requireNonNull(from, "from parameter can't be null");
96
97 inputRedirect = from;
98 return this;
99 }
100
101 @Override
102 public ShellExec stdin(Optional<? extends RedirectFrom> from) {
103 requireNonNull(from, "from parameter can't be null");
104 inputRedirect = from.orElse(null);
105 return this;
106 }
107
108 /**
109 * Sets redirection for the stdout, {@link RedirectTo} will be applied
110 * every time the process is started.
111 */
112 @Override
113 public ShellExec stdout(RedirectTo out) {
114 requireNonNull(out, "out parameter can't be null");
115 outputRedirect = out;
116 return this;
117 }
118
119 @Override
120 public ShellExec stdout(Optional<? extends RedirectTo> to) {
121 requireNonNull(to, "from parameter can't be null");
122 outputRedirect = to.orElse(null);
123 return this;
124 }
125
126 /**
127 * Sets redirection for the stderr, {@link RedirectTo} will be applied
128 * every time the process is started.
129 */
130 @Override
131 public ShellExec stderr(RedirectTo err) {
132 requireNonNull(err, "err parameter can't be null");
133 errorRedirect = err;
134 return this;
135 }
136
137 @Override
138 public ShellExec stderr(Optional<? extends RedirectTo> to) {
139 requireNonNull(to, "from parameter can't be null");
140 errorRedirect = to.orElse(null);
141 return this;
142 }
143
144 @Override
145 public ShellExec from(PipeSpec pipeSpec) {
146 ShellExec.super.from(pipeSpec);
147 return this;
148 }
149
150 @Override
151 public ShellExec from(EnvironmentSpec environmentSpec) {
152 ShellExec.super.from(environmentSpec);
153 return this;
154 }
155
156 /** Implement this function to */
157 protected abstract CompletableFuture<Integer> startInternal(
158 CS command,
159 EnvironmentSpec environment,
160 PipeSpec redirect) throws IOException;
161
162 /**
163 * Creates and starts new process and returns {@link CompletableFuture}. The
164 * process may be a remote process, or a shell process or etc.
165 *
166 * @return
167 * @throws IOException
168 */
169 public CompletableFuture<Integer> exec() throws IOException {
170 return startInternal(
171 command,
172 new SelfEnvironmentSpec(),
173 new SelfPipeSpec());
174 }
175
176 private class SelfEnvironmentSpec implements EnvironmentSpec {
177 @Override
178 public boolean inheritEnvironment() {
179 return inheritEnvironment;
180 }
181
182 @Override
183 public Map<String, String> environment() {
184 return environment;
185 }
186
187 @Override
188 public Optional<File> workingDirectory() {
189 return Optional.ofNullable(directory);
190 }
191 }
192
193 private class SelfPipeSpec implements PipeSpec {
194
195 @Override
196 public Optional<RedirectTo> stdout() {
197 return Optional.ofNullable(outputRedirect);
198 }
199
200 @Override
201 public Optional<RedirectTo> stderr() {
202 return Optional.ofNullable(errorRedirect);
203 }
204
205 @Override
206 public Optional<RedirectFrom> stdin() {
207 return Optional.ofNullable(inputRedirect);
208 }
209
210 }
211
212 }
@@ -0,0 +1,47
1 package org.implab.gradle.common.exec;
2
3 import java.util.List;
4 import static java.util.Objects.requireNonNull;
5
6 import java.util.ArrayList;
7
8 public record CommandSpecRecord(String executable, List<String> arguments) implements CommandSpec {
9
10 static class Builder implements CommandBuilder {
11
12 private String executable;
13
14 private List<String> arguments = new ArrayList<>();
15
16 @Override
17 public CommandBuilder executable(String executable) {
18 requireNonNull(executable, "cmd can't be null");
19 this.executable = executable;
20 return this;
21 }
22
23 @Override
24 public CommandBuilder arguments(Iterable<String> args) {
25 requireNonNull(args, "Args must not be null");
26 arguments.clear();
27 for (var arg : args)
28 arguments.add(arg);
29 return this;
30 }
31
32 @Override
33 public CommandBuilder addArguments(String... args) {
34 for (var arg : args)
35 arguments.add(requireNonNull(arg, "arguments element shouldn't be null"));
36
37 return this;
38 }
39
40 @Override
41 public CommandSpec build() {
42 requireNonNull(executable, "Executable must be specified");
43 return new CommandSpecRecord(executable, arguments);
44 }
45
46 }
47 }
@@ -0,0 +1,22
1 package org.implab.gradle.common.exec;
2
3 public interface Shell {
4
5 ShellExec of(CommandSpec spec);
6
7 /** Creates a new shell to start processes using ProcessBuilder. */
8 static Shell system() {
9 return spec -> new SystemExecBuilder(spec);
10 }
11
12 /** Creates a stub shell which echoes the specified command line. */
13 static Shell echo() {
14 return spec -> new EchoExecBuilder(spec, false);
15 }
16
17 /** Creates a stub shell which echoes the specified command line. */
18 static Shell echoStderr() {
19 return spec -> new EchoExecBuilder(spec, false);
20 }
21
22 }
@@ -0,0 +1,36
1 package org.implab.gradle.common.exec;
2
3 import java.io.IOException;
4 import java.util.ArrayList;
5 import java.util.List;
6 import java.util.concurrent.CompletableFuture;
7
8 import org.implab.gradle.common.utils.Exceptions;
9
10 public interface ShellExec extends PipeBuilder, EnvironmentBuilder {
11
12 default CompletableFuture<List<String>> readAllLines() throws IOException {
13 List<String> lines = new ArrayList<>();
14 stdout(RedirectTo.consumer(lines::add));
15
16 return exec()
17 .thenAccept(Exceptions.unchecked(this::assertExitCode))
18 .thenApply(v -> lines);
19 }
20
21 default CompletableFuture<String> readAllText() throws IOException {
22 List<String> lines = new ArrayList<>();
23 stdout(RedirectTo.consumer(lines::add));
24
25 return exec()
26 .thenAccept(Exceptions.unchecked(this::assertExitCode))
27 .thenApply(v -> String.join("\n", lines));
28 }
29
30 default void assertExitCode(Integer code) throws IOException {
31 if (code != 0)
32 throw new IOException(String.format("The process is terminated with code %d", code));
33 }
34
35 CompletableFuture<Integer> exec() throws IOException;
36 }
@@ -0,0 +1,108
1 package org.implab.gradle.common.tasks;
2
3 import java.io.IOException;
4 import java.util.Map;
5 import java.util.Optional;
6 import java.util.concurrent.ExecutionException;
7
8 import org.gradle.api.DefaultTask;
9 import org.gradle.api.file.DirectoryProperty;
10 import org.gradle.api.provider.MapProperty;
11 import org.gradle.api.provider.Property;
12 import org.gradle.api.tasks.Internal;
13 import org.gradle.api.tasks.TaskAction;
14 import org.implab.gradle.common.dsl.RedirectFromSpec;
15 import org.implab.gradle.common.dsl.RedirectToSpec;
16 import org.implab.gradle.common.dsl.TaskEnvSpecMixin;
17 import org.implab.gradle.common.dsl.TaskPipeSpecMixin;
18 import org.implab.gradle.common.exec.RedirectTo;
19 import org.implab.gradle.common.exec.ShellExec;
20 import org.implab.gradle.common.utils.Exceptions;
21 import org.implab.gradle.common.utils.Values;
22
23 public abstract class AbstractShellExecTask
24 extends DefaultTask
25 implements TaskPipeSpecMixin, TaskEnvSpecMixin {
26
27 private final RedirectToSpec redirectStderr = new RedirectToSpec();
28
29 private final RedirectToSpec redirectStdout = new RedirectToSpec();
30
31 private final RedirectFromSpec redirectStdin = new RedirectFromSpec();
32
33 @Internal
34 @Override
35 public abstract Property<Boolean> getInheritEnvironment();
36
37 @Internal
38 @Override
39 public abstract DirectoryProperty getWorkingDirectory();
40
41 @Internal
42 @Override
43 public abstract MapProperty<String, String> getEnvironment();
44
45 /**
46 * STDIN redirection, if not specified, no input will be passed to the command
47 */
48 @Internal
49 @Override
50 public RedirectFromSpec getStdin() {
51 return redirectStdin;
52 }
53
54 /**
55 * STDOUT redirection, if not specified, redirected to logger::info
56 */
57 @Internal
58 @Override
59 public RedirectToSpec getStdout() {
60 return redirectStdout;
61 }
62
63 /**
64 * STDERR redirection, if not specified, redirected to logger::error
65 */
66 @Internal
67 @Override
68 public RedirectToSpec getStderr() {
69 return redirectStderr;
70 }
71
72 protected abstract ShellExec execBuilder();
73
74 protected Optional<RedirectTo> conventionalStderr() {
75 return Optional.of(RedirectTo.eachLine(getLogger()::error));
76 }
77
78 protected Optional<RedirectTo> conventionalStdout() {
79 return Optional.of(RedirectTo.eachLine(getLogger()::info));
80 }
81
82 @TaskAction
83 public final void run() throws IOException, InterruptedException, ExecutionException {
84 // create new shell process
85 var execBuilder = execBuilder();
86
87 // configure environment
88 execBuilder
89 .workingDirectory(Values.optional(getWorkingDirectory().getAsFile()))
90 .environment(getEnvironment().getOrElse(Map.of()));
91
92 // configure redirects
93 execBuilder
94 .stdout(getStdout().getRedirection().or(this::conventionalStdout))
95 .stderr(getStderr().getRedirection().or(this::conventionalStderr))
96 .stdin(getStdin().getRedirection());
97
98 // execute
99 execBuilder.exec()
100 .thenAccept(Exceptions.unchecked(this::assertExitCode))
101 .join();
102 }
103
104 protected void assertExitCode(Integer code) throws IOException {
105 if (code != 0)
106 throw new IOException(String.format("The process is terminated with code %s", code));
107 }
108 } No newline at end of file
@@ -0,0 +1,51
1 package org.implab.gradle.common.utils;
2
3 import java.util.function.Consumer;
4 import java.util.function.Function;
5
6 public class Exceptions {
7 /**
8 * Helper function which declares that this block can throw the specified
9 * exception.
10 *
11 * @param <E>
12 * @param clazz
13 * @throws E
14 */
15 @SuppressWarnings("unused")
16 public static <E extends Throwable> void mayThrow(Class<E> clazz) throws E {
17 }
18
19 @SuppressWarnings("unchecked")
20 public static <E extends Throwable> E sneakyThrow(Throwable t) throws E {
21 throw (E) t;
22 }
23
24 public static <T, U> Function<T, U> unchecked(ThrowingFunction<T, U> fn) {
25 return val -> {
26 try {
27 return fn.apply(val);
28 } catch (Exception e) {
29 throw sneakyThrow(e);
30 }
31 };
32 }
33
34 public static <T> Consumer<T> unchecked(ThrowingConsumer<T> c) {
35 return val -> {
36 try {
37 c.accept(val);
38 } catch (Exception e) {
39 throw sneakyThrow(e);
40 }
41 };
42 }
43
44 public interface ThrowingConsumer<T> {
45 void accept(T value) throws Exception;
46 }
47
48 public interface ThrowingFunction<T, U> {
49 U apply(T value) throws Exception;
50 }
51 }
@@ -0,0 +1,84
1 package org.implab.gradle.common.utils;
2
3 import java.util.LinkedHashMap;
4 import java.util.Map;
5 import java.util.Objects;
6 import java.util.function.Function;
7 import java.util.stream.Stream;
8
9 import groovy.lang.Closure;
10 import groovy.lang.GroovyObjectSupport;
11
12 public class JsonDelegate extends GroovyObjectSupport {
13 private final Map<String, Object> content;
14
15 private JsonDelegate() {
16 content = new LinkedHashMap<>();
17 }
18
19 private JsonDelegate(Map<String, Object> content) {
20 this.content = new LinkedHashMap<>(content);
21 }
22
23 public Object invokeMethod(String name, Object args) {
24 Object val = null;
25 if (args != null && Object[].class.isAssignableFrom(args.getClass())) {
26 Object[] arr = (Object[]) args;
27 if (arr.length == 1) {
28 val = processValue(arr[0], content.get(name));
29 } else if (arr.length == 2 && arr[1] instanceof Closure<?> c) {
30 if (arr[0] instanceof Iterable<?> iter) {
31 val = processStream(Values.stream(iter.iterator()), c);
32 } else if(arr[0] != null && arr[0].getClass().isArray()) {
33 val = processStream(Stream.of((Object)arr[0]), c);
34 } else {
35 val = arr;
36 }
37 } else {
38 val = arr;
39 }
40 }
41
42 this.content.put(name, val);
43 return val;
44 }
45
46 private Object processValue(Object value, Object original) {
47 if (value instanceof Closure<?> c) {
48 return Objects.nonNull(original) ? of(c, original) : of(c);
49 } else {
50 return value;
51 }
52 }
53
54 private Object processStream(Stream<?> stream, Closure<?> c) {
55 return stream.map(transform(c)).toArray();
56 }
57
58 public static Map<String, Object> of(Closure<?> c) {
59 return invoke((Closure<?>) c.clone(), new JsonDelegate());
60 }
61
62 public static Function<Object, Map<String, Object>> transform(Closure<?> c) {
63 return o -> of(c, o);
64 }
65
66 public static Map<String, Object> of(Closure<?> c, Object o) {
67 return invoke((Closure<?>) c.curry(o), new JsonDelegate());
68 }
69
70 public static Map<String, Object> with(Closure<?> c, Map<String, Object> m) {
71 return invoke((Closure<?>) c.clone(), new JsonDelegate(m));
72 }
73
74 private static Map<String, Object> invoke(Closure<?> c, JsonDelegate d) {
75 c.setDelegate(d);
76 c.setResolveStrategy(1);
77 c.call();
78 return d.getContent();
79 }
80
81 public Map<String, Object> getContent() {
82 return this.content;
83 }
84 }
@@ -0,0 +1,63
1 package org.implab.gradle.common.utils;
2
3 import java.util.HashMap;
4 import java.util.Map;
5 import java.util.function.Function;
6
7 import org.gradle.api.Action;
8 import org.gradle.api.provider.ListProperty;
9 import org.gradle.api.provider.MapProperty;
10 import org.gradle.api.provider.Provider;
11
12 public final class Properties {
13 private Properties() {
14 }
15
16 public static <K> void mergeMap(MapProperty<K, Object> property, Map<K, Object> map) {
17 map.forEach((k, v) -> {
18 if (v instanceof Provider<?>)
19 property.put(k, (Provider<?>) v);
20 else
21 property.put(k, v);
22 });
23 }
24
25 public static <K, V> void mergeMap(MapProperty<K, V> property, Map<K, ?> map, Function<Object, V> mapper) {
26 map.forEach((k, v) -> {
27 if (v instanceof Provider<?>)
28 property.put(k, ((Provider<?>) v).map(mapper::apply));
29 else
30 property.put(k, mapper.apply(v));
31 });
32 }
33
34 public static void mergeList(ListProperty<Object> property, Iterable<Object> values) {
35 values.forEach(v -> {
36 if (v instanceof Provider<?>)
37 property.add((Provider<?>) v);
38 else
39 property.add(v);
40 });
41 }
42
43 public static <V> void mergeList(ListProperty<V> property, Iterable<Object> values, Function<Object, V> mapper) {
44 values.forEach(v -> {
45 if (v instanceof Provider<?>)
46 property.add(((Provider<?>) v).map(mapper::apply));
47 else
48 property.add(mapper.apply(v));
49 });
50 }
51
52 public static <K> void configureMap(MapProperty<K, Object> prop, Action<Map<K, ?>> configure) {
53 var map = new HashMap<K, Object>();
54 configure.execute(map);
55 mergeMap(prop, map);
56 }
57
58 public static <K, V> void configureMap(MapProperty<K, V> prop, Action<Map<K, ?>> configure, Function<Object, V> mapper) {
59 var map = new HashMap<K, Object>();
60 configure.execute(map);
61 mergeMap(prop, map, mapper);
62 }
63 }
@@ -1,45 +1,49
1 package org.implab.gradle.common.dsl;
1 package org.implab.gradle.common.dsl;
2
2
3 import java.io.File;
3 import java.io.File;
4 import java.io.InputStream;
4 import java.io.InputStream;
5 import java.util.Optional;
5 import java.util.Optional;
6 import java.util.function.Supplier;
6 import java.util.function.Supplier;
7
7
8 import org.gradle.api.provider.Provider;
8 import org.gradle.api.provider.Provider;
9 import org.implab.gradle.common.exec.RedirectFrom;
9 import org.implab.gradle.common.exec.RedirectFrom;
10
10
11 public class RedirectFromSpec {
11 public class RedirectFromSpec {
12 private Supplier<RedirectFrom> streamRedirect;
12 private Supplier<RedirectFrom> streamRedirect;
13
13
14 public boolean isRedirected() {
14 public boolean isRedirected() {
15 return streamRedirect != null;
15 return streamRedirect != null;
16 }
16 }
17
17
18 public Optional<RedirectFrom> getRedirection() {
18 public Optional<RedirectFrom> getRedirection() {
19 return streamRedirect != null ? Optional.ofNullable(streamRedirect.get()) : Optional.empty();
19 return streamRedirect != null ? Optional.ofNullable(streamRedirect.get()) : Optional.empty();
20 }
20 }
21
21
22 public void fromFile(File file) {
22 public void fromFile(File file) {
23 this.streamRedirect = () -> RedirectFrom.file(file);
23 streamRedirect = () -> RedirectFrom.file(file);
24 }
24 }
25
25
26 public void fromFile(Provider<File> fileProvider) {
26 public void fromFile(Provider<File> fileProvider) {
27 this.streamRedirect = fileProvider.map(RedirectFrom::file)::getOrNull;
27 streamRedirect = fileProvider.map(RedirectFrom::file)::getOrNull;
28 }
28 }
29
29
30 public void fromStream(InputStream stream) {
30 public void fromStream(InputStream stream) {
31 this.streamRedirect = () -> RedirectFrom.stream(stream);
31 streamRedirect = () -> RedirectFrom.stream(stream);
32 }
32 }
33
33
34 public void fromStream(Provider<InputStream> streamProvider) {
34 public void fromStream(Provider<InputStream> streamProvider) {
35 this.streamRedirect = streamProvider.map(RedirectFrom::stream)::getOrNull;
35 streamRedirect = streamProvider.map(RedirectFrom::stream)::getOrNull;
36 }
36 }
37
37
38 public void from(Object input) {
38 public void from(Object input) {
39 if (input instanceof Provider<?> inputProvider) {
39 if (input instanceof Provider<?> inputProvider) {
40 this.streamRedirect = inputProvider.map(RedirectFrom::any)::get;
40 streamRedirect = inputProvider.map(RedirectFrom::any)::get;
41 } else {
41 } else {
42 this.streamRedirect = () -> RedirectFrom.any(input);
42 streamRedirect = () -> RedirectFrom.any(input);
43 }
43 }
44 }
44 }
45
46 public void empty() {
47 streamRedirect = () -> null;
45 }
48 }
49 }
@@ -1,57 +1,65
1 package org.implab.gradle.common.dsl;
1 package org.implab.gradle.common.dsl;
2
2
3 import java.io.File;
3 import java.io.File;
4 import java.io.OutputStream;
4 import java.io.OutputStream;
5 import java.util.Optional;
5 import java.util.Optional;
6 import java.util.function.Consumer;
6 import java.util.function.Consumer;
7 import java.util.function.Supplier;
7 import java.util.function.Supplier;
8
8
9 import org.eclipse.jdt.annotation.NonNullByDefault;
9 import org.eclipse.jdt.annotation.NonNullByDefault;
10 import org.eclipse.jdt.annotation.Nullable;
10 import org.eclipse.jdt.annotation.Nullable;
11 import org.gradle.api.provider.Provider;
11 import org.gradle.api.provider.Provider;
12 import org.implab.gradle.common.exec.RedirectTo;
12 import org.implab.gradle.common.exec.RedirectTo;
13
13
14 @NonNullByDefault
14 @NonNullByDefault
15 public class RedirectToSpec {
15 public class RedirectToSpec {
16 private Supplier<RedirectTo> streamRedirect;
16 private Supplier<RedirectTo> streamRedirect;
17
17
18 public boolean isRedirected() {
18 public boolean isRedirected() {
19 return getRedirection().isPresent();
19 return getRedirection().isPresent();
20 }
20 }
21
21
22 public Optional<RedirectTo> getRedirection() {
22 public Optional<RedirectTo> getRedirection() {
23 return streamRedirect != null ? Optional.ofNullable(streamRedirect.get()) : Optional.empty();
23 return streamRedirect != null ? Optional.ofNullable(streamRedirect.get()) : Optional.empty();
24 }
24 }
25
25
26 public @Nullable RedirectTo getRedirectionOrNull() {
26 public @Nullable RedirectTo getRedirectionOrNull() {
27 return streamRedirect != null ? streamRedirect.get() : null;
27 return streamRedirect != null ? streamRedirect.get() : null;
28 }
28 }
29
29
30 public void toFile(File file) {
30 public void toFile(File file) {
31 this.streamRedirect = () -> RedirectTo.file(file);
31 streamRedirect = () -> RedirectTo.file(file);
32 }
32 }
33
33
34 public void toFile(Provider<File> fileProvider) {
34 public void toFile(Provider<File> fileProvider) {
35 this.streamRedirect = fileProvider.map(RedirectTo::file)::getOrNull;
35 streamRedirect = fileProvider.map(RedirectTo::file)::getOrNull;
36 }
36 }
37
37
38 public void toStream(OutputStream stream) {
38 public void toStream(OutputStream stream) {
39 this.streamRedirect = () -> RedirectTo.stream(stream);
39 streamRedirect = () -> RedirectTo.stream(stream);
40 }
40 }
41
41
42 public void toStream(Provider<OutputStream> streamProvider) {
42 public void toStream(Provider<OutputStream> streamProvider) {
43 this.streamRedirect = streamProvider.map(RedirectTo::stream)::getOrNull;
43 streamRedirect = streamProvider.map(RedirectTo::stream)::getOrNull;
44 }
44 }
45
45
46 public void to(Object output) {
46 public void to(Object output) {
47 if (output instanceof Provider<?> outputProvider) {
47 if (output instanceof Provider<?> outputProvider) {
48 this.streamRedirect = outputProvider.map(RedirectTo::any)::get;
48 streamRedirect = outputProvider.map(RedirectTo::any)::get;
49 } else {
49 } else {
50 this.streamRedirect = () -> RedirectTo.any(output);
50 streamRedirect = () -> RedirectTo.any(output);
51 }
51 }
52 }
52 }
53
53
54 public void consume(Consumer<String> consumer) {
54 public void eachLine(Consumer<String> consumer) {
55 this.streamRedirect = () -> RedirectTo.consumer(consumer);
55 streamRedirect = () -> RedirectTo.eachLine(consumer);
56 }
57
58 public void allText(Consumer<String> consumer) {
59 streamRedirect = () -> RedirectTo.allText(consumer);
60 }
61
62 public void discard() {
63 streamRedirect = () -> null;
56 }
64 }
57 }
65 }
@@ -1,13 +1,26
1 package org.implab.gradle.common.dsl;
1 package org.implab.gradle.common.dsl;
2
2
3 import java.util.stream.Stream;
3
4
4 import org.gradle.api.provider.ListProperty;
5 import org.gradle.api.provider.ListProperty;
6 import org.implab.gradle.common.utils.Properties;
5
7
6 public interface TaskCommandSpecMixin {
8 public interface TaskCommandSpecMixin {
7 ListProperty<String> getCommandLine();
9 ListProperty<String> getCommandLine();
8
10
9 void commandLine(Object arg0, Object... args);
11 default void commandLine(Object arg0, Object... args) {
12 getCommandLine().empty();
13 Properties.mergeList(
14 getCommandLine(),
15 () -> Stream.concat(Stream.of(arg0), Stream.of(args)).iterator(),
16 Object::toString);
17 }
10
18
11 void args(Object arg0, Object... args);
19 default void args(Object arg0, Object... args) {
20 Properties.mergeList(
21 getCommandLine(),
22 () -> Stream.concat(Stream.of(arg0), Stream.of(args)).iterator(),
23 Object::toString);
24 }
12
25
13 }
26 }
@@ -1,47 +1,45
1 package org.implab.gradle.common.dsl;
1 package org.implab.gradle.common.dsl;
2
2
3 import java.util.HashMap;
4 import java.util.Map;
3 import java.util.Map;
5
4
6 import org.gradle.api.Action;
5 import org.gradle.api.Action;
7 import org.gradle.api.file.DirectoryProperty;
6 import org.gradle.api.file.DirectoryProperty;
8 import org.gradle.api.provider.MapProperty;
7 import org.gradle.api.provider.MapProperty;
9 import org.gradle.api.provider.Property;
8 import org.gradle.api.provider.Property;
10 import org.gradle.api.provider.Provider;
11 import org.implab.gradle.common.utils.Closures;
9 import org.implab.gradle.common.utils.Closures;
12 import org.implab.gradle.common.utils.Values;
10 import org.implab.gradle.common.utils.Properties;
13
11
14 import groovy.lang.Closure;
12 import groovy.lang.Closure;
15
13
16 /**
14 /**
17 * Configuration properties of the execution shell. This object specifies a
15 * Configuration properties of the execution shell. This object specifies a
18 * working directory and environment variables for the processes started within
16 * working directory and environment variables for the processes started within
19 * this shell.
17 * this shell.
20 */
18 */
21 public interface TaskEnvSpecMixin {
19 public interface TaskEnvSpecMixin {
22
20
23 /** Inherit environment from current process */
21 /** Inherit environment from current process */
24 Property<Boolean> getInheritEnvironment();
22 Property<Boolean> getInheritEnvironment();
25
23
26 /** Working directory */
24 /** Working directory */
27 DirectoryProperty getWorkingDirectory();
25 DirectoryProperty getWorkingDirectory();
28
26
29 /** Environment variables */
27 /** Environment variables */
30 MapProperty<String, String> getEnvironment();
28 MapProperty<String, String> getEnvironment();
31
29
32 /**
30 /**
33 * Configures the environment variable using the specified action. The
31 * Configures the environment variable using the specified action. The
34 * action is called when the value is calculated;
32 * action is called when the value is calculated;
35 *
33 *
36 * <p>
34 * <p>
37 * The configuration action is called immediately. To support lazy evaluation,
35 * The configuration action is called immediately. To support lazy evaluation,
38 * properties may be assigned to providers.
36 * properties may be assigned to providers.
39 */
37 */
40 default void env(Action<Map<String, ?>> configure) {
38 default void env(Action<Map<String, ?>> configure) {
41 Values.configureMap(getEnvironment(), configure, Object.class, Values::toString);
39 Properties.configureMap(getEnvironment(), configure, Object::toString);
42 }
40 }
43
41
44 default void env(Closure<?> configure) {
42 default void env(Closure<?> configure) {
45 env(Closures.action(configure));
43 env(Closures.action(configure));
46 }
44 }
47 }
45 }
@@ -1,83 +1,85
1 package org.implab.gradle.common.exec;
1 package org.implab.gradle.common.exec;
2
2
3 import java.util.function.Consumer;
3 import java.util.function.Consumer;
4
4
5 import org.eclipse.jdt.annotation.NonNullByDefault;
5 import org.eclipse.jdt.annotation.NonNullByDefault;
6 import org.implab.gradle.common.utils.Values;
6 import org.implab.gradle.common.utils.Values;
7
7
8 /** Command builder interface, used to specify the executable and parameters */
8 /** Command builder interface, used to specify the executable and parameters */
9 @NonNullByDefault
9 @NonNullByDefault
10 public interface CommandBuilder {
10 public interface CommandBuilder {
11
11
12 /** Sets the executable, the parameters are left intact. */
12 /** Sets the executable, the parameters are left intact. */
13 CommandBuilder executable(String executable);
13 CommandBuilder executable(String executable);
14
14
15 /**
15 /**
16 * Sets the specified executable and parameters, old executable and parameters
16 * Sets the specified executable and parameters, old executable and parameters
17 * are discarded.
17 * are discarded.
18 */
18 */
19 default CommandBuilder commandLine(String executable, String... args) {
19 default CommandBuilder commandLine(String executable, String... args) {
20 return executable(executable)
20 return executable(executable)
21 .arguments(args);
21 .arguments(args);
22 }
22 }
23
23
24 /**
24 /**
25 * Sets the specified executable and parameters, old executable and parameters
25 * Sets the specified executable and parameters, old executable and parameters
26 * are discarded.
26 * are discarded.
27 *
27 *
28 * @param command The command line. Must contain at least one element
28 * @param command The command line. Must contain at least one element
29 * (executable).
29 * (executable).
30 *
30 *
31 */
31 */
32 default CommandBuilder commandLine(Iterable<? extends String> command) {
32 default CommandBuilder commandLine(Iterable<? extends String> command) {
33 var iterator = command.iterator();
33 var iterator = command.iterator();
34
34
35 // set executable
35 // set executable
36 executable(Values.take(iterator).orElseThrow());
36 executable(Values.take(iterator).orElseThrow());
37 // cleat arguments
37 // cleat arguments
38 arguments();
38 arguments();
39
39
40 // set new arguments
40 // set new arguments
41 while (iterator.hasNext())
41 while (iterator.hasNext())
42 addArguments(iterator.next());
42 addArguments(iterator.next());
43
43
44 return this;
44 return this;
45 }
45 }
46
46
47 default Consumer<Boolean> flag(String name) {
47 default Consumer<Boolean> flag(String name) {
48 return f -> {
48 return f -> {
49 if (f)
49 if (f)
50 addArguments(name);
50 addArguments(name);
51 };
51 };
52 }
52 }
53
53
54 default Consumer<String> param(String name) {
54 default Consumer<String> param(String name) {
55 return v -> {
55 return v -> {
56 if (v != null && v.length() > 0)
56 if (v != null && v.length() > 0)
57 addArguments(name, v);
57 addArguments(name, v);
58 };
58 };
59 }
59 }
60
60
61 /** Adds the specified arguments to this builder */
61 /** Adds the specified arguments to this builder */
62 CommandBuilder addArguments(String... args);
62 CommandBuilder addArguments(String... args);
63
63
64 default CommandBuilder addArguments(Iterable<String> args) {
64 default CommandBuilder addArguments(Iterable<String> args) {
65 for (String arg : args)
65 for (String arg : args)
66 addArguments(arg);
66 addArguments(arg);
67
67
68 return this;
68 return this;
69 }
69 }
70
70
71 /** Replaces arguments in the builder with the specified one. */
71 /** Replaces arguments in the builder with the specified one. */
72 default CommandBuilder arguments(String... args) {
72 default CommandBuilder arguments(String... args) {
73 return arguments(Values.iterable(args));
73 return arguments(Values.iterable(args));
74 }
74 }
75
75
76 /** Replaces arguments in the builder with the specified one. */
76 /** Replaces arguments in the builder with the specified one. */
77 CommandBuilder arguments(Iterable<String> args);
77 CommandBuilder arguments(Iterable<String> args);
78
78
79 default CommandBuilder from(CommandSpec commandSpec) {
79 default CommandBuilder from(CommandSpec commandSpec) {
80 return executable(commandSpec.executable())
80 return executable(commandSpec.executable())
81 .arguments(commandSpec.arguments());
81 .arguments(commandSpec.arguments());
82 }
82 }
83
84 CommandSpec build();
83 }
85 }
@@ -1,16 +1,20
1 package org.implab.gradle.common.exec;
1 package org.implab.gradle.common.exec;
2
2
3 import java.util.List;
3 import java.util.List;
4 import java.util.stream.Stream;
4 import java.util.stream.Stream;
5
5
6 public interface CommandSpec {
6 public interface CommandSpec {
7
7
8 String executable();
8 String executable();
9
9
10 List<String> arguments();
10 List<String> arguments();
11
11
12 default List<String> commandLine() {
12 default List<String> commandLine() {
13 return Stream.concat(Stream.of(executable()), arguments().stream()).toList();
13 return Stream.concat(Stream.of(executable()), arguments().stream()).toList();
14 }
14 }
15
15
16 public static CommandBuilder builder() {
17 return new CommandSpecRecord.Builder();
16 }
18 }
19
20 }
@@ -1,39 +1,40
1 package org.implab.gradle.common.exec;
1 package org.implab.gradle.common.exec;
2
2
3 import java.io.ByteArrayInputStream;
3 import java.io.ByteArrayInputStream;
4 import java.io.IOException;
4 import java.io.IOException;
5 import java.nio.charset.StandardCharsets;
5 import java.nio.charset.StandardCharsets;
6 import java.util.concurrent.CompletableFuture;
6 import java.util.concurrent.CompletableFuture;
7 import java.util.stream.Collectors;
7 import java.util.stream.Collectors;
8
8
9 class EchoExecBuilder extends ExecBuilder {
9 class EchoExecBuilder extends AbstractExecBuilder<CommandSpec> {
10
10
11 private final boolean echoToStderr;
11 private final boolean echoToStderr;
12
12
13 public EchoExecBuilder(boolean echoToStderr) {
13 public EchoExecBuilder(CommandSpec command, boolean echoToStderr) {
14 super(command);
14 this.echoToStderr = echoToStderr;
15 this.echoToStderr = echoToStderr;
15 }
16 }
16
17
17 @Override
18 @Override
18 protected CompletableFuture<Integer> startInternal(
19 protected CompletableFuture<Integer> startInternal(
19 CommandSpec command,
20 CommandSpec command,
20 EnvironmentSpec environment,
21 EnvironmentSpec environment,
21 PipeSpec redirect) throws IOException {
22 PipeSpec redirect) throws IOException {
22
23
23 var outputRedirect = echoToStderr ? redirect.stderr() : redirect.stdout();
24 var outputRedirect = echoToStderr ? redirect.stderr() : redirect.stdout();
24
25
25 return outputRedirect
26 return outputRedirect
26 .map(to -> {
27 .map(to -> {
27 var bytes = String
28 var bytes = String
28 .format(
29 .format(
29 "exec: %s",
30 "exec: %s",
30 command.commandLine().stream().collect(Collectors.joining(" ")))
31 command.commandLine().stream().collect(Collectors.joining(" ")))
31 .getBytes(StandardCharsets.UTF_8);
32 .getBytes(StandardCharsets.UTF_8);
32
33
33 return to.redirect(new ByteArrayInputStream(bytes))
34 return to.redirect(new ByteArrayInputStream(bytes))
34 .thenApply((x) -> 0);
35 .thenApply((x) -> 0);
35 })
36 })
36 .orElse(CompletableFuture.completedFuture(0));
37 .orElse(CompletableFuture.completedFuture(0));
37 }
38 }
38
39
39 }
40 }
@@ -1,14 +1,20
1 package org.implab.gradle.common.exec;
1 package org.implab.gradle.common.exec;
2
2
3 import java.util.Optional;
3 import java.util.Optional;
4
4
5 import org.eclipse.jdt.annotation.NonNullByDefault;
5 import org.eclipse.jdt.annotation.NonNullByDefault;
6
6
7 /**
8 * The execution shell uses this specification when starting
9 * a new process. The shell may check for the specified
10 * redirections and apply them when launching the process.
11 * The exact moment they are applied is at the shell’s discretion.
12 */
7 @NonNullByDefault
13 @NonNullByDefault
8 public interface PipeSpec {
14 public interface PipeSpec {
9 Optional<RedirectTo> stdout();
15 Optional<RedirectTo> stdout();
10
16
11 Optional<RedirectTo> stderr();
17 Optional<RedirectTo> stderr();
12
18
13 Optional<RedirectFrom> stdin();
19 Optional<RedirectFrom> stdin();
14 }
20 }
@@ -1,90 +1,111
1 package org.implab.gradle.common.exec;
1 package org.implab.gradle.common.exec;
2
2
3 import java.io.File;
3 import java.io.File;
4 import java.io.FileOutputStream;
4 import java.io.FileOutputStream;
5 import java.io.InputStream;
5 import java.io.InputStream;
6 import java.io.OutputStream;
6 import java.io.OutputStream;
7 import java.util.Scanner;
7 import java.util.Scanner;
8 import java.util.concurrent.CompletableFuture;
8 import java.util.concurrent.CompletableFuture;
9 import java.util.function.Consumer;
9 import java.util.function.Consumer;
10 import java.util.stream.Collectors;
11 import java.util.stream.Stream;
10
12
11 import org.eclipse.jdt.annotation.NonNullByDefault;
13 import org.eclipse.jdt.annotation.NonNullByDefault;
12
14
13 /**
15 /**
14 * Redirection specification for the {@link InputStream}. Redirection is invoked
16 * Redirection specification for the {@link InputStream}. Redirection is invoked
15 * when the {@link InputStream} becomes available, for example, on process
17 * when the {@link InputStream} becomes available, for example, on process
16 * start. Before the process is started the redirection isn't invoked and no
18 * start. Before the process is started the redirection isn't invoked and no
17 * resources are allocated or used.
19 * resources are allocated or used.
18 */
20 */
19 @NonNullByDefault
21 @NonNullByDefault
20 public interface RedirectTo {
22 public interface RedirectTo {
21 CompletableFuture<Void> redirect(InputStream from);
23 CompletableFuture<Void> redirect(InputStream from);
22
24
23 public interface StringConsumer extends Consumer<String> {
25 public interface StringConsumer extends Consumer<String> {
26 default void complete() {
27 }
24 }
28 }
25
29
26 /** Creates a redirect to the specified consumer */
30 /** Creates a redirect to the specified consumer */
27 public static RedirectTo consumer(final Consumer<String> consumer) {
31 public static RedirectTo eachLine(final Consumer<String> consumer) {
28 return consumer(new StringConsumer() {
32 return consumer(new StringConsumer() {
29 @Override
33 @Override
30 public void accept(String s) {
34 public void accept(String s) {
31 consumer.accept(s);
35 consumer.accept(s);
32 }
36 }
33 });
37 });
34 }
38 }
35
39
40 public static RedirectTo allText(final Consumer<String> consumer) {
41 return consumer(new StringConsumer() {
42 final Stream.Builder<String> builder = Stream.builder();
43
44 @Override
45 public void accept(String s) {
46 builder.accept(s);
47 }
48
49 @Override
50 public void complete() {
51 consumer.accept(builder.build().collect(Collectors.joining("\n")));
52 }
53 });
54 }
55
36 /** Creates a redirect to the specified consumer */
56 /** Creates a redirect to the specified consumer */
37 public static RedirectTo consumer(final StringConsumer consumer) {
57 public static RedirectTo consumer(final StringConsumer consumer) {
38 return (src) -> CompletableFuture.runAsync(() -> {
58 return (src) -> CompletableFuture.runAsync(() -> {
39 try (Scanner sc = new Scanner(src)) {
59 try (Scanner sc = new Scanner(src)) {
40 while (sc.hasNextLine()) {
60 while (sc.hasNextLine()) {
41 consumer.accept(sc.nextLine());
61 consumer.accept(sc.nextLine());
42 }
62 }
63 consumer.complete();
43 }
64 }
44 });
65 });
45 }
66 }
46
67
47 /**
68 /**
48 * Creates a redirect to the specified file. There will be opened the output
69 * 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
70 * stream in this redirection and original stream will be transferred to this
50 * this output stream.
71 * this output stream.
51 */
72 */
52 public static RedirectTo file(final File file) {
73 public static RedirectTo file(final File file) {
53 return src -> CompletableFuture.runAsync(() -> {
74 return src -> CompletableFuture.runAsync(() -> {
54 try (OutputStream out = new FileOutputStream(file)) {
75 try (src; OutputStream out = new FileOutputStream(file)) {
55 src.transferTo(out);
76 src.transferTo(out);
56 } catch (Exception e) {
77 } catch (Exception e) {
57 // silence!
78 // silence!
58 }
79 }
59 });
80 });
60 }
81 }
61
82
62 /** Creates a redirect to the specified output stream. */
83 /** Creates a redirect to the specified output stream. */
63 public static RedirectTo stream(final OutputStream dest) {
84 public static RedirectTo stream(final OutputStream dest) {
64 return src -> CompletableFuture.runAsync(() -> {
85 return src -> CompletableFuture.runAsync(() -> {
65 try (dest; src) {
86 try (dest; src) {
66 src.transferTo(dest);
87 src.transferTo(dest);
67 } catch (Exception e) {
88 } catch (Exception e) {
68 // silence!
89 // silence!
69 }
90 }
70 });
91 });
71 }
92 }
72
93
73 /**
94 /**
74 * Creates the redirection to the specified destination, actual type of
95 * Creates the redirection to the specified destination, actual type of
75 * redirection will be determined from the type of the output object.
96 * redirection will be determined from the type of the output object.
76 */
97 */
77 public static RedirectTo any(final Object output) {
98 public static RedirectTo any(final Object output) {
78 if (output instanceof StringConsumer fn) {
99 if (output instanceof StringConsumer fn) {
79 return consumer(s -> fn.accept(s));
100 return consumer(s -> fn.accept(s));
80 } else if (output instanceof File f) {
101 } else if (output instanceof File f) {
81 return file(f);
102 return file(f);
82 } else if (output instanceof OutputStream stm) {
103 } else if (output instanceof OutputStream stm) {
83 return stream(stm);
104 return stream(stm);
84 } else if (output instanceof RedirectTo self) {
105 } else if (output instanceof RedirectTo self) {
85 return self;
106 return self;
86 } else {
107 } else {
87 throw new IllegalArgumentException("The specified argument type isn't supported: " + output.getClass());
108 throw new IllegalArgumentException("The specified argument type isn't supported: " + output.getClass());
88 }
109 }
89 }
110 }
90 } No newline at end of file
111 }
@@ -1,45 +1,49
1 package org.implab.gradle.common.exec;
1 package org.implab.gradle.common.exec;
2
2
3 import java.io.IOException;
3 import java.io.IOException;
4 import java.lang.ProcessBuilder.Redirect;
4 import java.lang.ProcessBuilder.Redirect;
5 import java.util.ArrayList;
5 import java.util.ArrayList;
6 import java.util.concurrent.CompletableFuture;
6 import java.util.concurrent.CompletableFuture;
7
7
8 class SystemExecBuilder extends ExecBuilder {
8 class SystemExecBuilder extends AbstractExecBuilder<CommandSpec> {
9 SystemExecBuilder(CommandSpec command) {
10 super(command);
11 }
12
9 @Override
13 @Override
10 protected CompletableFuture<Integer> startInternal(
14 protected CompletableFuture<Integer> startInternal(
11 CommandSpec command,
15 CommandSpec command,
12 EnvironmentSpec environment,
16 EnvironmentSpec environment,
13 PipeSpec redirect) throws IOException {
17 PipeSpec redirect) throws IOException {
14
18
15 var builder = new ProcessBuilder(command.commandLine());
19 var builder = new ProcessBuilder(command.commandLine());
16
20
17 environment.workingDirectory().ifPresent(builder::directory);
21 environment.workingDirectory().ifPresent(builder::directory);
18
22
19 // if the env isn't inherited we need to clear it
23 // if the env isn't inherited we need to clear it
20 if (!environment.inheritEnvironment())
24 if (!environment.inheritEnvironment())
21 builder.environment().clear();
25 builder.environment().clear();
22
26
23 builder.environment().putAll(environment.environment());
27 builder.environment().putAll(environment.environment());
24
28
25 var tasks = new ArrayList<CompletableFuture<?>>();
29 var tasks = new ArrayList<CompletableFuture<?>>();
26
30
27 if (!redirect.stdout().isPresent())
31 if (!redirect.stdout().isPresent())
28 builder.redirectOutput(Redirect.DISCARD);
32 builder.redirectOutput(Redirect.DISCARD);
29 if (!redirect.stderr().isPresent())
33 if (!redirect.stderr().isPresent())
30 builder.redirectError(Redirect.DISCARD);
34 builder.redirectError(Redirect.DISCARD);
31
35
32 // run process
36 // run process
33 var proc = builder.start();
37 var proc = builder.start();
34
38
35 tasks.add(proc.onExit());
39 tasks.add(proc.onExit());
36
40
37 redirect.stdin().map(from -> from.redirect(proc.getOutputStream())).ifPresent(tasks::add);
41 redirect.stdin().map(from -> from.redirect(proc.getOutputStream())).ifPresent(tasks::add);
38 redirect.stdout().map(to -> to.redirect(proc.getInputStream())).ifPresent(tasks::add);
42 redirect.stdout().map(to -> to.redirect(proc.getInputStream())).ifPresent(tasks::add);
39 redirect.stderr().map(to -> to.redirect(proc.getErrorStream())).ifPresent(tasks::add);
43 redirect.stderr().map(to -> to.redirect(proc.getErrorStream())).ifPresent(tasks::add);
40
44
41 return CompletableFuture
45 return CompletableFuture
42 .allOf(tasks.toArray(new CompletableFuture<?>[0]))
46 .allOf(tasks.toArray(new CompletableFuture<?>[0]))
43 .thenApply(t -> proc.exitValue());
47 .thenApply(t -> proc.exitValue());
44 }
48 }
45 }
49 }
@@ -1,115 +1,31
1 package org.implab.gradle.common.tasks;
1 package org.implab.gradle.common.tasks;
2
2
3 import java.io.IOException;
4 import java.util.Map;
5 import java.util.concurrent.ExecutionException;
6 import java.util.stream.Stream;
7
8 import org.gradle.api.DefaultTask;
9 import org.gradle.api.file.DirectoryProperty;
10 import org.gradle.api.provider.ListProperty;
3 import org.gradle.api.provider.ListProperty;
11 import org.gradle.api.provider.MapProperty;
12 import org.gradle.api.provider.Property;
4 import org.gradle.api.provider.Property;
13 import org.gradle.api.tasks.Internal;
5 import org.gradle.api.tasks.Internal;
14 import org.gradle.api.tasks.TaskAction;
15 import org.implab.gradle.common.dsl.TaskCommandSpecMixin;
6 import org.implab.gradle.common.dsl.TaskCommandSpecMixin;
16 import org.implab.gradle.common.dsl.TaskPipeSpecMixin;
7 import org.implab.gradle.common.exec.CommandSpec;
17 import org.implab.gradle.common.dsl.RedirectFromSpec;
8 import org.implab.gradle.common.exec.Shell;
18 import org.implab.gradle.common.dsl.RedirectToSpec;
9 import org.implab.gradle.common.exec.ShellExec;
19 import org.implab.gradle.common.dsl.TaskEnvSpecMixin;
20 import org.implab.gradle.common.exec.ExecBuilder;
21 import org.implab.gradle.common.utils.ObjectsMixin;
22 import org.implab.gradle.common.utils.Strings;
23 import org.implab.gradle.common.utils.ThrowingConsumer;
24
10
25 public abstract class ShellExecTask
11 public abstract class ShellExecTask
26 extends DefaultTask
12 extends AbstractShellExecTask
27 implements TaskCommandSpecMixin, TaskPipeSpecMixin, TaskEnvSpecMixin, ObjectsMixin {
13 implements TaskCommandSpecMixin {
28
29 private final RedirectToSpec redirectStderr = new RedirectToSpec();
30
31 private final RedirectToSpec redirectStdout = new RedirectToSpec();
32
33 private final RedirectFromSpec redirectStdin = new RedirectFromSpec();
34
14
35 @Internal
15 @Internal
36 @Override
16 public abstract Property<Shell> getShell();
37 public abstract Property<Boolean> getInheritEnvironment();
38
39 @Internal
40 @Override
41 public abstract DirectoryProperty getWorkingDirectory();
42
43 @Internal
44 @Override
45 public abstract MapProperty<String, String> getEnvironment();
46
17
47 @Internal
18 @Internal
48 @Override
19 @Override
49 public abstract ListProperty<String> getCommandLine();
20 public abstract ListProperty<String> getCommandLine();
50
21
51 /**
52 * STDIN redirection, if not specified, no input will be passed to the command
53 */
54 @Internal
55 @Override
56 public RedirectFromSpec getStdin() {
57 return redirectStdin;
58 }
59
60 /**
61 * STDOUT redirection, if not specified, redirected to logger::info
62 */
63 @Internal
64 @Override
65 public RedirectToSpec getStdout() {
66 return redirectStdout;
67 }
68
69 /**
70 * STDERR redirection, if not specified, redirected to logger::error
71 */
72 @Internal
73 @Override
74 public RedirectToSpec getStderr() {
75 return redirectStderr;
76 }
77
22
78 @Override
23 @Override
79 public void commandLine(Object arg0, Object... args) {
24 protected ShellExec execBuilder() {
80 getCommandLine().set(provider(() -> Stream.concat(
25 return getShell().get()
81 Stream.of(arg0),
26 .of(CommandSpec.builder()
82 Stream.of(args))
27 .commandLine(getCommandLine().get())
83 .map(Strings::asString).toList()));
28 .build());
84
85 }
86
87 @Override
88 public void args(Object arg0, Object... args) {
89 getCommandLine().addAll(provider(() -> Stream.concat(
90 Stream.of(arg0),
91 Stream.of(args))
92 .map(Strings::asString).toList()));
93 }
94
95 protected abstract ExecBuilder execBuilder();
96
97 @TaskAction
98 public final void run() throws IOException, InterruptedException, ExecutionException {
99 var execBuilder = execBuilder();
100 execBuilder.workingDirectory(getWorkingDirectory().get().getAsFile());
101 execBuilder.environment(getEnvironment().getOrElse(Map.of()));
102 execBuilder.commandLine(getCommandLine().get());
103
104 getStdout().getRedirection().ifPresent(execBuilder::stdout);
105 getStderr().getRedirection().ifPresent(execBuilder::stderr);
106 getStdin().getRedirection().ifPresent(execBuilder::stdin);
107
108 execBuilder.exec().thenAccept(ThrowingConsumer.guard(this::checkRetCode)).join();
109 }
110
111 protected void checkRetCode(Integer code) throws IOException {
112 throw new IOException(String.format("The process is terminated with code %s", code));
113 }
29 }
114
30
115 }
31 }
@@ -1,34 +1,32
1 package org.implab.gradle.common.utils;
1 package org.implab.gradle.common.utils;
2
2
3 import groovy.lang.Closure;
3 import groovy.lang.Closure;
4
4
5 import org.eclipse.jdt.annotation.NonNullByDefault;
5 import org.eclipse.jdt.annotation.NonNullByDefault;
6 import org.gradle.api.Action;
6 import org.gradle.api.Action;
7
7
8 @NonNullByDefault
8 @NonNullByDefault
9 public final class Closures {
9 public final class Closures {
10 private Closures() {
10 private Closures() {
11 }
11 }
12
12
13 /**
13 /**
14 * Wraps {@link Action} around the specified closure. The parameter
14 * Wraps {@link Action} around the specified closure. The parameter
15 * of the action will be used as delegate in the specified closure.
15 * of the action will be used as delegate in the specified closure.
16 *
16 *
17 * @param <T> The type of the action parameter
17 * @param <T> The type of the action parameter
18 * @param closure The closure
18 * @param closure The closure
19 * @return
19 * @return
20 */
20 */
21 public static <T> Action<T> action(Closure<?> closure) {
21 public static <T> Action<T> action(Closure<?> closure) {
22 return arg -> apply(closure, arg);
22 return arg -> apply(closure, arg);
23 }
23 }
24
24
25 public static void apply(Closure<?> action, Object target) {
25 public static void apply(Closure<?> action, Object target) {
26 var prevDelegate = action.getDelegate();
26 var c = (Closure<?>)action.clone();
27 try {
27 c.setResolveStrategy(0);
28 action.setDelegate(target);
28 c.setDelegate(target);
29 action.call(target);
29 c.call(target);
30 } finally {
30
31 action.setDelegate(prevDelegate);
32 }
31 }
33 }
32 }
34 }
@@ -1,32 +1,54
1 package org.implab.gradle.common.utils;
1 package org.implab.gradle.common.utils;
2
2
3 import java.util.regex.Pattern;
3 import java.util.regex.Pattern;
4
4
5 import org.eclipse.jdt.annotation.NonNullByDefault;
5 import org.eclipse.jdt.annotation.NonNullByDefault;
6 import org.gradle.api.provider.Provider;
6 import org.gradle.api.provider.Provider;
7
7
8 @NonNullByDefault
8 @NonNullByDefault
9 public class Strings {
9 public class Strings {
10
10
11 private static final Pattern firstLetter = Pattern.compile("^\\w");
11 private static final Pattern firstLetter = Pattern.compile("^\\w");
12
12
13 public static String capitalize(String string) {
13 public static String capitalize(String string) {
14 return string == null ? null
14 return string == null ? null
15 : string.length() == 0 ? string
15 : string.length() == 0 ? string
16 : firstLetter.matcher(string).replaceFirst(m -> m.group().toUpperCase());
16 : firstLetter.matcher(string).replaceFirst(m -> m.group().toUpperCase());
17 }
17 }
18
18
19 public static String toCamelCase(String name) {
20 if (name == null || name.isEmpty())
21 return name;
22 StringBuilder out = new StringBuilder(name.length());
23 boolean up = false;
24 boolean first = true;
25 for (int i = 0; i < name.length(); i++) {
26 char c = name.charAt(i);
27 switch (c) {
28 case '-', '_', ' ', '.' -> up = true;
29 default -> {
30 out.append(
31 first ? Character.toLowerCase(c)
32 : up ? Character.toUpperCase(c): c);
33 up = false;
34 first = false;
35 }
36 }
37 }
38 return out.toString();
39 }
40
19 public static void argumentNotNullOrEmpty(String value, String argumentName) {
41 public static void argumentNotNullOrEmpty(String value, String argumentName) {
20 if (value == null || value.length() == 0)
42 if (value == null || value.length() == 0)
21 throw new IllegalArgumentException(String.format("Argument %s can't be null or empty", argumentName));
43 throw new IllegalArgumentException(String.format("Argument %s can't be null or empty", argumentName));
22 }
44 }
23
45
24 public static String asString(Object value) {
46 public static String asString(Object value) {
25 if (value == null)
47 if (value == null)
26 return null;
48 return null;
27 if (value instanceof Provider<?> provider)
49 if (value instanceof Provider<?> provider)
28 return asString(provider.get());
50 return asString(provider.get());
29 else
51 else
30 return value.toString();
52 return value.toString();
31 }
53 }
32 }
54 }
@@ -1,123 +1,72
1 package org.implab.gradle.common.utils;
1 package org.implab.gradle.common.utils;
2
2
3 import java.util.HashMap;
4 import java.util.Iterator;
3 import java.util.Iterator;
5 import java.util.Map;
6 import java.util.Spliterators;
4 import java.util.Spliterators;
7 import java.util.Map.Entry;
8 import java.util.Optional;
5 import java.util.Optional;
9 import java.util.function.Function;
10 import java.util.function.Supplier;
6 import java.util.function.Supplier;
11 import java.util.stream.Collectors;
12 import java.util.stream.Stream;
7 import java.util.stream.Stream;
13 import java.util.stream.StreamSupport;
8 import java.util.stream.StreamSupport;
14
9
15 import org.gradle.api.Action;
16 import org.gradle.api.provider.MapProperty;
17 import org.gradle.api.provider.Provider;
10 import org.gradle.api.provider.Provider;
18
11
19 public final class Values {
12 public final class Values {
20
13
21 private Values() {
14 private Values() {
22 }
15 }
23
16
24 /**
17 /**
25 * Converts values in the specified map
26 *
27 * @param <K>
28 * @param <V>
29 * @param <U>
30 * @param map
31 * @param mapper
32 * @return
33 */
34 public static <K, V, U> Map<K, U> mapValues(Map<K, V> map, Function<V, U> mapper) {
35 Function<Entry<K, V>, V> getter = Entry::getValue;
36
37 return map.entrySet().stream()
38 .collect(Collectors.toMap(Entry::getKey, getter.andThen(mapper)));
39 }
40
41 public static <K, V> void mergeMap(MapProperty<K,V> prop, Map<K, ? > map, Class<V> clazz) {
42 map.forEach((k,v) -> {
43 if(v instanceof Provider<?>)
44 prop.put(k, ((Provider<?>)v).map(clazz::cast));
45 else
46 prop.put(k, clazz.cast(v));
47 });
48 }
49
50 public static <K,V,U> void mergeMap(MapProperty<K,V> prop, Map<K, ?> map, Class<U> clazz, Function<U,V> mapper) {
51 Function<Object, U> caster = clazz::cast;
52 var castMap = caster.andThen(mapper);
53
54 map.forEach((k,v) -> {
55 if(v instanceof Provider<?>)
56 prop.put(k, ((Provider<?>)v).map(x -> castMap.apply(x)));
57 else
58 prop.put(k, castMap.apply(v));
59 });
60 }
61
62 public static <K,V,U> void configureMap(MapProperty<K,V> prop, Action<Map<K, ?>> configure, Class<U> clazz, Function<U,V> mapper) {
63 var map = new HashMap<K,V>();
64 configure.execute(map);
65 mergeMap(prop, map, clazz, mapper);
66 }
67
68 /**
69 * Converts the supplied value to a string.
18 * Converts the supplied value to a string.
70 */
19 */
71 public static String toString(Object value) {
20 public static String toString(Object value) {
72 if (value == null) {
21 if (value == null) {
73 return null;
22 return null;
74 } else if (value instanceof String string) {
23 } else if (value instanceof String string) {
75 return string;
24 return string;
76 } else if (value instanceof Provider<?> provider) {
25 } else if (value instanceof Provider<?> provider) {
77 return toString(provider.getOrNull());
26 return toString(provider.getOrNull());
78 } else if (value instanceof Supplier<?> supplier) {
27 } else if (value instanceof Supplier<?> supplier) {
79 return toString(supplier.get());
28 return toString(supplier.get());
80 } else {
29 } else {
81 return value.toString();
30 return value.toString();
82 }
31 }
83 }
32 }
84
33
85 public static <T> Stream<T> stream(Iterator<T> remaining) {
34 public static <T> Stream<T> stream(Iterator<T> remaining) {
86 return StreamSupport.stream(
35 return StreamSupport.stream(
87 Spliterators.spliteratorUnknownSize(remaining, 0),
36 Spliterators.spliteratorUnknownSize(remaining, 0),
88 false);
37 false);
89 }
38 }
90
39
91 public static <T> Optional<T> take(Iterator<T> iterator) {
40 public static <T> Optional<T> take(Iterator<T> iterator) {
92 return iterator.hasNext() ? Optional.of(iterator.next()) : Optional.empty();
41 return iterator.hasNext() ? Optional.of(iterator.next()) : Optional.empty();
93 }
42 }
94
43
95 public static <T> Iterable<T> iterable(T[] values) {
44 public static <T> Iterable<T> iterable(T[] values) {
96 return () -> new ArrayIterator<>(values);
45 return () -> new ArrayIterator<>(values);
97 }
46 }
98
47
99 public static <T> Optional<T> optional(Provider<T> provider) {
48 public static <T> Optional<T> optional(Provider<T> provider) {
100 return provider.isPresent() ? Optional.of(provider.get()) : Optional.empty();
49 return provider.isPresent() ? Optional.of(provider.get()) : Optional.empty();
101 }
50 }
102
51
103 private static class ArrayIterator<T> implements Iterator<T> {
52 private static class ArrayIterator<T> implements Iterator<T> {
104 private final T[] data;
53 private final T[] data;
105
54
106 private int pos = 0;
55 private int pos = 0;
107
56
108 ArrayIterator(T[] data) {
57 ArrayIterator(T[] data) {
109 this.data = data;
58 this.data = data;
110 }
59 }
111
60
112 @Override
61 @Override
113 public boolean hasNext() {
62 public boolean hasNext() {
114 return pos < data.length;
63 return pos < data.length;
115 }
64 }
116
65
117 @Override
66 @Override
118 public T next() {
67 public T next() {
119 return data[pos++];
68 return data[pos++];
120 }
69 }
121 }
70 }
122
71
123 }
72 }
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now