| @@ -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 | } | |||
| @@ -20,26 +20,30 public class RedirectFromSpec { | |||||
| 20 | } |
|
20 | } | |
| 21 |
|
21 | |||
| 22 | public void fromFile(File file) { |
|
22 | public void fromFile(File file) { | |
| 23 |
|
|
23 | streamRedirect = () -> RedirectFrom.file(file); | |
| 24 | } |
|
24 | } | |
| 25 |
|
25 | |||
| 26 | public void fromFile(Provider<File> fileProvider) { |
|
26 | public void fromFile(Provider<File> fileProvider) { | |
| 27 |
|
|
27 | streamRedirect = fileProvider.map(RedirectFrom::file)::getOrNull; | |
| 28 | } |
|
28 | } | |
| 29 |
|
29 | |||
| 30 | public void fromStream(InputStream stream) { |
|
30 | public void fromStream(InputStream stream) { | |
| 31 |
|
|
31 | streamRedirect = () -> RedirectFrom.stream(stream); | |
| 32 | } |
|
32 | } | |
| 33 |
|
33 | |||
| 34 | public void fromStream(Provider<InputStream> streamProvider) { |
|
34 | public void fromStream(Provider<InputStream> streamProvider) { | |
| 35 |
|
|
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 |
|
|
40 | streamRedirect = inputProvider.map(RedirectFrom::any)::get; | |
| 41 | } else { |
|
41 | } else { | |
| 42 |
|
|
42 | streamRedirect = () -> RedirectFrom.any(input); | |
| 43 | } |
|
43 | } | |
| 44 | } |
|
44 | } | |
|
|
45 | ||||
|
|
46 | public void empty() { | |||
|
|
47 | streamRedirect = () -> null; | |||
|
|
48 | } | |||
| 45 | } |
|
49 | } | |
| @@ -14,7 +14,7 import org.implab.gradle.common.exec.Red | |||||
| 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 | } | |
| @@ -28,30 +28,38 public class RedirectToSpec { | |||||
| 28 | } |
|
28 | } | |
| 29 |
|
29 | |||
| 30 | public void toFile(File file) { |
|
30 | public void toFile(File file) { | |
| 31 |
|
|
31 | streamRedirect = () -> RedirectTo.file(file); | |
| 32 | } |
|
32 | } | |
| 33 |
|
33 | |||
| 34 | public void toFile(Provider<File> fileProvider) { |
|
34 | public void toFile(Provider<File> fileProvider) { | |
| 35 |
|
|
35 | streamRedirect = fileProvider.map(RedirectTo::file)::getOrNull; | |
| 36 | } |
|
36 | } | |
| 37 |
|
37 | |||
| 38 | public void toStream(OutputStream stream) { |
|
38 | public void toStream(OutputStream stream) { | |
| 39 |
|
|
39 | streamRedirect = () -> RedirectTo.stream(stream); | |
| 40 | } |
|
40 | } | |
| 41 |
|
41 | |||
| 42 | public void toStream(Provider<OutputStream> streamProvider) { |
|
42 | public void toStream(Provider<OutputStream> streamProvider) { | |
| 43 |
|
|
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 |
|
|
48 | streamRedirect = outputProvider.map(RedirectTo::any)::get; | |
| 49 | } else { |
|
49 | } else { | |
| 50 |
|
|
50 | streamRedirect = () -> RedirectTo.any(output); | |
| 51 | } |
|
51 | } | |
| 52 | } |
|
52 | } | |
| 53 |
|
53 | |||
| 54 |
public void |
|
54 | public void eachLine(Consumer<String> consumer) { | |
| 55 |
|
|
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,15 +1,13 | |||||
| 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. |
|
10 | import org.implab.gradle.common.utils.Properties; | |
| 13 |
|
11 | |||
| 14 | import groovy.lang.Closure; |
|
12 | import groovy.lang.Closure; | |
| 15 |
|
13 | |||
| @@ -38,7 +36,7 public interface TaskEnvSpecMixin { | |||||
| 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 |
|
|
39 | Properties.configureMap(getEnvironment(), configure, Object::toString); | |
| 42 | } |
|
40 | } | |
| 43 |
|
41 | |||
| 44 | default void env(Closure<?> configure) { |
|
42 | default void env(Closure<?> configure) { | |
| @@ -24,10 +24,10 public interface CommandBuilder { | |||||
| 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(); | |
| @@ -80,4 +80,6 public interface CommandBuilder { | |||||
| 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 | } | |
| @@ -13,4 +13,8 public interface CommandSpec { | |||||
| 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(); | |||
|
|
18 | } | |||
|
|
19 | ||||
| 16 | } |
|
20 | } | |
| @@ -6,11 +6,12 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 | |||
| @@ -4,6 +4,12 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(); | |
| @@ -7,6 +7,8 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 | |||
| @@ -21,10 +23,12 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 |
|
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) { | |
| @@ -33,6 +37,22 public interface RedirectTo { | |||||
| 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(() -> { | |
| @@ -40,6 +60,7 public interface RedirectTo { | |||||
| 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 | } | |
| @@ -51,7 +72,7 public interface RedirectTo { | |||||
| 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! | |
| @@ -5,7 +5,11 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, | |
| @@ -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. |
|
7 | import org.implab.gradle.common.exec.CommandSpec; | |
| 17 |
import org.implab.gradle.common. |
|
8 | import org.implab.gradle.common.exec.Shell; | |
| 18 |
import org.implab.gradle.common. |
|
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 |
|
12 | extends AbstractShellExecTask | |
| 27 |
implements TaskCommandSpecMixin |
|
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 |
|
|
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 | } | |
| @@ -23,12 +23,10 public final class Closures { | |||||
| 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 |
|
|
28 | c.setDelegate(target); | |
| 29 |
|
|
29 | c.call(target); | |
| 30 | } finally { |
|
30 | ||
| 31 | action.setDelegate(prevDelegate); |
|
|||
| 32 | } |
|
|||
| 33 | } |
|
31 | } | |
| 34 | } |
|
32 | } | |
| @@ -16,6 +16,28 public class Strings { | |||||
| 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)); | |
| @@ -1,19 +1,12 | |||||
| 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 { | |
| @@ -22,50 +15,6 public final class 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) { | |
| 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
