| @@ -0,0 +1,52 | |||
|
|
1 | package org.implab.gradle.common.exec; | |
|
|
2 | ||
|
|
3 | import static java.util.Objects.requireNonNull; | |
|
|
4 | ||
|
|
5 | import java.util.Optional; | |
|
|
6 | ||
|
|
7 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
|
|
8 | import org.implab.gradle.common.utils.Values; | |
|
|
9 | ||
|
|
10 | @NonNullByDefault | |
|
|
11 | public interface CommandArgumentsBuilder<S extends CommandArgumentsBuilder<S>> { | |
|
|
12 | ||
|
|
13 | S self(); | |
|
|
14 | ||
|
|
15 | default S flag(String name, boolean value) { | |
|
|
16 | requireNonNull(name, "Parameter name cannot be null"); | |
|
|
17 | if (value) | |
|
|
18 | addArguments(name); | |
|
|
19 | return self(); | |
|
|
20 | } | |
|
|
21 | ||
|
|
22 | default S param(String name, String value) { | |
|
|
23 | requireNonNull(name, "Parameter name cannot be null"); | |
|
|
24 | requireNonNull(value, "Parameter value cannot be null"); | |
|
|
25 | if (value != null && value.length() > 0) | |
|
|
26 | addArguments(name, value); | |
|
|
27 | return self(); | |
|
|
28 | } | |
|
|
29 | ||
|
|
30 | default S param(String name, Optional<String> value) { | |
|
|
31 | value.ifPresent(v -> param(name, v)); | |
|
|
32 | return self(); | |
|
|
33 | } | |
|
|
34 | ||
|
|
35 | /** Adds the specified arguments to this builder */ | |
|
|
36 | S addArguments(String... args); | |
|
|
37 | ||
|
|
38 | default S addArguments(Iterable<String> args) { | |
|
|
39 | for (String arg : args) | |
|
|
40 | addArguments(arg); | |
|
|
41 | ||
|
|
42 | return self(); | |
|
|
43 | } | |
|
|
44 | ||
|
|
45 | /** Replaces arguments in the builder with the specified one. */ | |
|
|
46 | default S arguments(String... args) { | |
|
|
47 | return arguments(Values.iterable(args)); | |
|
|
48 | } | |
|
|
49 | ||
|
|
50 | /** Replaces arguments in the builder with the specified one. */ | |
|
|
51 | S arguments(Iterable<String> args); | |
|
|
52 | } | |
| @@ -0,0 +1,40 | |||
|
|
1 | package org.implab.gradle.common.exec; | |
|
|
2 | ||
|
|
3 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
|
|
4 | import org.gradle.api.Action; | |
|
|
5 | ||
|
|
6 | @NonNullByDefault | |
|
|
7 | public class ShellTool { | |
|
|
8 | ||
|
|
9 | private final Shell shell; | |
|
|
10 | ||
|
|
11 | private final String executable; | |
|
|
12 | ||
|
|
13 | ShellTool(String executable, Shell shell) { | |
|
|
14 | this.shell = shell; | |
|
|
15 | this.executable = executable; | |
|
|
16 | } | |
|
|
17 | ||
|
|
18 | public ShellExec arguments(String... args) { | |
|
|
19 | return shell.create(CommandSpec.builder() | |
|
|
20 | .executable(executable) | |
|
|
21 | .arguments(args) | |
|
|
22 | .build()); | |
|
|
23 | } | |
|
|
24 | ||
|
|
25 | public ShellExec arguments(Iterable<String> args) { | |
|
|
26 | return shell.create(CommandSpec.builder() | |
|
|
27 | .executable(executable) | |
|
|
28 | .arguments(args) | |
|
|
29 | .build()); | |
|
|
30 | } | |
|
|
31 | ||
|
|
32 | public ShellExec arguments(Action<CommandArgumentsBuilder<?>> args) { | |
|
|
33 | var builder = CommandSpec.builder() | |
|
|
34 | .executable(executable); | |
|
|
35 | args.execute(builder); | |
|
|
36 | ||
|
|
37 | return shell.create(builder.build()); | |
|
|
38 | } | |
|
|
39 | ||
|
|
40 | } | |
| @@ -0,0 +1,35 | |||
|
|
1 | package org.implab.gradle.common.utils; | |
|
|
2 | ||
|
|
3 | import java.io.File; | |
|
|
4 | import java.util.Optional; | |
|
|
5 | ||
|
|
6 | import org.implab.gradle.common.utils.os.SystemResolver; | |
|
|
7 | ||
|
|
8 | public interface OperatingSystem { | |
|
|
9 | ||
|
|
10 | public static OperatingSystem CURRENT = SystemResolver.current(); | |
|
|
11 | ||
|
|
12 | public static final String WINDOWS_FAMILY = "windows"; | |
|
|
13 | ||
|
|
14 | public static final String LINUX_FAMILY = "linux"; | |
|
|
15 | ||
|
|
16 | public static final String FREE_BSD_FAMILY = "freebsd"; | |
|
|
17 | ||
|
|
18 | public static final String MAC_OS_FAMILY = "os x"; | |
|
|
19 | ||
|
|
20 | public static final String UNKNOWN_FAMILY = "unknown"; | |
|
|
21 | ||
|
|
22 | String family(); | |
|
|
23 | ||
|
|
24 | String name(); | |
|
|
25 | ||
|
|
26 | String version(); | |
|
|
27 | ||
|
|
28 | Optional<File> which(String cmd); | |
|
|
29 | ||
|
|
30 | Optional<File> which(String cmd, Iterable<? extends File> paths); | |
|
|
31 | ||
|
|
32 | public static OperatingSystem current() { | |
|
|
33 | return CURRENT; | |
|
|
34 | } | |
|
|
35 | } | |
| @@ -0,0 +1,16 | |||
|
|
1 | package org.implab.gradle.common.utils.os; | |
|
|
2 | ||
|
|
3 | import org.implab.gradle.common.utils.OperatingSystem; | |
|
|
4 | ||
|
|
5 | public class FreeBsd extends GenericSystem{ | |
|
|
6 | ||
|
|
7 | FreeBsd(String name, String version) { | |
|
|
8 | super(name, version); | |
|
|
9 | } | |
|
|
10 | ||
|
|
11 | @Override | |
|
|
12 | public String family() { | |
|
|
13 | return OperatingSystem.FREE_BSD_FAMILY; | |
|
|
14 | } | |
|
|
15 | ||
|
|
16 | } | |
| @@ -0,0 +1,64 | |||
|
|
1 | package org.implab.gradle.common.utils.os; | |
|
|
2 | ||
|
|
3 | import java.io.File; | |
|
|
4 | import java.util.Arrays; | |
|
|
5 | import java.util.Optional; | |
|
|
6 | import java.util.function.Function; | |
|
|
7 | import java.util.regex.Pattern; | |
|
|
8 | import java.util.stream.Stream; | |
|
|
9 | ||
|
|
10 | import org.implab.gradle.common.utils.OperatingSystem; | |
|
|
11 | import org.implab.gradle.common.utils.Values; | |
|
|
12 | ||
|
|
13 | class GenericSystem implements OperatingSystem { | |
|
|
14 | ||
|
|
15 | private final String name; | |
|
|
16 | ||
|
|
17 | private final String version; | |
|
|
18 | ||
|
|
19 | GenericSystem(String name, String version) { | |
|
|
20 | this.name = name; | |
|
|
21 | this.version = version; | |
|
|
22 | } | |
|
|
23 | ||
|
|
24 | public String getPathVar() { | |
|
|
25 | return "PATH"; | |
|
|
26 | } | |
|
|
27 | ||
|
|
28 | @Override | |
|
|
29 | public String family() { | |
|
|
30 | return OperatingSystem.UNKNOWN_FAMILY; | |
|
|
31 | } | |
|
|
32 | ||
|
|
33 | @Override | |
|
|
34 | public String name() { | |
|
|
35 | return name; | |
|
|
36 | } | |
|
|
37 | ||
|
|
38 | @Override | |
|
|
39 | public String version() { | |
|
|
40 | return version; | |
|
|
41 | } | |
|
|
42 | ||
|
|
43 | protected Stream<File> getPath() { | |
|
|
44 | String path = System.getenv(getPathVar()); | |
|
|
45 | return path == null | |
|
|
46 | ? Stream.empty() | |
|
|
47 | : Arrays.stream(path.split(Pattern.quote(File.pathSeparator))) | |
|
|
48 | .map(File::new); | |
|
|
49 | } | |
|
|
50 | ||
|
|
51 | protected Function<File, Stream<File>> candidates(String cmd) { | |
|
|
52 | return base -> Stream.of(new File(base, cmd)); | |
|
|
53 | } | |
|
|
54 | ||
|
|
55 | @Override | |
|
|
56 | public Optional<File> which(String cmd) { | |
|
|
57 | return getPath().flatMap(candidates(cmd)).filter(File::isFile).findAny(); | |
|
|
58 | } | |
|
|
59 | ||
|
|
60 | @Override | |
|
|
61 | public Optional<File> which(String cmd, Iterable<? extends File> paths) { | |
|
|
62 | return Values.stream(paths.iterator()).flatMap(candidates(cmd)).filter(File::isFile).findAny(); | |
|
|
63 | } | |
|
|
64 | } | |
| @@ -0,0 +1,16 | |||
|
|
1 | package org.implab.gradle.common.utils.os; | |
|
|
2 | ||
|
|
3 | import org.implab.gradle.common.utils.OperatingSystem; | |
|
|
4 | ||
|
|
5 | public class Linux extends GenericSystem { | |
|
|
6 | ||
|
|
7 | Linux(String name, String version) { | |
|
|
8 | super(name, version); | |
|
|
9 | } | |
|
|
10 | ||
|
|
11 | @Override | |
|
|
12 | public String family() { | |
|
|
13 | return OperatingSystem.LINUX_FAMILY; | |
|
|
14 | } | |
|
|
15 | ||
|
|
16 | } | |
| @@ -0,0 +1,15 | |||
|
|
1 | package org.implab.gradle.common.utils.os; | |
|
|
2 | ||
|
|
3 | import org.implab.gradle.common.utils.OperatingSystem; | |
|
|
4 | ||
|
|
5 | public class MacOs extends GenericSystem { | |
|
|
6 | ||
|
|
7 | MacOs(String name, String version) { | |
|
|
8 | super(name, version); | |
|
|
9 | } | |
|
|
10 | ||
|
|
11 | @Override | |
|
|
12 | public String family() { | |
|
|
13 | return OperatingSystem.MAC_OS_FAMILY; | |
|
|
14 | } | |
|
|
15 | } | |
| @@ -0,0 +1,29 | |||
|
|
1 | package org.implab.gradle.common.utils.os; | |
|
|
2 | ||
|
|
3 | import org.implab.gradle.common.utils.OperatingSystem; | |
|
|
4 | ||
|
|
5 | public class SystemResolver { | |
|
|
6 | public static OperatingSystem current() { | |
|
|
7 | var osName = System.getProperty("os.name"); | |
|
|
8 | var osVersion = System.getProperty("os.version"); | |
|
|
9 | ||
|
|
10 | return forName(osName, osVersion); | |
|
|
11 | } | |
|
|
12 | ||
|
|
13 | public static OperatingSystem forName(String os, String version) { | |
|
|
14 | var osName = os.toLowerCase(); | |
|
|
15 | ||
|
|
16 | if (osName.contains("windows")) { | |
|
|
17 | return new Windows(osName, version); | |
|
|
18 | } else if (osName.contains("mac os x") || osName.contains("darwin") || osName.contains("osx")) { | |
|
|
19 | return new MacOs(osName, version); | |
|
|
20 | } else if (osName.contains("linux")) { | |
|
|
21 | return new Linux(osName, version); | |
|
|
22 | } else if (osName.contains("freebsd")) { | |
|
|
23 | return new FreeBsd(osName, version); | |
|
|
24 | } else { | |
|
|
25 | // Not strictly true | |
|
|
26 | return new GenericSystem(osName, version); | |
|
|
27 | } | |
|
|
28 | } | |
|
|
29 | } | |
| @@ -0,0 +1,26 | |||
|
|
1 | package org.implab.gradle.common.utils.os; | |
|
|
2 | ||
|
|
3 | import java.io.File; | |
|
|
4 | import java.util.function.Function; | |
|
|
5 | import java.util.stream.Stream; | |
|
|
6 | ||
|
|
7 | import org.implab.gradle.common.utils.OperatingSystem; | |
|
|
8 | ||
|
|
9 | class Windows extends GenericSystem { | |
|
|
10 | ||
|
|
11 | private Stream<String> exeSuffixes = Stream.of(".cmd", ".bat", ".exe"); | |
|
|
12 | ||
|
|
13 | Windows(String name, String version) { | |
|
|
14 | super(name, version); | |
|
|
15 | } | |
|
|
16 | ||
|
|
17 | @Override | |
|
|
18 | public String family() { | |
|
|
19 | return OperatingSystem.WINDOWS_FAMILY; | |
|
|
20 | } | |
|
|
21 | ||
|
|
22 | @Override | |
|
|
23 | protected Function<File, Stream<File>> candidates(String cmd) { | |
|
|
24 | return base -> exeSuffixes.map(suffix -> new File(base, cmd + suffix)); | |
|
|
25 | } | |
|
|
26 | } | |
| @@ -1,85 +1,63 | |||
|
|
1 | 1 | package org.implab.gradle.common.exec; |
|
|
2 | 2 | |
|
|
3 | import java.util.function.Consumer; | |
|
|
3 | import java.io.File; | |
|
|
4 | 4 | |
|
|
5 | 5 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
|
6 | 6 | import org.implab.gradle.common.utils.Values; |
|
|
7 | 7 | |
|
|
8 | 8 | /** Command builder interface, used to specify the executable and parameters */ |
|
|
9 | 9 | @NonNullByDefault |
|
|
10 | public interface CommandBuilder { | |
|
|
10 | public interface CommandBuilder extends CommandArgumentsBuilder<CommandBuilder> { | |
|
|
11 | ||
|
|
12 | @Override | |
|
|
13 | default CommandBuilder self() { | |
|
|
14 | return this; | |
|
|
15 | } | |
|
|
11 | 16 | |
|
|
12 | 17 | /** Sets the executable, the parameters are left intact. */ |
|
|
13 | 18 | CommandBuilder executable(String executable); |
|
|
14 | 19 | |
|
|
20 | default CommandBuilder executable(File executable) { | |
|
|
21 | executable(executable.toString()); | |
|
|
22 | return this; | |
|
|
23 | } | |
|
|
24 | ||
|
|
15 | 25 | /** |
|
|
16 | 26 | * Sets the specified executable and parameters, old executable and parameters |
|
|
17 | 27 | * are discarded. |
|
|
18 | 28 | */ |
|
|
19 | 29 | default CommandBuilder commandLine(String executable, String... args) { |
|
|
20 | 30 | return executable(executable) |
|
|
21 | 31 | .arguments(args); |
|
|
22 | 32 | } |
|
|
23 | 33 | |
|
|
24 | 34 | /** |
|
|
25 | 35 | * Sets the specified executable and parameters, old executable and parameters |
|
|
26 | 36 | * are discarded. |
|
|
27 | 37 | * |
|
|
28 | 38 | * @param command The command line. Must contain at least one element |
|
|
29 | 39 | * (executable). |
|
|
30 | 40 | * |
|
|
31 | 41 | */ |
|
|
32 | 42 | default CommandBuilder commandLine(Iterable<? extends String> command) { |
|
|
33 | 43 | var iterator = command.iterator(); |
|
|
34 | 44 | |
|
|
35 | 45 | // set executable |
|
|
36 | 46 | executable(Values.take(iterator).orElseThrow()); |
|
|
37 | 47 | // cleat arguments |
|
|
38 | 48 | arguments(); |
|
|
39 | 49 | |
|
|
40 | 50 | // set new arguments |
|
|
41 | 51 | while (iterator.hasNext()) |
|
|
42 | 52 | addArguments(iterator.next()); |
|
|
43 | 53 | |
|
|
44 | 54 | return this; |
|
|
45 | 55 | } |
|
|
46 | 56 | |
|
|
47 | default Consumer<Boolean> flag(String name) { | |
|
|
48 | return f -> { | |
|
|
49 | if (f) | |
|
|
50 | addArguments(name); | |
|
|
51 | }; | |
|
|
52 | } | |
|
|
53 | ||
|
|
54 | default Consumer<String> param(String name) { | |
|
|
55 | return v -> { | |
|
|
56 | if (v != null && v.length() > 0) | |
|
|
57 | addArguments(name, v); | |
|
|
58 | }; | |
|
|
59 | } | |
|
|
60 | ||
|
|
61 | /** Adds the specified arguments to this builder */ | |
|
|
62 | CommandBuilder addArguments(String... args); | |
|
|
63 | ||
|
|
64 | default CommandBuilder addArguments(Iterable<String> args) { | |
|
|
65 | for (String arg : args) | |
|
|
66 | addArguments(arg); | |
|
|
67 | ||
|
|
68 | return this; | |
|
|
69 | } | |
|
|
70 | ||
|
|
71 | /** Replaces arguments in the builder with the specified one. */ | |
|
|
72 | default CommandBuilder arguments(String... args) { | |
|
|
73 | return arguments(Values.iterable(args)); | |
|
|
74 | } | |
|
|
75 | ||
|
|
76 | /** Replaces arguments in the builder with the specified one. */ | |
|
|
77 | CommandBuilder arguments(Iterable<String> args); | |
|
|
78 | ||
|
|
79 | 57 | default CommandBuilder from(CommandSpec commandSpec) { |
|
|
80 | 58 | return executable(commandSpec.executable()) |
|
|
81 | 59 | .arguments(commandSpec.arguments()); |
|
|
82 | 60 | } |
|
|
83 | 61 | |
|
|
84 | 62 | CommandSpec build(); |
|
|
85 | 63 | } |
| @@ -1,20 +1,23 | |||
|
|
1 | 1 | package org.implab.gradle.common.exec; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.util.List; |
|
|
4 | 4 | import java.util.stream.Stream; |
|
|
5 | 5 | |
|
|
6 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
|
|
7 | ||
|
|
8 | @NonNullByDefault | |
|
|
6 | 9 | public interface CommandSpec { |
|
|
7 | 10 | |
|
|
8 | 11 | String executable(); |
|
|
9 | 12 | |
|
|
10 | 13 | List<String> arguments(); |
|
|
11 | 14 | |
|
|
12 | 15 | default List<String> commandLine() { |
|
|
13 | 16 | return Stream.concat(Stream.of(executable()), arguments().stream()).toList(); |
|
|
14 | 17 | } |
|
|
15 | 18 | |
|
|
16 | 19 | public static CommandBuilder builder() { |
|
|
17 | 20 | return new CommandSpecRecord.Builder(); |
|
|
18 | 21 | } |
|
|
19 | 22 | |
|
|
20 | 23 | } |
| @@ -1,47 +1,51 | |||
|
|
1 | 1 | package org.implab.gradle.common.exec; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.util.List; |
|
|
4 | ||
|
|
5 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
|
|
6 | ||
|
|
4 | 7 | import static java.util.Objects.requireNonNull; |
|
|
5 | 8 | |
|
|
6 | 9 | import java.util.ArrayList; |
|
|
7 | 10 | |
|
|
11 | @NonNullByDefault | |
|
|
8 | 12 | public record CommandSpecRecord(String executable, List<String> arguments) implements CommandSpec { |
|
|
9 | 13 | |
|
|
10 | 14 | static class Builder implements CommandBuilder { |
|
|
11 | 15 | |
|
|
12 | 16 | private String executable; |
|
|
13 | 17 | |
|
|
14 | 18 | private List<String> arguments = new ArrayList<>(); |
|
|
15 | 19 | |
|
|
16 | 20 | @Override |
|
|
17 | 21 | public CommandBuilder executable(String executable) { |
|
|
18 | 22 | requireNonNull(executable, "cmd can't be null"); |
|
|
19 | 23 | this.executable = executable; |
|
|
20 | 24 | return this; |
|
|
21 | 25 | } |
|
|
22 | 26 | |
|
|
23 | 27 | @Override |
|
|
24 | 28 | public CommandBuilder arguments(Iterable<String> args) { |
|
|
25 | 29 | requireNonNull(args, "Args must not be null"); |
|
|
26 | 30 | arguments.clear(); |
|
|
27 | 31 | for (var arg : args) |
|
|
28 | 32 | arguments.add(arg); |
|
|
29 | 33 | return this; |
|
|
30 | 34 | } |
|
|
31 | 35 | |
|
|
32 | 36 | @Override |
|
|
33 | 37 | public CommandBuilder addArguments(String... args) { |
|
|
34 | 38 | for (var arg : args) |
|
|
35 | 39 | arguments.add(requireNonNull(arg, "arguments element shouldn't be null")); |
|
|
36 | 40 | |
|
|
37 | 41 | return this; |
|
|
38 | 42 | } |
|
|
39 | 43 | |
|
|
40 | 44 | @Override |
|
|
41 | 45 | public CommandSpec build() { |
|
|
42 | 46 | requireNonNull(executable, "Executable must be specified"); |
|
|
43 | 47 | return new CommandSpecRecord(executable, arguments); |
|
|
44 | 48 | } |
|
|
45 | 49 | |
|
|
46 | 50 | } |
|
|
47 | 51 | } |
| @@ -1,40 +1,43 | |||
|
|
1 | 1 | package org.implab.gradle.common.exec; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.io.ByteArrayInputStream; |
|
|
4 | 4 | import java.io.IOException; |
|
|
5 | 5 | import java.nio.charset.StandardCharsets; |
|
|
6 | 6 | import java.util.concurrent.CompletableFuture; |
|
|
7 | 7 | import java.util.stream.Collectors; |
|
|
8 | 8 | |
|
|
9 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
|
|
10 | ||
|
|
11 | @NonNullByDefault | |
|
|
9 | 12 | class EchoExecBuilder extends AbstractExecBuilder<CommandSpec> { |
|
|
10 | 13 | |
|
|
11 | 14 | private final boolean echoToStderr; |
|
|
12 | 15 | |
|
|
13 | 16 | public EchoExecBuilder(CommandSpec command, boolean echoToStderr) { |
|
|
14 | 17 | super(command); |
|
|
15 | 18 | this.echoToStderr = echoToStderr; |
|
|
16 | 19 | } |
|
|
17 | 20 | |
|
|
18 | 21 | @Override |
|
|
19 | 22 | protected CompletableFuture<Integer> startInternal( |
|
|
20 | 23 | CommandSpec command, |
|
|
21 | 24 | EnvironmentSpec environment, |
|
|
22 | 25 | PipeSpec redirect) throws IOException { |
|
|
23 | 26 | |
|
|
24 | 27 | var outputRedirect = echoToStderr ? redirect.stderr() : redirect.stdout(); |
|
|
25 | 28 | |
|
|
26 | 29 | return outputRedirect |
|
|
27 | 30 | .map(to -> { |
|
|
28 | 31 | var bytes = String |
|
|
29 | 32 | .format( |
|
|
30 | 33 | "exec: %s", |
|
|
31 | 34 | command.commandLine().stream().collect(Collectors.joining(" "))) |
|
|
32 | 35 | .getBytes(StandardCharsets.UTF_8); |
|
|
33 | 36 | |
|
|
34 | 37 | return to.redirect(new ByteArrayInputStream(bytes)) |
|
|
35 | 38 | .thenApply((x) -> 0); |
|
|
36 | 39 | }) |
|
|
37 | 40 | .orElse(CompletableFuture.completedFuture(0)); |
|
|
38 | 41 | } |
|
|
39 | 42 | |
|
|
40 | 43 | } |
| @@ -1,14 +1,17 | |||
|
|
1 | 1 | package org.implab.gradle.common.exec; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.io.File; |
|
|
4 | 4 | import java.util.Map; |
|
|
5 | 5 | import java.util.Optional; |
|
|
6 | 6 | |
|
|
7 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
|
|
8 | ||
|
|
9 | @NonNullByDefault | |
|
|
7 | 10 | public interface EnvironmentSpec { |
|
|
8 | 11 | |
|
|
9 | 12 | boolean inheritEnvironment(); |
|
|
10 | 13 | |
|
|
11 | 14 | Map<String,String> environment(); |
|
|
12 | 15 | |
|
|
13 | 16 | Optional<File> workingDirectory(); |
|
|
14 | 17 | } |
| @@ -1,53 +1,56 | |||
|
|
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 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
|
|
10 | ||
|
|
9 | 11 | /** |
|
|
10 | 12 | * Describes how to redirect input streams. This interface is used to configure |
|
|
11 | 13 | * lazy redirection. {@link #redirect(OutputStream)} is called when the process |
|
|
12 | 14 | * is started. Before the process is started the redirection isn't invoked and |
|
|
13 | 15 | * no resources are allocated or used. |
|
|
14 | 16 | */ |
|
|
17 | @NonNullByDefault | |
|
|
15 | 18 | public interface RedirectFrom { |
|
|
16 | 19 | CompletableFuture<Void> redirect(OutputStream to); |
|
|
17 | 20 | |
|
|
18 | 21 | /** |
|
|
19 | 22 | * Read file contents and redirect it to the output stream. |
|
|
20 | 23 | */ |
|
|
21 | 24 | public static RedirectFrom file(final File file) { |
|
|
22 | 25 | return to -> CompletableFuture.runAsync(() -> { |
|
|
23 | 26 | try (var from = new FileInputStream(file); to) { |
|
|
24 | 27 | from.transferTo(to); |
|
|
25 | 28 | } catch (Exception e) { |
|
|
26 | 29 | // silence! |
|
|
27 | 30 | } |
|
|
28 | 31 | }); |
|
|
29 | 32 | } |
|
|
30 | 33 | |
|
|
31 | 34 | public static RedirectFrom stream(final InputStream from) { |
|
|
32 | 35 | return to -> CompletableFuture.runAsync(() -> { |
|
|
33 | 36 | try (from; to) { |
|
|
34 | 37 | from.transferTo(to); |
|
|
35 | 38 | } catch (Exception e) { |
|
|
36 | 39 | // silence! |
|
|
37 | 40 | } |
|
|
38 | 41 | }); |
|
|
39 | 42 | } |
|
|
40 | 43 | |
|
|
41 | 44 | public static RedirectFrom any(final Object output) { |
|
|
42 | 45 | if (output instanceof File f) { |
|
|
43 | 46 | return file(f); |
|
|
44 | 47 | } else if (output instanceof InputStream stm) { |
|
|
45 | 48 | return stream(stm); |
|
|
46 | 49 | } else if (output instanceof RedirectFrom self) { |
|
|
47 | 50 | return self; |
|
|
48 | 51 | } else { |
|
|
49 | 52 | throw new IllegalArgumentException("The specified argument type isn't supported: " + output.getClass()); |
|
|
50 | 53 | } |
|
|
51 | 54 | } |
|
|
52 | 55 | |
|
|
53 | 56 | } |
| @@ -1,22 +1,29 | |||
|
|
1 | 1 | package org.implab.gradle.common.exec; |
|
|
2 | 2 | |
|
|
3 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
|
|
4 | ||
|
|
5 | @NonNullByDefault | |
|
|
3 | 6 | public interface Shell { |
|
|
4 | 7 | |
|
|
5 |
ShellExec |
|
|
|
8 | ShellExec create(CommandSpec spec); | |
|
|
9 | ||
|
|
10 | default ShellTool tool(String executable) { | |
|
|
11 | return new ShellTool(executable, this); | |
|
|
12 | } | |
|
|
6 | 13 | |
|
|
7 | 14 | /** Creates a new shell to start processes using ProcessBuilder. */ |
|
|
8 | 15 | static Shell system() { |
|
|
9 | 16 | return spec -> new SystemExecBuilder(spec); |
|
|
10 | 17 | } |
|
|
11 | 18 | |
|
|
12 | 19 | /** Creates a stub shell which echoes the specified command line. */ |
|
|
13 | 20 | static Shell echo() { |
|
|
14 | 21 | return spec -> new EchoExecBuilder(spec, false); |
|
|
15 | 22 | } |
|
|
16 | 23 | |
|
|
17 | 24 | /** Creates a stub shell which echoes the specified command line. */ |
|
|
18 | 25 | static Shell echoStderr() { |
|
|
19 | 26 | return spec -> new EchoExecBuilder(spec, false); |
|
|
20 | 27 | } |
|
|
21 | 28 | |
|
|
22 | 29 | } |
| @@ -1,36 +1,38 | |||
|
|
1 | 1 | package org.implab.gradle.common.exec; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.io.IOException; |
|
|
4 | 4 | import java.util.ArrayList; |
|
|
5 | 5 | import java.util.List; |
|
|
6 | 6 | import java.util.concurrent.CompletableFuture; |
|
|
7 | 7 | |
|
|
8 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
|
|
8 | 9 | import org.implab.gradle.common.utils.Exceptions; |
|
|
9 | 10 | |
|
|
11 | @NonNullByDefault | |
|
|
10 | 12 | public interface ShellExec extends PipeBuilder, EnvironmentBuilder { |
|
|
11 | 13 | |
|
|
12 | 14 | default CompletableFuture<List<String>> readAllLines() throws IOException { |
|
|
13 | 15 | List<String> lines = new ArrayList<>(); |
|
|
14 | 16 | stdout(RedirectTo.consumer(lines::add)); |
|
|
15 | 17 | |
|
|
16 | 18 | return exec() |
|
|
17 | 19 | .thenAccept(Exceptions.unchecked(this::assertExitCode)) |
|
|
18 | 20 | .thenApply(v -> lines); |
|
|
19 | 21 | } |
|
|
20 | 22 | |
|
|
21 | 23 | default CompletableFuture<String> readAllText() throws IOException { |
|
|
22 | 24 | List<String> lines = new ArrayList<>(); |
|
|
23 | 25 | stdout(RedirectTo.consumer(lines::add)); |
|
|
24 | 26 | |
|
|
25 | 27 | return exec() |
|
|
26 | 28 | .thenAccept(Exceptions.unchecked(this::assertExitCode)) |
|
|
27 | 29 | .thenApply(v -> String.join("\n", lines)); |
|
|
28 | 30 | } |
|
|
29 | 31 | |
|
|
30 | 32 | default void assertExitCode(Integer code) throws IOException { |
|
|
31 | 33 | if (code != 0) |
|
|
32 | 34 | throw new IOException(String.format("The process is terminated with code %d", code)); |
|
|
33 | 35 | } |
|
|
34 | 36 | |
|
|
35 | 37 | CompletableFuture<Integer> exec() throws IOException; |
|
|
36 | 38 | } |
| @@ -1,49 +1,52 | |||
|
|
1 | 1 | package org.implab.gradle.common.exec; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.io.IOException; |
|
|
4 | 4 | import java.lang.ProcessBuilder.Redirect; |
|
|
5 | 5 | import java.util.ArrayList; |
|
|
6 | 6 | import java.util.concurrent.CompletableFuture; |
|
|
7 | 7 | |
|
|
8 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
|
|
9 | ||
|
|
10 | @NonNullByDefault | |
|
|
8 | 11 | class SystemExecBuilder extends AbstractExecBuilder<CommandSpec> { |
|
|
9 | 12 | SystemExecBuilder(CommandSpec command) { |
|
|
10 | 13 | super(command); |
|
|
11 | 14 | } |
|
|
12 | 15 | |
|
|
13 | 16 | @Override |
|
|
14 | 17 | protected CompletableFuture<Integer> startInternal( |
|
|
15 | 18 | CommandSpec command, |
|
|
16 | 19 | EnvironmentSpec environment, |
|
|
17 | 20 | PipeSpec redirect) throws IOException { |
|
|
18 | 21 | |
|
|
19 | 22 | var builder = new ProcessBuilder(command.commandLine()); |
|
|
20 | 23 | |
|
|
21 | 24 | environment.workingDirectory().ifPresent(builder::directory); |
|
|
22 | 25 | |
|
|
23 | 26 | // if the env isn't inherited we need to clear it |
|
|
24 | 27 | if (!environment.inheritEnvironment()) |
|
|
25 | 28 | builder.environment().clear(); |
|
|
26 | 29 | |
|
|
27 | 30 | builder.environment().putAll(environment.environment()); |
|
|
28 | 31 | |
|
|
29 | 32 | var tasks = new ArrayList<CompletableFuture<?>>(); |
|
|
30 | 33 | |
|
|
31 | 34 | if (!redirect.stdout().isPresent()) |
|
|
32 | 35 | builder.redirectOutput(Redirect.DISCARD); |
|
|
33 | 36 | if (!redirect.stderr().isPresent()) |
|
|
34 | 37 | builder.redirectError(Redirect.DISCARD); |
|
|
35 | 38 | |
|
|
36 | 39 | // run process |
|
|
37 | 40 | var proc = builder.start(); |
|
|
38 | 41 | |
|
|
39 | 42 | tasks.add(proc.onExit()); |
|
|
40 | 43 | |
|
|
41 | 44 | redirect.stdin().map(from -> from.redirect(proc.getOutputStream())).ifPresent(tasks::add); |
|
|
42 | 45 | redirect.stdout().map(to -> to.redirect(proc.getInputStream())).ifPresent(tasks::add); |
|
|
43 | 46 | redirect.stderr().map(to -> to.redirect(proc.getErrorStream())).ifPresent(tasks::add); |
|
|
44 | 47 | |
|
|
45 | 48 | return CompletableFuture |
|
|
46 | 49 | .allOf(tasks.toArray(new CompletableFuture<?>[0])) |
|
|
47 | 50 | .thenApply(t -> proc.exitValue()); |
|
|
48 | 51 | } |
|
|
49 | 52 | } |
| @@ -1,31 +1,31 | |||
|
|
1 | 1 | package org.implab.gradle.common.tasks; |
|
|
2 | 2 | |
|
|
3 | 3 | import org.gradle.api.provider.ListProperty; |
|
|
4 | 4 | import org.gradle.api.provider.Property; |
|
|
5 | 5 | import org.gradle.api.tasks.Internal; |
|
|
6 | 6 | import org.implab.gradle.common.dsl.TaskCommandSpecMixin; |
|
|
7 | 7 | import org.implab.gradle.common.exec.CommandSpec; |
|
|
8 | 8 | import org.implab.gradle.common.exec.Shell; |
|
|
9 | 9 | import org.implab.gradle.common.exec.ShellExec; |
|
|
10 | 10 | |
|
|
11 | 11 | public abstract class ShellExecTask |
|
|
12 | 12 | extends AbstractShellExecTask |
|
|
13 | 13 | implements TaskCommandSpecMixin { |
|
|
14 | 14 | |
|
|
15 | 15 | @Internal |
|
|
16 | 16 | public abstract Property<Shell> getShell(); |
|
|
17 | 17 | |
|
|
18 | 18 | @Internal |
|
|
19 | 19 | @Override |
|
|
20 | 20 | public abstract ListProperty<String> getCommandLine(); |
|
|
21 | 21 | |
|
|
22 | 22 | |
|
|
23 | 23 | @Override |
|
|
24 | 24 | protected ShellExec execBuilder() { |
|
|
25 | 25 | return getShell().get() |
|
|
26 |
. |
|
|
|
26 | .create(CommandSpec.builder() | |
|
|
27 | 27 | .commandLine(getCommandLine().get()) |
|
|
28 | 28 | .build()); |
|
|
29 | 29 | } |
|
|
30 | 30 | |
|
|
31 | 31 | } |
| @@ -1,72 +1,87 | |||
|
|
1 | 1 | package org.implab.gradle.common.utils; |
|
|
2 | 2 | |
|
|
3 | import java.text.MessageFormat; | |
|
|
3 | 4 | import java.util.Iterator; |
|
|
4 | 5 | import java.util.Spliterators; |
|
|
5 | 6 | import java.util.Optional; |
|
|
7 | import java.util.Set; | |
|
|
8 | import java.util.function.Function; | |
|
|
6 | 9 | import java.util.function.Supplier; |
|
|
10 | import java.util.stream.Collectors; | |
|
|
7 | 11 | import java.util.stream.Stream; |
|
|
8 | 12 | import java.util.stream.StreamSupport; |
|
|
9 | 13 | |
|
|
10 | 14 | import org.gradle.api.provider.Provider; |
|
|
11 | 15 | |
|
|
12 | 16 | public final class Values { |
|
|
13 | 17 | |
|
|
14 | 18 | private Values() { |
|
|
15 | 19 | } |
|
|
16 | 20 | |
|
|
17 | 21 | /** |
|
|
18 | 22 | * Converts the supplied value to a string. |
|
|
19 | 23 | */ |
|
|
20 | 24 | public static String toString(Object value) { |
|
|
21 | 25 | if (value == null) { |
|
|
22 | 26 | return null; |
|
|
23 | 27 | } else if (value instanceof String string) { |
|
|
24 | 28 | return string; |
|
|
25 | 29 | } else if (value instanceof Provider<?> provider) { |
|
|
26 | 30 | return toString(provider.getOrNull()); |
|
|
27 | 31 | } else if (value instanceof Supplier<?> supplier) { |
|
|
28 | 32 | return toString(supplier.get()); |
|
|
29 | 33 | } else { |
|
|
30 | 34 | return value.toString(); |
|
|
31 | 35 | } |
|
|
32 | 36 | } |
|
|
33 | 37 | |
|
|
34 | 38 | public static <T> Stream<T> stream(Iterator<T> remaining) { |
|
|
35 | 39 | return StreamSupport.stream( |
|
|
36 | 40 | Spliterators.spliteratorUnknownSize(remaining, 0), |
|
|
37 | 41 | false); |
|
|
38 | 42 | } |
|
|
39 | 43 | |
|
|
40 | 44 | public static <T> Optional<T> take(Iterator<T> iterator) { |
|
|
41 | 45 | return iterator.hasNext() ? Optional.of(iterator.next()) : Optional.empty(); |
|
|
42 | 46 | } |
|
|
43 | 47 | |
|
|
44 | 48 | public static <T> Iterable<T> iterable(T[] values) { |
|
|
45 | 49 | return () -> new ArrayIterator<>(values); |
|
|
46 | 50 | } |
|
|
47 | 51 | |
|
|
48 | 52 | public static <T> Optional<T> optional(Provider<T> provider) { |
|
|
49 | 53 | return provider.isPresent() ? Optional.of(provider.get()) : Optional.empty(); |
|
|
50 | 54 | } |
|
|
51 | 55 | |
|
|
56 | public static <T> T required(Provider<T> provider, String providerName) { | |
|
|
57 | if (!provider.isPresent()) | |
|
|
58 | throw new IllegalStateException( | |
|
|
59 | MessageFormat.format("The value for the '{0}' provider must be specified", providerName)); | |
|
|
60 | return provider.get(); | |
|
|
61 | } | |
|
|
62 | ||
|
|
63 | public static <U,V> Set<V> mapSet(Set<U> values, Function<U,V> mapper) { | |
|
|
64 | return values.stream().map(mapper).collect(Collectors.toUnmodifiableSet()); | |
|
|
65 | } | |
|
|
66 | ||
|
|
52 | 67 | private static class ArrayIterator<T> implements Iterator<T> { |
|
|
53 | 68 | private final T[] data; |
|
|
54 | 69 | |
|
|
55 | 70 | private int pos = 0; |
|
|
56 | 71 | |
|
|
57 | 72 | ArrayIterator(T[] data) { |
|
|
58 | 73 | this.data = data; |
|
|
59 | 74 | } |
|
|
60 | 75 | |
|
|
61 | 76 | @Override |
|
|
62 | 77 | public boolean hasNext() { |
|
|
63 | 78 | return pos < data.length; |
|
|
64 | 79 | } |
|
|
65 | 80 | |
|
|
66 | 81 | @Override |
|
|
67 | 82 | public T next() { |
|
|
68 | 83 | return data[pos++]; |
|
|
69 | 84 | } |
|
|
70 | 85 | } |
|
|
71 | 86 | |
|
|
72 | 87 | } |
|
|
1 | NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now
