| @@ -0,0 +1,16 | |||
|
|
1 | package org.implab.gradle.common.exec; | |
|
|
2 | ||
|
|
3 | /** Shell used to start processes */ | |
|
|
4 | public interface ExecShell { | |
|
|
5 | ||
|
|
6 | /** Creates new process builder {@link ExecBuilder} */ | |
|
|
7 | ExecBuilder builder(); | |
|
|
8 | ||
|
|
9 | /** | |
|
|
10 | * Creates default process execution "shell" which will execute processes | |
|
|
11 | * directly. | |
|
|
12 | */ | |
|
|
13 | static ExecShell system() { | |
|
|
14 | return () -> new SystemExecBuilder(); | |
|
|
15 | } | |
|
|
16 | } | |
| @@ -1,78 +1,78 | |||
|
|
1 | 1 | package org.implab.gradle.common.dsl; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.io.File; |
|
|
4 | 4 | import java.util.ArrayList; |
|
|
5 | 5 | import java.util.Collection; |
|
|
6 | 6 | import java.util.List; |
|
|
7 | 7 | |
|
|
8 |
import org.implab.gradle.common.exec.Exec |
|
|
|
8 | import org.implab.gradle.common.exec.ExecBuilder; | |
|
|
9 | 9 | import org.implab.gradle.common.exec.RedirectFrom; |
|
|
10 | 10 | import org.implab.gradle.common.exec.RedirectTo; |
|
|
11 | 11 | |
|
|
12 | 12 | public class ProcessSpec { |
|
|
13 | 13 | private RedirectFrom inputRedirect; |
|
|
14 | 14 | |
|
|
15 | 15 | private RedirectTo outputRedirect; |
|
|
16 | 16 | |
|
|
17 | 17 | private RedirectTo errorRedirect; |
|
|
18 | 18 | |
|
|
19 | 19 | private List<String> command = new ArrayList<>(); |
|
|
20 | 20 | |
|
|
21 | 21 | private File directory; |
|
|
22 | 22 | |
|
|
23 | 23 | public ProcessSpec command(String... args) { |
|
|
24 | 24 | command = new ArrayList<>(); |
|
|
25 | 25 | args(args); |
|
|
26 | 26 | return this; |
|
|
27 | 27 | } |
|
|
28 | 28 | |
|
|
29 | 29 | public ProcessSpec directory(File workingDir) { |
|
|
30 | 30 | directory = workingDir; |
|
|
31 | 31 | return this; |
|
|
32 | 32 | } |
|
|
33 | 33 | |
|
|
34 | 34 | public ProcessSpec command(Collection<String> args) { |
|
|
35 | 35 | command = new ArrayList<>(); |
|
|
36 | 36 | args(args); |
|
|
37 | 37 | return this; |
|
|
38 | 38 | } |
|
|
39 | 39 | |
|
|
40 | 40 | public ProcessSpec args(String... args) { |
|
|
41 | 41 | for (String arg : args) |
|
|
42 | 42 | command.add(arg); |
|
|
43 | 43 | return this; |
|
|
44 | 44 | } |
|
|
45 | 45 | |
|
|
46 | 46 | public ProcessSpec args(Collection<String> args) { |
|
|
47 | 47 | command.addAll(args); |
|
|
48 | 48 | return this; |
|
|
49 | 49 | } |
|
|
50 | 50 | |
|
|
51 | 51 | public ProcessSpec stderr(RedirectTo to) { |
|
|
52 | 52 | errorRedirect = to; |
|
|
53 | 53 | return this; |
|
|
54 | 54 | } |
|
|
55 | 55 | |
|
|
56 | 56 | public ProcessSpec stdin(RedirectFrom from) { |
|
|
57 | 57 | inputRedirect = from; |
|
|
58 | 58 | return this; |
|
|
59 | 59 | } |
|
|
60 | 60 | |
|
|
61 | 61 | public ProcessSpec stdout(RedirectTo to) { |
|
|
62 | 62 | outputRedirect = to; |
|
|
63 | 63 | return this; |
|
|
64 | 64 | } |
|
|
65 | 65 | |
|
|
66 |
public void accept(Exec |
|
|
|
66 | public void accept(ExecBuilder executor) { | |
|
|
67 | 67 | command.forEach(executor::argument); |
|
|
68 | 68 | executor.directory(directory); |
|
|
69 | 69 | if (inputRedirect != null) |
|
|
70 | 70 | executor.stdin(inputRedirect); |
|
|
71 | 71 | if (outputRedirect != null) |
|
|
72 | 72 | executor.stdout(outputRedirect); |
|
|
73 | 73 | if (errorRedirect != null) |
|
|
74 | 74 | executor.stderr(errorRedirect); |
|
|
75 | 75 | } |
|
|
76 | 76 | |
|
|
77 | 77 | |
|
|
78 | 78 | } |
| @@ -1,25 +1,61 | |||
|
|
1 | 1 | package org.implab.gradle.common.exec; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.util.concurrent.CompletableFuture; |
|
|
4 | 4 | |
|
|
5 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
|
|
6 | ||
|
|
5 | 7 | import java.io.File; |
|
|
6 | 8 | import java.io.IOException; |
|
|
7 | 9 | |
|
|
8 | /** Interface for a command line execution. */ | |
|
|
9 | public interface Executor { | |
|
|
10 | /** Command line builder */ | |
|
|
11 | @NonNullByDefault | |
|
|
12 | public interface ExecBuilder { | |
|
|
13 | ||
|
|
14 | /** Sets command line, clears previous one if any */ | |
|
|
15 | void command(String cmd, String... args); | |
|
|
16 | ||
|
|
17 | /** Adds an argument to the command line */ | |
|
|
10 | 18 | void argument(String arg); |
|
|
11 | 19 | |
|
|
20 | /** Sets the working directory */ | |
|
|
12 | 21 | void directory(File directory); |
|
|
13 | 22 | |
|
|
23 | /** | |
|
|
24 | * Sets the environment value. The value cannot be null. | |
|
|
25 | * | |
|
|
26 | * @param envVar The name of the environment variable | |
|
|
27 | * @param value The value to set, | |
|
|
28 | */ | |
|
|
29 | void setEnvironment(String envVar, String value); | |
|
|
30 | ||
|
|
31 | void unsetEnvironment(String envVar); | |
|
|
32 | ||
|
|
33 | /** | |
|
|
34 | * Sets redirection for the stdin, {@link RedirectFrom} will be applied | |
|
|
35 | * every time the process is started. | |
|
|
36 | * | |
|
|
37 | * <p>If redirection | |
|
|
38 | */ | |
|
|
14 | 39 | void stdin(RedirectFrom from); |
|
|
15 | 40 | |
|
|
41 | /** | |
|
|
42 | * Sets redirection for the stdout, {@link RedirectTo} will be applied | |
|
|
43 | * every time the process is started. | |
|
|
44 | */ | |
|
|
16 | 45 | void stdout(RedirectTo out); |
|
|
17 | 46 | |
|
|
47 | /** | |
|
|
48 | * Sets redirection for the stderr, {@link RedirectTo} will be applied | |
|
|
49 | * every time the process is started. | |
|
|
50 | */ | |
|
|
18 | 51 | void stderr(RedirectTo err); |
|
|
19 | ||
|
|
52 | ||
|
|
53 | /** | |
|
|
54 | * Creates and starts new process and returns {@link CompletableFuture}. The | |
|
|
55 | * process may be a remote process, or a shell process or etc. | |
|
|
56 | * @return | |
|
|
57 | * @throws IOException | |
|
|
58 | */ | |
|
|
20 | 59 | CompletableFuture<Integer> start() throws IOException; |
|
|
21 | 60 | |
|
|
22 | public static Executor system() { | |
|
|
23 | return new SystemExec(); | |
|
|
24 | } | |
|
|
25 | 61 | } |
| @@ -1,33 +1,37 | |||
|
|
1 | 1 | package org.implab.gradle.common.exec; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.io.File; |
|
|
4 | 4 | import java.io.FileInputStream; |
|
|
5 | 5 | import java.io.InputStream; |
|
|
6 | 6 | import java.io.OutputStream; |
|
|
7 | 7 | import java.util.concurrent.CompletableFuture; |
|
|
8 | 8 | |
|
|
9 | /** Describes how to redirect input streams. This interface is used to configure | |
|
|
10 | * lazy redirection. {@link #redirect(OutputStream)} is called when the process | |
|
|
11 | * is started. | |
|
|
12 | */ | |
|
|
9 | 13 | public interface RedirectFrom { |
|
|
10 | 14 | CompletableFuture<Void> redirect(OutputStream to); |
|
|
11 | 15 | |
|
|
12 | 16 | public static RedirectFrom file(final File file) { |
|
|
13 | 17 | return to -> CompletableFuture.runAsync(() -> { |
|
|
14 | 18 | try (var from = new FileInputStream(file); to) { |
|
|
15 | 19 | from.transferTo(to); |
|
|
16 | 20 | } catch (Exception e) { |
|
|
17 | 21 | // silence! |
|
|
18 | 22 | } |
|
|
19 | 23 | }); |
|
|
20 | 24 | } |
|
|
21 | 25 | |
|
|
22 | 26 | public static RedirectFrom stream(final InputStream from) { |
|
|
23 | 27 | return to -> CompletableFuture.runAsync(() -> { |
|
|
24 | 28 | try (from; to) { |
|
|
25 | 29 | from.transferTo(to); |
|
|
26 | 30 | } catch (Exception e) { |
|
|
27 | 31 | // silence! |
|
|
28 | 32 | } |
|
|
29 | 33 | }); |
|
|
30 | 34 | } |
|
|
31 | 35 | |
|
|
32 | 36 | |
|
|
33 | 37 | } |
| @@ -1,76 +1,98 | |||
|
|
1 | 1 | package org.implab.gradle.common.exec; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.io.File; |
|
|
4 | 4 | import java.io.IOException; |
|
|
5 | 5 | import java.lang.ProcessBuilder.Redirect; |
|
|
6 | 6 | import java.util.ArrayList; |
|
|
7 | 7 | import java.util.concurrent.CompletableFuture; |
|
|
8 | 8 | |
|
|
9 |
class SystemExec implements Exec |
|
|
|
9 | class SystemExecBuilder implements ExecBuilder { | |
|
|
10 | 10 | |
|
|
11 | 11 | private final ProcessBuilder builder = new ProcessBuilder(); |
|
|
12 | 12 | |
|
|
13 | 13 | private RedirectFrom inputRedirect; |
|
|
14 | 14 | |
|
|
15 | 15 | private RedirectTo outputRedirect; |
|
|
16 | 16 | |
|
|
17 | 17 | private RedirectTo errorRedirect; |
|
|
18 | 18 | |
|
|
19 | 19 | @Override |
|
|
20 | 20 | public CompletableFuture<Integer> start() throws IOException { |
|
|
21 | 21 | // TODO Auto-generated method stub |
|
|
22 | 22 | var tasks = new ArrayList<CompletableFuture<?>>(); |
|
|
23 | 23 | |
|
|
24 | 24 | // discard stdout if not redirected |
|
|
25 | 25 | if (outputRedirect == null) |
|
|
26 | 26 | builder.redirectOutput(Redirect.DISCARD); |
|
|
27 | 27 | |
|
|
28 | 28 | // discard stderr if not redirected |
|
|
29 | 29 | if (errorRedirect == null) |
|
|
30 | 30 | builder.redirectError(Redirect.DISCARD); |
|
|
31 | 31 | |
|
|
32 | 32 | // run process |
|
|
33 | 33 | var proc = builder.start(); |
|
|
34 | 34 | |
|
|
35 | 35 | tasks.add(proc.onExit()); |
|
|
36 | 36 | |
|
|
37 | 37 | if (inputRedirect != null) |
|
|
38 | 38 | tasks.add(inputRedirect.redirect(proc.getOutputStream())); |
|
|
39 | 39 | |
|
|
40 | 40 | if (outputRedirect != null) |
|
|
41 | 41 | tasks.add(outputRedirect.redirect(proc.getInputStream())); |
|
|
42 | 42 | |
|
|
43 | 43 | if (errorRedirect != null) |
|
|
44 | 44 | tasks.add(errorRedirect.redirect(proc.getErrorStream())); |
|
|
45 | 45 | |
|
|
46 | 46 | return CompletableFuture |
|
|
47 | 47 | .allOf(tasks.toArray(new CompletableFuture<?>[0])) |
|
|
48 | 48 | .thenApply(t -> proc.exitValue()); |
|
|
49 | 49 | } |
|
|
50 | 50 | |
|
|
51 | public void command(String... args) { | |
|
|
52 | builder.command(args); | |
|
|
53 | } | |
|
|
54 | ||
|
|
51 | @Override | |
|
|
55 | 52 | public void directory(File workingDir) { |
|
|
56 | 53 | builder.directory(workingDir); |
|
|
57 | 54 | } |
|
|
58 | 55 | |
|
|
56 | @Override | |
|
|
59 | 57 | public void stderr(RedirectTo to) { |
|
|
60 | 58 | errorRedirect = to; |
|
|
61 | 59 | } |
|
|
62 | 60 | |
|
|
61 | @Override | |
|
|
63 | 62 | public void stdin(RedirectFrom from) { |
|
|
64 | 63 | inputRedirect = from; |
|
|
65 | 64 | } |
|
|
66 | 65 | |
|
|
66 | @Override | |
|
|
67 | 67 | public void stdout(RedirectTo to) { |
|
|
68 | 68 | outputRedirect = to; |
|
|
69 | 69 | } |
|
|
70 | 70 | |
|
|
71 | 71 | @Override |
|
|
72 | 72 | public void argument(String arg) { |
|
|
73 | 73 | builder.command().add(arg); |
|
|
74 | 74 | } |
|
|
75 | 75 | |
|
|
76 | @Override | |
|
|
77 | public void setEnvironment(String envVar, String value) { | |
|
|
78 | builder.environment().put(envVar, value); | |
|
|
79 | ||
|
|
80 | } | |
|
|
81 | ||
|
|
82 | @Override | |
|
|
83 | public void unsetEnvironment(String envVar) { | |
|
|
84 | builder.environment().remove(envVar); | |
|
|
85 | } | |
|
|
86 | ||
|
|
87 | @Override | |
|
|
88 | public void command(String cmd, String... args) { | |
|
|
89 | var commandLine = new ArrayList<String>(); | |
|
|
90 | ||
|
|
91 | commandLine.add(cmd); | |
|
|
92 | for (var arg : args) | |
|
|
93 | commandLine.add(arg); | |
|
|
94 | ||
|
|
95 | builder.command(commandLine); | |
|
|
96 | } | |
|
|
97 | ||
|
|
76 | 98 | } |
| @@ -1,104 +1,104 | |||
|
|
1 | 1 | package org.implab.gradle.common.tasks; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.io.IOException; |
|
|
4 | 4 | import java.util.Optional; |
|
|
5 | 5 | import java.util.concurrent.ExecutionException; |
|
|
6 | 6 | |
|
|
7 | 7 | import org.gradle.api.DefaultTask; |
|
|
8 | 8 | import org.implab.gradle.common.dsl.ProcessSpec; |
|
|
9 |
import org.implab.gradle.common.exec.Exec |
|
|
|
9 | import org.implab.gradle.common.exec.ExecBuilder; | |
|
|
10 | 10 | import org.implab.gradle.common.exec.RedirectFrom; |
|
|
11 | 11 | import org.implab.gradle.common.exec.RedirectTo; |
|
|
12 | 12 | |
|
|
13 | 13 | public abstract class ExternalTask extends DefaultTask { |
|
|
14 | 14 | |
|
|
15 | 15 | /** |
|
|
16 | 16 | * A default redirection to the build log when loglevel is set to INFO, |
|
|
17 | 17 | * otherwise returns an empty redirection. |
|
|
18 | 18 | */ |
|
|
19 | 19 | protected Optional<RedirectTo> loggerInfoRedirect() { |
|
|
20 | 20 | return getLogger().isInfoEnabled() |
|
|
21 | 21 | ? Optional.of(RedirectTo.consumer(getLogger()::info)) |
|
|
22 | 22 | : Optional.empty(); |
|
|
23 | 23 | } |
|
|
24 | 24 | |
|
|
25 | 25 | /** |
|
|
26 | 26 | * A default redirection to the build log when loglevel is set to ERROR, |
|
|
27 | 27 | * otherwise returns an empty redirection. Note that ERROR level is set |
|
|
28 | 28 | * by default for a build runs. |
|
|
29 | 29 | */ |
|
|
30 | 30 | protected Optional<RedirectTo> loggerErrorRedirect() { |
|
|
31 | 31 | return getLogger().isErrorEnabled() |
|
|
32 | 32 | ? Optional.of(RedirectTo.consumer(getLogger()::error)) |
|
|
33 | 33 | : Optional.empty(); |
|
|
34 | 34 | } |
|
|
35 | 35 | |
|
|
36 | 36 | /** |
|
|
37 | 37 | * Stdout redirection for {@link #exec(ProcessSpec)}, default implementation |
|
|
38 | 38 | * will forward stdout to the build log through {@link #loggerErrorRedirect()} |
|
|
39 | 39 | */ |
|
|
40 | 40 | protected Optional<RedirectTo> stdoutRedirection() { |
|
|
41 | 41 | return loggerInfoRedirect(); |
|
|
42 | 42 | } |
|
|
43 | 43 | |
|
|
44 | 44 | /** |
|
|
45 | 45 | * Stderr redirection for {@link #exec(ProcessSpec)}, default implementation |
|
|
46 | 46 | * will forward stderr to build log through {@link #loggerErrorRedirect()}. |
|
|
47 | 47 | */ |
|
|
48 | 48 | protected Optional<RedirectTo> stderrRedirection() { |
|
|
49 | 49 | return loggerErrorRedirect(); |
|
|
50 | 50 | } |
|
|
51 | 51 | |
|
|
52 | 52 | /** |
|
|
53 | 53 | * Stdin redirection for {@link #exec(ProcessSpec)}, empty by default. |
|
|
54 | 54 | */ |
|
|
55 | 55 | protected Optional<RedirectFrom> stdinRedirection() { |
|
|
56 | 56 | return Optional.empty(); |
|
|
57 | 57 | } |
|
|
58 | 58 | |
|
|
59 | 59 | /** |
|
|
60 | 60 | * Executes the specified process specification |
|
|
61 | 61 | * |
|
|
62 | 62 | * @param spec |
|
|
63 | 63 | * @throws InterruptedException |
|
|
64 | 64 | * @throws ExecutionException |
|
|
65 | 65 | * @throws IOException |
|
|
66 | 66 | */ |
|
|
67 | 67 | protected void exec(ProcessSpec spec) throws InterruptedException, ExecutionException, IOException { |
|
|
68 | 68 | var executor = exec(); |
|
|
69 | 69 | |
|
|
70 | 70 | stdoutRedirection().ifPresent(executor::stdout); |
|
|
71 | 71 | stderrRedirection().ifPresent(executor::stderr); |
|
|
72 | 72 | stdinRedirection().ifPresent(executor::stdin); |
|
|
73 | 73 | |
|
|
74 | 74 | getLogger().info("Staring: {}", spec.command()); |
|
|
75 | 75 | |
|
|
76 | 76 | spec.accept(executor); |
|
|
77 | 77 | |
|
|
78 | 78 | // runs the command and checks the error code |
|
|
79 | 79 | var code = executor.start().get(); |
|
|
80 | 80 | |
|
|
81 | 81 | // check success code |
|
|
82 | 82 | if (code != 0) |
|
|
83 | 83 | throw new IOException("The process exited with error code " + code); |
|
|
84 | 84 | } |
|
|
85 | 85 | |
|
|
86 | 86 | protected boolean checkRetCode(ProcessSpec proc, int code) |
|
|
87 | 87 | throws InterruptedException, ExecutionException, IOException { |
|
|
88 | 88 | |
|
|
89 | 89 | var executor = exec(); |
|
|
90 | 90 | |
|
|
91 | 91 | if (getLogger().isInfoEnabled()) { |
|
|
92 | 92 | loggerInfoRedirect().ifPresent(executor::stdout); |
|
|
93 | 93 | loggerInfoRedirect().ifPresent(executor::stderr); |
|
|
94 | 94 | } |
|
|
95 | 95 | |
|
|
96 | 96 | getLogger().info("Starting: {}", proc.command()); |
|
|
97 | 97 | |
|
|
98 | 98 | proc.accept(executor); |
|
|
99 | 99 | |
|
|
100 | 100 | return executor.start().get() == code; |
|
|
101 | 101 | } |
|
|
102 | 102 | |
|
|
103 |
protected abstract Exec |
|
|
|
103 | protected abstract ExecBuilder exec(); | |
|
|
104 | 104 | } |
General Comments 0
You need to be logged in to leave comments.
Login now
