| @@ -1,7 +1,8 | |||||
| 1 | { |
|
1 | { | |
| 2 | "java.configuration.updateBuildConfiguration": "automatic", |
|
2 | "java.configuration.updateBuildConfiguration": "automatic", | |
| 3 | "java.compile.nullAnalysis.mode": "automatic", |
|
3 | "java.compile.nullAnalysis.mode": "automatic", | |
| 4 | "cSpell.words": [ |
|
4 | "cSpell.words": [ | |
| 5 | "implab" |
|
5 | "implab", | |
|
|
6 | "rawtypes" | |||
| 6 | ] |
|
7 | ] | |
| 7 | } No newline at end of file |
|
8 | } | |
| @@ -1,15 +1,15 | |||||
| 1 |
package org.implab.gradle.common. |
|
1 | package org.implab.gradle.common.core.gradle; | |
| 2 |
|
2 | |||
| 3 | import java.util.function.Consumer; |
|
3 | import java.util.function.Consumer; | |
| 4 |
|
4 | |||
| 5 | import org.gradle.api.plugins.ExtensionContainer; |
|
5 | import org.gradle.api.plugins.ExtensionContainer; | |
| 6 |
|
6 | |||
| 7 | public final class Extensions { |
|
7 | public final class Extensions { | |
| 8 | private Extensions() { |
|
8 | private Extensions() { | |
| 9 | } |
|
9 | } | |
| 10 |
|
10 | |||
| 11 | public static Consumer<Class<?>> registerClass(ExtensionContainer extensions) { |
|
11 | public static Consumer<Class<?>> registerClass(ExtensionContainer extensions) { | |
| 12 | var extra = extensions.getExtraProperties(); |
|
12 | var extra = extensions.getExtraProperties(); | |
| 13 | return clazz -> extra.set(clazz.getSimpleName(), clazz); |
|
13 | return clazz -> extra.set(clazz.getSimpleName(), clazz); | |
| 14 | } |
|
14 | } | |
| 15 | } |
|
15 | } | |
| @@ -1,69 +1,69 | |||||
| 1 |
package org.implab.gradle.common. |
|
1 | package org.implab.gradle.common.core.gradle; | |
| 2 |
|
2 | |||
| 3 | import java.util.HashMap; |
|
3 | import java.util.HashMap; | |
| 4 | import java.util.Map; |
|
4 | import java.util.Map; | |
| 5 | import java.util.function.Function; |
|
5 | import java.util.function.Function; | |
| 6 | import org.gradle.api.Action; |
|
6 | import org.gradle.api.Action; | |
| 7 | import org.gradle.api.provider.ListProperty; |
|
7 | import org.gradle.api.provider.ListProperty; | |
| 8 | import org.gradle.api.provider.MapProperty; |
|
8 | import org.gradle.api.provider.MapProperty; | |
| 9 | import org.gradle.api.provider.Provider; |
|
9 | import org.gradle.api.provider.Provider; | |
| 10 |
|
10 | |||
| 11 | public final class Properties { |
|
11 | public final class Properties { | |
| 12 | private Properties() { |
|
12 | private Properties() { | |
| 13 | } |
|
13 | } | |
| 14 |
|
14 | |||
| 15 | public static <K> void mergeMap(MapProperty<K, Object> property, Map<K, Object> map) { |
|
15 | public static <K> void mergeMap(MapProperty<K, Object> property, Map<K, Object> map) { | |
| 16 | mergeMap(property, map, Function.identity()); |
|
16 | mergeMap(property, map, Function.identity()); | |
| 17 | } |
|
17 | } | |
| 18 |
|
18 | |||
| 19 | public static <K, V> void putMapEntry(MapProperty<K, V> property, K key, Object value, |
|
19 | public static <K, V> void putMapEntry(MapProperty<K, V> property, K key, Object value, | |
| 20 | Function<Object, ? extends V> mapper) { |
|
20 | Function<Object, ? extends V> mapper) { | |
| 21 | if (value instanceof Provider<?>) |
|
21 | if (value instanceof Provider<?>) | |
| 22 | property.put(key, ((Provider<?>) value).map(mapper::apply)); |
|
22 | property.put(key, ((Provider<?>) value).map(mapper::apply)); | |
| 23 | else |
|
23 | else | |
| 24 | property.put(key, mapper.apply(value)); |
|
24 | property.put(key, mapper.apply(value)); | |
| 25 | } |
|
25 | } | |
| 26 |
|
26 | |||
| 27 | public static <K> void putMapEntry(MapProperty<K, Object> property, K key, Object value) { |
|
27 | public static <K> void putMapEntry(MapProperty<K, Object> property, K key, Object value) { | |
| 28 | putMapEntry(property, key, value, Function.identity()); |
|
28 | putMapEntry(property, key, value, Function.identity()); | |
| 29 | } |
|
29 | } | |
| 30 |
|
30 | |||
| 31 | public static <K, V> void putMapEntry(MapProperty<K, V> property, K key, Object value, Class<V> valueType) { |
|
31 | public static <K, V> void putMapEntry(MapProperty<K, V> property, K key, Object value, Class<V> valueType) { | |
| 32 | putMapEntry(property, key, value, valueType::cast); |
|
32 | putMapEntry(property, key, value, valueType::cast); | |
| 33 | } |
|
33 | } | |
| 34 |
|
34 | |||
| 35 | public static <K, V> void mergeMap(MapProperty<K, V> property, Map<K, ?> map, |
|
35 | public static <K, V> void mergeMap(MapProperty<K, V> property, Map<K, ?> map, | |
| 36 | Function<Object, ? extends V> mapper) { |
|
36 | Function<Object, ? extends V> mapper) { | |
| 37 | map.forEach((k, v) -> { |
|
37 | map.forEach((k, v) -> { | |
| 38 | if (v instanceof Provider<?>) |
|
38 | if (v instanceof Provider<?>) | |
| 39 | property.put(k, ((Provider<?>) v).map(mapper::apply)); |
|
39 | property.put(k, ((Provider<?>) v).map(mapper::apply)); | |
| 40 | else |
|
40 | else | |
| 41 | property.put(k, mapper.apply(v)); |
|
41 | property.put(k, mapper.apply(v)); | |
| 42 | }); |
|
42 | }); | |
| 43 | } |
|
43 | } | |
| 44 |
|
44 | |||
| 45 | public static void mergeList(ListProperty<Object> property, Iterable<Object> values) { |
|
45 | public static void mergeList(ListProperty<Object> property, Iterable<Object> values) { | |
| 46 | mergeList(property, values, Function.identity()); |
|
46 | mergeList(property, values, Function.identity()); | |
| 47 | } |
|
47 | } | |
| 48 |
|
48 | |||
| 49 | public static <V> void mergeList(ListProperty<V> property, Iterable<Object> values, |
|
49 | public static <V> void mergeList(ListProperty<V> property, Iterable<Object> values, | |
| 50 | Function<Object, ? extends V> mapper) { |
|
50 | Function<Object, ? extends V> mapper) { | |
| 51 | values.forEach(v -> { |
|
51 | values.forEach(v -> { | |
| 52 | if (v instanceof Provider<?>) |
|
52 | if (v instanceof Provider<?>) | |
| 53 | property.add(((Provider<?>) v).map(mapper::apply)); |
|
53 | property.add(((Provider<?>) v).map(mapper::apply)); | |
| 54 | else |
|
54 | else | |
| 55 | property.add(mapper.apply(v)); |
|
55 | property.add(mapper.apply(v)); | |
| 56 | }); |
|
56 | }); | |
| 57 | } |
|
57 | } | |
| 58 |
|
58 | |||
| 59 | public static <K> void configureMap(MapProperty<K, Object> prop, Action<? super Map<K, Object>> configure) { |
|
59 | public static <K> void configureMap(MapProperty<K, Object> prop, Action<? super Map<K, Object>> configure) { | |
| 60 | configureMap(prop, configure, Function.identity()); |
|
60 | configureMap(prop, configure, Function.identity()); | |
| 61 | } |
|
61 | } | |
| 62 |
|
62 | |||
| 63 | public static <K, V> void configureMap(MapProperty<K, V> prop, Action<? super Map<K, Object>> configure, |
|
63 | public static <K, V> void configureMap(MapProperty<K, V> prop, Action<? super Map<K, Object>> configure, | |
| 64 | Function<Object, V> mapper) { |
|
64 | Function<Object, V> mapper) { | |
| 65 | var map = new HashMap<K, Object>(); |
|
65 | var map = new HashMap<K, Object>(); | |
| 66 | configure.execute(map); |
|
66 | configure.execute(map); | |
| 67 | mergeMap(prop, map, mapper); |
|
67 | mergeMap(prop, map, mapper); | |
| 68 | } |
|
68 | } | |
| 69 | } |
|
69 | } | |
| @@ -1,29 +1,29 | |||||
| 1 |
package org.implab.gradle.common. |
|
1 | package org.implab.gradle.common.core.gradle; | |
| 2 |
|
2 | |||
| 3 | import org.gradle.api.Project; |
|
3 | import org.gradle.api.Project; | |
| 4 | import org.gradle.api.Task; |
|
4 | import org.gradle.api.Task; | |
| 5 | import org.gradle.api.logging.Logger; |
|
5 | import org.gradle.api.logging.Logger; | |
| 6 | import org.gradle.api.specs.Spec; |
|
6 | import org.gradle.api.specs.Spec; | |
| 7 | import org.gradle.api.tasks.Internal; |
|
7 | import org.gradle.api.tasks.Internal; | |
| 8 |
|
8 | |||
| 9 | /** Task methods available by default, this interface is used by mixins to |
|
9 | /** Task methods available by default, this interface is used by mixins to | |
| 10 | * interact with their task. |
|
10 | * interact with their task. | |
| 11 | */ |
|
11 | */ | |
| 12 | public interface TaskExtra { |
|
12 | public interface TaskExtra { | |
| 13 | @Internal |
|
13 | @Internal | |
| 14 | Project getProject(); |
|
14 | Project getProject(); | |
| 15 |
|
15 | |||
| 16 | void onlyIf(Spec<? super Task> spec); |
|
16 | void onlyIf(Spec<? super Task> spec); | |
| 17 |
|
17 | |||
| 18 | @Internal |
|
18 | @Internal | |
| 19 | Logger getLogger(); |
|
19 | Logger getLogger(); | |
| 20 |
|
20 | |||
| 21 | default void onlyIfReason(String skipReason, Spec<? super Task> spec) { |
|
21 | default void onlyIfReason(String skipReason, Spec<? super Task> spec) { | |
| 22 | onlyIf(self -> { |
|
22 | onlyIf(self -> { | |
| 23 | var satisfied = spec.isSatisfiedBy(self); |
|
23 | var satisfied = spec.isSatisfiedBy(self); | |
| 24 | if (!satisfied) |
|
24 | if (!satisfied) | |
| 25 | getLogger().info("SKIP: {}", skipReason); |
|
25 | getLogger().info("SKIP: {}", skipReason); | |
| 26 | return satisfied; |
|
26 | return satisfied; | |
| 27 | }); |
|
27 | }); | |
| 28 | } |
|
28 | } | |
| 29 | } |
|
29 | } | |
| @@ -1,31 +1,31 | |||||
| 1 |
package org.implab.gradle.common. |
|
1 | package org.implab.gradle.common.core.lang; | |
| 2 |
|
2 | |||
| 3 | import groovy.lang.Closure; |
|
3 | import groovy.lang.Closure; | |
| 4 |
|
4 | |||
| 5 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
5 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
| 6 | import org.gradle.api.Action; |
|
6 | import org.gradle.api.Action; | |
| 7 |
|
7 | |||
| 8 | @NonNullByDefault |
|
8 | @NonNullByDefault | |
| 9 | public final class Closures { |
|
9 | public final class Closures { | |
| 10 | private Closures() { |
|
10 | private Closures() { | |
| 11 | } |
|
11 | } | |
| 12 |
|
12 | |||
| 13 | /** |
|
13 | /** | |
| 14 | * Wraps {@link Action} around the specified closure. The parameter |
|
14 | * Wraps {@link Action} around the specified closure. The parameter | |
| 15 | * of the action will be used as delegate in the specified closure. |
|
15 | * of the action will be used as delegate in the specified closure. | |
| 16 | * |
|
16 | * | |
| 17 | * @param <T> The type of the action parameter |
|
17 | * @param <T> The type of the action parameter | |
| 18 | * @param closure The closure |
|
18 | * @param closure The closure | |
| 19 | * @return |
|
19 | * @return | |
| 20 | */ |
|
20 | */ | |
| 21 | public static <T> Action<T> action(Closure<?> closure) { |
|
21 | public static <T> Action<T> action(Closure<?> closure) { | |
| 22 | return arg -> apply(closure, arg); |
|
22 | return arg -> apply(closure, arg); | |
| 23 | } |
|
23 | } | |
| 24 |
|
24 | |||
| 25 | public static void apply(Closure<?> action, Object target) { |
|
25 | public static void apply(Closure<?> action, Object target) { | |
| 26 | var c = (Closure<?>)action.clone(); |
|
26 | var c = (Closure<?>)action.clone(); | |
| 27 | c.setResolveStrategy(Closure.DELEGATE_FIRST); |
|
27 | c.setResolveStrategy(Closure.DELEGATE_FIRST); | |
| 28 | c.setDelegate(target); |
|
28 | c.setDelegate(target); | |
| 29 | c.call(target); |
|
29 | c.call(target); | |
| 30 | } |
|
30 | } | |
| 31 | } |
|
31 | } | |
| @@ -1,51 +1,50 | |||||
| 1 |
package org.implab.gradle.common. |
|
1 | package org.implab.gradle.common.core.lang; | |
| 2 |
|
2 | |||
| 3 | import java.util.function.Consumer; |
|
3 | import java.util.function.Consumer; | |
| 4 | import java.util.function.Function; |
|
4 | import java.util.function.Function; | |
| 5 |
|
5 | |||
| 6 | public class Exceptions { |
|
6 | public class Exceptions { | |
| 7 | /** |
|
7 | /** | |
| 8 | * Helper function which declares that this block can throw the specified |
|
8 | * Helper function which declares that this block can throw the specified | |
| 9 | * exception. |
|
9 | * exception. | |
| 10 | * |
|
10 | * | |
| 11 | * @param <E> |
|
11 | * @param <E> | |
| 12 | * @param clazz |
|
12 | * @param clazz | |
| 13 | * @throws E |
|
13 | * @throws E | |
| 14 | */ |
|
14 | */ | |
| 15 | @SuppressWarnings("unused") |
|
|||
| 16 | public static <E extends Throwable> void mayThrow(Class<E> clazz) throws E { |
|
15 | public static <E extends Throwable> void mayThrow(Class<E> clazz) throws E { | |
| 17 | } |
|
16 | } | |
| 18 |
|
17 | |||
| 19 | @SuppressWarnings("unchecked") |
|
18 | @SuppressWarnings("unchecked") | |
| 20 | public static <E extends Throwable> E sneakyThrow(Throwable t) throws E { |
|
19 | public static <E extends Throwable> E sneakyThrow(Throwable t) throws E { | |
| 21 | throw (E) t; |
|
20 | throw (E) t; | |
| 22 | } |
|
21 | } | |
| 23 |
|
22 | |||
| 24 | public static <T, U> Function<T, U> unchecked(ThrowingFunction<T, U> fn) { |
|
23 | public static <T, U> Function<T, U> unchecked(ThrowingFunction<T, U> fn) { | |
| 25 | return val -> { |
|
24 | return val -> { | |
| 26 | try { |
|
25 | try { | |
| 27 | return fn.apply(val); |
|
26 | return fn.apply(val); | |
| 28 | } catch (Exception e) { |
|
27 | } catch (Exception e) { | |
| 29 | throw sneakyThrow(e); |
|
28 | throw sneakyThrow(e); | |
| 30 | } |
|
29 | } | |
| 31 | }; |
|
30 | }; | |
| 32 | } |
|
31 | } | |
| 33 |
|
32 | |||
| 34 | public static <T> Consumer<T> unchecked(ThrowingConsumer<T> c) { |
|
33 | public static <T> Consumer<T> unchecked(ThrowingConsumer<T> c) { | |
| 35 | return val -> { |
|
34 | return val -> { | |
| 36 | try { |
|
35 | try { | |
| 37 | c.accept(val); |
|
36 | c.accept(val); | |
| 38 | } catch (Exception e) { |
|
37 | } catch (Exception e) { | |
| 39 | throw sneakyThrow(e); |
|
38 | throw sneakyThrow(e); | |
| 40 | } |
|
39 | } | |
| 41 | }; |
|
40 | }; | |
| 42 | } |
|
41 | } | |
| 43 |
|
42 | |||
| 44 | public interface ThrowingConsumer<T> { |
|
43 | public interface ThrowingConsumer<T> { | |
| 45 | void accept(T value) throws Exception; |
|
44 | void accept(T value) throws Exception; | |
| 46 | } |
|
45 | } | |
| 47 |
|
46 | |||
| 48 | public interface ThrowingFunction<T, U> { |
|
47 | public interface ThrowingFunction<T, U> { | |
| 49 | U apply(T value) throws Exception; |
|
48 | U apply(T value) throws Exception; | |
| 50 | } |
|
49 | } | |
| 51 | } |
|
50 | } | |
| @@ -1,29 +1,34 | |||||
| 1 |
package org.implab.gradle.common. |
|
1 | package org.implab.gradle.common.core.lang; | |
| 2 |
|
2 | |||
| 3 | import java.util.concurrent.atomic.AtomicReference; |
|
3 | import java.util.Objects; | |
| 4 | import java.util.function.Supplier; |
|
4 | import java.util.function.Supplier; | |
| 5 |
|
5 | |||
| 6 | public class LazyValue<T> implements Supplier<T> { |
|
6 | public class LazyValue<T> implements Supplier<T> { | |
| 7 | private final AtomicReference<T> reference = new AtomicReference<>(); |
|
7 | private volatile T value; | |
| 8 |
|
8 | |||
| 9 | private final Supplier<T> innerSupplier; |
|
9 | private final Supplier<T> innerSupplier; | |
| 10 |
|
10 | |||
| 11 | public LazyValue(Supplier<T> supplier) { |
|
11 | public LazyValue(Supplier<T> supplier) { | |
| 12 | this.innerSupplier = supplier; |
|
12 | this.innerSupplier = supplier; | |
| 13 | } |
|
13 | } | |
| 14 |
|
14 | |||
| 15 | @Override |
|
15 | @Override | |
| 16 | public T get() { |
|
16 | public T get() { | |
| 17 |
var |
|
17 | var v = value; | |
| 18 |
if ( |
|
18 | if (v != null) { | |
| 19 |
return |
|
19 | return v; | |
| 20 | return updateValue(); |
|
20 | } | |
| 21 | } |
|
|||
| 22 |
|
21 | |||
| 23 | private T updateValue() { |
|
22 | synchronized (this) { | |
| 24 | var v = innerSupplier.get(); |
|
23 | v = value; | |
| 25 | var t = reference.compareAndExchange(null, v); |
|
24 | if (v == null) { | |
| 26 | return t != null ? t : v; |
|
25 | v = Objects.requireNonNull( | |
|
|
26 | innerSupplier.get(), | |||
|
|
27 | "LazyValue supplier returned null"); | |||
|
|
28 | value = v; | |||
|
|
29 | } | |||
|
|
30 | return v; | |||
|
|
31 | } | |||
| 27 | } |
|
32 | } | |
| 28 |
|
33 | |||
| 29 | } |
|
34 | } | |
| @@ -1,316 +1,316 | |||||
| 1 |
package org.implab.gradle.common. |
|
1 | package org.implab.gradle.common.core.lang; | |
| 2 |
|
2 | |||
| 3 | import com.fasterxml.jackson.annotation.JsonCreator; |
|
3 | import com.fasterxml.jackson.annotation.JsonCreator; | |
| 4 | import com.fasterxml.jackson.annotation.JsonValue; |
|
4 | import com.fasterxml.jackson.annotation.JsonValue; | |
| 5 |
|
5 | |||
| 6 | import java.util.Objects; |
|
6 | import java.util.Objects; | |
| 7 | import java.util.Optional; |
|
7 | import java.util.Optional; | |
| 8 | import java.util.regex.Matcher; |
|
8 | import java.util.regex.Matcher; | |
| 9 | import java.util.regex.Pattern; |
|
9 | import java.util.regex.Pattern; | |
| 10 |
|
10 | |||
| 11 | /** |
|
11 | /** | |
| 12 | * Immutable Semantic Version (SemVer 2.0.0) of the form: |
|
12 | * Immutable Semantic Version (SemVer 2.0.0) of the form: | |
| 13 | * |
|
13 | * | |
| 14 | * <pre> |
|
14 | * <pre> | |
| 15 | * MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD] |
|
15 | * MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD] | |
| 16 | * </pre> |
|
16 | * </pre> | |
| 17 | * |
|
17 | * | |
| 18 | * Ordering (as defined by {@link #compareTo(SemVersion)}) follows |
|
18 | * Ordering (as defined by {@link #compareTo(SemVersion)}) follows | |
| 19 | * the SemVer 2.0.0 precedence rules: |
|
19 | * the SemVer 2.0.0 precedence rules: | |
| 20 | * <ul> |
|
20 | * <ul> | |
| 21 | * <li>Compare {@code major}, then {@code minor}, then {@code patch}.</li> |
|
21 | * <li>Compare {@code major}, then {@code minor}, then {@code patch}.</li> | |
| 22 | * <li>Pre-release versions have lower precedence than the corresponding |
|
22 | * <li>Pre-release versions have lower precedence than the corresponding | |
| 23 | * normal version.</li> |
|
23 | * normal version.</li> | |
| 24 | * <li>Build metadata does not affect precedence.</li> |
|
24 | * <li>Build metadata does not affect precedence.</li> | |
| 25 | * </ul> |
|
25 | * </ul> | |
| 26 | * |
|
26 | * | |
| 27 | * Public API does not use {@code null} for semantic values: |
|
27 | * Public API does not use {@code null} for semantic values: | |
| 28 | * {@link Optional} is used for pre-release and build metadata. |
|
28 | * {@link Optional} is used for pre-release and build metadata. | |
| 29 | */ |
|
29 | */ | |
| 30 | public record SemVersion( |
|
30 | public record SemVersion( | |
| 31 | int major, |
|
31 | int major, | |
| 32 | int minor, |
|
32 | int minor, | |
| 33 | int patch, |
|
33 | int patch, | |
| 34 | Optional<String> preRelease, |
|
34 | Optional<String> preRelease, | |
| 35 | Optional<String> buildMetadata) implements Comparable<SemVersion> { |
|
35 | Optional<String> buildMetadata) implements Comparable<SemVersion> { | |
| 36 |
|
36 | |||
| 37 | // Pattern close to the official SemVer 2.0.0 recommendation. |
|
37 | // Pattern close to the official SemVer 2.0.0 recommendation. | |
| 38 | // Groups: |
|
38 | // Groups: | |
| 39 | // 1 - major |
|
39 | // 1 - major | |
| 40 | // 2 - minor |
|
40 | // 2 - minor | |
| 41 | // 3 - patch |
|
41 | // 3 - patch | |
| 42 | // 4 - pre-release (without '-') |
|
42 | // 4 - pre-release (without '-') | |
| 43 | // 5 - build metadata (without '+') |
|
43 | // 5 - build metadata (without '+') | |
| 44 | private static final Pattern SEMVER_PATTERN = Pattern.compile( |
|
44 | private static final Pattern SEMVER_PATTERN = Pattern.compile( | |
| 45 | "^(0|[1-9]\\d*)" + // major |
|
45 | "^(0|[1-9]\\d*)" + // major | |
| 46 | "\\.(0|[1-9]\\d*)" + // minor |
|
46 | "\\.(0|[1-9]\\d*)" + // minor | |
| 47 | "\\.(0|[1-9]\\d*)" + // patch |
|
47 | "\\.(0|[1-9]\\d*)" + // patch | |
| 48 | "(?:-((?:0|[1-9]\\d*|[0-9A-Za-z-][0-9A-Za-z-]*)" + |
|
48 | "(?:-((?:0|[1-9]\\d*|[0-9A-Za-z-][0-9A-Za-z-]*)" + | |
| 49 | "(?:\\.(?:0|[1-9]\\d*|[0-9A-Za-z-][0-9A-Za-z-]*))*" + |
|
49 | "(?:\\.(?:0|[1-9]\\d*|[0-9A-Za-z-][0-9A-Za-z-]*))*" + | |
| 50 | "))?" + // pre-release |
|
50 | "))?" + // pre-release | |
| 51 | "(?:\\+([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?$" // build metadata |
|
51 | "(?:\\+([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?$" // build metadata | |
| 52 | ); |
|
52 | ); | |
| 53 |
|
53 | |||
| 54 | /** |
|
54 | /** | |
| 55 | * Compact constructor with basic invariants. |
|
55 | * Compact constructor with basic invariants. | |
| 56 | */ |
|
56 | */ | |
| 57 | public SemVersion { |
|
57 | public SemVersion { | |
| 58 | if (major < 0 || minor < 0 || patch < 0) { |
|
58 | if (major < 0 || minor < 0 || patch < 0) { | |
| 59 | throw new IllegalArgumentException("Version numbers must be >= 0"); |
|
59 | throw new IllegalArgumentException("Version numbers must be >= 0"); | |
| 60 | } |
|
60 | } | |
| 61 | // Be tolerant internally, but never expose null Optionals. |
|
61 | // Be tolerant internally, but never expose null Optionals. | |
| 62 | preRelease = (preRelease != null) ? preRelease : Optional.empty(); |
|
62 | preRelease = (preRelease != null) ? preRelease : Optional.empty(); | |
| 63 | buildMetadata = (buildMetadata != null) ? buildMetadata : Optional.empty(); |
|
63 | buildMetadata = (buildMetadata != null) ? buildMetadata : Optional.empty(); | |
| 64 | } |
|
64 | } | |
| 65 |
|
65 | |||
| 66 | /** |
|
66 | /** | |
| 67 | * Creates a version without pre-release and build metadata. |
|
67 | * Creates a version without pre-release and build metadata. | |
| 68 | * |
|
68 | * | |
| 69 | * @param major non-negative MAJOR number |
|
69 | * @param major non-negative MAJOR number | |
| 70 | * @param minor non-negative MINOR number |
|
70 | * @param minor non-negative MINOR number | |
| 71 | * @param patch non-negative PATCH number |
|
71 | * @param patch non-negative PATCH number | |
| 72 | */ |
|
72 | */ | |
| 73 | public static SemVersion of(int major, int minor, int patch) { |
|
73 | public static SemVersion of(int major, int minor, int patch) { | |
| 74 | return new SemVersion(major, minor, patch, Optional.empty(), Optional.empty()); |
|
74 | return new SemVersion(major, minor, patch, Optional.empty(), Optional.empty()); | |
| 75 | } |
|
75 | } | |
| 76 |
|
76 | |||
| 77 | /** |
|
77 | /** | |
| 78 | * Creates a version from components. |
|
78 | * Creates a version from components. | |
| 79 | * |
|
79 | * | |
| 80 | * @param major non-negative MAJOR number |
|
80 | * @param major non-negative MAJOR number | |
| 81 | * @param minor non-negative MINOR number |
|
81 | * @param minor non-negative MINOR number | |
| 82 | * @param patch non-negative PATCH number |
|
82 | * @param patch non-negative PATCH number | |
| 83 | * @param preRelease optional pre-release part (without '-') |
|
83 | * @param preRelease optional pre-release part (without '-') | |
| 84 | * @param buildMetadata optional build metadata (without '+') |
|
84 | * @param buildMetadata optional build metadata (without '+') | |
| 85 | */ |
|
85 | */ | |
| 86 | public static SemVersion of( |
|
86 | public static SemVersion of( | |
| 87 | int major, |
|
87 | int major, | |
| 88 | int minor, |
|
88 | int minor, | |
| 89 | int patch, |
|
89 | int patch, | |
| 90 | Optional<String> preRelease, |
|
90 | Optional<String> preRelease, | |
| 91 | Optional<String> buildMetadata) { |
|
91 | Optional<String> buildMetadata) { | |
| 92 | return new SemVersion( |
|
92 | return new SemVersion( | |
| 93 | major, |
|
93 | major, | |
| 94 | minor, |
|
94 | minor, | |
| 95 | patch, |
|
95 | patch, | |
| 96 | Objects.requireNonNull(preRelease, "preRelease"), |
|
96 | Objects.requireNonNull(preRelease, "preRelease"), | |
| 97 | Objects.requireNonNull(buildMetadata, "buildMetadata")); |
|
97 | Objects.requireNonNull(buildMetadata, "buildMetadata")); | |
| 98 | } |
|
98 | } | |
| 99 |
|
99 | |||
| 100 | /** |
|
100 | /** | |
| 101 | * Parses a SemVer string of the form |
|
101 | * Parses a SemVer string of the form | |
| 102 | * {@code MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]}. |
|
102 | * {@code MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]}. | |
| 103 | * |
|
103 | * | |
| 104 | * @param value the string to parse (must not be {@code null}) |
|
104 | * @param value the string to parse (must not be {@code null}) | |
| 105 | * @return a new {@code SemVersion} |
|
105 | * @return a new {@code SemVersion} | |
| 106 | * @throws IllegalArgumentException if the string is not a valid SemVer |
|
106 | * @throws IllegalArgumentException if the string is not a valid SemVer | |
| 107 | */ |
|
107 | */ | |
| 108 | public static SemVersion parse(String value) { |
|
108 | public static SemVersion parse(String value) { | |
| 109 | Objects.requireNonNull(value, "value"); |
|
109 | Objects.requireNonNull(value, "value"); | |
| 110 |
|
110 | |||
| 111 | Matcher m = SEMVER_PATTERN.matcher(value); |
|
111 | Matcher m = SEMVER_PATTERN.matcher(value); | |
| 112 | if (!m.matches()) { |
|
112 | if (!m.matches()) { | |
| 113 | throw new IllegalArgumentException("Invalid SemVer: " + value); |
|
113 | throw new IllegalArgumentException("Invalid SemVer: " + value); | |
| 114 | } |
|
114 | } | |
| 115 |
|
115 | |||
| 116 | int major = Integer.parseInt(m.group(1)); |
|
116 | int major = Integer.parseInt(m.group(1)); | |
| 117 | int minor = Integer.parseInt(m.group(2)); |
|
117 | int minor = Integer.parseInt(m.group(2)); | |
| 118 | int patch = Integer.parseInt(m.group(3)); |
|
118 | int patch = Integer.parseInt(m.group(3)); | |
| 119 |
|
119 | |||
| 120 | String pre = m.group(4); |
|
120 | String pre = m.group(4); | |
| 121 | String meta = m.group(5); |
|
121 | String meta = m.group(5); | |
| 122 |
|
122 | |||
| 123 | Optional<String> preOpt = Optional.ofNullable(pre); |
|
123 | Optional<String> preOpt = Optional.ofNullable(pre); | |
| 124 | Optional<String> metaOpt = Optional.ofNullable(meta); |
|
124 | Optional<String> metaOpt = Optional.ofNullable(meta); | |
| 125 |
|
125 | |||
| 126 | // Extra validation for numeric pre-release identifiers (no leading zeros) |
|
126 | // Extra validation for numeric pre-release identifiers (no leading zeros) | |
| 127 | preOpt.ifPresent(SemVersion::validatePreReleaseIdentifiers); |
|
127 | preOpt.ifPresent(SemVersion::validatePreReleaseIdentifiers); | |
| 128 |
|
128 | |||
| 129 | return new SemVersion(major, minor, patch, preOpt, metaOpt); |
|
129 | return new SemVersion(major, minor, patch, preOpt, metaOpt); | |
| 130 | } |
|
130 | } | |
| 131 |
|
131 | |||
| 132 | private static void validatePreReleaseIdentifiers(String preRelease) { |
|
132 | private static void validatePreReleaseIdentifiers(String preRelease) { | |
| 133 | String[] parts = preRelease.split("\\."); |
|
133 | String[] parts = preRelease.split("\\."); | |
| 134 | for (String p : parts) { |
|
134 | for (String p : parts) { | |
| 135 | if (isNumericIdentifier(p)) { |
|
135 | if (isNumericIdentifier(p)) { | |
| 136 | // numeric identifiers must not have leading zeros (except "0") |
|
136 | // numeric identifiers must not have leading zeros (except "0") | |
| 137 | if (p.length() > 1 && p.charAt(0) == '0') { |
|
137 | if (p.length() > 1 && p.charAt(0) == '0') { | |
| 138 | throw new IllegalArgumentException( |
|
138 | throw new IllegalArgumentException( | |
| 139 | "Numeric pre-release identifier must not contain leading zeros: " + p); |
|
139 | "Numeric pre-release identifier must not contain leading zeros: " + p); | |
| 140 | } |
|
140 | } | |
| 141 | } |
|
141 | } | |
| 142 | } |
|
142 | } | |
| 143 | } |
|
143 | } | |
| 144 |
|
144 | |||
| 145 | private static boolean isNumericIdentifier(String s) { |
|
145 | private static boolean isNumericIdentifier(String s) { | |
| 146 | int len = s.length(); |
|
146 | int len = s.length(); | |
| 147 | if (len == 0) { |
|
147 | if (len == 0) { | |
| 148 | return false; |
|
148 | return false; | |
| 149 | } |
|
149 | } | |
| 150 | for (int i = 0; i < len; i++) { |
|
150 | for (int i = 0; i < len; i++) { | |
| 151 | char c = s.charAt(i); |
|
151 | char c = s.charAt(i); | |
| 152 | if (c < '0' || c > '9') { |
|
152 | if (c < '0' || c > '9') { | |
| 153 | return false; |
|
153 | return false; | |
| 154 | } |
|
154 | } | |
| 155 | } |
|
155 | } | |
| 156 | return true; |
|
156 | return true; | |
| 157 | } |
|
157 | } | |
| 158 |
|
158 | |||
| 159 | /** |
|
159 | /** | |
| 160 | * Returns {@code true} if this version has a pre-release part. |
|
160 | * Returns {@code true} if this version has a pre-release part. | |
| 161 | * |
|
161 | * | |
| 162 | * @return {@code true} if {@link #preRelease()} is present |
|
162 | * @return {@code true} if {@link #preRelease()} is present | |
| 163 | */ |
|
163 | */ | |
| 164 | public boolean isPreRelease() { |
|
164 | public boolean isPreRelease() { | |
| 165 | return preRelease().isPresent(); |
|
165 | return preRelease().isPresent(); | |
| 166 | } |
|
166 | } | |
| 167 |
|
167 | |||
| 168 | /** |
|
168 | /** | |
| 169 | * Returns {@code true} if this version is considered "stable". |
|
169 | * Returns {@code true} if this version is considered "stable". | |
| 170 | * <p> |
|
170 | * <p> | |
| 171 | * By convention: |
|
171 | * By convention: | |
| 172 | * <ul> |
|
172 | * <ul> | |
| 173 | * <li>MAJOR must be greater than 0</li> |
|
173 | * <li>MAJOR must be greater than 0</li> | |
| 174 | * <li>No pre-release part is present</li> |
|
174 | * <li>No pre-release part is present</li> | |
| 175 | * </ul> |
|
175 | * </ul> | |
| 176 | * |
|
176 | * | |
| 177 | * @return {@code true} if this version is a stable release |
|
177 | * @return {@code true} if this version is a stable release | |
| 178 | */ |
|
178 | */ | |
| 179 | public boolean isStable() { |
|
179 | public boolean isStable() { | |
| 180 | return !isPreRelease() && major() > 0; |
|
180 | return !isPreRelease() && major() > 0; | |
| 181 | } |
|
181 | } | |
| 182 |
|
182 | |||
| 183 | /** |
|
183 | /** | |
| 184 | * Returns {@code true} if this version has strictly lower precedence |
|
184 | * Returns {@code true} if this version has strictly lower precedence | |
| 185 | * than the given {@code other} version according to |
|
185 | * than the given {@code other} version according to | |
| 186 | * {@link #compareTo(SemVersion)}. |
|
186 | * {@link #compareTo(SemVersion)}. | |
| 187 | * |
|
187 | * | |
| 188 | * @param other the version to compare to |
|
188 | * @param other the version to compare to | |
| 189 | * @return {@code true} if {@code this.compareTo(other) < 0} |
|
189 | * @return {@code true} if {@code this.compareTo(other) < 0} | |
| 190 | */ |
|
190 | */ | |
| 191 | public boolean isBefore(SemVersion other) { |
|
191 | public boolean isBefore(SemVersion other) { | |
| 192 | return compareTo(other) < 0; |
|
192 | return compareTo(other) < 0; | |
| 193 | } |
|
193 | } | |
| 194 |
|
194 | |||
| 195 | /** |
|
195 | /** | |
| 196 | * Returns {@code true} if this version has strictly higher precedence |
|
196 | * Returns {@code true} if this version has strictly higher precedence | |
| 197 | * than the given {@code other} version according to |
|
197 | * than the given {@code other} version according to | |
| 198 | * {@link #compareTo(SemVersion)}. |
|
198 | * {@link #compareTo(SemVersion)}. | |
| 199 | * |
|
199 | * | |
| 200 | * @param other the version to compare to |
|
200 | * @param other the version to compare to | |
| 201 | * @return {@code true} if {@code this.compareTo(other) > 0} |
|
201 | * @return {@code true} if {@code this.compareTo(other) > 0} | |
| 202 | */ |
|
202 | */ | |
| 203 | public boolean isAfter(SemVersion other) { |
|
203 | public boolean isAfter(SemVersion other) { | |
| 204 | return compareTo(other) > 0; |
|
204 | return compareTo(other) > 0; | |
| 205 | } |
|
205 | } | |
| 206 |
|
206 | |||
| 207 | /** |
|
207 | /** | |
| 208 | * Canonical SemVer string representation. |
|
208 | * Canonical SemVer string representation. | |
| 209 | * <p> |
|
209 | * <p> | |
| 210 | * This method is also used by Jackson during serialization. |
|
210 | * This method is also used by Jackson during serialization. | |
| 211 | * |
|
211 | * | |
| 212 | * @return canonical SemVer string, e.g. {@code "1.2.3-alpha+build.1"} |
|
212 | * @return canonical SemVer string, e.g. {@code "1.2.3-alpha+build.1"} | |
| 213 | */ |
|
213 | */ | |
| 214 | @JsonValue |
|
214 | @JsonValue | |
| 215 | public String asString() { |
|
215 | public String asString() { | |
| 216 | StringBuilder sb = new StringBuilder() |
|
216 | StringBuilder sb = new StringBuilder() | |
| 217 | .append(major).append('.') |
|
217 | .append(major).append('.') | |
| 218 | .append(minor).append('.') |
|
218 | .append(minor).append('.') | |
| 219 | .append(patch); |
|
219 | .append(patch); | |
| 220 |
|
220 | |||
| 221 | preRelease.ifPresent(pr -> sb.append('-').append(pr)); |
|
221 | preRelease.ifPresent(pr -> sb.append('-').append(pr)); | |
| 222 | buildMetadata.ifPresent(md -> sb.append('+').append(md)); |
|
222 | buildMetadata.ifPresent(md -> sb.append('+').append(md)); | |
| 223 |
|
223 | |||
| 224 | return sb.toString(); |
|
224 | return sb.toString(); | |
| 225 | } |
|
225 | } | |
| 226 |
|
226 | |||
| 227 | /** |
|
227 | /** | |
| 228 | * Creates a {@code SemVersion} from a canonical SemVer string. |
|
228 | * Creates a {@code SemVersion} from a canonical SemVer string. | |
| 229 | * <p> |
|
229 | * <p> | |
| 230 | * Jackson will use this factory method when deserializing from a JSON string. |
|
230 | * Jackson will use this factory method when deserializing from a JSON string. | |
| 231 | * |
|
231 | * | |
| 232 | * @param value canonical SemVer string |
|
232 | * @param value canonical SemVer string | |
| 233 | * @return parsed {@code SemVersion} |
|
233 | * @return parsed {@code SemVersion} | |
| 234 | */ |
|
234 | */ | |
| 235 | @JsonCreator(mode = JsonCreator.Mode.DELEGATING) |
|
235 | @JsonCreator(mode = JsonCreator.Mode.DELEGATING) | |
| 236 | public static SemVersion fromJson(String value) { |
|
236 | public static SemVersion fromJson(String value) { | |
| 237 | return parse(value); |
|
237 | return parse(value); | |
| 238 | } |
|
238 | } | |
| 239 |
|
239 | |||
| 240 | /** |
|
240 | /** | |
| 241 | * Compares this version to another {@link SemVersion} according to the |
|
241 | * Compares this version to another {@link SemVersion} according to the | |
| 242 | * SemVer 2.0.0 precedence rules. |
|
242 | * SemVer 2.0.0 precedence rules. | |
| 243 | * <p> |
|
243 | * <p> | |
| 244 | * Build metadata is ignored for ordering. |
|
244 | * Build metadata is ignored for ordering. | |
| 245 | */ |
|
245 | */ | |
| 246 | @Override |
|
246 | @Override | |
| 247 | public int compareTo(SemVersion other) { |
|
247 | public int compareTo(SemVersion other) { | |
| 248 | // 1. major / minor / patch |
|
248 | // 1. major / minor / patch | |
| 249 | int c = Integer.compare(this.major, other.major); |
|
249 | int c = Integer.compare(this.major, other.major); | |
| 250 | if (c != 0) |
|
250 | if (c != 0) | |
| 251 | return c; |
|
251 | return c; | |
| 252 |
|
252 | |||
| 253 | c = Integer.compare(this.minor, other.minor); |
|
253 | c = Integer.compare(this.minor, other.minor); | |
| 254 | if (c != 0) |
|
254 | if (c != 0) | |
| 255 | return c; |
|
255 | return c; | |
| 256 |
|
256 | |||
| 257 | c = Integer.compare(this.patch, other.patch); |
|
257 | c = Integer.compare(this.patch, other.patch); | |
| 258 | if (c != 0) |
|
258 | if (c != 0) | |
| 259 | return c; |
|
259 | return c; | |
| 260 |
|
260 | |||
| 261 | // 2. pre-release (absence > presence) |
|
261 | // 2. pre-release (absence > presence) | |
| 262 | boolean thisHasPre = this.preRelease.isPresent(); |
|
262 | boolean thisHasPre = this.preRelease.isPresent(); | |
| 263 | boolean otherHasPre = other.preRelease.isPresent(); |
|
263 | boolean otherHasPre = other.preRelease.isPresent(); | |
| 264 |
|
264 | |||
| 265 | if (!thisHasPre && !otherHasPre) { |
|
265 | if (!thisHasPre && !otherHasPre) { | |
| 266 | return 0; |
|
266 | return 0; | |
| 267 | } else if (!thisHasPre) { |
|
267 | } else if (!thisHasPre) { | |
| 268 | // normal version > pre-release |
|
268 | // normal version > pre-release | |
| 269 | return 1; |
|
269 | return 1; | |
| 270 | } else if (!otherHasPre) { |
|
270 | } else if (!otherHasPre) { | |
| 271 | return -1; |
|
271 | return -1; | |
| 272 | } |
|
272 | } | |
| 273 |
|
273 | |||
| 274 | // 3. both have pre-release: compare identifiers |
|
274 | // 3. both have pre-release: compare identifiers | |
| 275 | return comparePreRelease(this.preRelease.get(), other.preRelease.get()); |
|
275 | return comparePreRelease(this.preRelease.get(), other.preRelease.get()); | |
| 276 | } |
|
276 | } | |
| 277 |
|
277 | |||
| 278 | private static int comparePreRelease(String a, String b) { |
|
278 | private static int comparePreRelease(String a, String b) { | |
| 279 | String[] aParts = a.split("\\."); |
|
279 | String[] aParts = a.split("\\."); | |
| 280 | String[] bParts = b.split("\\."); |
|
280 | String[] bParts = b.split("\\."); | |
| 281 |
|
281 | |||
| 282 | int len = Math.min(aParts.length, bParts.length); |
|
282 | int len = Math.min(aParts.length, bParts.length); | |
| 283 | for (int i = 0; i < len; i++) { |
|
283 | for (int i = 0; i < len; i++) { | |
| 284 | String ai = aParts[i]; |
|
284 | String ai = aParts[i]; | |
| 285 | String bi = bParts[i]; |
|
285 | String bi = bParts[i]; | |
| 286 |
|
286 | |||
| 287 | boolean aNum = isNumericIdentifier(ai); |
|
287 | boolean aNum = isNumericIdentifier(ai); | |
| 288 | boolean bNum = isNumericIdentifier(bi); |
|
288 | boolean bNum = isNumericIdentifier(bi); | |
| 289 |
|
289 | |||
| 290 | if (aNum && bNum) { |
|
290 | if (aNum && bNum) { | |
| 291 | int aiVal = Integer.parseInt(ai); |
|
291 | int aiVal = Integer.parseInt(ai); | |
| 292 | int biVal = Integer.parseInt(bi); |
|
292 | int biVal = Integer.parseInt(bi); | |
| 293 | int c = Integer.compare(aiVal, biVal); |
|
293 | int c = Integer.compare(aiVal, biVal); | |
| 294 | if (c != 0) |
|
294 | if (c != 0) | |
| 295 | return c; |
|
295 | return c; | |
| 296 | } else if (aNum && !bNum) { |
|
296 | } else if (aNum && !bNum) { | |
| 297 | // numeric identifiers have lower precedence than non-numeric |
|
297 | // numeric identifiers have lower precedence than non-numeric | |
| 298 | return -1; |
|
298 | return -1; | |
| 299 | } else if (!aNum && bNum) { |
|
299 | } else if (!aNum && bNum) { | |
| 300 | return 1; |
|
300 | return 1; | |
| 301 | } else { |
|
301 | } else { | |
| 302 | int c = ai.compareTo(bi); |
|
302 | int c = ai.compareTo(bi); | |
| 303 | if (c != 0) |
|
303 | if (c != 0) | |
| 304 | return c; |
|
304 | return c; | |
| 305 | } |
|
305 | } | |
| 306 | } |
|
306 | } | |
| 307 |
|
307 | |||
| 308 | // If all common identifiers are equal, the shorter one has lower precedence |
|
308 | // If all common identifiers are equal, the shorter one has lower precedence | |
| 309 | return Integer.compare(aParts.length, bParts.length); |
|
309 | return Integer.compare(aParts.length, bParts.length); | |
| 310 | } |
|
310 | } | |
| 311 |
|
311 | |||
| 312 | @Override |
|
312 | @Override | |
| 313 | public String toString() { |
|
313 | public String toString() { | |
| 314 | return asString(); |
|
314 | return asString(); | |
| 315 | } |
|
315 | } | |
| 316 | } |
|
316 | } | |
| @@ -1,54 +1,54 | |||||
| 1 |
package org.implab.gradle.common. |
|
1 | package org.implab.gradle.common.core.lang; | |
| 2 |
|
2 | |||
| 3 | import java.util.regex.Pattern; |
|
3 | import java.util.regex.Pattern; | |
| 4 |
|
4 | |||
| 5 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
5 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
| 6 | import org.gradle.api.provider.Provider; |
|
6 | import org.gradle.api.provider.Provider; | |
| 7 |
|
7 | |||
| 8 | @NonNullByDefault |
|
8 | @NonNullByDefault | |
| 9 | public class Strings { |
|
9 | public class Strings { | |
| 10 |
|
10 | |||
| 11 | private static final Pattern firstLetter = Pattern.compile("^\\w"); |
|
11 | private static final Pattern firstLetter = Pattern.compile("^\\w"); | |
| 12 |
|
12 | |||
| 13 | public static String capitalize(String string) { |
|
13 | public static String capitalize(String string) { | |
| 14 | return string == null ? null |
|
14 | return string == null ? null | |
| 15 | : string.length() == 0 ? string |
|
15 | : string.length() == 0 ? string | |
| 16 | : firstLetter.matcher(string).replaceFirst(m -> m.group().toUpperCase()); |
|
16 | : firstLetter.matcher(string).replaceFirst(m -> m.group().toUpperCase()); | |
| 17 | } |
|
17 | } | |
| 18 |
|
18 | |||
| 19 | public static String toCamelCase(String name) { |
|
19 | public static String toCamelCase(String name) { | |
| 20 | if (name == null || name.isEmpty()) |
|
20 | if (name == null || name.isEmpty()) | |
| 21 | return name; |
|
21 | return name; | |
| 22 | StringBuilder out = new StringBuilder(name.length()); |
|
22 | StringBuilder out = new StringBuilder(name.length()); | |
| 23 | boolean up = false; |
|
23 | boolean up = false; | |
| 24 | boolean first = true; |
|
24 | boolean first = true; | |
| 25 | for (int i = 0; i < name.length(); i++) { |
|
25 | for (int i = 0; i < name.length(); i++) { | |
| 26 | char c = name.charAt(i); |
|
26 | char c = name.charAt(i); | |
| 27 | switch (c) { |
|
27 | switch (c) { | |
| 28 | case '-', '_', ' ', '.' -> up = true; |
|
28 | case '-', '_', ' ', '.' -> up = true; | |
| 29 | default -> { |
|
29 | default -> { | |
| 30 | out.append( |
|
30 | out.append( | |
| 31 | first ? Character.toLowerCase(c) |
|
31 | first ? Character.toLowerCase(c) | |
| 32 | : up ? Character.toUpperCase(c): c); |
|
32 | : up ? Character.toUpperCase(c): c); | |
| 33 | up = false; |
|
33 | up = false; | |
| 34 | first = false; |
|
34 | first = false; | |
| 35 | } |
|
35 | } | |
| 36 | } |
|
36 | } | |
| 37 | } |
|
37 | } | |
| 38 | return out.toString(); |
|
38 | return out.toString(); | |
| 39 | } |
|
39 | } | |
| 40 |
|
40 | |||
| 41 | public static void argumentNotNullOrEmpty(String value, String argumentName) { |
|
41 | public static void argumentNotNullOrEmpty(String value, String argumentName) { | |
| 42 | if (value == null || value.length() == 0) |
|
42 | if (value == null || value.length() == 0) | |
| 43 | 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)); | |
| 44 | } |
|
44 | } | |
| 45 |
|
45 | |||
| 46 | public static String asString(Object value) { |
|
46 | public static String asString(Object value) { | |
| 47 | if (value == null) |
|
47 | if (value == null) | |
| 48 | return null; |
|
48 | return null; | |
| 49 | if (value instanceof Provider<?> provider) |
|
49 | if (value instanceof Provider<?> provider) | |
| 50 | return asString(provider.get()); |
|
50 | return asString(provider.get()); | |
| 51 | else |
|
51 | else | |
| 52 | return value.toString(); |
|
52 | return value.toString(); | |
| 53 | } |
|
53 | } | |
| 54 | } |
|
54 | } | |
| @@ -1,98 +1,98 | |||||
| 1 |
package org.implab.gradle.common. |
|
1 | package org.implab.gradle.common.core.lang; | |
| 2 |
|
2 | |||
| 3 | import java.text.MessageFormat; |
|
3 | import java.text.MessageFormat; | |
| 4 | import java.util.Iterator; |
|
4 | import java.util.Iterator; | |
| 5 | import java.util.Spliterators; |
|
5 | import java.util.Spliterators; | |
| 6 | import java.util.Optional; |
|
6 | import java.util.Optional; | |
| 7 | import java.util.function.Supplier; |
|
7 | import java.util.function.Supplier; | |
| 8 | import java.util.stream.Stream; |
|
8 | import java.util.stream.Stream; | |
| 9 | import java.util.stream.StreamSupport; |
|
9 | import java.util.stream.StreamSupport; | |
| 10 |
|
10 | |||
| 11 | import org.gradle.api.provider.Provider; |
|
11 | import org.gradle.api.provider.Provider; | |
| 12 |
|
12 | |||
| 13 | public final class Values { |
|
13 | public final class Values { | |
| 14 |
|
14 | |||
| 15 | private Values() { |
|
15 | private Values() { | |
| 16 | } |
|
16 | } | |
| 17 |
|
17 | |||
| 18 | /** |
|
18 | /** | |
| 19 | * Converts the supplied value to a string. |
|
19 | * Converts the supplied value to a string. | |
| 20 | */ |
|
20 | */ | |
| 21 | public static String toString(Object value) { |
|
21 | public static String toString(Object value) { | |
| 22 | if (value == null) { |
|
22 | if (value == null) { | |
| 23 | return null; |
|
23 | return null; | |
| 24 | } else if (value instanceof String string) { |
|
24 | } else if (value instanceof String string) { | |
| 25 | return string; |
|
25 | return string; | |
| 26 | } else if (value instanceof Provider<?> provider) { |
|
26 | } else if (value instanceof Provider<?> provider) { | |
| 27 | return toString(provider.getOrNull()); |
|
27 | return toString(provider.getOrNull()); | |
| 28 | } else if (value instanceof Supplier<?> supplier) { |
|
28 | } else if (value instanceof Supplier<?> supplier) { | |
| 29 | return toString(supplier.get()); |
|
29 | return toString(supplier.get()); | |
| 30 | } else { |
|
30 | } else { | |
| 31 | return value.toString(); |
|
31 | return value.toString(); | |
| 32 | } |
|
32 | } | |
| 33 | } |
|
33 | } | |
| 34 |
|
34 | |||
| 35 | public static <T> Stream<T> stream(Iterator<T> remaining) { |
|
35 | public static <T> Stream<T> stream(Iterator<T> remaining) { | |
| 36 | return StreamSupport.stream( |
|
36 | return StreamSupport.stream( | |
| 37 | Spliterators.spliteratorUnknownSize(remaining, 0), |
|
37 | Spliterators.spliteratorUnknownSize(remaining, 0), | |
| 38 | false); |
|
38 | false); | |
| 39 | } |
|
39 | } | |
| 40 |
|
40 | |||
| 41 | public static <T> Optional<T> take(Iterator<T> iterator) { |
|
41 | public static <T> Optional<T> take(Iterator<T> iterator) { | |
| 42 | return iterator.hasNext() ? Optional.of(iterator.next()) : Optional.empty(); |
|
42 | return iterator.hasNext() ? Optional.of(iterator.next()) : Optional.empty(); | |
| 43 | } |
|
43 | } | |
| 44 |
|
44 | |||
| 45 | public static <T> Iterable<T> iterable(T[] values) { |
|
45 | public static <T> Iterable<T> iterable(T[] values) { | |
| 46 | return () -> new ArrayIterator<>(values); |
|
46 | return () -> new ArrayIterator<>(values); | |
| 47 | } |
|
47 | } | |
| 48 |
|
48 | |||
| 49 | public static <T> Optional<T> optional(Provider<T> provider) { |
|
49 | public static <T> Optional<T> optional(Provider<T> provider) { | |
| 50 | return provider.isPresent() ? Optional.of(provider.get()) : Optional.empty(); |
|
50 | return provider.isPresent() ? Optional.of(provider.get()) : Optional.empty(); | |
| 51 | } |
|
51 | } | |
| 52 |
|
52 | |||
| 53 | public static <T> T required(Provider<T> provider, String providerName) { |
|
53 | public static <T> T required(Provider<T> provider, String providerName) { | |
| 54 | if (!provider.isPresent()) |
|
54 | if (!provider.isPresent()) | |
| 55 | throw new IllegalStateException( |
|
55 | throw new IllegalStateException( | |
| 56 | MessageFormat.format("The value for the '{0}' provider must be specified", providerName)); |
|
56 | MessageFormat.format("The value for the '{0}' provider must be specified", providerName)); | |
| 57 | return provider.get(); |
|
57 | return provider.get(); | |
| 58 | } |
|
58 | } | |
| 59 |
|
59 | |||
| 60 | public static boolean parseBoolean(Object value) { |
|
60 | public static boolean parseBoolean(Object value) { | |
| 61 | if (value instanceof Boolean) { |
|
61 | if (value instanceof Boolean) { | |
| 62 | return (Boolean) value; |
|
62 | return (Boolean) value; | |
| 63 | } else { |
|
63 | } else { | |
| 64 | var text = toString(value); |
|
64 | var text = toString(value); | |
| 65 | switch (text != null ? text.toLowerCase() : "") { |
|
65 | switch (text != null ? text.toLowerCase() : "") { | |
| 66 | case "true", "yes", "1" -> { |
|
66 | case "true", "yes", "1" -> { | |
| 67 | return true; |
|
67 | return true; | |
| 68 | } |
|
68 | } | |
| 69 | case "false", "no", "0", "" -> { |
|
69 | case "false", "no", "0", "" -> { | |
| 70 | return false; |
|
70 | return false; | |
| 71 | } |
|
71 | } | |
| 72 | default -> throw new IllegalArgumentException( |
|
72 | default -> throw new IllegalArgumentException( | |
| 73 | MessageFormat.format("Cannot parse boolean value from ''{0}''", text)); |
|
73 | MessageFormat.format("Cannot parse boolean value from ''{0}''", text)); | |
| 74 | } |
|
74 | } | |
| 75 | } |
|
75 | } | |
| 76 | } |
|
76 | } | |
| 77 |
|
77 | |||
| 78 | private static class ArrayIterator<T> implements Iterator<T> { |
|
78 | private static class ArrayIterator<T> implements Iterator<T> { | |
| 79 | private final T[] data; |
|
79 | private final T[] data; | |
| 80 |
|
80 | |||
| 81 | private int pos = 0; |
|
81 | private int pos = 0; | |
| 82 |
|
82 | |||
| 83 | ArrayIterator(T[] data) { |
|
83 | ArrayIterator(T[] data) { | |
| 84 | this.data = data; |
|
84 | this.data = data; | |
| 85 | } |
|
85 | } | |
| 86 |
|
86 | |||
| 87 | @Override |
|
87 | @Override | |
| 88 | public boolean hasNext() { |
|
88 | public boolean hasNext() { | |
| 89 | return pos < data.length; |
|
89 | return pos < data.length; | |
| 90 | } |
|
90 | } | |
| 91 |
|
91 | |||
| 92 | @Override |
|
92 | @Override | |
| 93 | public T next() { |
|
93 | public T next() { | |
| 94 | return data[pos++]; |
|
94 | return data[pos++]; | |
| 95 | } |
|
95 | } | |
| 96 | } |
|
96 | } | |
| 97 |
|
97 | |||
| 98 | } |
|
98 | } | |
| @@ -1,16 +1,14 | |||||
| 1 |
package org.implab.gradle.common. |
|
1 | package org.implab.gradle.common.core.os; | |
| 2 |
|
2 | |||
| 3 | import org.implab.gradle.common.utils.OperatingSystem; |
|
3 | class FreeBsd extends GenericSystem{ | |
| 4 |
|
||||
| 5 | public class FreeBsd extends GenericSystem{ |
|
|||
| 6 |
|
4 | |||
| 7 | FreeBsd(String name, String version) { |
|
5 | FreeBsd(String name, String version) { | |
| 8 | super(name, version); |
|
6 | super(name, version); | |
| 9 | } |
|
7 | } | |
| 10 |
|
8 | |||
| 11 | @Override |
|
9 | @Override | |
| 12 | public String family() { |
|
10 | public String family() { | |
| 13 | return OperatingSystem.FREE_BSD_FAMILY; |
|
11 | return OperatingSystem.FREE_BSD_FAMILY; | |
| 14 | } |
|
12 | } | |
| 15 |
|
13 | |||
| 16 | } |
|
14 | } | |
| @@ -1,64 +1,63 | |||||
| 1 |
package org.implab.gradle.common. |
|
1 | package org.implab.gradle.common.core.os; | |
| 2 |
|
2 | |||
| 3 | import java.io.File; |
|
3 | import java.io.File; | |
| 4 | import java.util.Arrays; |
|
4 | import java.util.Arrays; | |
| 5 | import java.util.Optional; |
|
5 | import java.util.Optional; | |
| 6 | import java.util.function.Function; |
|
6 | import java.util.function.Function; | |
| 7 | import java.util.regex.Pattern; |
|
7 | import java.util.regex.Pattern; | |
| 8 | import java.util.stream.Stream; |
|
8 | import java.util.stream.Stream; | |
| 9 |
|
9 | |||
| 10 |
import org.implab.gradle.common. |
|
10 | import org.implab.gradle.common.core.lang.Values; | |
| 11 | import org.implab.gradle.common.utils.Values; |
|
|||
| 12 |
|
11 | |||
| 13 | class GenericSystem implements OperatingSystem { |
|
12 | class GenericSystem implements OperatingSystem { | |
| 14 |
|
13 | |||
| 15 | private final String name; |
|
14 | private final String name; | |
| 16 |
|
15 | |||
| 17 | private final String version; |
|
16 | private final String version; | |
| 18 |
|
17 | |||
| 19 | GenericSystem(String name, String version) { |
|
18 | GenericSystem(String name, String version) { | |
| 20 | this.name = name; |
|
19 | this.name = name; | |
| 21 | this.version = version; |
|
20 | this.version = version; | |
| 22 | } |
|
21 | } | |
| 23 |
|
22 | |||
| 24 | public String getPathVar() { |
|
23 | public String getPathVar() { | |
| 25 | return "PATH"; |
|
24 | return "PATH"; | |
| 26 | } |
|
25 | } | |
| 27 |
|
26 | |||
| 28 | @Override |
|
27 | @Override | |
| 29 | public String family() { |
|
28 | public String family() { | |
| 30 | return OperatingSystem.UNKNOWN_FAMILY; |
|
29 | return OperatingSystem.UNKNOWN_FAMILY; | |
| 31 | } |
|
30 | } | |
| 32 |
|
31 | |||
| 33 | @Override |
|
32 | @Override | |
| 34 | public String name() { |
|
33 | public String name() { | |
| 35 | return name; |
|
34 | return name; | |
| 36 | } |
|
35 | } | |
| 37 |
|
36 | |||
| 38 | @Override |
|
37 | @Override | |
| 39 | public String version() { |
|
38 | public String version() { | |
| 40 | return version; |
|
39 | return version; | |
| 41 | } |
|
40 | } | |
| 42 |
|
41 | |||
| 43 | protected Stream<File> getPath() { |
|
42 | protected Stream<File> getPath() { | |
| 44 | String path = System.getenv(getPathVar()); |
|
43 | String path = System.getenv(getPathVar()); | |
| 45 | return path == null |
|
44 | return path == null | |
| 46 | ? Stream.empty() |
|
45 | ? Stream.empty() | |
| 47 | : Arrays.stream(path.split(Pattern.quote(File.pathSeparator))) |
|
46 | : Arrays.stream(path.split(Pattern.quote(File.pathSeparator))) | |
| 48 | .map(File::new); |
|
47 | .map(File::new); | |
| 49 | } |
|
48 | } | |
| 50 |
|
49 | |||
| 51 | protected Function<File, Stream<File>> candidates(String cmd) { |
|
50 | protected Function<File, Stream<File>> candidates(String cmd) { | |
| 52 | return base -> Stream.of(new File(base, cmd)); |
|
51 | return base -> Stream.of(new File(base, cmd)); | |
| 53 | } |
|
52 | } | |
| 54 |
|
53 | |||
| 55 | @Override |
|
54 | @Override | |
| 56 | public Optional<File> which(String cmd) { |
|
55 | public Optional<File> which(String cmd) { | |
| 57 | return getPath().flatMap(candidates(cmd)).filter(File::isFile).findAny(); |
|
56 | return getPath().flatMap(candidates(cmd)).filter(File::isFile).findAny(); | |
| 58 | } |
|
57 | } | |
| 59 |
|
58 | |||
| 60 | @Override |
|
59 | @Override | |
| 61 | public Optional<File> which(String cmd, Iterable<? extends File> paths) { |
|
60 | public Optional<File> which(String cmd, Iterable<? extends File> paths) { | |
| 62 | return Values.stream(paths.iterator()).flatMap(candidates(cmd)).filter(File::isFile).findAny(); |
|
61 | return Values.stream(paths.iterator()).flatMap(candidates(cmd)).filter(File::isFile).findAny(); | |
| 63 | } |
|
62 | } | |
| 64 | } |
|
63 | } | |
| @@ -1,16 +1,14 | |||||
| 1 |
package org.implab.gradle.common. |
|
1 | package org.implab.gradle.common.core.os; | |
| 2 |
|
2 | |||
| 3 | import org.implab.gradle.common.utils.OperatingSystem; |
|
3 | class Linux extends GenericSystem { | |
| 4 |
|
||||
| 5 | public class Linux extends GenericSystem { |
|
|||
| 6 |
|
4 | |||
| 7 | Linux(String name, String version) { |
|
5 | Linux(String name, String version) { | |
| 8 | super(name, version); |
|
6 | super(name, version); | |
| 9 | } |
|
7 | } | |
| 10 |
|
8 | |||
| 11 | @Override |
|
9 | @Override | |
| 12 | public String family() { |
|
10 | public String family() { | |
| 13 | return OperatingSystem.LINUX_FAMILY; |
|
11 | return OperatingSystem.LINUX_FAMILY; | |
| 14 | } |
|
12 | } | |
| 15 |
|
13 | |||
| 16 | } |
|
14 | } | |
| @@ -1,15 +1,13 | |||||
| 1 |
package org.implab.gradle.common. |
|
1 | package org.implab.gradle.common.core.os; | |
| 2 |
|
2 | |||
| 3 | import org.implab.gradle.common.utils.OperatingSystem; |
|
3 | class MacOs extends GenericSystem { | |
| 4 |
|
||||
| 5 | public class MacOs extends GenericSystem { |
|
|||
| 6 |
|
4 | |||
| 7 | MacOs(String name, String version) { |
|
5 | MacOs(String name, String version) { | |
| 8 | super(name, version); |
|
6 | super(name, version); | |
| 9 | } |
|
7 | } | |
| 10 |
|
8 | |||
| 11 | @Override |
|
9 | @Override | |
| 12 | public String family() { |
|
10 | public String family() { | |
| 13 | return OperatingSystem.MAC_OS_FAMILY; |
|
11 | return OperatingSystem.MAC_OS_FAMILY; | |
| 14 | } |
|
12 | } | |
| 15 | } |
|
13 | } | |
| @@ -1,33 +1,31 | |||||
| 1 |
package org.implab.gradle.common. |
|
1 | package org.implab.gradle.common.core.os; | |
| 2 |
|
2 | |||
| 3 | import java.io.File; |
|
3 | import java.io.File; | |
| 4 | import java.util.Optional; |
|
4 | import java.util.Optional; | |
| 5 |
|
5 | |||
| 6 | import org.implab.gradle.common.utils.os.SystemResolver; |
|
|||
| 7 |
|
||||
| 8 | public interface OperatingSystem { |
|
6 | public interface OperatingSystem { | |
| 9 |
|
7 | |||
| 10 | public static final String WINDOWS_FAMILY = "windows"; |
|
8 | public static final String WINDOWS_FAMILY = "windows"; | |
| 11 |
|
9 | |||
| 12 | public static final String LINUX_FAMILY = "linux"; |
|
10 | public static final String LINUX_FAMILY = "linux"; | |
| 13 |
|
11 | |||
| 14 | public static final String FREE_BSD_FAMILY = "freebsd"; |
|
12 | public static final String FREE_BSD_FAMILY = "freebsd"; | |
| 15 |
|
13 | |||
| 16 | public static final String MAC_OS_FAMILY = "os x"; |
|
14 | public static final String MAC_OS_FAMILY = "os x"; | |
| 17 |
|
15 | |||
| 18 | public static final String UNKNOWN_FAMILY = "unknown"; |
|
16 | public static final String UNKNOWN_FAMILY = "unknown"; | |
| 19 |
|
17 | |||
| 20 | String family(); |
|
18 | String family(); | |
| 21 |
|
19 | |||
| 22 | String name(); |
|
20 | String name(); | |
| 23 |
|
21 | |||
| 24 | String version(); |
|
22 | String version(); | |
| 25 |
|
23 | |||
| 26 | Optional<File> which(String cmd); |
|
24 | Optional<File> which(String cmd); | |
| 27 |
|
25 | |||
| 28 | Optional<File> which(String cmd, Iterable<? extends File> paths); |
|
26 | Optional<File> which(String cmd, Iterable<? extends File> paths); | |
| 29 |
|
27 | |||
| 30 | public static OperatingSystem current() { |
|
28 | public static OperatingSystem current() { | |
| 31 | return SystemResolver.current(); |
|
29 | return SystemResolver.current(); | |
| 32 | } |
|
30 | } | |
| 33 | } |
|
31 | } | |
| @@ -1,37 +1,36 | |||||
| 1 |
package org.implab.gradle.common. |
|
1 | package org.implab.gradle.common.core.os; | |
| 2 |
|
2 | |||
| 3 |
import org.implab.gradle.common. |
|
3 | import org.implab.gradle.common.core.lang.LazyValue; | |
| 4 | import org.implab.gradle.common.utils.OperatingSystem; |
|
|||
| 5 |
|
4 | |||
| 6 |
|
|
5 | class SystemResolver { | |
| 7 |
|
6 | |||
| 8 | private final static LazyValue<OperatingSystem> current = new LazyValue<>(SystemResolver::resolveCurrent); |
|
7 | private final static LazyValue<OperatingSystem> current = new LazyValue<>(SystemResolver::resolveCurrent); | |
| 9 |
|
8 | |||
| 10 | private static OperatingSystem resolveCurrent() { |
|
9 | private static OperatingSystem resolveCurrent() { | |
| 11 | var osName = System.getProperty("os.name"); |
|
10 | var osName = System.getProperty("os.name"); | |
| 12 | var osVersion = System.getProperty("os.version"); |
|
11 | var osVersion = System.getProperty("os.version"); | |
| 13 |
|
12 | |||
| 14 | return forName(osName, osVersion); |
|
13 | return forName(osName, osVersion); | |
| 15 | } |
|
14 | } | |
| 16 |
|
15 | |||
| 17 | public static OperatingSystem current() { |
|
16 | public static OperatingSystem current() { | |
| 18 | return current.get(); |
|
17 | return current.get(); | |
| 19 | } |
|
18 | } | |
| 20 |
|
19 | |||
| 21 | public static OperatingSystem forName(String os, String version) { |
|
20 | public static OperatingSystem forName(String os, String version) { | |
| 22 | var osName = os.toLowerCase(); |
|
21 | var osName = os.toLowerCase(); | |
| 23 |
|
22 | |||
| 24 | if (osName.contains("windows")) { |
|
23 | if (osName.contains("windows")) { | |
| 25 | return new Windows(osName, version); |
|
24 | return new Windows(osName, version); | |
| 26 | } else if (osName.contains("mac os x") || osName.contains("darwin") || osName.contains("osx")) { |
|
25 | } else if (osName.contains("mac os x") || osName.contains("darwin") || osName.contains("osx")) { | |
| 27 | return new MacOs(osName, version); |
|
26 | return new MacOs(osName, version); | |
| 28 | } else if (osName.contains("linux")) { |
|
27 | } else if (osName.contains("linux")) { | |
| 29 | return new Linux(osName, version); |
|
28 | return new Linux(osName, version); | |
| 30 | } else if (osName.contains("freebsd")) { |
|
29 | } else if (osName.contains("freebsd")) { | |
| 31 | return new FreeBsd(osName, version); |
|
30 | return new FreeBsd(osName, version); | |
| 32 | } else { |
|
31 | } else { | |
| 33 | // Not strictly true |
|
32 | // Not strictly true | |
| 34 | return new GenericSystem(osName, version); |
|
33 | return new GenericSystem(osName, version); | |
| 35 | } |
|
34 | } | |
| 36 | } |
|
35 | } | |
| 37 | } |
|
36 | } | |
| @@ -1,26 +1,24 | |||||
| 1 |
package org.implab.gradle.common. |
|
1 | package org.implab.gradle.common.core.os; | |
| 2 |
|
2 | |||
| 3 | import java.io.File; |
|
3 | import java.io.File; | |
| 4 | import java.util.function.Function; |
|
4 | import java.util.function.Function; | |
| 5 | import java.util.stream.Stream; |
|
5 | import java.util.stream.Stream; | |
| 6 |
|
6 | |||
| 7 | import org.implab.gradle.common.utils.OperatingSystem; |
|
|||
| 8 |
|
||||
| 9 | class Windows extends GenericSystem { |
|
7 | class Windows extends GenericSystem { | |
| 10 |
|
8 | |||
| 11 | private Stream<String> exeSuffixes = Stream.of(".cmd", ".bat", ".exe"); |
|
9 | private Stream<String> exeSuffixes = Stream.of(".cmd", ".bat", ".exe"); | |
| 12 |
|
10 | |||
| 13 | Windows(String name, String version) { |
|
11 | Windows(String name, String version) { | |
| 14 | super(name, version); |
|
12 | super(name, version); | |
| 15 | } |
|
13 | } | |
| 16 |
|
14 | |||
| 17 | @Override |
|
15 | @Override | |
| 18 | public String family() { |
|
16 | public String family() { | |
| 19 | return OperatingSystem.WINDOWS_FAMILY; |
|
17 | return OperatingSystem.WINDOWS_FAMILY; | |
| 20 | } |
|
18 | } | |
| 21 |
|
19 | |||
| 22 | @Override |
|
20 | @Override | |
| 23 | protected Function<File, Stream<File>> candidates(String cmd) { |
|
21 | protected Function<File, Stream<File>> candidates(String cmd) { | |
| 24 | return base -> exeSuffixes.map(suffix -> new File(base, cmd + suffix)); |
|
22 | return base -> exeSuffixes.map(suffix -> new File(base, cmd + suffix)); | |
| 25 | } |
|
23 | } | |
| 26 | } |
|
24 | } | |
| @@ -1,61 +1,61 | |||||
| 1 | package org.implab.gradle.common.dsl; |
|
1 | package org.implab.gradle.common.exec.dsl; | |
| 2 |
|
2 | |||
| 3 | import java.io.File; |
|
3 | import java.io.File; | |
| 4 | import java.io.InputStream; |
|
4 | import java.io.InputStream; | |
| 5 | import java.util.Optional; |
|
5 | import java.util.Optional; | |
| 6 | import java.util.function.Supplier; |
|
6 | import java.util.function.Supplier; | |
| 7 |
|
7 | |||
| 8 | import org.gradle.api.provider.Provider; |
|
8 | import org.gradle.api.provider.Provider; | |
| 9 | import org.gradle.util.Configurable; |
|
9 | import org.gradle.util.Configurable; | |
| 10 |
import org.implab.gradle.common. |
|
10 | import org.implab.gradle.common.core.lang.Closures; | |
| 11 |
import org.implab.gradle.common. |
|
11 | import org.implab.gradle.common.exec.runtime.RedirectFrom; | |
| 12 |
|
12 | |||
| 13 | import groovy.lang.Closure; |
|
13 | import groovy.lang.Closure; | |
| 14 |
|
14 | |||
| 15 | public class RedirectFromSpec implements Configurable<RedirectFromSpec> { |
|
15 | public class RedirectFromSpec implements Configurable<RedirectFromSpec> { | |
| 16 | private Supplier<RedirectFrom> streamRedirect; |
|
16 | private Supplier<RedirectFrom> streamRedirect; | |
| 17 |
|
17 | |||
| 18 | public boolean isRedirected() { |
|
18 | public boolean isRedirected() { | |
| 19 | return streamRedirect != null; |
|
19 | return streamRedirect != null; | |
| 20 | } |
|
20 | } | |
| 21 |
|
21 | |||
| 22 | public Optional<RedirectFrom> getRedirection() { |
|
22 | public Optional<RedirectFrom> getRedirection() { | |
| 23 | return streamRedirect != null ? Optional.ofNullable(streamRedirect.get()) : Optional.empty(); |
|
23 | return streamRedirect != null ? Optional.ofNullable(streamRedirect.get()) : Optional.empty(); | |
| 24 | } |
|
24 | } | |
| 25 |
|
25 | |||
| 26 | public void fromFile(File file) { |
|
26 | public void fromFile(File file) { | |
| 27 | streamRedirect = () -> RedirectFrom.file(file); |
|
27 | streamRedirect = () -> RedirectFrom.file(file); | |
| 28 | } |
|
28 | } | |
| 29 |
|
29 | |||
| 30 | public void fromFile(Provider<File> fileProvider) { |
|
30 | public void fromFile(Provider<File> fileProvider) { | |
| 31 | streamRedirect = fileProvider.map(RedirectFrom::file)::getOrNull; |
|
31 | streamRedirect = fileProvider.map(RedirectFrom::file)::getOrNull; | |
| 32 | } |
|
32 | } | |
| 33 |
|
33 | |||
| 34 | public void fromStream(InputStream stream) { |
|
34 | public void fromStream(InputStream stream) { | |
| 35 | streamRedirect = () -> RedirectFrom.stream(stream); |
|
35 | streamRedirect = () -> RedirectFrom.stream(stream); | |
| 36 | } |
|
36 | } | |
| 37 |
|
37 | |||
| 38 | public void fromStream(Provider<InputStream> streamProvider) { |
|
38 | public void fromStream(Provider<InputStream> streamProvider) { | |
| 39 | streamRedirect = streamProvider.map(RedirectFrom::stream)::getOrNull; |
|
39 | streamRedirect = streamProvider.map(RedirectFrom::stream)::getOrNull; | |
| 40 | } |
|
40 | } | |
| 41 |
|
41 | |||
| 42 | public void from(Object input) { |
|
42 | public void from(Object input) { | |
| 43 | if (input instanceof Provider<?> inputProvider) { |
|
43 | if (input instanceof Provider<?> inputProvider) { | |
| 44 | streamRedirect = inputProvider.map(RedirectFrom::any)::get; |
|
44 | streamRedirect = inputProvider.map(RedirectFrom::any)::get; | |
| 45 | } else { |
|
45 | } else { | |
| 46 | streamRedirect = () -> RedirectFrom.any(input); |
|
46 | streamRedirect = () -> RedirectFrom.any(input); | |
| 47 | } |
|
47 | } | |
| 48 | } |
|
48 | } | |
| 49 |
|
49 | |||
| 50 | public void empty() { |
|
50 | public void empty() { | |
| 51 | streamRedirect = () -> null; |
|
51 | streamRedirect = () -> null; | |
| 52 | } |
|
52 | } | |
| 53 |
|
53 | |||
| 54 | @Override |
|
54 | @Override | |
| 55 | public RedirectFromSpec configure(Closure cl) { |
|
55 | public RedirectFromSpec configure(Closure cl) { | |
| 56 | Closures.apply(cl, this); |
|
56 | Closures.apply(cl, this); | |
| 57 | return this; |
|
57 | return this; | |
| 58 | } |
|
58 | } | |
| 59 |
|
59 | |||
| 60 |
|
60 | |||
| 61 | } |
|
61 | } | |
| @@ -1,75 +1,75 | |||||
| 1 | package org.implab.gradle.common.dsl; |
|
1 | package org.implab.gradle.common.exec.dsl; | |
| 2 |
|
2 | |||
| 3 | import java.io.File; |
|
3 | import java.io.File; | |
| 4 | import java.io.OutputStream; |
|
4 | import java.io.OutputStream; | |
| 5 | import java.util.Optional; |
|
5 | import java.util.Optional; | |
| 6 | import java.util.function.Consumer; |
|
6 | import java.util.function.Consumer; | |
| 7 | import java.util.function.Supplier; |
|
7 | import java.util.function.Supplier; | |
| 8 |
|
8 | |||
| 9 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
9 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
| 10 | import org.eclipse.jdt.annotation.Nullable; |
|
10 | import org.eclipse.jdt.annotation.Nullable; | |
| 11 | import org.gradle.api.provider.Provider; |
|
11 | import org.gradle.api.provider.Provider; | |
| 12 | import org.gradle.util.Configurable; |
|
12 | import org.gradle.util.Configurable; | |
| 13 |
import org.implab.gradle.common. |
|
13 | import org.implab.gradle.common.core.lang.Closures; | |
| 14 |
import org.implab.gradle.common. |
|
14 | import org.implab.gradle.common.exec.runtime.RedirectTo; | |
| 15 |
|
15 | |||
| 16 | import groovy.lang.Closure; |
|
16 | import groovy.lang.Closure; | |
| 17 |
|
17 | |||
| 18 | @NonNullByDefault |
|
18 | @NonNullByDefault | |
| 19 | public class RedirectToSpec implements Configurable<RedirectToSpec> { |
|
19 | public class RedirectToSpec implements Configurable<RedirectToSpec> { | |
| 20 | private Supplier<RedirectTo> streamRedirect; |
|
20 | private Supplier<RedirectTo> streamRedirect; | |
| 21 |
|
21 | |||
| 22 | public boolean isRedirected() { |
|
22 | public boolean isRedirected() { | |
| 23 | return getRedirection().isPresent(); |
|
23 | return getRedirection().isPresent(); | |
| 24 | } |
|
24 | } | |
| 25 |
|
25 | |||
| 26 | public Optional<RedirectTo> getRedirection() { |
|
26 | public Optional<RedirectTo> getRedirection() { | |
| 27 | return streamRedirect != null ? Optional.ofNullable(streamRedirect.get()) : Optional.empty(); |
|
27 | return streamRedirect != null ? Optional.ofNullable(streamRedirect.get()) : Optional.empty(); | |
| 28 | } |
|
28 | } | |
| 29 |
|
29 | |||
| 30 | public @Nullable RedirectTo getRedirectionOrNull() { |
|
30 | public @Nullable RedirectTo getRedirectionOrNull() { | |
| 31 | return streamRedirect != null ? streamRedirect.get() : null; |
|
31 | return streamRedirect != null ? streamRedirect.get() : null; | |
| 32 | } |
|
32 | } | |
| 33 |
|
33 | |||
| 34 | public void toFile(File file) { |
|
34 | public void toFile(File file) { | |
| 35 | streamRedirect = () -> RedirectTo.file(file); |
|
35 | streamRedirect = () -> RedirectTo.file(file); | |
| 36 | } |
|
36 | } | |
| 37 |
|
37 | |||
| 38 | public void toFile(Provider<File> fileProvider) { |
|
38 | public void toFile(Provider<File> fileProvider) { | |
| 39 | streamRedirect = fileProvider.map(RedirectTo::file)::getOrNull; |
|
39 | streamRedirect = fileProvider.map(RedirectTo::file)::getOrNull; | |
| 40 | } |
|
40 | } | |
| 41 |
|
41 | |||
| 42 | public void toStream(OutputStream stream) { |
|
42 | public void toStream(OutputStream stream) { | |
| 43 | streamRedirect = () -> RedirectTo.stream(stream); |
|
43 | streamRedirect = () -> RedirectTo.stream(stream); | |
| 44 | } |
|
44 | } | |
| 45 |
|
45 | |||
| 46 | public void toStream(Provider<OutputStream> streamProvider) { |
|
46 | public void toStream(Provider<OutputStream> streamProvider) { | |
| 47 | streamRedirect = streamProvider.map(RedirectTo::stream)::getOrNull; |
|
47 | streamRedirect = streamProvider.map(RedirectTo::stream)::getOrNull; | |
| 48 | } |
|
48 | } | |
| 49 |
|
49 | |||
| 50 | public void to(Object output) { |
|
50 | public void to(Object output) { | |
| 51 | if (output instanceof Provider<?> outputProvider) { |
|
51 | if (output instanceof Provider<?> outputProvider) { | |
| 52 | streamRedirect = outputProvider.map(RedirectTo::any)::get; |
|
52 | streamRedirect = outputProvider.map(RedirectTo::any)::get; | |
| 53 | } else { |
|
53 | } else { | |
| 54 | streamRedirect = () -> RedirectTo.any(output); |
|
54 | streamRedirect = () -> RedirectTo.any(output); | |
| 55 | } |
|
55 | } | |
| 56 | } |
|
56 | } | |
| 57 |
|
57 | |||
| 58 | public void eachLine(Consumer<String> consumer) { |
|
58 | public void eachLine(Consumer<String> consumer) { | |
| 59 | streamRedirect = () -> RedirectTo.eachLine(consumer); |
|
59 | streamRedirect = () -> RedirectTo.eachLine(consumer); | |
| 60 | } |
|
60 | } | |
| 61 |
|
61 | |||
| 62 | public void allText(Consumer<String> consumer) { |
|
62 | public void allText(Consumer<String> consumer) { | |
| 63 | streamRedirect = () -> RedirectTo.allText(consumer); |
|
63 | streamRedirect = () -> RedirectTo.allText(consumer); | |
| 64 | } |
|
64 | } | |
| 65 |
|
65 | |||
| 66 | public void discard() { |
|
66 | public void discard() { | |
| 67 | streamRedirect = () -> null; |
|
67 | streamRedirect = () -> null; | |
| 68 | } |
|
68 | } | |
| 69 |
|
69 | |||
| 70 | @Override |
|
70 | @Override | |
| 71 | public RedirectToSpec configure(Closure cl) { |
|
71 | public RedirectToSpec configure(Closure cl) { | |
| 72 | Closures.apply(cl, this); |
|
72 | Closures.apply(cl, this); | |
| 73 | return this; |
|
73 | return this; | |
| 74 | } |
|
74 | } | |
| 75 | } |
|
75 | } | |
| @@ -1,26 +1,26 | |||||
| 1 | package org.implab.gradle.common.dsl; |
|
1 | package org.implab.gradle.common.exec.dsl; | |
| 2 |
|
2 | |||
| 3 | import java.util.stream.Stream; |
|
3 | import java.util.stream.Stream; | |
| 4 |
|
4 | |||
| 5 | import org.gradle.api.provider.ListProperty; |
|
5 | import org.gradle.api.provider.ListProperty; | |
| 6 |
import org.implab.gradle.common. |
|
6 | import org.implab.gradle.common.core.gradle.Properties; | |
| 7 |
|
7 | |||
| 8 | public interface TaskCommandSpecMixin { |
|
8 | public interface TaskCommandSpecMixin { | |
| 9 | ListProperty<String> getCommandLine(); |
|
9 | ListProperty<String> getCommandLine(); | |
| 10 |
|
10 | |||
| 11 | default void commandLine(Object arg0, Object... args) { |
|
11 | default void commandLine(Object arg0, Object... args) { | |
| 12 | getCommandLine().empty(); |
|
12 | getCommandLine().empty(); | |
| 13 | Properties.mergeList( |
|
13 | Properties.mergeList( | |
| 14 | getCommandLine(), |
|
14 | getCommandLine(), | |
| 15 | () -> Stream.concat(Stream.of(arg0), Stream.of(args)).iterator(), |
|
15 | () -> Stream.concat(Stream.of(arg0), Stream.of(args)).iterator(), | |
| 16 | Object::toString); |
|
16 | Object::toString); | |
| 17 | } |
|
17 | } | |
| 18 |
|
18 | |||
| 19 | default void args(Object arg0, Object... args) { |
|
19 | default void args(Object arg0, Object... args) { | |
| 20 | Properties.mergeList( |
|
20 | Properties.mergeList( | |
| 21 | getCommandLine(), |
|
21 | getCommandLine(), | |
| 22 | () -> Stream.concat(Stream.of(arg0), Stream.of(args)).iterator(), |
|
22 | () -> Stream.concat(Stream.of(arg0), Stream.of(args)).iterator(), | |
| 23 | Object::toString); |
|
23 | Object::toString); | |
| 24 | } |
|
24 | } | |
| 25 |
|
25 | |||
| 26 | } |
|
26 | } | |
| @@ -1,45 +1,45 | |||||
| 1 | package org.implab.gradle.common.dsl; |
|
1 | package org.implab.gradle.common.exec.dsl; | |
| 2 |
|
2 | |||
| 3 | import java.util.Map; |
|
3 | import java.util.Map; | |
| 4 |
|
4 | |||
| 5 | import org.gradle.api.Action; |
|
5 | import org.gradle.api.Action; | |
| 6 | import org.gradle.api.file.DirectoryProperty; |
|
6 | import org.gradle.api.file.DirectoryProperty; | |
| 7 | import org.gradle.api.provider.MapProperty; |
|
7 | import org.gradle.api.provider.MapProperty; | |
| 8 | import org.gradle.api.provider.Property; |
|
8 | import org.gradle.api.provider.Property; | |
| 9 |
import org.implab.gradle.common. |
|
9 | import org.implab.gradle.common.core.gradle.Properties; | |
| 10 |
import org.implab.gradle.common. |
|
10 | import org.implab.gradle.common.core.lang.Closures; | |
| 11 |
|
11 | |||
| 12 | import groovy.lang.Closure; |
|
12 | import groovy.lang.Closure; | |
| 13 |
|
13 | |||
| 14 | /** |
|
14 | /** | |
| 15 | * Configuration properties of the execution shell. This object specifies a |
|
15 | * Configuration properties of the execution shell. This object specifies a | |
| 16 | * working directory and environment variables for the processes started within |
|
16 | * working directory and environment variables for the processes started within | |
| 17 | * this shell. |
|
17 | * this shell. | |
| 18 | */ |
|
18 | */ | |
| 19 | public interface TaskEnvSpecMixin { |
|
19 | public interface TaskEnvSpecMixin { | |
| 20 |
|
20 | |||
| 21 | /** Inherit environment from current process */ |
|
21 | /** Inherit environment from current process */ | |
| 22 | Property<Boolean> getInheritEnvironment(); |
|
22 | Property<Boolean> getInheritEnvironment(); | |
| 23 |
|
23 | |||
| 24 | /** Working directory */ |
|
24 | /** Working directory */ | |
| 25 | DirectoryProperty getWorkingDirectory(); |
|
25 | DirectoryProperty getWorkingDirectory(); | |
| 26 |
|
26 | |||
| 27 | /** Environment variables */ |
|
27 | /** Environment variables */ | |
| 28 | MapProperty<String, String> getEnvironment(); |
|
28 | MapProperty<String, String> getEnvironment(); | |
| 29 |
|
29 | |||
| 30 | /** |
|
30 | /** | |
| 31 | * Configures the environment variable using the specified action. The |
|
31 | * Configures the environment variable using the specified action. The | |
| 32 | * action is called when the value is calculated; |
|
32 | * action is called when the value is calculated; | |
| 33 | * |
|
33 | * | |
| 34 | * <p> |
|
34 | * <p> | |
| 35 | * The configuration action is called immediately. To support lazy evaluation, |
|
35 | * The configuration action is called immediately. To support lazy evaluation, | |
| 36 | * properties may be assigned to providers. |
|
36 | * properties may be assigned to providers. | |
| 37 | */ |
|
37 | */ | |
| 38 | default void env(Action<Map<String, Object>> configure) { |
|
38 | default void env(Action<Map<String, Object>> configure) { | |
| 39 | Properties.configureMap(getEnvironment(), configure, Object::toString); |
|
39 | Properties.configureMap(getEnvironment(), configure, Object::toString); | |
| 40 | } |
|
40 | } | |
| 41 |
|
41 | |||
| 42 | default void env(Closure<?> configure) { |
|
42 | default void env(Closure<?> configure) { | |
| 43 | env(Closures.action(configure)); |
|
43 | env(Closures.action(configure)); | |
| 44 | } |
|
44 | } | |
| 45 | } |
|
45 | } | |
| @@ -1,12 +1,12 | |||||
| 1 | package org.implab.gradle.common.dsl; |
|
1 | package org.implab.gradle.common.exec.dsl; | |
| 2 |
|
2 | |||
| 3 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
3 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
| 4 |
|
4 | |||
| 5 | @NonNullByDefault |
|
5 | @NonNullByDefault | |
| 6 | public interface TaskPipeSpecMixin { |
|
6 | public interface TaskPipeSpecMixin { | |
| 7 | RedirectToSpec getStdout(); |
|
7 | RedirectToSpec getStdout(); | |
| 8 |
|
8 | |||
| 9 | RedirectToSpec getStderr(); |
|
9 | RedirectToSpec getStderr(); | |
| 10 |
|
10 | |||
| 11 | RedirectFromSpec getStdin(); |
|
11 | RedirectFromSpec getStdin(); | |
| 12 | } |
|
12 | } | |
| @@ -1,52 +1,52 | |||||
| 1 | package org.implab.gradle.common.exec; |
|
1 | package org.implab.gradle.common.exec.model; | |
| 2 |
|
2 | |||
| 3 | import static java.util.Objects.requireNonNull; |
|
3 | import static java.util.Objects.requireNonNull; | |
| 4 |
|
4 | |||
| 5 | import java.util.Optional; |
|
5 | import java.util.Optional; | |
| 6 |
|
6 | |||
| 7 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
7 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
| 8 |
import org.implab.gradle.common. |
|
8 | import org.implab.gradle.common.core.lang.Values; | |
| 9 |
|
9 | |||
| 10 | @NonNullByDefault |
|
10 | @NonNullByDefault | |
| 11 | public interface CommandArgumentsBuilder<S extends CommandArgumentsBuilder<S>> { |
|
11 | public interface CommandArgumentsBuilder<S extends CommandArgumentsBuilder<S>> { | |
| 12 |
|
12 | |||
| 13 | S self(); |
|
13 | S self(); | |
| 14 |
|
14 | |||
| 15 | default S flag(String name, boolean value) { |
|
15 | default S flag(String name, boolean value) { | |
| 16 | requireNonNull(name, "Parameter name cannot be null"); |
|
16 | requireNonNull(name, "Parameter name cannot be null"); | |
| 17 | if (value) |
|
17 | if (value) | |
| 18 | addArguments(name); |
|
18 | addArguments(name); | |
| 19 | return self(); |
|
19 | return self(); | |
| 20 | } |
|
20 | } | |
| 21 |
|
21 | |||
| 22 | default S param(String name, String value) { |
|
22 | default S param(String name, String value) { | |
| 23 | requireNonNull(name, "Parameter name cannot be null"); |
|
23 | requireNonNull(name, "Parameter name cannot be null"); | |
| 24 | requireNonNull(value, "Parameter value cannot be null"); |
|
24 | requireNonNull(value, "Parameter value cannot be null"); | |
| 25 | if (value != null && value.length() > 0) |
|
25 | if (value != null && value.length() > 0) | |
| 26 | addArguments(name, value); |
|
26 | addArguments(name, value); | |
| 27 | return self(); |
|
27 | return self(); | |
| 28 | } |
|
28 | } | |
| 29 |
|
29 | |||
| 30 | default S param(String name, Optional<String> value) { |
|
30 | default S param(String name, Optional<String> value) { | |
| 31 | value.ifPresent(v -> param(name, v)); |
|
31 | value.ifPresent(v -> param(name, v)); | |
| 32 | return self(); |
|
32 | return self(); | |
| 33 | } |
|
33 | } | |
| 34 |
|
34 | |||
| 35 | /** Adds the specified arguments to this builder */ |
|
35 | /** Adds the specified arguments to this builder */ | |
| 36 | S addArguments(String... args); |
|
36 | S addArguments(String... args); | |
| 37 |
|
37 | |||
| 38 | default S addArguments(Iterable<String> args) { |
|
38 | default S addArguments(Iterable<String> args) { | |
| 39 | for (String arg : args) |
|
39 | for (String arg : args) | |
| 40 | addArguments(arg); |
|
40 | addArguments(arg); | |
| 41 |
|
41 | |||
| 42 | return self(); |
|
42 | return self(); | |
| 43 | } |
|
43 | } | |
| 44 |
|
44 | |||
| 45 | /** Replaces arguments in the builder with the specified one. */ |
|
45 | /** Replaces arguments in the builder with the specified one. */ | |
| 46 | default S arguments(String... args) { |
|
46 | default S arguments(String... args) { | |
| 47 | return arguments(Values.iterable(args)); |
|
47 | return arguments(Values.iterable(args)); | |
| 48 | } |
|
48 | } | |
| 49 |
|
49 | |||
| 50 | /** Replaces arguments in the builder with the specified one. */ |
|
50 | /** Replaces arguments in the builder with the specified one. */ | |
| 51 | S arguments(Iterable<String> args); |
|
51 | S arguments(Iterable<String> args); | |
| 52 | } |
|
52 | } | |
| @@ -1,63 +1,63 | |||||
| 1 | package org.implab.gradle.common.exec; |
|
1 | package org.implab.gradle.common.exec.model; | |
| 2 |
|
2 | |||
| 3 | import java.io.File; |
|
3 | import java.io.File; | |
| 4 |
|
4 | |||
| 5 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
5 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
| 6 |
import org.implab.gradle.common. |
|
6 | import org.implab.gradle.common.core.lang.Values; | |
| 7 |
|
7 | |||
| 8 | /** Command builder interface, used to specify the executable and parameters */ |
|
8 | /** Command builder interface, used to specify the executable and parameters */ | |
| 9 | @NonNullByDefault |
|
9 | @NonNullByDefault | |
| 10 | public interface CommandBuilder extends CommandArgumentsBuilder<CommandBuilder> { |
|
10 | public interface CommandBuilder extends CommandArgumentsBuilder<CommandBuilder> { | |
| 11 |
|
11 | |||
| 12 | @Override |
|
12 | @Override | |
| 13 | default CommandBuilder self() { |
|
13 | default CommandBuilder self() { | |
| 14 | return this; |
|
14 | return this; | |
| 15 | } |
|
15 | } | |
| 16 |
|
16 | |||
| 17 | /** Sets the executable, the parameters are left intact. */ |
|
17 | /** Sets the executable, the parameters are left intact. */ | |
| 18 | CommandBuilder executable(String executable); |
|
18 | CommandBuilder executable(String executable); | |
| 19 |
|
19 | |||
| 20 | default CommandBuilder executable(File executable) { |
|
20 | default CommandBuilder executable(File executable) { | |
| 21 | executable(executable.toString()); |
|
21 | executable(executable.toString()); | |
| 22 | return this; |
|
22 | return this; | |
| 23 | } |
|
23 | } | |
| 24 |
|
24 | |||
| 25 | /** |
|
25 | /** | |
| 26 | * Sets the specified executable and parameters, old executable and parameters |
|
26 | * Sets the specified executable and parameters, old executable and parameters | |
| 27 | * are discarded. |
|
27 | * are discarded. | |
| 28 | */ |
|
28 | */ | |
| 29 | default CommandBuilder commandLine(String executable, String... args) { |
|
29 | default CommandBuilder commandLine(String executable, String... args) { | |
| 30 | return executable(executable) |
|
30 | return executable(executable) | |
| 31 | .arguments(args); |
|
31 | .arguments(args); | |
| 32 | } |
|
32 | } | |
| 33 |
|
33 | |||
| 34 | /** |
|
34 | /** | |
| 35 | * Sets the specified executable and parameters, old executable and parameters |
|
35 | * Sets the specified executable and parameters, old executable and parameters | |
| 36 | * are discarded. |
|
36 | * are discarded. | |
| 37 | * |
|
37 | * | |
| 38 | * @param command The command line. Must contain at least one element |
|
38 | * @param command The command line. Must contain at least one element | |
| 39 | * (executable). |
|
39 | * (executable). | |
| 40 | * |
|
40 | * | |
| 41 | */ |
|
41 | */ | |
| 42 | default CommandBuilder commandLine(Iterable<? extends String> command) { |
|
42 | default CommandBuilder commandLine(Iterable<? extends String> command) { | |
| 43 | var iterator = command.iterator(); |
|
43 | var iterator = command.iterator(); | |
| 44 |
|
44 | |||
| 45 | // set executable |
|
45 | // set executable | |
| 46 | executable(Values.take(iterator).orElseThrow()); |
|
46 | executable(Values.take(iterator).orElseThrow()); | |
| 47 | // cleat arguments |
|
47 | // cleat arguments | |
| 48 | arguments(); |
|
48 | arguments(); | |
| 49 |
|
49 | |||
| 50 | // set new arguments |
|
50 | // set new arguments | |
| 51 | while (iterator.hasNext()) |
|
51 | while (iterator.hasNext()) | |
| 52 | addArguments(iterator.next()); |
|
52 | addArguments(iterator.next()); | |
| 53 |
|
53 | |||
| 54 | return this; |
|
54 | return this; | |
| 55 | } |
|
55 | } | |
| 56 |
|
56 | |||
| 57 | default CommandBuilder from(CommandSpec commandSpec) { |
|
57 | default CommandBuilder from(CommandSpec commandSpec) { | |
| 58 | return executable(commandSpec.executable()) |
|
58 | return executable(commandSpec.executable()) | |
| 59 | .arguments(commandSpec.arguments()); |
|
59 | .arguments(commandSpec.arguments()); | |
| 60 | } |
|
60 | } | |
| 61 |
|
61 | |||
| 62 | CommandSpec build(); |
|
62 | CommandSpec build(); | |
| 63 | } |
|
63 | } | |
| @@ -1,23 +1,23 | |||||
| 1 | package org.implab.gradle.common.exec; |
|
1 | package org.implab.gradle.common.exec.model; | |
| 2 |
|
2 | |||
| 3 | import java.util.List; |
|
3 | import java.util.List; | |
| 4 | import java.util.stream.Stream; |
|
4 | import java.util.stream.Stream; | |
| 5 |
|
5 | |||
| 6 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
6 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
| 7 |
|
7 | |||
| 8 | @NonNullByDefault |
|
8 | @NonNullByDefault | |
| 9 | public interface CommandSpec { |
|
9 | public interface CommandSpec { | |
| 10 |
|
10 | |||
| 11 | String executable(); |
|
11 | String executable(); | |
| 12 |
|
12 | |||
| 13 | List<String> arguments(); |
|
13 | List<String> arguments(); | |
| 14 |
|
14 | |||
| 15 | default List<String> commandLine() { |
|
15 | default List<String> commandLine() { | |
| 16 | return Stream.concat(Stream.of(executable()), arguments().stream()).toList(); |
|
16 | return Stream.concat(Stream.of(executable()), arguments().stream()).toList(); | |
| 17 | } |
|
17 | } | |
| 18 |
|
18 | |||
| 19 | public static CommandBuilder builder() { |
|
19 | public static CommandBuilder builder() { | |
| 20 | return new CommandSpecRecord.Builder(); |
|
20 | return new CommandSpecRecord.Builder(); | |
| 21 | } |
|
21 | } | |
| 22 |
|
22 | |||
| 23 | } |
|
23 | } | |
| @@ -1,51 +1,51 | |||||
| 1 | package org.implab.gradle.common.exec; |
|
1 | package org.implab.gradle.common.exec.model; | |
| 2 |
|
2 | |||
| 3 | import java.util.List; |
|
3 | import java.util.List; | |
| 4 |
|
4 | |||
| 5 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
5 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
| 6 |
|
6 | |||
| 7 | import static java.util.Objects.requireNonNull; |
|
7 | import static java.util.Objects.requireNonNull; | |
| 8 |
|
8 | |||
| 9 | import java.util.ArrayList; |
|
9 | import java.util.ArrayList; | |
| 10 |
|
10 | |||
| 11 | @NonNullByDefault |
|
11 | @NonNullByDefault | |
| 12 | public record CommandSpecRecord(String executable, List<String> arguments) implements CommandSpec { |
|
12 | public record CommandSpecRecord(String executable, List<String> arguments) implements CommandSpec { | |
| 13 |
|
13 | |||
| 14 | static class Builder implements CommandBuilder { |
|
14 | static class Builder implements CommandBuilder { | |
| 15 |
|
15 | |||
| 16 | private String executable; |
|
16 | private String executable; | |
| 17 |
|
17 | |||
| 18 | private List<String> arguments = new ArrayList<>(); |
|
18 | private List<String> arguments = new ArrayList<>(); | |
| 19 |
|
19 | |||
| 20 | @Override |
|
20 | @Override | |
| 21 | public CommandBuilder executable(String executable) { |
|
21 | public CommandBuilder executable(String executable) { | |
| 22 | requireNonNull(executable, "cmd can't be null"); |
|
22 | requireNonNull(executable, "cmd can't be null"); | |
| 23 | this.executable = executable; |
|
23 | this.executable = executable; | |
| 24 | return this; |
|
24 | return this; | |
| 25 | } |
|
25 | } | |
| 26 |
|
26 | |||
| 27 | @Override |
|
27 | @Override | |
| 28 | public CommandBuilder arguments(Iterable<String> args) { |
|
28 | public CommandBuilder arguments(Iterable<String> args) { | |
| 29 | requireNonNull(args, "Args must not be null"); |
|
29 | requireNonNull(args, "Args must not be null"); | |
| 30 | arguments.clear(); |
|
30 | arguments.clear(); | |
| 31 | for (var arg : args) |
|
31 | for (var arg : args) | |
| 32 | arguments.add(arg); |
|
32 | arguments.add(arg); | |
| 33 | return this; |
|
33 | return this; | |
| 34 | } |
|
34 | } | |
| 35 |
|
35 | |||
| 36 | @Override |
|
36 | @Override | |
| 37 | public CommandBuilder addArguments(String... args) { |
|
37 | public CommandBuilder addArguments(String... args) { | |
| 38 | for (var arg : args) |
|
38 | for (var arg : args) | |
| 39 | arguments.add(requireNonNull(arg, "arguments element shouldn't be null")); |
|
39 | arguments.add(requireNonNull(arg, "arguments element shouldn't be null")); | |
| 40 |
|
40 | |||
| 41 | return this; |
|
41 | return this; | |
| 42 | } |
|
42 | } | |
| 43 |
|
43 | |||
| 44 | @Override |
|
44 | @Override | |
| 45 | public CommandSpec build() { |
|
45 | public CommandSpec build() { | |
| 46 | requireNonNull(executable, "Executable must be specified"); |
|
46 | requireNonNull(executable, "Executable must be specified"); | |
| 47 | return new CommandSpecRecord(executable, arguments); |
|
47 | return new CommandSpecRecord(executable, arguments); | |
| 48 | } |
|
48 | } | |
| 49 |
|
49 | |||
| 50 | } |
|
50 | } | |
| 51 | } |
|
51 | } | |
| @@ -1,17 +1,17 | |||||
| 1 | package org.implab.gradle.common.exec; |
|
1 | package org.implab.gradle.common.exec.model; | |
| 2 |
|
2 | |||
| 3 | import java.io.File; |
|
3 | import java.io.File; | |
| 4 | import java.util.Map; |
|
4 | import java.util.Map; | |
| 5 | import java.util.Optional; |
|
5 | import java.util.Optional; | |
| 6 |
|
6 | |||
| 7 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
7 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
| 8 |
|
8 | |||
| 9 | @NonNullByDefault |
|
9 | @NonNullByDefault | |
| 10 | public interface EnvironmentSpec { |
|
10 | public interface EnvironmentSpec { | |
| 11 |
|
11 | |||
| 12 | boolean inheritEnvironment(); |
|
12 | boolean inheritEnvironment(); | |
| 13 |
|
13 | |||
| 14 | Map<String,String> environment(); |
|
14 | Map<String,String> environment(); | |
| 15 |
|
15 | |||
| 16 | Optional<File> workingDirectory(); |
|
16 | Optional<File> workingDirectory(); | |
| 17 | } |
|
17 | } | |
| @@ -1,20 +1,23 | |||||
| 1 | package org.implab.gradle.common.exec; |
|
1 | package org.implab.gradle.common.exec.model; | |
|
|
2 | ||||
|
|
3 | import org.implab.gradle.common.exec.runtime.RedirectFrom; | |||
|
|
4 | import org.implab.gradle.common.exec.runtime.RedirectTo; | |||
| 2 |
|
5 | |||
| 3 | import java.util.Optional; |
|
6 | import java.util.Optional; | |
| 4 |
|
7 | |||
| 5 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
8 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
| 6 |
|
9 | |||
| 7 | /** |
|
10 | /** | |
| 8 | * The execution shell uses this specification when starting |
|
11 | * The execution shell uses this specification when starting | |
| 9 | * a new process. The shell may check for the specified |
|
12 | * a new process. The shell may check for the specified | |
| 10 | * redirections and apply them when launching the process. |
|
13 | * redirections and apply them when launching the process. | |
| 11 | * The exact moment they are applied is at the shellβs discretion. |
|
14 | * The exact moment they are applied is at the shellβs discretion. | |
| 12 | */ |
|
15 | */ | |
| 13 | @NonNullByDefault |
|
16 | @NonNullByDefault | |
| 14 | public interface PipeSpec { |
|
17 | public interface PipeSpec { | |
| 15 | Optional<RedirectTo> stdout(); |
|
18 | Optional<RedirectTo> stdout(); | |
| 16 |
|
19 | |||
| 17 | Optional<RedirectTo> stderr(); |
|
20 | Optional<RedirectTo> stderr(); | |
| 18 |
|
21 | |||
| 19 | Optional<RedirectFrom> stdin(); |
|
22 | Optional<RedirectFrom> stdin(); | |
| 20 | } |
|
23 | } | |
| @@ -1,212 +1,214 | |||||
| 1 | package org.implab.gradle.common.exec; |
|
1 | package org.implab.gradle.common.exec.runtime; | |
| 2 |
|
2 | |||
| 3 | import java.util.HashMap; |
|
3 | import java.util.HashMap; | |
| 4 | import java.util.Map; |
|
4 | import java.util.Map; | |
| 5 | import java.util.Optional; |
|
5 | import java.util.Optional; | |
| 6 | import java.util.concurrent.CompletableFuture; |
|
6 | import java.util.concurrent.CompletableFuture; | |
| 7 |
|
7 | |||
| 8 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
8 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
| 9 | import org.eclipse.jdt.annotation.Nullable; |
|
9 | import org.eclipse.jdt.annotation.Nullable; | |
|
|
10 | import org.implab.gradle.common.exec.model.EnvironmentSpec; | |||
|
|
11 | import org.implab.gradle.common.exec.model.PipeSpec; | |||
| 10 |
|
12 | |||
| 11 | import java.io.File; |
|
13 | import java.io.File; | |
| 12 | import java.io.IOException; |
|
14 | import java.io.IOException; | |
| 13 |
|
15 | |||
| 14 | import static java.util.Objects.requireNonNull; |
|
16 | import static java.util.Objects.requireNonNull; | |
| 15 |
|
17 | |||
| 16 | /** Command line builder */ |
|
18 | /** Command line builder */ | |
| 17 | @NonNullByDefault |
|
19 | @NonNullByDefault | |
| 18 | public abstract class AbstractExecBuilder<CS> implements ShellExec { |
|
20 | public abstract class AbstractExecBuilder<CS> implements ShellExec { | |
| 19 |
|
21 | |||
| 20 | private boolean inheritEnvironment = true; |
|
22 | private boolean inheritEnvironment = true; | |
| 21 |
|
23 | |||
| 22 | private final Map<String, String> environment = new HashMap<>(); |
|
24 | private final Map<String, String> environment = new HashMap<>(); | |
| 23 |
|
25 | |||
| 24 | private @Nullable File directory; |
|
26 | private @Nullable File directory; | |
| 25 |
|
27 | |||
| 26 | private RedirectFrom inputRedirect; |
|
28 | private RedirectFrom inputRedirect; | |
| 27 |
|
29 | |||
| 28 | private RedirectTo outputRedirect; |
|
30 | private RedirectTo outputRedirect; | |
| 29 |
|
31 | |||
| 30 | private RedirectTo errorRedirect; |
|
32 | private RedirectTo errorRedirect; | |
| 31 |
|
33 | |||
| 32 | private final CS command; |
|
34 | private final CS command; | |
| 33 |
|
35 | |||
| 34 | protected AbstractExecBuilder(CS command) { |
|
36 | protected AbstractExecBuilder(CS command) { | |
| 35 | this.command = command; |
|
37 | this.command = command; | |
| 36 | } |
|
38 | } | |
| 37 |
|
39 | |||
| 38 |
|
40 | |||
| 39 | /** Sets the working directory */ |
|
41 | /** Sets the working directory */ | |
| 40 | @Override |
|
42 | @Override | |
| 41 | public ShellExec workingDirectory(File directory) { |
|
43 | public ShellExec workingDirectory(File directory) { | |
| 42 | requireNonNull(directory, "directory parameter can't be null"); |
|
44 | requireNonNull(directory, "directory parameter can't be null"); | |
| 43 |
|
45 | |||
| 44 | this.directory = directory; |
|
46 | this.directory = directory; | |
| 45 | return this; |
|
47 | return this; | |
| 46 | } |
|
48 | } | |
| 47 |
|
49 | |||
| 48 | @Override |
|
50 | @Override | |
| 49 | public ShellExec workingDirectory(Optional<? extends File> directory) { |
|
51 | public ShellExec workingDirectory(Optional<? extends File> directory) { | |
| 50 | requireNonNull(directory, "directory parameter can't be null"); |
|
52 | requireNonNull(directory, "directory parameter can't be null"); | |
| 51 |
|
53 | |||
| 52 | this.directory = directory.orElse(null); |
|
54 | this.directory = directory.orElse(null); | |
| 53 | return this; |
|
55 | return this; | |
| 54 | } |
|
56 | } | |
| 55 |
|
57 | |||
| 56 | @Override |
|
58 | @Override | |
| 57 | public ShellExec inheritEnvironment(boolean inherit) { |
|
59 | public ShellExec inheritEnvironment(boolean inherit) { | |
| 58 | this.inheritEnvironment = inherit; |
|
60 | this.inheritEnvironment = inherit; | |
| 59 | return this; |
|
61 | return this; | |
| 60 | } |
|
62 | } | |
| 61 |
|
63 | |||
| 62 | /** |
|
64 | /** | |
| 63 | * Sets the environment value. The value cannot be null. |
|
65 | * Sets the environment value. The value cannot be null. | |
| 64 | * |
|
66 | * | |
| 65 | * @param envVar The name of the environment variable |
|
67 | * @param envVar The name of the environment variable | |
| 66 | * @param value The value to set, |
|
68 | * @param value The value to set, | |
| 67 | */ |
|
69 | */ | |
| 68 | @Override |
|
70 | @Override | |
| 69 | public ShellExec putEnvironment(String envVar, String value) { |
|
71 | public ShellExec putEnvironment(String envVar, String value) { | |
| 70 | requireNonNull(value, "Value can't be null"); |
|
72 | requireNonNull(value, "Value can't be null"); | |
| 71 | requireNonNull(envVar, "envVar parameter can't be null"); |
|
73 | requireNonNull(envVar, "envVar parameter can't be null"); | |
| 72 |
|
74 | |||
| 73 | environment.put(envVar, value); |
|
75 | environment.put(envVar, value); | |
| 74 | return this; |
|
76 | return this; | |
| 75 | } |
|
77 | } | |
| 76 |
|
78 | |||
| 77 | @Override |
|
79 | @Override | |
| 78 | public ShellExec environment(Map<String, ? extends String> env) { |
|
80 | public ShellExec environment(Map<String, ? extends String> env) { | |
| 79 | requireNonNull(env, "env parameter can't be null"); |
|
81 | requireNonNull(env, "env parameter can't be null"); | |
| 80 |
|
82 | |||
| 81 | environment.clear(); |
|
83 | environment.clear(); | |
| 82 | environment.putAll(env); |
|
84 | environment.putAll(env); | |
| 83 | return this; |
|
85 | return this; | |
| 84 | } |
|
86 | } | |
| 85 |
|
87 | |||
| 86 | /** |
|
88 | /** | |
| 87 | * Sets redirection for the stdin, {@link RedirectFrom} will be applied |
|
89 | * Sets redirection for the stdin, {@link RedirectFrom} will be applied | |
| 88 | * every time the process is started. |
|
90 | * every time the process is started. | |
| 89 | * |
|
91 | * | |
| 90 | * <p> |
|
92 | * <p> | |
| 91 | * If redirection |
|
93 | * If redirection | |
| 92 | */ |
|
94 | */ | |
| 93 | @Override |
|
95 | @Override | |
| 94 | public ShellExec stdin(RedirectFrom from) { |
|
96 | public ShellExec stdin(RedirectFrom from) { | |
| 95 | requireNonNull(from, "from parameter can't be null"); |
|
97 | requireNonNull(from, "from parameter can't be null"); | |
| 96 |
|
98 | |||
| 97 | inputRedirect = from; |
|
99 | inputRedirect = from; | |
| 98 | return this; |
|
100 | return this; | |
| 99 | } |
|
101 | } | |
| 100 |
|
102 | |||
| 101 | @Override |
|
103 | @Override | |
| 102 | public ShellExec stdin(Optional<? extends RedirectFrom> from) { |
|
104 | public ShellExec stdin(Optional<? extends RedirectFrom> from) { | |
| 103 | requireNonNull(from, "from parameter can't be null"); |
|
105 | requireNonNull(from, "from parameter can't be null"); | |
| 104 | inputRedirect = from.orElse(null); |
|
106 | inputRedirect = from.orElse(null); | |
| 105 | return this; |
|
107 | return this; | |
| 106 | } |
|
108 | } | |
| 107 |
|
109 | |||
| 108 | /** |
|
110 | /** | |
| 109 | * Sets redirection for the stdout, {@link RedirectTo} will be applied |
|
111 | * Sets redirection for the stdout, {@link RedirectTo} will be applied | |
| 110 | * every time the process is started. |
|
112 | * every time the process is started. | |
| 111 | */ |
|
113 | */ | |
| 112 | @Override |
|
114 | @Override | |
| 113 | public ShellExec stdout(RedirectTo out) { |
|
115 | public ShellExec stdout(RedirectTo out) { | |
| 114 | requireNonNull(out, "out parameter can't be null"); |
|
116 | requireNonNull(out, "out parameter can't be null"); | |
| 115 | outputRedirect = out; |
|
117 | outputRedirect = out; | |
| 116 | return this; |
|
118 | return this; | |
| 117 | } |
|
119 | } | |
| 118 |
|
120 | |||
| 119 | @Override |
|
121 | @Override | |
| 120 | public ShellExec stdout(Optional<? extends RedirectTo> to) { |
|
122 | public ShellExec stdout(Optional<? extends RedirectTo> to) { | |
| 121 | requireNonNull(to, "from parameter can't be null"); |
|
123 | requireNonNull(to, "from parameter can't be null"); | |
| 122 | outputRedirect = to.orElse(null); |
|
124 | outputRedirect = to.orElse(null); | |
| 123 | return this; |
|
125 | return this; | |
| 124 | } |
|
126 | } | |
| 125 |
|
127 | |||
| 126 | /** |
|
128 | /** | |
| 127 | * Sets redirection for the stderr, {@link RedirectTo} will be applied |
|
129 | * Sets redirection for the stderr, {@link RedirectTo} will be applied | |
| 128 | * every time the process is started. |
|
130 | * every time the process is started. | |
| 129 | */ |
|
131 | */ | |
| 130 | @Override |
|
132 | @Override | |
| 131 | public ShellExec stderr(RedirectTo err) { |
|
133 | public ShellExec stderr(RedirectTo err) { | |
| 132 | requireNonNull(err, "err parameter can't be null"); |
|
134 | requireNonNull(err, "err parameter can't be null"); | |
| 133 | errorRedirect = err; |
|
135 | errorRedirect = err; | |
| 134 | return this; |
|
136 | return this; | |
| 135 | } |
|
137 | } | |
| 136 |
|
138 | |||
| 137 | @Override |
|
139 | @Override | |
| 138 | public ShellExec stderr(Optional<? extends RedirectTo> to) { |
|
140 | public ShellExec stderr(Optional<? extends RedirectTo> to) { | |
| 139 | requireNonNull(to, "from parameter can't be null"); |
|
141 | requireNonNull(to, "from parameter can't be null"); | |
| 140 | errorRedirect = to.orElse(null); |
|
142 | errorRedirect = to.orElse(null); | |
| 141 | return this; |
|
143 | return this; | |
| 142 | } |
|
144 | } | |
| 143 |
|
145 | |||
| 144 | @Override |
|
146 | @Override | |
| 145 | public ShellExec from(PipeSpec pipeSpec) { |
|
147 | public ShellExec from(PipeSpec pipeSpec) { | |
| 146 | ShellExec.super.from(pipeSpec); |
|
148 | ShellExec.super.from(pipeSpec); | |
| 147 | return this; |
|
149 | return this; | |
| 148 | } |
|
150 | } | |
| 149 |
|
151 | |||
| 150 | @Override |
|
152 | @Override | |
| 151 | public ShellExec from(EnvironmentSpec environmentSpec) { |
|
153 | public ShellExec from(EnvironmentSpec environmentSpec) { | |
| 152 | ShellExec.super.from(environmentSpec); |
|
154 | ShellExec.super.from(environmentSpec); | |
| 153 | return this; |
|
155 | return this; | |
| 154 | } |
|
156 | } | |
| 155 |
|
157 | |||
| 156 | /** Implement this function to */ |
|
158 | /** Implement this function to */ | |
| 157 | protected abstract CompletableFuture<Integer> startInternal( |
|
159 | protected abstract CompletableFuture<Integer> startInternal( | |
| 158 | CS command, |
|
160 | CS command, | |
| 159 | EnvironmentSpec environment, |
|
161 | EnvironmentSpec environment, | |
| 160 | PipeSpec redirect) throws IOException; |
|
162 | PipeSpec redirect) throws IOException; | |
| 161 |
|
163 | |||
| 162 | /** |
|
164 | /** | |
| 163 | * Creates and starts new process and returns {@link CompletableFuture}. The |
|
165 | * Creates and starts new process and returns {@link CompletableFuture}. The | |
| 164 | * process may be a remote process, or a shell process or etc. |
|
166 | * process may be a remote process, or a shell process or etc. | |
| 165 | * |
|
167 | * | |
| 166 | * @return |
|
168 | * @return | |
| 167 | * @throws IOException |
|
169 | * @throws IOException | |
| 168 | */ |
|
170 | */ | |
| 169 | public CompletableFuture<Integer> exec() throws IOException { |
|
171 | public CompletableFuture<Integer> exec() throws IOException { | |
| 170 | return startInternal( |
|
172 | return startInternal( | |
| 171 | command, |
|
173 | command, | |
| 172 | new SelfEnvironmentSpec(), |
|
174 | new SelfEnvironmentSpec(), | |
| 173 | new SelfPipeSpec()); |
|
175 | new SelfPipeSpec()); | |
| 174 | } |
|
176 | } | |
| 175 |
|
177 | |||
| 176 | private class SelfEnvironmentSpec implements EnvironmentSpec { |
|
178 | private class SelfEnvironmentSpec implements EnvironmentSpec { | |
| 177 | @Override |
|
179 | @Override | |
| 178 | public boolean inheritEnvironment() { |
|
180 | public boolean inheritEnvironment() { | |
| 179 | return inheritEnvironment; |
|
181 | return inheritEnvironment; | |
| 180 | } |
|
182 | } | |
| 181 |
|
183 | |||
| 182 | @Override |
|
184 | @Override | |
| 183 | public Map<String, String> environment() { |
|
185 | public Map<String, String> environment() { | |
| 184 | return environment; |
|
186 | return environment; | |
| 185 | } |
|
187 | } | |
| 186 |
|
188 | |||
| 187 | @Override |
|
189 | @Override | |
| 188 | public Optional<File> workingDirectory() { |
|
190 | public Optional<File> workingDirectory() { | |
| 189 | return Optional.ofNullable(directory); |
|
191 | return Optional.ofNullable(directory); | |
| 190 | } |
|
192 | } | |
| 191 | } |
|
193 | } | |
| 192 |
|
194 | |||
| 193 | private class SelfPipeSpec implements PipeSpec { |
|
195 | private class SelfPipeSpec implements PipeSpec { | |
| 194 |
|
196 | |||
| 195 | @Override |
|
197 | @Override | |
| 196 | public Optional<RedirectTo> stdout() { |
|
198 | public Optional<RedirectTo> stdout() { | |
| 197 | return Optional.ofNullable(outputRedirect); |
|
199 | return Optional.ofNullable(outputRedirect); | |
| 198 | } |
|
200 | } | |
| 199 |
|
201 | |||
| 200 | @Override |
|
202 | @Override | |
| 201 | public Optional<RedirectTo> stderr() { |
|
203 | public Optional<RedirectTo> stderr() { | |
| 202 | return Optional.ofNullable(errorRedirect); |
|
204 | return Optional.ofNullable(errorRedirect); | |
| 203 | } |
|
205 | } | |
| 204 |
|
206 | |||
| 205 | @Override |
|
207 | @Override | |
| 206 | public Optional<RedirectFrom> stdin() { |
|
208 | public Optional<RedirectFrom> stdin() { | |
| 207 | return Optional.ofNullable(inputRedirect); |
|
209 | return Optional.ofNullable(inputRedirect); | |
| 208 | } |
|
210 | } | |
| 209 |
|
211 | |||
| 210 | } |
|
212 | } | |
| 211 |
|
213 | |||
| 212 | } |
|
214 | } | |
| @@ -1,43 +1,46 | |||||
| 1 | package org.implab.gradle.common.exec; |
|
1 | package org.implab.gradle.common.exec.runtime; | |
| 2 |
|
2 | |||
| 3 | import java.io.ByteArrayInputStream; |
|
3 | import java.io.ByteArrayInputStream; | |
| 4 | import java.io.IOException; |
|
4 | import java.io.IOException; | |
| 5 | import java.nio.charset.StandardCharsets; |
|
5 | import java.nio.charset.StandardCharsets; | |
| 6 | import java.util.concurrent.CompletableFuture; |
|
6 | import java.util.concurrent.CompletableFuture; | |
| 7 | import java.util.stream.Collectors; |
|
7 | import java.util.stream.Collectors; | |
| 8 |
|
8 | |||
| 9 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
9 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
|
|
10 | import org.implab.gradle.common.exec.model.CommandSpec; | |||
|
|
11 | import org.implab.gradle.common.exec.model.EnvironmentSpec; | |||
|
|
12 | import org.implab.gradle.common.exec.model.PipeSpec; | |||
| 10 |
|
13 | |||
| 11 | @NonNullByDefault |
|
14 | @NonNullByDefault | |
| 12 | class EchoExecBuilder extends AbstractExecBuilder<CommandSpec> { |
|
15 | class EchoExecBuilder extends AbstractExecBuilder<CommandSpec> { | |
| 13 |
|
16 | |||
| 14 | private final boolean echoToStderr; |
|
17 | private final boolean echoToStderr; | |
| 15 |
|
18 | |||
| 16 | public EchoExecBuilder(CommandSpec command, boolean echoToStderr) { |
|
19 | public EchoExecBuilder(CommandSpec command, boolean echoToStderr) { | |
| 17 | super(command); |
|
20 | super(command); | |
| 18 | this.echoToStderr = echoToStderr; |
|
21 | this.echoToStderr = echoToStderr; | |
| 19 | } |
|
22 | } | |
| 20 |
|
23 | |||
| 21 | @Override |
|
24 | @Override | |
| 22 | protected CompletableFuture<Integer> startInternal( |
|
25 | protected CompletableFuture<Integer> startInternal( | |
| 23 | CommandSpec command, |
|
26 | CommandSpec command, | |
| 24 | EnvironmentSpec environment, |
|
27 | EnvironmentSpec environment, | |
| 25 | PipeSpec redirect) throws IOException { |
|
28 | PipeSpec redirect) throws IOException { | |
| 26 |
|
29 | |||
| 27 | var outputRedirect = echoToStderr ? redirect.stderr() : redirect.stdout(); |
|
30 | var outputRedirect = echoToStderr ? redirect.stderr() : redirect.stdout(); | |
| 28 |
|
31 | |||
| 29 | return outputRedirect |
|
32 | return outputRedirect | |
| 30 | .map(to -> { |
|
33 | .map(to -> { | |
| 31 | var bytes = String |
|
34 | var bytes = String | |
| 32 | .format( |
|
35 | .format( | |
| 33 | "exec: %s", |
|
36 | "exec: %s", | |
| 34 | command.commandLine().stream().collect(Collectors.joining(" "))) |
|
37 | command.commandLine().stream().collect(Collectors.joining(" "))) | |
| 35 | .getBytes(StandardCharsets.UTF_8); |
|
38 | .getBytes(StandardCharsets.UTF_8); | |
| 36 |
|
39 | |||
| 37 | return to.redirect(new ByteArrayInputStream(bytes)) |
|
40 | return to.redirect(new ByteArrayInputStream(bytes)) | |
| 38 | .thenApply((x) -> 0); |
|
41 | .thenApply((x) -> 0); | |
| 39 | }) |
|
42 | }) | |
| 40 | .orElse(CompletableFuture.completedFuture(0)); |
|
43 | .orElse(CompletableFuture.completedFuture(0)); | |
| 41 | } |
|
44 | } | |
| 42 |
|
45 | |||
| 43 | } |
|
46 | } | |
| @@ -1,33 +1,34 | |||||
| 1 | package org.implab.gradle.common.exec; |
|
1 | package org.implab.gradle.common.exec.runtime; | |
| 2 |
|
2 | |||
| 3 | import java.io.File; |
|
3 | import java.io.File; | |
| 4 | import java.util.Map; |
|
4 | import java.util.Map; | |
| 5 | import java.util.Optional; |
|
5 | import java.util.Optional; | |
| 6 |
|
6 | |||
| 7 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
7 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
|
|
8 | import org.implab.gradle.common.exec.model.EnvironmentSpec; | |||
| 8 |
|
9 | |||
| 9 | @NonNullByDefault |
|
10 | @NonNullByDefault | |
| 10 | public interface EnvironmentBuilder { |
|
11 | public interface EnvironmentBuilder { | |
| 11 | /** Sets the specified environment variable */ |
|
12 | /** Sets the specified environment variable */ | |
| 12 | EnvironmentBuilder putEnvironment(String envVar, String value); |
|
13 | EnvironmentBuilder putEnvironment(String envVar, String value); | |
| 13 |
|
14 | |||
| 14 | /** Replaces environment with the supplied one */ |
|
15 | /** Replaces environment with the supplied one */ | |
| 15 | EnvironmentBuilder environment(Map<String, ? extends String> env); |
|
16 | EnvironmentBuilder environment(Map<String, ? extends String> env); | |
| 16 |
|
17 | |||
| 17 | /** |
|
18 | /** | |
| 18 | * Enables or disables environment inheritance for the child process |
|
19 | * Enables or disables environment inheritance for the child process | |
| 19 | */ |
|
20 | */ | |
| 20 | EnvironmentBuilder inheritEnvironment(boolean inherit); |
|
21 | EnvironmentBuilder inheritEnvironment(boolean inherit); | |
| 21 |
|
22 | |||
| 22 | /** Specifies the working directory */ |
|
23 | /** Specifies the working directory */ | |
| 23 | EnvironmentBuilder workingDirectory(File directory); |
|
24 | EnvironmentBuilder workingDirectory(File directory); | |
| 24 |
|
25 | |||
| 25 | /** Specifies the working directory */ |
|
26 | /** Specifies the working directory */ | |
| 26 | EnvironmentBuilder workingDirectory(Optional<? extends File> directory); |
|
27 | EnvironmentBuilder workingDirectory(Optional<? extends File> directory); | |
| 27 |
|
28 | |||
| 28 | /** Copies the supplied environment to this one */ |
|
29 | /** Copies the supplied environment to this one */ | |
| 29 | default EnvironmentBuilder from(EnvironmentSpec environmentSpec) { |
|
30 | default EnvironmentBuilder from(EnvironmentSpec environmentSpec) { | |
| 30 | return environment(environmentSpec.environment()) |
|
31 | return environment(environmentSpec.environment()) | |
| 31 | .workingDirectory(environmentSpec.workingDirectory()); |
|
32 | .workingDirectory(environmentSpec.workingDirectory()); | |
| 32 | } |
|
33 | } | |
| 33 | } |
|
34 | } | |
| @@ -1,27 +1,28 | |||||
| 1 | package org.implab.gradle.common.exec; |
|
1 | package org.implab.gradle.common.exec.runtime; | |
| 2 |
|
2 | |||
| 3 | import java.util.Optional; |
|
3 | import java.util.Optional; | |
| 4 |
|
4 | |||
| 5 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
5 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
|
|
6 | import org.implab.gradle.common.exec.model.PipeSpec; | |||
| 6 |
|
7 | |||
| 7 | @NonNullByDefault |
|
8 | @NonNullByDefault | |
| 8 | public interface PipeBuilder { |
|
9 | public interface PipeBuilder { | |
| 9 | PipeBuilder stdin(RedirectFrom from); |
|
10 | PipeBuilder stdin(RedirectFrom from); | |
| 10 |
|
11 | |||
| 11 | PipeBuilder stdin(Optional<? extends RedirectFrom> from); |
|
12 | PipeBuilder stdin(Optional<? extends RedirectFrom> from); | |
| 12 |
|
13 | |||
| 13 | PipeBuilder stdout(RedirectTo to); |
|
14 | PipeBuilder stdout(RedirectTo to); | |
| 14 |
|
15 | |||
| 15 | PipeBuilder stdout(Optional<? extends RedirectTo> to); |
|
16 | PipeBuilder stdout(Optional<? extends RedirectTo> to); | |
| 16 |
|
17 | |||
| 17 | PipeBuilder stderr(RedirectTo to); |
|
18 | PipeBuilder stderr(RedirectTo to); | |
| 18 |
|
19 | |||
| 19 | PipeBuilder stderr(Optional<? extends RedirectTo> to); |
|
20 | PipeBuilder stderr(Optional<? extends RedirectTo> to); | |
| 20 |
|
21 | |||
| 21 | default PipeBuilder from(PipeSpec pipeSpec) { |
|
22 | default PipeBuilder from(PipeSpec pipeSpec) { | |
| 22 | return stdin(pipeSpec.stdin()) |
|
23 | return stdin(pipeSpec.stdin()) | |
| 23 | .stdout(pipeSpec.stdout()) |
|
24 | .stdout(pipeSpec.stdout()) | |
| 24 | .stderr(pipeSpec.stderr()); |
|
25 | .stderr(pipeSpec.stderr()); | |
| 25 | } |
|
26 | } | |
| 26 |
|
27 | |||
| 27 | } |
|
28 | } | |
| @@ -1,56 +1,56 | |||||
| 1 | package org.implab.gradle.common.exec; |
|
1 | package org.implab.gradle.common.exec.runtime; | |
| 2 |
|
2 | |||
| 3 | import java.io.File; |
|
3 | import java.io.File; | |
| 4 | import java.io.FileInputStream; |
|
4 | import java.io.FileInputStream; | |
| 5 | import java.io.InputStream; |
|
5 | import java.io.InputStream; | |
| 6 | import java.io.OutputStream; |
|
6 | import java.io.OutputStream; | |
| 7 | import java.util.concurrent.CompletableFuture; |
|
7 | import java.util.concurrent.CompletableFuture; | |
| 8 |
|
8 | |||
| 9 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
9 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
| 10 |
|
10 | |||
| 11 | /** |
|
11 | /** | |
| 12 | * Describes how to redirect input streams. This interface is used to configure |
|
12 | * Describes how to redirect input streams. This interface is used to configure | |
| 13 | * lazy redirection. {@link #redirect(OutputStream)} is called when the process |
|
13 | * lazy redirection. {@link #redirect(OutputStream)} is called when the process | |
| 14 | * is started. Before the process is started the redirection isn't invoked and |
|
14 | * is started. Before the process is started the redirection isn't invoked and | |
| 15 | * no resources are allocated or used. |
|
15 | * no resources are allocated or used. | |
| 16 | */ |
|
16 | */ | |
| 17 | @NonNullByDefault |
|
17 | @NonNullByDefault | |
| 18 | public interface RedirectFrom { |
|
18 | public interface RedirectFrom { | |
| 19 | CompletableFuture<Void> redirect(OutputStream to); |
|
19 | CompletableFuture<Void> redirect(OutputStream to); | |
| 20 |
|
20 | |||
| 21 | /** |
|
21 | /** | |
| 22 | * Read file contents and redirect it to the output stream. |
|
22 | * Read file contents and redirect it to the output stream. | |
| 23 | */ |
|
23 | */ | |
| 24 | public static RedirectFrom file(final File file) { |
|
24 | public static RedirectFrom file(final File file) { | |
| 25 | return to -> CompletableFuture.runAsync(() -> { |
|
25 | return to -> CompletableFuture.runAsync(() -> { | |
| 26 | try (var from = new FileInputStream(file); to) { |
|
26 | try (var from = new FileInputStream(file); to) { | |
| 27 | from.transferTo(to); |
|
27 | from.transferTo(to); | |
| 28 | } catch (Exception e) { |
|
28 | } catch (Exception e) { | |
| 29 | // silence! |
|
29 | // silence! | |
| 30 | } |
|
30 | } | |
| 31 | }); |
|
31 | }); | |
| 32 | } |
|
32 | } | |
| 33 |
|
33 | |||
| 34 | public static RedirectFrom stream(final InputStream from) { |
|
34 | public static RedirectFrom stream(final InputStream from) { | |
| 35 | return to -> CompletableFuture.runAsync(() -> { |
|
35 | return to -> CompletableFuture.runAsync(() -> { | |
| 36 | try (from; to) { |
|
36 | try (from; to) { | |
| 37 | from.transferTo(to); |
|
37 | from.transferTo(to); | |
| 38 | } catch (Exception e) { |
|
38 | } catch (Exception e) { | |
| 39 | // silence! |
|
39 | // silence! | |
| 40 | } |
|
40 | } | |
| 41 | }); |
|
41 | }); | |
| 42 | } |
|
42 | } | |
| 43 |
|
43 | |||
| 44 | public static RedirectFrom any(final Object output) { |
|
44 | public static RedirectFrom any(final Object output) { | |
| 45 | if (output instanceof File f) { |
|
45 | if (output instanceof File f) { | |
| 46 | return file(f); |
|
46 | return file(f); | |
| 47 | } else if (output instanceof InputStream stm) { |
|
47 | } else if (output instanceof InputStream stm) { | |
| 48 | return stream(stm); |
|
48 | return stream(stm); | |
| 49 | } else if (output instanceof RedirectFrom self) { |
|
49 | } else if (output instanceof RedirectFrom self) { | |
| 50 | return self; |
|
50 | return self; | |
| 51 | } else { |
|
51 | } else { | |
| 52 | throw new IllegalArgumentException("The specified argument type isn't supported: " + output.getClass()); |
|
52 | throw new IllegalArgumentException("The specified argument type isn't supported: " + output.getClass()); | |
| 53 | } |
|
53 | } | |
| 54 | } |
|
54 | } | |
| 55 |
|
55 | |||
| 56 | } |
|
56 | } | |
| @@ -1,111 +1,111 | |||||
| 1 | package org.implab.gradle.common.exec; |
|
1 | package org.implab.gradle.common.exec.runtime; | |
| 2 |
|
2 | |||
| 3 | import java.io.File; |
|
3 | import java.io.File; | |
| 4 | import java.io.FileOutputStream; |
|
4 | import java.io.FileOutputStream; | |
| 5 | import java.io.InputStream; |
|
5 | import java.io.InputStream; | |
| 6 | import java.io.OutputStream; |
|
6 | import java.io.OutputStream; | |
| 7 | import java.util.Scanner; |
|
7 | import java.util.Scanner; | |
| 8 | import java.util.concurrent.CompletableFuture; |
|
8 | import java.util.concurrent.CompletableFuture; | |
| 9 | import java.util.function.Consumer; |
|
9 | import java.util.function.Consumer; | |
| 10 | import java.util.stream.Collectors; |
|
10 | import java.util.stream.Collectors; | |
| 11 | import java.util.stream.Stream; |
|
11 | import java.util.stream.Stream; | |
| 12 |
|
12 | |||
| 13 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
13 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
| 14 |
|
14 | |||
| 15 | /** |
|
15 | /** | |
| 16 | * Redirection specification for the {@link InputStream}. Redirection is invoked |
|
16 | * Redirection specification for the {@link InputStream}. Redirection is invoked | |
| 17 | * when the {@link InputStream} becomes available, for example, on process |
|
17 | * when the {@link InputStream} becomes available, for example, on process | |
| 18 | * start. Before the process is started the redirection isn't invoked and no |
|
18 | * start. Before the process is started the redirection isn't invoked and no | |
| 19 | * resources are allocated or used. |
|
19 | * resources are allocated or used. | |
| 20 | */ |
|
20 | */ | |
| 21 | @NonNullByDefault |
|
21 | @NonNullByDefault | |
| 22 | public interface RedirectTo { |
|
22 | public interface RedirectTo { | |
| 23 | CompletableFuture<Void> redirect(InputStream from); |
|
23 | CompletableFuture<Void> redirect(InputStream from); | |
| 24 |
|
24 | |||
| 25 | public interface StringConsumer extends Consumer<String> { |
|
25 | public interface StringConsumer extends Consumer<String> { | |
| 26 | default void complete() { |
|
26 | default void complete() { | |
| 27 | } |
|
27 | } | |
| 28 | } |
|
28 | } | |
| 29 |
|
29 | |||
| 30 | /** Creates a redirect to the specified consumer */ |
|
30 | /** Creates a redirect to the specified consumer */ | |
| 31 | public static RedirectTo eachLine(final Consumer<String> consumer) { |
|
31 | public static RedirectTo eachLine(final Consumer<String> consumer) { | |
| 32 | return consumer(new StringConsumer() { |
|
32 | return consumer(new StringConsumer() { | |
| 33 | @Override |
|
33 | @Override | |
| 34 | public void accept(String s) { |
|
34 | public void accept(String s) { | |
| 35 | consumer.accept(s); |
|
35 | consumer.accept(s); | |
| 36 | } |
|
36 | } | |
| 37 | }); |
|
37 | }); | |
| 38 | } |
|
38 | } | |
| 39 |
|
39 | |||
| 40 | public static RedirectTo allText(final Consumer<String> consumer) { |
|
40 | public static RedirectTo allText(final Consumer<String> consumer) { | |
| 41 | return consumer(new StringConsumer() { |
|
41 | return consumer(new StringConsumer() { | |
| 42 | final Stream.Builder<String> builder = Stream.builder(); |
|
42 | final Stream.Builder<String> builder = Stream.builder(); | |
| 43 |
|
43 | |||
| 44 | @Override |
|
44 | @Override | |
| 45 | public void accept(String s) { |
|
45 | public void accept(String s) { | |
| 46 | builder.accept(s); |
|
46 | builder.accept(s); | |
| 47 | } |
|
47 | } | |
| 48 |
|
48 | |||
| 49 | @Override |
|
49 | @Override | |
| 50 | public void complete() { |
|
50 | public void complete() { | |
| 51 | consumer.accept(builder.build().collect(Collectors.joining("\n"))); |
|
51 | consumer.accept(builder.build().collect(Collectors.joining("\n"))); | |
| 52 | } |
|
52 | } | |
| 53 | }); |
|
53 | }); | |
| 54 | } |
|
54 | } | |
| 55 |
|
55 | |||
| 56 | /** Creates a redirect to the specified consumer */ |
|
56 | /** Creates a redirect to the specified consumer */ | |
| 57 | public static RedirectTo consumer(final StringConsumer consumer) { |
|
57 | public static RedirectTo consumer(final StringConsumer consumer) { | |
| 58 | return (src) -> CompletableFuture.runAsync(() -> { |
|
58 | return (src) -> CompletableFuture.runAsync(() -> { | |
| 59 | try (Scanner sc = new Scanner(src)) { |
|
59 | try (Scanner sc = new Scanner(src)) { | |
| 60 | while (sc.hasNextLine()) { |
|
60 | while (sc.hasNextLine()) { | |
| 61 | consumer.accept(sc.nextLine()); |
|
61 | consumer.accept(sc.nextLine()); | |
| 62 | } |
|
62 | } | |
| 63 | consumer.complete(); |
|
63 | consumer.complete(); | |
| 64 | } |
|
64 | } | |
| 65 | }); |
|
65 | }); | |
| 66 | } |
|
66 | } | |
| 67 |
|
67 | |||
| 68 | /** |
|
68 | /** | |
| 69 | * Creates a redirect to the specified file. There will be opened the output |
|
69 | * Creates a redirect to the specified file. There will be opened the output | |
| 70 | * stream in this redirection and original stream will be transferred to this |
|
70 | * stream in this redirection and original stream will be transferred to this | |
| 71 | * this output stream. |
|
71 | * this output stream. | |
| 72 | */ |
|
72 | */ | |
| 73 | public static RedirectTo file(final File file) { |
|
73 | public static RedirectTo file(final File file) { | |
| 74 | return src -> CompletableFuture.runAsync(() -> { |
|
74 | return src -> CompletableFuture.runAsync(() -> { | |
| 75 | try (src; OutputStream out = new FileOutputStream(file)) { |
|
75 | try (src; OutputStream out = new FileOutputStream(file)) { | |
| 76 | src.transferTo(out); |
|
76 | src.transferTo(out); | |
| 77 | } catch (Exception e) { |
|
77 | } catch (Exception e) { | |
| 78 | // silence! |
|
78 | // silence! | |
| 79 | } |
|
79 | } | |
| 80 | }); |
|
80 | }); | |
| 81 | } |
|
81 | } | |
| 82 |
|
82 | |||
| 83 | /** Creates a redirect to the specified output stream. */ |
|
83 | /** Creates a redirect to the specified output stream. */ | |
| 84 | public static RedirectTo stream(final OutputStream dest) { |
|
84 | public static RedirectTo stream(final OutputStream dest) { | |
| 85 | return src -> CompletableFuture.runAsync(() -> { |
|
85 | return src -> CompletableFuture.runAsync(() -> { | |
| 86 | try (dest; src) { |
|
86 | try (dest; src) { | |
| 87 | src.transferTo(dest); |
|
87 | src.transferTo(dest); | |
| 88 | } catch (Exception e) { |
|
88 | } catch (Exception e) { | |
| 89 | // silence! |
|
89 | // silence! | |
| 90 | } |
|
90 | } | |
| 91 | }); |
|
91 | }); | |
| 92 | } |
|
92 | } | |
| 93 |
|
93 | |||
| 94 | /** |
|
94 | /** | |
| 95 | * Creates the redirection to the specified destination, actual type of |
|
95 | * Creates the redirection to the specified destination, actual type of | |
| 96 | * redirection will be determined from the type of the output object. |
|
96 | * redirection will be determined from the type of the output object. | |
| 97 | */ |
|
97 | */ | |
| 98 | public static RedirectTo any(final Object output) { |
|
98 | public static RedirectTo any(final Object output) { | |
| 99 | if (output instanceof StringConsumer fn) { |
|
99 | if (output instanceof StringConsumer fn) { | |
| 100 | return consumer(s -> fn.accept(s)); |
|
100 | return consumer(s -> fn.accept(s)); | |
| 101 | } else if (output instanceof File f) { |
|
101 | } else if (output instanceof File f) { | |
| 102 | return file(f); |
|
102 | return file(f); | |
| 103 | } else if (output instanceof OutputStream stm) { |
|
103 | } else if (output instanceof OutputStream stm) { | |
| 104 | return stream(stm); |
|
104 | return stream(stm); | |
| 105 | } else if (output instanceof RedirectTo self) { |
|
105 | } else if (output instanceof RedirectTo self) { | |
| 106 | return self; |
|
106 | return self; | |
| 107 | } else { |
|
107 | } else { | |
| 108 | throw new IllegalArgumentException("The specified argument type isn't supported: " + output.getClass()); |
|
108 | throw new IllegalArgumentException("The specified argument type isn't supported: " + output.getClass()); | |
| 109 | } |
|
109 | } | |
| 110 | } |
|
110 | } | |
| 111 | } No newline at end of file |
|
111 | } | |
| @@ -1,29 +1,30 | |||||
| 1 | package org.implab.gradle.common.exec; |
|
1 | package org.implab.gradle.common.exec.runtime; | |
| 2 |
|
2 | |||
| 3 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
3 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
|
|
4 | import org.implab.gradle.common.exec.model.CommandSpec; | |||
| 4 |
|
5 | |||
| 5 | @NonNullByDefault |
|
6 | @NonNullByDefault | |
| 6 | public interface Shell { |
|
7 | public interface Shell { | |
| 7 |
|
8 | |||
| 8 | ShellExec create(CommandSpec spec); |
|
9 | ShellExec create(CommandSpec spec); | |
| 9 |
|
10 | |||
| 10 | default ShellTool tool(String executable) { |
|
11 | default ShellTool tool(String executable) { | |
| 11 | return new ShellTool(executable, this); |
|
12 | return new ShellTool(executable, this); | |
| 12 | } |
|
13 | } | |
| 13 |
|
14 | |||
| 14 | /** Creates a new shell to start processes using ProcessBuilder. */ |
|
15 | /** Creates a new shell to start processes using ProcessBuilder. */ | |
| 15 | static Shell system() { |
|
16 | static Shell system() { | |
| 16 | return spec -> new SystemExecBuilder(spec); |
|
17 | return spec -> new SystemExecBuilder(spec); | |
| 17 | } |
|
18 | } | |
| 18 |
|
19 | |||
| 19 | /** Creates a stub shell which echoes the specified command line. */ |
|
20 | /** Creates a stub shell which echoes the specified command line. */ | |
| 20 | static Shell echo() { |
|
21 | static Shell echo() { | |
| 21 | return spec -> new EchoExecBuilder(spec, false); |
|
22 | return spec -> new EchoExecBuilder(spec, false); | |
| 22 | } |
|
23 | } | |
| 23 |
|
24 | |||
| 24 | /** Creates a stub shell which echoes the specified command line. */ |
|
25 | /** Creates a stub shell which echoes the specified command line. */ | |
| 25 | static Shell echoStderr() { |
|
26 | static Shell echoStderr() { | |
| 26 | return spec -> new EchoExecBuilder(spec, false); |
|
27 | return spec -> new EchoExecBuilder(spec, false); | |
| 27 | } |
|
28 | } | |
| 28 |
|
29 | |||
| 29 | } |
|
30 | } | |
| @@ -1,38 +1,38 | |||||
| 1 | package org.implab.gradle.common.exec; |
|
1 | package org.implab.gradle.common.exec.runtime; | |
| 2 |
|
2 | |||
| 3 | import java.io.IOException; |
|
3 | import java.io.IOException; | |
| 4 | import java.util.ArrayList; |
|
4 | import java.util.ArrayList; | |
| 5 | import java.util.List; |
|
5 | import java.util.List; | |
| 6 | import java.util.concurrent.CompletableFuture; |
|
6 | import java.util.concurrent.CompletableFuture; | |
| 7 |
|
7 | |||
| 8 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
8 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
| 9 |
import org.implab.gradle.common. |
|
9 | import org.implab.gradle.common.core.lang.Exceptions; | |
| 10 |
|
10 | |||
| 11 | @NonNullByDefault |
|
11 | @NonNullByDefault | |
| 12 | public interface ShellExec extends PipeBuilder, EnvironmentBuilder { |
|
12 | public interface ShellExec extends PipeBuilder, EnvironmentBuilder { | |
| 13 |
|
13 | |||
| 14 | default CompletableFuture<List<String>> readAllLines() throws IOException { |
|
14 | default CompletableFuture<List<String>> readAllLines() throws IOException { | |
| 15 | List<String> lines = new ArrayList<>(); |
|
15 | List<String> lines = new ArrayList<>(); | |
| 16 | stdout(RedirectTo.consumer(lines::add)); |
|
16 | stdout(RedirectTo.consumer(lines::add)); | |
| 17 |
|
17 | |||
| 18 | return exec() |
|
18 | return exec() | |
| 19 | .thenAccept(Exceptions.unchecked(this::assertExitCode)) |
|
19 | .thenAccept(Exceptions.unchecked(this::assertExitCode)) | |
| 20 | .thenApply(v -> lines); |
|
20 | .thenApply(v -> lines); | |
| 21 | } |
|
21 | } | |
| 22 |
|
22 | |||
| 23 | default CompletableFuture<String> readAllText() throws IOException { |
|
23 | default CompletableFuture<String> readAllText() throws IOException { | |
| 24 | List<String> lines = new ArrayList<>(); |
|
24 | List<String> lines = new ArrayList<>(); | |
| 25 | stdout(RedirectTo.consumer(lines::add)); |
|
25 | stdout(RedirectTo.consumer(lines::add)); | |
| 26 |
|
26 | |||
| 27 | return exec() |
|
27 | return exec() | |
| 28 | .thenAccept(Exceptions.unchecked(this::assertExitCode)) |
|
28 | .thenAccept(Exceptions.unchecked(this::assertExitCode)) | |
| 29 | .thenApply(v -> String.join("\n", lines)); |
|
29 | .thenApply(v -> String.join("\n", lines)); | |
| 30 | } |
|
30 | } | |
| 31 |
|
31 | |||
| 32 | default void assertExitCode(Integer code) throws IOException { |
|
32 | default void assertExitCode(Integer code) throws IOException { | |
| 33 | if (code != 0) |
|
33 | if (code != 0) | |
| 34 | throw new IOException(String.format("The process is terminated with code %d", code)); |
|
34 | throw new IOException(String.format("The process is terminated with code %d", code)); | |
| 35 | } |
|
35 | } | |
| 36 |
|
36 | |||
| 37 | CompletableFuture<Integer> exec() throws IOException; |
|
37 | CompletableFuture<Integer> exec() throws IOException; | |
| 38 | } |
|
38 | } | |
| @@ -1,40 +1,42 | |||||
| 1 | package org.implab.gradle.common.exec; |
|
1 | package org.implab.gradle.common.exec.runtime; | |
| 2 |
|
2 | |||
| 3 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
3 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
| 4 | import org.gradle.api.Action; |
|
4 | import org.gradle.api.Action; | |
|
|
5 | import org.implab.gradle.common.exec.model.CommandArgumentsBuilder; | |||
|
|
6 | import org.implab.gradle.common.exec.model.CommandSpec; | |||
| 5 |
|
7 | |||
| 6 | @NonNullByDefault |
|
8 | @NonNullByDefault | |
| 7 | public class ShellTool { |
|
9 | public class ShellTool { | |
| 8 |
|
10 | |||
| 9 | private final Shell shell; |
|
11 | private final Shell shell; | |
| 10 |
|
12 | |||
| 11 | private final String executable; |
|
13 | private final String executable; | |
| 12 |
|
14 | |||
| 13 | ShellTool(String executable, Shell shell) { |
|
15 | ShellTool(String executable, Shell shell) { | |
| 14 | this.shell = shell; |
|
16 | this.shell = shell; | |
| 15 | this.executable = executable; |
|
17 | this.executable = executable; | |
| 16 | } |
|
18 | } | |
| 17 |
|
19 | |||
| 18 | public ShellExec arguments(String... args) { |
|
20 | public ShellExec arguments(String... args) { | |
| 19 | return shell.create(CommandSpec.builder() |
|
21 | return shell.create(CommandSpec.builder() | |
| 20 | .executable(executable) |
|
22 | .executable(executable) | |
| 21 | .arguments(args) |
|
23 | .arguments(args) | |
| 22 | .build()); |
|
24 | .build()); | |
| 23 | } |
|
25 | } | |
| 24 |
|
26 | |||
| 25 | public ShellExec arguments(Iterable<String> args) { |
|
27 | public ShellExec arguments(Iterable<String> args) { | |
| 26 | return shell.create(CommandSpec.builder() |
|
28 | return shell.create(CommandSpec.builder() | |
| 27 | .executable(executable) |
|
29 | .executable(executable) | |
| 28 | .arguments(args) |
|
30 | .arguments(args) | |
| 29 | .build()); |
|
31 | .build()); | |
| 30 | } |
|
32 | } | |
| 31 |
|
33 | |||
| 32 | public ShellExec arguments(Action<CommandArgumentsBuilder<?>> args) { |
|
34 | public ShellExec arguments(Action<CommandArgumentsBuilder<?>> args) { | |
| 33 | var builder = CommandSpec.builder() |
|
35 | var builder = CommandSpec.builder() | |
| 34 | .executable(executable); |
|
36 | .executable(executable); | |
| 35 | args.execute(builder); |
|
37 | args.execute(builder); | |
| 36 |
|
38 | |||
| 37 | return shell.create(builder.build()); |
|
39 | return shell.create(builder.build()); | |
| 38 | } |
|
40 | } | |
| 39 |
|
41 | |||
| 40 | } |
|
42 | } | |
| @@ -1,52 +1,55 | |||||
| 1 | package org.implab.gradle.common.exec; |
|
1 | package org.implab.gradle.common.exec.runtime; | |
| 2 |
|
2 | |||
| 3 | import java.io.IOException; |
|
3 | import java.io.IOException; | |
| 4 | import java.lang.ProcessBuilder.Redirect; |
|
4 | import java.lang.ProcessBuilder.Redirect; | |
| 5 | import java.util.ArrayList; |
|
5 | import java.util.ArrayList; | |
| 6 | import java.util.concurrent.CompletableFuture; |
|
6 | import java.util.concurrent.CompletableFuture; | |
| 7 |
|
7 | |||
| 8 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
8 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
|
|
9 | import org.implab.gradle.common.exec.model.CommandSpec; | |||
|
|
10 | import org.implab.gradle.common.exec.model.EnvironmentSpec; | |||
|
|
11 | import org.implab.gradle.common.exec.model.PipeSpec; | |||
| 9 |
|
12 | |||
| 10 | @NonNullByDefault |
|
13 | @NonNullByDefault | |
| 11 | class SystemExecBuilder extends AbstractExecBuilder<CommandSpec> { |
|
14 | class SystemExecBuilder extends AbstractExecBuilder<CommandSpec> { | |
| 12 | SystemExecBuilder(CommandSpec command) { |
|
15 | SystemExecBuilder(CommandSpec command) { | |
| 13 | super(command); |
|
16 | super(command); | |
| 14 | } |
|
17 | } | |
| 15 |
|
18 | |||
| 16 | @Override |
|
19 | @Override | |
| 17 | protected CompletableFuture<Integer> startInternal( |
|
20 | protected CompletableFuture<Integer> startInternal( | |
| 18 | CommandSpec command, |
|
21 | CommandSpec command, | |
| 19 | EnvironmentSpec environment, |
|
22 | EnvironmentSpec environment, | |
| 20 | PipeSpec redirect) throws IOException { |
|
23 | PipeSpec redirect) throws IOException { | |
| 21 |
|
24 | |||
| 22 | var builder = new ProcessBuilder(command.commandLine()); |
|
25 | var builder = new ProcessBuilder(command.commandLine()); | |
| 23 |
|
26 | |||
| 24 | environment.workingDirectory().ifPresent(builder::directory); |
|
27 | environment.workingDirectory().ifPresent(builder::directory); | |
| 25 |
|
28 | |||
| 26 | // if the env isn't inherited we need to clear it |
|
29 | // if the env isn't inherited we need to clear it | |
| 27 | if (!environment.inheritEnvironment()) |
|
30 | if (!environment.inheritEnvironment()) | |
| 28 | builder.environment().clear(); |
|
31 | builder.environment().clear(); | |
| 29 |
|
32 | |||
| 30 | builder.environment().putAll(environment.environment()); |
|
33 | builder.environment().putAll(environment.environment()); | |
| 31 |
|
34 | |||
| 32 | var tasks = new ArrayList<CompletableFuture<?>>(); |
|
35 | var tasks = new ArrayList<CompletableFuture<?>>(); | |
| 33 |
|
36 | |||
| 34 | if (!redirect.stdout().isPresent()) |
|
37 | if (!redirect.stdout().isPresent()) | |
| 35 | builder.redirectOutput(Redirect.DISCARD); |
|
38 | builder.redirectOutput(Redirect.DISCARD); | |
| 36 | if (!redirect.stderr().isPresent()) |
|
39 | if (!redirect.stderr().isPresent()) | |
| 37 | builder.redirectError(Redirect.DISCARD); |
|
40 | builder.redirectError(Redirect.DISCARD); | |
| 38 |
|
41 | |||
| 39 | // run process |
|
42 | // run process | |
| 40 | var proc = builder.start(); |
|
43 | var proc = builder.start(); | |
| 41 |
|
44 | |||
| 42 | tasks.add(proc.onExit()); |
|
45 | tasks.add(proc.onExit()); | |
| 43 |
|
46 | |||
| 44 | redirect.stdin().map(from -> from.redirect(proc.getOutputStream())).ifPresent(tasks::add); |
|
47 | redirect.stdin().map(from -> from.redirect(proc.getOutputStream())).ifPresent(tasks::add); | |
| 45 | redirect.stdout().map(to -> to.redirect(proc.getInputStream())).ifPresent(tasks::add); |
|
48 | redirect.stdout().map(to -> to.redirect(proc.getInputStream())).ifPresent(tasks::add); | |
| 46 | redirect.stderr().map(to -> to.redirect(proc.getErrorStream())).ifPresent(tasks::add); |
|
49 | redirect.stderr().map(to -> to.redirect(proc.getErrorStream())).ifPresent(tasks::add); | |
| 47 |
|
50 | |||
| 48 | return CompletableFuture |
|
51 | return CompletableFuture | |
| 49 | .allOf(tasks.toArray(new CompletableFuture<?>[0])) |
|
52 | .allOf(tasks.toArray(new CompletableFuture<?>[0])) | |
| 50 | .thenApply(t -> proc.exitValue()); |
|
53 | .thenApply(t -> proc.exitValue()); | |
| 51 | } |
|
54 | } | |
| 52 | } |
|
55 | } | |
| @@ -1,108 +1,108 | |||||
| 1 | package org.implab.gradle.common.tasks; |
|
1 | package org.implab.gradle.common.exec.tasks; | |
| 2 |
|
2 | |||
| 3 | import java.io.IOException; |
|
3 | import java.io.IOException; | |
| 4 | import java.util.Map; |
|
4 | import java.util.Map; | |
| 5 | import java.util.Optional; |
|
5 | import java.util.Optional; | |
| 6 | import java.util.concurrent.ExecutionException; |
|
6 | import java.util.concurrent.ExecutionException; | |
| 7 |
|
7 | |||
| 8 | import org.gradle.api.DefaultTask; |
|
8 | import org.gradle.api.DefaultTask; | |
| 9 | import org.gradle.api.file.DirectoryProperty; |
|
9 | import org.gradle.api.file.DirectoryProperty; | |
| 10 | import org.gradle.api.provider.MapProperty; |
|
10 | import org.gradle.api.provider.MapProperty; | |
| 11 | import org.gradle.api.provider.Property; |
|
11 | import org.gradle.api.provider.Property; | |
| 12 | import org.gradle.api.tasks.Internal; |
|
12 | import org.gradle.api.tasks.Internal; | |
| 13 | import org.gradle.api.tasks.TaskAction; |
|
13 | import org.gradle.api.tasks.TaskAction; | |
| 14 |
import org.implab.gradle.common. |
|
14 | import org.implab.gradle.common.core.lang.Exceptions; | |
| 15 |
import org.implab.gradle.common. |
|
15 | import org.implab.gradle.common.core.lang.Values; | |
| 16 |
import org.implab.gradle.common. |
|
16 | import org.implab.gradle.common.exec.dsl.RedirectFromSpec; | |
| 17 |
import org.implab.gradle.common. |
|
17 | import org.implab.gradle.common.exec.dsl.RedirectToSpec; | |
| 18 |
import org.implab.gradle.common.exec. |
|
18 | import org.implab.gradle.common.exec.dsl.TaskEnvSpecMixin; | |
| 19 |
import org.implab.gradle.common.exec. |
|
19 | import org.implab.gradle.common.exec.dsl.TaskPipeSpecMixin; | |
| 20 |
import org.implab.gradle.common. |
|
20 | import org.implab.gradle.common.exec.runtime.RedirectTo; | |
| 21 |
import org.implab.gradle.common. |
|
21 | import org.implab.gradle.common.exec.runtime.ShellExec; | |
| 22 |
|
22 | |||
| 23 | public abstract class AbstractShellExecTask |
|
23 | public abstract class AbstractShellExecTask | |
| 24 | extends DefaultTask |
|
24 | extends DefaultTask | |
| 25 | implements TaskPipeSpecMixin, TaskEnvSpecMixin { |
|
25 | implements TaskPipeSpecMixin, TaskEnvSpecMixin { | |
| 26 |
|
26 | |||
| 27 | private final RedirectToSpec redirectStderr = new RedirectToSpec(); |
|
27 | private final RedirectToSpec redirectStderr = new RedirectToSpec(); | |
| 28 |
|
28 | |||
| 29 | private final RedirectToSpec redirectStdout = new RedirectToSpec(); |
|
29 | private final RedirectToSpec redirectStdout = new RedirectToSpec(); | |
| 30 |
|
30 | |||
| 31 | private final RedirectFromSpec redirectStdin = new RedirectFromSpec(); |
|
31 | private final RedirectFromSpec redirectStdin = new RedirectFromSpec(); | |
| 32 |
|
32 | |||
| 33 | @Internal |
|
33 | @Internal | |
| 34 | @Override |
|
34 | @Override | |
| 35 | public abstract Property<Boolean> getInheritEnvironment(); |
|
35 | public abstract Property<Boolean> getInheritEnvironment(); | |
| 36 |
|
36 | |||
| 37 | @Internal |
|
37 | @Internal | |
| 38 | @Override |
|
38 | @Override | |
| 39 | public abstract DirectoryProperty getWorkingDirectory(); |
|
39 | public abstract DirectoryProperty getWorkingDirectory(); | |
| 40 |
|
40 | |||
| 41 | @Internal |
|
41 | @Internal | |
| 42 | @Override |
|
42 | @Override | |
| 43 | public abstract MapProperty<String, String> getEnvironment(); |
|
43 | public abstract MapProperty<String, String> getEnvironment(); | |
| 44 |
|
44 | |||
| 45 | /** |
|
45 | /** | |
| 46 | * STDIN redirection, if not specified, no input will be passed to the command |
|
46 | * STDIN redirection, if not specified, no input will be passed to the command | |
| 47 | */ |
|
47 | */ | |
| 48 | @Internal |
|
48 | @Internal | |
| 49 | @Override |
|
49 | @Override | |
| 50 | public RedirectFromSpec getStdin() { |
|
50 | public RedirectFromSpec getStdin() { | |
| 51 | return redirectStdin; |
|
51 | return redirectStdin; | |
| 52 | } |
|
52 | } | |
| 53 |
|
53 | |||
| 54 | /** |
|
54 | /** | |
| 55 | * STDOUT redirection, if not specified, redirected to logger::info |
|
55 | * STDOUT redirection, if not specified, redirected to logger::info | |
| 56 | */ |
|
56 | */ | |
| 57 | @Internal |
|
57 | @Internal | |
| 58 | @Override |
|
58 | @Override | |
| 59 | public RedirectToSpec getStdout() { |
|
59 | public RedirectToSpec getStdout() { | |
| 60 | return redirectStdout; |
|
60 | return redirectStdout; | |
| 61 | } |
|
61 | } | |
| 62 |
|
62 | |||
| 63 | /** |
|
63 | /** | |
| 64 | * STDERR redirection, if not specified, redirected to logger::error |
|
64 | * STDERR redirection, if not specified, redirected to logger::error | |
| 65 | */ |
|
65 | */ | |
| 66 | @Internal |
|
66 | @Internal | |
| 67 | @Override |
|
67 | @Override | |
| 68 | public RedirectToSpec getStderr() { |
|
68 | public RedirectToSpec getStderr() { | |
| 69 | return redirectStderr; |
|
69 | return redirectStderr; | |
| 70 | } |
|
70 | } | |
| 71 |
|
71 | |||
| 72 | protected abstract ShellExec execBuilder(); |
|
72 | protected abstract ShellExec execBuilder(); | |
| 73 |
|
73 | |||
| 74 | protected Optional<RedirectTo> conventionalStderr() { |
|
74 | protected Optional<RedirectTo> conventionalStderr() { | |
| 75 | return Optional.of(RedirectTo.eachLine(getLogger()::error)); |
|
75 | return Optional.of(RedirectTo.eachLine(getLogger()::error)); | |
| 76 | } |
|
76 | } | |
| 77 |
|
77 | |||
| 78 | protected Optional<RedirectTo> conventionalStdout() { |
|
78 | protected Optional<RedirectTo> conventionalStdout() { | |
| 79 | return Optional.of(RedirectTo.eachLine(getLogger()::info)); |
|
79 | return Optional.of(RedirectTo.eachLine(getLogger()::info)); | |
| 80 | } |
|
80 | } | |
| 81 |
|
81 | |||
| 82 | @TaskAction |
|
82 | @TaskAction | |
| 83 | public final void run() throws IOException, InterruptedException, ExecutionException { |
|
83 | public final void run() throws IOException, InterruptedException, ExecutionException { | |
| 84 | // create new shell process |
|
84 | // create new shell process | |
| 85 | var execBuilder = execBuilder(); |
|
85 | var execBuilder = execBuilder(); | |
| 86 |
|
86 | |||
| 87 | // configure environment |
|
87 | // configure environment | |
| 88 | execBuilder |
|
88 | execBuilder | |
| 89 | .workingDirectory(Values.optional(getWorkingDirectory().getAsFile())) |
|
89 | .workingDirectory(Values.optional(getWorkingDirectory().getAsFile())) | |
| 90 | .environment(getEnvironment().getOrElse(Map.of())); |
|
90 | .environment(getEnvironment().getOrElse(Map.of())); | |
| 91 |
|
91 | |||
| 92 | // configure redirects |
|
92 | // configure redirects | |
| 93 | execBuilder |
|
93 | execBuilder | |
| 94 | .stdout(getStdout().getRedirection().or(this::conventionalStdout)) |
|
94 | .stdout(getStdout().getRedirection().or(this::conventionalStdout)) | |
| 95 | .stderr(getStderr().getRedirection().or(this::conventionalStderr)) |
|
95 | .stderr(getStderr().getRedirection().or(this::conventionalStderr)) | |
| 96 | .stdin(getStdin().getRedirection()); |
|
96 | .stdin(getStdin().getRedirection()); | |
| 97 |
|
97 | |||
| 98 | // execute |
|
98 | // execute | |
| 99 | execBuilder.exec() |
|
99 | execBuilder.exec() | |
| 100 | .thenAccept(Exceptions.unchecked(this::assertExitCode)) |
|
100 | .thenAccept(Exceptions.unchecked(this::assertExitCode)) | |
| 101 | .join(); |
|
101 | .join(); | |
| 102 | } |
|
102 | } | |
| 103 |
|
103 | |||
| 104 | protected void assertExitCode(Integer code) throws IOException { |
|
104 | protected void assertExitCode(Integer code) throws IOException { | |
| 105 | if (code != 0) |
|
105 | if (code != 0) | |
| 106 | throw new IOException(String.format("The process is terminated with code %s", code)); |
|
106 | throw new IOException(String.format("The process is terminated with code %s", code)); | |
| 107 | } |
|
107 | } | |
| 108 | } No newline at end of file |
|
108 | } | |
| @@ -1,31 +1,31 | |||||
| 1 | package org.implab.gradle.common.tasks; |
|
1 | package org.implab.gradle.common.exec.tasks; | |
| 2 |
|
2 | |||
| 3 | import org.gradle.api.provider.ListProperty; |
|
3 | import org.gradle.api.provider.ListProperty; | |
| 4 | import org.gradle.api.provider.Property; |
|
4 | import org.gradle.api.provider.Property; | |
| 5 | import org.gradle.api.tasks.Internal; |
|
5 | import org.gradle.api.tasks.Internal; | |
| 6 | import org.implab.gradle.common.dsl.TaskCommandSpecMixin; |
|
6 | import org.implab.gradle.common.exec.dsl.TaskCommandSpecMixin; | |
| 7 | import org.implab.gradle.common.exec.CommandSpec; |
|
7 | import org.implab.gradle.common.exec.model.CommandSpec; | |
| 8 | import org.implab.gradle.common.exec.Shell; |
|
8 | import org.implab.gradle.common.exec.runtime.Shell; | |
| 9 | import org.implab.gradle.common.exec.ShellExec; |
|
9 | import org.implab.gradle.common.exec.runtime.ShellExec; | |
| 10 |
|
10 | |||
| 11 | public abstract class ShellExecTask |
|
11 | public abstract class ShellExecTask | |
| 12 | extends AbstractShellExecTask |
|
12 | extends AbstractShellExecTask | |
| 13 | implements TaskCommandSpecMixin { |
|
13 | implements TaskCommandSpecMixin { | |
| 14 |
|
14 | |||
| 15 | @Internal |
|
15 | @Internal | |
| 16 | public abstract Property<Shell> getShell(); |
|
16 | public abstract Property<Shell> getShell(); | |
| 17 |
|
17 | |||
| 18 | @Internal |
|
18 | @Internal | |
| 19 | @Override |
|
19 | @Override | |
| 20 | public abstract ListProperty<String> getCommandLine(); |
|
20 | public abstract ListProperty<String> getCommandLine(); | |
| 21 |
|
21 | |||
| 22 |
|
22 | |||
| 23 | @Override |
|
23 | @Override | |
| 24 | protected ShellExec execBuilder() { |
|
24 | protected ShellExec execBuilder() { | |
| 25 | return getShell().get() |
|
25 | return getShell().get() | |
| 26 | .create(CommandSpec.builder() |
|
26 | .create(CommandSpec.builder() | |
| 27 | .commandLine(getCommandLine().get()) |
|
27 | .commandLine(getCommandLine().get()) | |
| 28 | .build()); |
|
28 | .build()); | |
| 29 | } |
|
29 | } | |
| 30 |
|
30 | |||
| 31 | } |
|
31 | } | |
| @@ -1,142 +1,142 | |||||
| 1 | package org.implab.gradle.common.json; |
|
1 | package org.implab.gradle.common.json.core; | |
| 2 |
|
2 | |||
| 3 | import java.io.File; |
|
3 | import java.io.File; | |
| 4 | import java.io.IOException; |
|
4 | import java.io.IOException; | |
| 5 | import java.util.Map; |
|
5 | import java.util.Map; | |
| 6 | import java.util.function.Consumer; |
|
6 | import java.util.function.Consumer; | |
| 7 | import java.util.function.Function; |
|
7 | import java.util.function.Function; | |
| 8 |
|
8 | |||
| 9 |
import org.implab.gradle.common. |
|
9 | import org.implab.gradle.common.core.lang.LazyValue; | |
| 10 |
|
10 | |||
| 11 | import com.fasterxml.jackson.annotation.JsonInclude.Include; |
|
11 | import com.fasterxml.jackson.annotation.JsonInclude.Include; | |
| 12 | import com.fasterxml.jackson.core.JsonProcessingException; |
|
12 | import com.fasterxml.jackson.core.JsonProcessingException; | |
| 13 | import com.fasterxml.jackson.core.type.TypeReference; |
|
13 | import com.fasterxml.jackson.core.type.TypeReference; | |
| 14 | import com.fasterxml.jackson.core.util.DefaultIndenter; |
|
14 | import com.fasterxml.jackson.core.util.DefaultIndenter; | |
| 15 | import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; |
|
15 | import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; | |
| 16 | import com.fasterxml.jackson.databind.JsonMappingException; |
|
16 | import com.fasterxml.jackson.databind.JsonMappingException; | |
| 17 | import com.fasterxml.jackson.databind.ObjectMapper; |
|
17 | import com.fasterxml.jackson.databind.ObjectMapper; | |
| 18 | import com.fasterxml.jackson.databind.SerializationFeature; |
|
18 | import com.fasterxml.jackson.databind.SerializationFeature; | |
| 19 | import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; |
|
19 | import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; | |
| 20 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; |
|
20 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; | |
| 21 |
|
21 | |||
| 22 | public class Json { |
|
22 | public class Json { | |
| 23 |
|
23 | |||
| 24 | private final static LazyValue<ObjectMapper> mapper = new LazyValue<>(Json::createMapper); |
|
24 | private final static LazyValue<ObjectMapper> mapper = new LazyValue<>(Json::createMapper); | |
| 25 |
|
25 | |||
| 26 | private static ObjectMapper createMapper() { |
|
26 | private static ObjectMapper createMapper() { | |
| 27 | DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter(); |
|
27 | DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter(); | |
| 28 | prettyPrinter.indentArraysWith(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE); |
|
28 | prettyPrinter.indentArraysWith(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE); | |
| 29 |
|
29 | |||
| 30 | return new ObjectMapper() |
|
30 | return new ObjectMapper() | |
| 31 | .registerModule(new Jdk8Module()) |
|
31 | .registerModule(new Jdk8Module()) | |
| 32 | .registerModule(new JavaTimeModule()) |
|
32 | .registerModule(new JavaTimeModule()) | |
| 33 | .setSerializationInclusion(Include.NON_EMPTY) |
|
33 | .setSerializationInclusion(Include.NON_EMPTY) | |
| 34 | .enable(SerializationFeature.INDENT_OUTPUT) |
|
34 | .enable(SerializationFeature.INDENT_OUTPUT) | |
| 35 | .setDefaultPrettyPrinter(prettyPrinter); |
|
35 | .setDefaultPrettyPrinter(prettyPrinter); | |
| 36 | } |
|
36 | } | |
| 37 |
|
37 | |||
| 38 | public static ObjectMapper jsonMapper() { |
|
38 | public static ObjectMapper jsonMapper() { | |
| 39 | return mapper.get(); |
|
39 | return mapper.get(); | |
| 40 | } |
|
40 | } | |
| 41 |
|
41 | |||
| 42 | public static Consumer<Object> fileWriter(File file) { |
|
42 | public static Consumer<Object> fileWriter(File file) { | |
| 43 | return value -> { |
|
43 | return value -> { | |
| 44 | try { |
|
44 | try { | |
| 45 | jsonMapper().writeValue(file, value); |
|
45 | jsonMapper().writeValue(file, value); | |
| 46 | } catch (IOException e) { |
|
46 | } catch (IOException e) { | |
| 47 | throw new SerializationException(e); |
|
47 | throw new SerializationException(e); | |
| 48 | } |
|
48 | } | |
| 49 | }; |
|
49 | }; | |
| 50 | } |
|
50 | } | |
| 51 |
|
51 | |||
| 52 | public static void write(File file, Object value) { |
|
52 | public static void write(File file, Object value) { | |
| 53 | try { |
|
53 | try { | |
| 54 | jsonMapper().writeValue(file, value); |
|
54 | jsonMapper().writeValue(file, value); | |
| 55 | } catch (IOException e) { |
|
55 | } catch (IOException e) { | |
| 56 | throw new SerializationException(e); |
|
56 | throw new SerializationException(e); | |
| 57 | } |
|
57 | } | |
| 58 | } |
|
58 | } | |
| 59 |
|
59 | |||
| 60 | public static String stringify(Object value) { |
|
60 | public static String stringify(Object value) { | |
| 61 | try { |
|
61 | try { | |
| 62 | return jsonMapper().writeValueAsString(value); |
|
62 | return jsonMapper().writeValueAsString(value); | |
| 63 | } catch (JsonProcessingException e) { |
|
63 | } catch (JsonProcessingException e) { | |
| 64 | throw new SerializationException(e); |
|
64 | throw new SerializationException(e); | |
| 65 | } |
|
65 | } | |
| 66 | } |
|
66 | } | |
| 67 |
|
67 | |||
| 68 | public static <T> T parse(String json, Class<T> clazz) { |
|
68 | public static <T> T parse(String json, Class<T> clazz) { | |
| 69 | try { |
|
69 | try { | |
| 70 | return jsonMapper().readValue(json, clazz); |
|
70 | return jsonMapper().readValue(json, clazz); | |
| 71 | } catch (JsonProcessingException e) { |
|
71 | } catch (JsonProcessingException e) { | |
| 72 | throw new SerializationException(e); |
|
72 | throw new SerializationException(e); | |
| 73 | } |
|
73 | } | |
| 74 | } |
|
74 | } | |
| 75 |
|
75 | |||
| 76 | public static <T> T parse(String json, TypeReference<T> type) { |
|
76 | public static <T> T parse(String json, TypeReference<T> type) { | |
| 77 | try { |
|
77 | try { | |
| 78 | return jsonMapper().readValue(json, type); |
|
78 | return jsonMapper().readValue(json, type); | |
| 79 | } catch (JsonProcessingException e) { |
|
79 | } catch (JsonProcessingException e) { | |
| 80 | throw new SerializationException(e); |
|
80 | throw new SerializationException(e); | |
| 81 | } |
|
81 | } | |
| 82 | } |
|
82 | } | |
| 83 |
|
83 | |||
| 84 | public static <V> Map<String, V> parseMap(String json, Class<V> clazz) { |
|
84 | public static <V> Map<String, V> parseMap(String json, Class<V> clazz) { | |
| 85 | return parse(json, new TypeReference<>() { |
|
85 | return parse(json, new TypeReference<>() { | |
| 86 | }); |
|
86 | }); | |
| 87 | } |
|
87 | } | |
| 88 |
|
88 | |||
| 89 | public static <V> Function<String, Map<String, V>> mapParser(Class<V> clazz) { |
|
89 | public static <V> Function<String, Map<String, V>> mapParser(Class<V> clazz) { | |
| 90 | return json -> parseMap(json, clazz); |
|
90 | return json -> parseMap(json, clazz); | |
| 91 | } |
|
91 | } | |
| 92 |
|
92 | |||
| 93 | public static <T> T read(File file, TypeReference<T> type) { |
|
93 | public static <T> T read(File file, TypeReference<T> type) { | |
| 94 | try { |
|
94 | try { | |
| 95 | return jsonMapper().readValue(file, type); |
|
95 | return jsonMapper().readValue(file, type); | |
| 96 | } catch (IOException e) { |
|
96 | } catch (IOException e) { | |
| 97 | throw new SerializationException(e); |
|
97 | throw new SerializationException(e); | |
| 98 | } |
|
98 | } | |
| 99 | } |
|
99 | } | |
| 100 |
|
100 | |||
| 101 | public static <V> Map<String, V> readMap(File file, Class<V> clazz) { |
|
101 | public static <V> Map<String, V> readMap(File file, Class<V> clazz) { | |
| 102 | return read(file, new TypeReference<>() { |
|
102 | return read(file, new TypeReference<>() { | |
| 103 | }); |
|
103 | }); | |
| 104 | } |
|
104 | } | |
| 105 |
|
105 | |||
| 106 | public static <T> T updateValue(T target, Object source) { |
|
106 | public static <T> T updateValue(T target, Object source) { | |
| 107 | try { |
|
107 | try { | |
| 108 | return jsonMapper().updateValue(target, source); |
|
108 | return jsonMapper().updateValue(target, source); | |
| 109 | } catch (JsonMappingException e) { |
|
109 | } catch (JsonMappingException e) { | |
| 110 | throw new SerializationException(e); |
|
110 | throw new SerializationException(e); | |
| 111 | } |
|
111 | } | |
| 112 | } |
|
112 | } | |
| 113 |
|
113 | |||
| 114 | public static <T> T convertValue(Object source, Class<T> clazz) { |
|
114 | public static <T> T convertValue(Object source, Class<T> clazz) { | |
| 115 | var mapper = jsonMapper(); |
|
115 | var mapper = jsonMapper(); | |
| 116 | var buf = mapper.getSerializerProvider().bufferForValueConversion(jsonMapper()); |
|
116 | var buf = mapper.getSerializerProvider().bufferForValueConversion(jsonMapper()); | |
| 117 |
|
117 | |||
| 118 | try { |
|
118 | try { | |
| 119 | mapper.writeValue(buf, source); |
|
119 | mapper.writeValue(buf, source); | |
| 120 | return mapper.readValue(buf.asParser(), clazz); |
|
120 | return mapper.readValue(buf.asParser(), clazz); | |
| 121 | } catch (IOException e) { |
|
121 | } catch (IOException e) { | |
| 122 | throw new SerializationException(e); |
|
122 | throw new SerializationException(e); | |
| 123 | } |
|
123 | } | |
| 124 | } |
|
124 | } | |
| 125 |
|
125 | |||
| 126 | public static class SerializationException extends RuntimeException { |
|
126 | public static class SerializationException extends RuntimeException { | |
| 127 | public SerializationException() { |
|
127 | public SerializationException() { | |
| 128 | } |
|
128 | } | |
| 129 |
|
129 | |||
| 130 | public SerializationException(String message) { |
|
130 | public SerializationException(String message) { | |
| 131 | super(message); |
|
131 | super(message); | |
| 132 | } |
|
132 | } | |
| 133 |
|
133 | |||
| 134 | public SerializationException(Throwable cause) { |
|
134 | public SerializationException(Throwable cause) { | |
| 135 | super(cause); |
|
135 | super(cause); | |
| 136 | } |
|
136 | } | |
| 137 |
|
137 | |||
| 138 | public SerializationException(String message, Throwable cause) { |
|
138 | public SerializationException(String message, Throwable cause) { | |
| 139 | super(message, cause); |
|
139 | super(message, cause); | |
| 140 | } |
|
140 | } | |
| 141 | } |
|
141 | } | |
| 142 | } |
|
142 | } | |
| @@ -1,66 +1,66 | |||||
| 1 | package org.implab.gradle.common.json; |
|
1 | package org.implab.gradle.common.json.core; | |
| 2 |
|
2 | |||
| 3 | import java.util.Arrays; |
|
3 | import java.util.Arrays; | |
| 4 | import java.util.LinkedHashSet; |
|
4 | import java.util.LinkedHashSet; | |
| 5 | import java.util.Set; |
|
5 | import java.util.Set; | |
| 6 | import java.util.function.Predicate; |
|
6 | import java.util.function.Predicate; | |
| 7 |
|
7 | |||
| 8 | import org.gradle.api.Action; |
|
8 | import org.gradle.api.Action; | |
| 9 |
import org.implab.gradle.common. |
|
9 | import org.implab.gradle.common.core.lang.Closures; | |
| 10 |
|
10 | |||
| 11 | import groovy.lang.Closure; |
|
11 | import groovy.lang.Closure; | |
| 12 |
|
12 | |||
| 13 | /** |
|
13 | /** | |
| 14 | * ΠΡΠΎΡΡΠ°Ρ include/exclude-ΠΌΠ°ΡΠΊΠ° ΠΏΠΎ ΠΊΠ»ΡΡΠ°ΠΌ Π²Π΅ΡΡ Π½Π΅Π³ΠΎ ΡΡΠΎΠ²Π½Ρ. |
|
14 | * ΠΡΠΎΡΡΠ°Ρ include/exclude-ΠΌΠ°ΡΠΊΠ° ΠΏΠΎ ΠΊΠ»ΡΡΠ°ΠΌ Π²Π΅ΡΡ Π½Π΅Π³ΠΎ ΡΡΠΎΠ²Π½Ρ. | |
| 15 | */ |
|
15 | */ | |
| 16 | public class MapImportSpec { |
|
16 | public class MapImportSpec { | |
| 17 |
|
17 | |||
| 18 | private final Set<String> includes = new LinkedHashSet<>(); |
|
18 | private final Set<String> includes = new LinkedHashSet<>(); | |
| 19 | private final Set<String> excludes = new LinkedHashSet<>(); |
|
19 | private final Set<String> excludes = new LinkedHashSet<>(); | |
| 20 |
|
20 | |||
| 21 | public void include(String... keys) { |
|
21 | public void include(String... keys) { | |
| 22 | includes.addAll(Arrays.asList(keys)); |
|
22 | includes.addAll(Arrays.asList(keys)); | |
| 23 | } |
|
23 | } | |
| 24 |
|
24 | |||
| 25 | public void exclude(String... keys) { |
|
25 | public void exclude(String... keys) { | |
| 26 | excludes.addAll(Arrays.asList(keys)); |
|
26 | excludes.addAll(Arrays.asList(keys)); | |
| 27 | } |
|
27 | } | |
| 28 |
|
28 | |||
| 29 | public Set<String> getIncludes() { |
|
29 | public Set<String> getIncludes() { | |
| 30 | return includes; |
|
30 | return includes; | |
| 31 | } |
|
31 | } | |
| 32 |
|
32 | |||
| 33 | public Set<String> getExcludes() { |
|
33 | public Set<String> getExcludes() { | |
| 34 | return excludes; |
|
34 | return excludes; | |
| 35 | } |
|
35 | } | |
| 36 |
|
36 | |||
| 37 | public boolean hasIncludes() { |
|
37 | public boolean hasIncludes() { | |
| 38 | return !includes.isEmpty(); |
|
38 | return !includes.isEmpty(); | |
| 39 | } |
|
39 | } | |
| 40 |
|
40 | |||
| 41 | public boolean isExcluded(String key) { |
|
41 | public boolean isExcluded(String key) { | |
| 42 | return excludes.contains(key); |
|
42 | return excludes.contains(key); | |
| 43 | } |
|
43 | } | |
| 44 |
|
44 | |||
| 45 | public boolean isIncluded(String key) { |
|
45 | public boolean isIncluded(String key) { | |
| 46 | return !hasIncludes() || includes.contains(key); |
|
46 | return !hasIncludes() || includes.contains(key); | |
| 47 | } |
|
47 | } | |
| 48 |
|
48 | |||
| 49 | public boolean matches(String key) { |
|
49 | public boolean matches(String key) { | |
| 50 | return isIncluded(key) && !isExcluded(key); |
|
50 | return isIncluded(key) && !isExcluded(key); | |
| 51 | } |
|
51 | } | |
| 52 |
|
52 | |||
| 53 | public Predicate<String> toPredicate() { |
|
53 | public Predicate<String> toPredicate() { | |
| 54 | return this::matches; |
|
54 | return this::matches; | |
| 55 | } |
|
55 | } | |
| 56 |
|
56 | |||
| 57 | public static Predicate<String> buildPredicate(Action<? super MapImportSpec> action) { |
|
57 | public static Predicate<String> buildPredicate(Action<? super MapImportSpec> action) { | |
| 58 | var spec = new MapImportSpec(); |
|
58 | var spec = new MapImportSpec(); | |
| 59 | action.execute(spec); |
|
59 | action.execute(spec); | |
| 60 | return spec.toPredicate(); |
|
60 | return spec.toPredicate(); | |
| 61 | } |
|
61 | } | |
| 62 |
|
62 | |||
| 63 | public static Predicate<String> buildPredicate(Closure<?> closure) { |
|
63 | public static Predicate<String> buildPredicate(Closure<?> closure) { | |
| 64 | return buildPredicate(Closures.action(closure)); |
|
64 | return buildPredicate(Closures.action(closure)); | |
| 65 | } |
|
65 | } | |
| 66 | } No newline at end of file |
|
66 | } | |
| @@ -1,29 +1,29 | |||||
| 1 | package org.implab.gradle.common.json; |
|
1 | package org.implab.gradle.common.json.dsl; | |
| 2 |
|
2 | |||
| 3 |
|
3 | |||
| 4 | import org.gradle.api.provider.Provider; |
|
4 | import org.gradle.api.provider.Provider; | |
| 5 |
|
5 | |||
| 6 | import java.util.ArrayList; |
|
6 | import java.util.ArrayList; | |
| 7 | import java.util.Collections; |
|
7 | import java.util.Collections; | |
| 8 | import java.util.List; |
|
8 | import java.util.List; | |
| 9 |
|
9 | |||
| 10 |
|
10 | |||
| 11 | public class DefaultJsonArraySpec implements JsonArraySpec { |
|
11 | public class DefaultJsonArraySpec implements JsonArraySpec { | |
| 12 |
|
12 | |||
| 13 | private final List<Object> values = new ArrayList<>(); |
|
13 | private final List<Object> values = new ArrayList<>(); | |
| 14 |
|
14 | |||
| 15 | @Override |
|
15 | @Override | |
| 16 | public void add(Object value) { |
|
16 | public void add(Object value) { | |
| 17 | if (value instanceof Provider<?>) { |
|
17 | if (value instanceof Provider<?>) { | |
| 18 | throw new IllegalArgumentException( |
|
18 | throw new IllegalArgumentException( | |
| 19 | "Providers are not allowed inside JSON arrays; " + |
|
19 | "Providers are not allowed inside JSON arrays; " + | |
| 20 | "use top-level metadata.set(\"key\", provider) instead." |
|
20 | "use top-level metadata.set(\"key\", provider) instead." | |
| 21 | ); |
|
21 | ); | |
| 22 | } |
|
22 | } | |
| 23 | values.add(value); |
|
23 | values.add(value); | |
| 24 | } |
|
24 | } | |
| 25 |
|
25 | |||
| 26 | public List<Object> toList() { |
|
26 | public List<Object> toList() { | |
| 27 | return Collections.unmodifiableList(values); |
|
27 | return Collections.unmodifiableList(values); | |
| 28 | } |
|
28 | } | |
| 29 | } No newline at end of file |
|
29 | } | |
| @@ -1,26 +1,26 | |||||
| 1 | package org.implab.gradle.common.json; |
|
1 | package org.implab.gradle.common.json.dsl; | |
| 2 |
|
2 | |||
| 3 | import org.gradle.api.provider.Provider; |
|
3 | import org.gradle.api.provider.Provider; | |
| 4 |
|
4 | |||
| 5 | import java.util.Collections; |
|
5 | import java.util.Collections; | |
| 6 | import java.util.LinkedHashMap; |
|
6 | import java.util.LinkedHashMap; | |
| 7 | import java.util.Map; |
|
7 | import java.util.Map; | |
| 8 |
|
8 | |||
| 9 | public class DefaultJsonObjectSpec implements GroovyObjectSpec { |
|
9 | public class DefaultJsonObjectSpec implements GroovyObjectSpec { | |
| 10 |
|
10 | |||
| 11 | private final Map<String, Object> values = new LinkedHashMap<>(); |
|
11 | private final Map<String, Object> values = new LinkedHashMap<>(); | |
| 12 |
|
12 | |||
| 13 | @Override |
|
13 | @Override | |
| 14 | public void set(String key, Object value) { |
|
14 | public void set(String key, Object value) { | |
| 15 | if (value instanceof Provider<?>) { |
|
15 | if (value instanceof Provider<?>) { | |
| 16 | throw new IllegalArgumentException( |
|
16 | throw new IllegalArgumentException( | |
| 17 | "Providers are not allowed inside nested JSON objects; " + |
|
17 | "Providers are not allowed inside nested JSON objects; " + | |
| 18 | "use top-level metadata.set(\"" + key + "\", provider) instead."); |
|
18 | "use top-level metadata.set(\"" + key + "\", provider) instead."); | |
| 19 | } |
|
19 | } | |
| 20 | values.put(key, value); |
|
20 | values.put(key, value); | |
| 21 | } |
|
21 | } | |
| 22 |
|
22 | |||
| 23 | public Map<String, Object> toMap() { |
|
23 | public Map<String, Object> toMap() { | |
| 24 | return Collections.unmodifiableMap(values); |
|
24 | return Collections.unmodifiableMap(values); | |
| 25 | } |
|
25 | } | |
| 26 | } No newline at end of file |
|
26 | } | |
| @@ -1,38 +1,38 | |||||
| 1 | package org.implab.gradle.common.json; |
|
1 | package org.implab.gradle.common.json.dsl; | |
| 2 |
|
2 | |||
| 3 | import java.util.Arrays; |
|
3 | import java.util.Arrays; | |
| 4 |
|
4 | |||
| 5 | import groovy.lang.Closure; |
|
5 | import groovy.lang.Closure; | |
| 6 | import groovy.lang.MissingMethodException; |
|
6 | import groovy.lang.MissingMethodException; | |
| 7 |
|
7 | |||
| 8 | public interface GroovyObjectSpec extends JsonObjectSpec { |
|
8 | public interface GroovyObjectSpec extends JsonObjectSpec { | |
| 9 |
|
9 | |||
| 10 | default void propertyMissing(String name, Object value) { |
|
10 | default void propertyMissing(String name, Object value) { | |
| 11 | set(name, value); |
|
11 | set(name, value); | |
| 12 | } |
|
12 | } | |
| 13 |
|
13 | |||
| 14 | default Object methodMissing(String name, Object args) { |
|
14 | default Object methodMissing(String name, Object args) { | |
| 15 | Object[] arr = (Object[]) args; |
|
15 | Object[] arr = (Object[]) args; | |
| 16 |
|
16 | |||
| 17 | // author { ... }, repository { ... } |
|
17 | // author { ... }, repository { ... } | |
| 18 | if (arr.length == 1 && arr[0] instanceof Closure<?>) { |
|
18 | if (arr.length == 1 && arr[0] instanceof Closure<?>) { | |
| 19 | DefaultJsonObjectSpec spec = new DefaultJsonObjectSpec(); |
|
19 | DefaultJsonObjectSpec spec = new DefaultJsonObjectSpec(); | |
| 20 | Closure<?> cl = (Closure<?>) arr[0]; |
|
20 | Closure<?> cl = (Closure<?>) arr[0]; | |
| 21 | cl.setDelegate(spec); |
|
21 | cl.setDelegate(spec); | |
| 22 | cl.setResolveStrategy(Closure.DELEGATE_FIRST); |
|
22 | cl.setResolveStrategy(Closure.DELEGATE_FIRST); | |
| 23 | cl.call(); |
|
23 | cl.call(); | |
| 24 | set(name, spec.toMap()); |
|
24 | set(name, spec.toMap()); | |
| 25 | return null; |
|
25 | return null; | |
| 26 | } else { |
|
26 | } else { | |
| 27 | boolean hasClosure = Arrays.stream(arr) |
|
27 | boolean hasClosure = Arrays.stream(arr) | |
| 28 | .anyMatch(a -> a instanceof Closure<?>); |
|
28 | .anyMatch(a -> a instanceof Closure<?>); | |
| 29 |
|
29 | |||
| 30 | if (!hasClosure) { |
|
30 | if (!hasClosure) { | |
| 31 | set(name, Arrays.asList(arr)); |
|
31 | set(name, Arrays.asList(arr)); | |
| 32 | return null; |
|
32 | return null; | |
| 33 | } |
|
33 | } | |
| 34 | } |
|
34 | } | |
| 35 |
|
35 | |||
| 36 | throw new MissingMethodException(name, getClass(), arr); |
|
36 | throw new MissingMethodException(name, getClass(), arr); | |
| 37 | } |
|
37 | } | |
| 38 | } |
|
38 | } | |
| @@ -1,20 +1,20 | |||||
| 1 | package org.implab.gradle.common.json; |
|
1 | package org.implab.gradle.common.json.dsl; | |
| 2 |
|
2 | |||
| 3 | import org.gradle.api.Action; |
|
3 | import org.gradle.api.Action; | |
| 4 |
|
4 | |||
| 5 | public interface JsonArraySpec { |
|
5 | public interface JsonArraySpec { | |
| 6 |
|
6 | |||
| 7 | void add(Object value); |
|
7 | void add(Object value); | |
| 8 |
|
8 | |||
| 9 | default void obj(Action<? super JsonObjectSpec> action) { |
|
9 | default void obj(Action<? super JsonObjectSpec> action) { | |
| 10 | DefaultJsonObjectSpec child = new DefaultJsonObjectSpec(); |
|
10 | DefaultJsonObjectSpec child = new DefaultJsonObjectSpec(); | |
| 11 | action.execute(child); |
|
11 | action.execute(child); | |
| 12 | add(child.toMap()); |
|
12 | add(child.toMap()); | |
| 13 | } |
|
13 | } | |
| 14 |
|
14 | |||
| 15 | default void arr(Action<? super JsonArraySpec> action) { |
|
15 | default void arr(Action<? super JsonArraySpec> action) { | |
| 16 | DefaultJsonArraySpec child = new DefaultJsonArraySpec(); |
|
16 | DefaultJsonArraySpec child = new DefaultJsonArraySpec(); | |
| 17 | action.execute(child); |
|
17 | action.execute(child); | |
| 18 | add(child.toList()); |
|
18 | add(child.toList()); | |
| 19 | } |
|
19 | } | |
| 20 | } No newline at end of file |
|
20 | } | |
| @@ -1,23 +1,24 | |||||
| 1 | package org.implab.gradle.common.json; |
|
1 | package org.implab.gradle.common.json.dsl; | |
| 2 |
|
2 | |||
| 3 | import java.io.File; |
|
3 | import java.io.File; | |
| 4 |
|
4 | |||
| 5 | import org.gradle.api.Action; |
|
5 | import org.gradle.api.Action; | |
| 6 | import org.gradle.api.file.RegularFile; |
|
6 | import org.gradle.api.file.RegularFile; | |
| 7 | import org.gradle.api.provider.Provider; |
|
7 | import org.gradle.api.provider.Provider; | |
|
|
8 | import org.implab.gradle.common.json.core.MapImportSpec; | |||
| 8 |
|
9 | |||
| 9 | public interface JsonMapSpec extends GroovyObjectSpec { |
|
10 | public interface JsonMapSpec extends GroovyObjectSpec { | |
| 10 | void from(File file, Action<? super MapImportSpec> action); |
|
11 | void from(File file, Action<? super MapImportSpec> action); | |
| 11 |
|
12 | |||
| 12 | default void from(File file) { |
|
13 | default void from(File file) { | |
| 13 | from(file, spec -> { |
|
14 | from(file, spec -> { | |
| 14 | }); |
|
15 | }); | |
| 15 | } |
|
16 | } | |
| 16 |
|
17 | |||
| 17 | void from(Provider<RegularFile> fileProvider, Action<? super MapImportSpec> action); |
|
18 | void from(Provider<RegularFile> fileProvider, Action<? super MapImportSpec> action); | |
| 18 |
|
19 | |||
| 19 | default void from(Provider<RegularFile> fileProvider) { |
|
20 | default void from(Provider<RegularFile> fileProvider) { | |
| 20 | from(fileProvider, spec -> { |
|
21 | from(fileProvider, spec -> { | |
| 21 | }); |
|
22 | }); | |
| 22 | } |
|
23 | } | |
| 23 | } |
|
24 | } | |
| @@ -1,20 +1,20 | |||||
| 1 | package org.implab.gradle.common.json; |
|
1 | package org.implab.gradle.common.json.dsl; | |
| 2 |
|
2 | |||
| 3 | import org.gradle.api.Action; |
|
3 | import org.gradle.api.Action; | |
| 4 |
|
4 | |||
| 5 | public interface JsonObjectSpec { |
|
5 | public interface JsonObjectSpec { | |
| 6 |
|
6 | |||
| 7 | void set(String key, Object value); |
|
7 | void set(String key, Object value); | |
| 8 |
|
8 | |||
| 9 | default void obj(String key, Action<? super JsonObjectSpec> action) { |
|
9 | default void obj(String key, Action<? super JsonObjectSpec> action) { | |
| 10 | DefaultJsonObjectSpec child = new DefaultJsonObjectSpec(); |
|
10 | DefaultJsonObjectSpec child = new DefaultJsonObjectSpec(); | |
| 11 | action.execute(child); |
|
11 | action.execute(child); | |
| 12 | set(key, child.toMap()); |
|
12 | set(key, child.toMap()); | |
| 13 | } |
|
13 | } | |
| 14 |
|
14 | |||
| 15 | default void arr(String key, Action<? super JsonArraySpec> action) { |
|
15 | default void arr(String key, Action<? super JsonArraySpec> action) { | |
| 16 | DefaultJsonArraySpec child = new DefaultJsonArraySpec(); |
|
16 | DefaultJsonArraySpec child = new DefaultJsonArraySpec(); | |
| 17 | action.execute(child); |
|
17 | action.execute(child); | |
| 18 | set(key, child.toList()); |
|
18 | set(key, child.toList()); | |
| 19 | } |
|
19 | } | |
| 20 | } No newline at end of file |
|
20 | } | |
| @@ -1,181 +1,180 | |||||
| 1 | package org.implab.gradle.common.tasks; |
|
1 | package org.implab.gradle.common.json.tasks; | |
| 2 |
|
2 | |||
| 3 | import java.util.LinkedHashMap; |
|
3 | import java.util.LinkedHashMap; | |
| 4 | import java.util.Map; |
|
4 | import java.util.Map; | |
| 5 | import java.util.Objects; |
|
5 | import java.util.Objects; | |
| 6 | import java.util.function.Predicate; |
|
6 | import java.util.function.Predicate; | |
| 7 | import java.util.stream.Collectors; |
|
7 | import java.util.stream.Collectors; | |
| 8 |
|
8 | |||
| 9 | import javax.inject.Inject; |
|
9 | import javax.inject.Inject; | |
| 10 |
|
10 | |||
| 11 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
11 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
| 12 | import org.gradle.api.Action; |
|
12 | import org.gradle.api.Action; | |
| 13 | import org.gradle.api.DefaultTask; |
|
13 | import org.gradle.api.DefaultTask; | |
| 14 | import org.gradle.api.file.RegularFile; |
|
14 | import org.gradle.api.file.RegularFile; | |
| 15 | import org.gradle.api.file.RegularFileProperty; |
|
15 | import org.gradle.api.file.RegularFileProperty; | |
| 16 | import org.gradle.api.provider.MapProperty; |
|
16 | import org.gradle.api.provider.MapProperty; | |
| 17 | import org.gradle.api.provider.Provider; |
|
17 | import org.gradle.api.provider.Provider; | |
| 18 | import org.gradle.api.provider.ProviderFactory; |
|
18 | import org.gradle.api.provider.ProviderFactory; | |
| 19 | import org.gradle.api.tasks.Input; |
|
19 | import org.gradle.api.tasks.Input; | |
| 20 | import org.gradle.api.tasks.Internal; |
|
20 | import org.gradle.api.tasks.Internal; | |
| 21 | import org.gradle.api.tasks.Optional; |
|
21 | import org.gradle.api.tasks.Optional; | |
| 22 | import org.gradle.api.tasks.OutputFile; |
|
22 | import org.gradle.api.tasks.OutputFile; | |
| 23 | import org.gradle.api.tasks.SkipWhenEmpty; |
|
|||
| 24 | import org.gradle.api.tasks.TaskAction; |
|
23 | import org.gradle.api.tasks.TaskAction; | |
| 25 |
import org.implab.gradle.common. |
|
24 | import org.implab.gradle.common.core.gradle.Properties; | |
| 26 |
import org.implab.gradle.common. |
|
25 | import org.implab.gradle.common.core.lang.Closures; | |
| 27 |
import org.implab.gradle.common.json.Json |
|
26 | import org.implab.gradle.common.json.core.Json; | |
| 28 |
import org.implab.gradle.common. |
|
27 | import org.implab.gradle.common.json.core.MapImportSpec; | |
| 29 |
import org.implab.gradle.common. |
|
28 | import org.implab.gradle.common.json.dsl.JsonObjectSpec; | |
| 30 |
|
29 | |||
| 31 | import groovy.lang.Closure; |
|
30 | import groovy.lang.Closure; | |
| 32 |
|
31 | |||
| 33 | /** |
|
32 | /** | |
| 34 | * A Gradle task that writes JSON content to a file. |
|
33 | * A Gradle task that writes JSON content to a file. | |
| 35 | * |
|
34 | * | |
| 36 | * This task allows you to build up a JSON object through various methods and |
|
35 | * This task allows you to build up a JSON object through various methods and | |
| 37 | * write it to a file. |
|
36 | * write it to a file. | |
| 38 | * You can add content directly, import from other JSON files, or merge in map |
|
37 | * You can add content directly, import from other JSON files, or merge in map | |
| 39 | * data with optional filtering. |
|
38 | * data with optional filtering. | |
| 40 | * |
|
39 | * | |
| 41 | * <h3>Usage Example:</h3> |
|
40 | * <h3>Usage Example:</h3> | |
| 42 | * |
|
41 | * | |
| 43 | * <pre> |
|
42 | * <pre> | |
| 44 | * tasks.register('generateConfig', WriteJson) { |
|
43 | * tasks.register('generateConfig', WriteJson) { | |
| 45 | * outputFile = file('build/config.json') |
|
44 | * outputFile = file('build/config.json') | |
| 46 | * content { |
|
45 | * content { | |
| 47 | * version = '1.0.0' |
|
46 | * version = '1.0.0' | |
| 48 | * name = 'myapp' |
|
47 | * name = 'myapp' | |
| 49 | * } |
|
48 | * } | |
| 50 | * from file('src/base-config.json') |
|
49 | * from file('src/base-config.json') | |
| 51 | * putAll providers.provider { ['debug': true] } |
|
50 | * putAll providers.provider { ['debug': true] } | |
| 52 | * } |
|
51 | * } | |
| 53 | * </pre> |
|
52 | * </pre> | |
| 54 | * |
|
53 | * | |
| 55 | * <h3>Properties:</h3> |
|
54 | * <h3>Properties:</h3> | |
| 56 | * <ul> |
|
55 | * <ul> | |
| 57 | * <li><strong>content</strong> - A map of string keys to object values that |
|
56 | * <li><strong>content</strong> - A map of string keys to object values that | |
| 58 | * will be serialized to JSON</li> |
|
57 | * will be serialized to JSON</li> | |
| 59 | * <li><strong>outputFile</strong> - The file where the JSON will be |
|
58 | * <li><strong>outputFile</strong> - The file where the JSON will be | |
| 60 | * written</li> |
|
59 | * written</li> | |
| 61 | * </ul> |
|
60 | * </ul> | |
| 62 | * |
|
61 | * | |
| 63 | * <h3>Methods:</h3> |
|
62 | * <h3>Methods:</h3> | |
| 64 | * <ul> |
|
63 | * <ul> | |
| 65 | * <li><strong>content()</strong> - Configure content using a closure or |
|
64 | * <li><strong>content()</strong> - Configure content using a closure or | |
| 66 | * action</li> |
|
65 | * action</li> | |
| 67 | * <li><strong>from()</strong> - Import JSON content from a file with optional |
|
66 | * <li><strong>from()</strong> - Import JSON content from a file with optional | |
| 68 | * key filtering</li> |
|
67 | * key filtering</li> | |
| 69 | * <li><strong>putAll()</strong> - Merge a map provider with optional key |
|
68 | * <li><strong>putAll()</strong> - Merge a map provider with optional key | |
| 70 | * filtering</li> |
|
69 | * filtering</li> | |
| 71 | * </ul> |
|
70 | * </ul> | |
| 72 | * |
|
71 | * | |
| 73 | * @since 1.0 |
|
72 | * @since 1.0 | |
| 74 | */ |
|
73 | */ | |
| 75 | @NonNullByDefault |
|
74 | @NonNullByDefault | |
| 76 | public abstract class WriteJson extends DefaultTask { |
|
75 | public abstract class WriteJson extends DefaultTask { | |
| 77 |
|
76 | |||
| 78 | private final ProviderFactory providers; |
|
77 | private final ProviderFactory providers; | |
| 79 |
|
78 | |||
| 80 | private final JsonObjectSpec contentSpec = new JsonObjectSpec() { |
|
79 | private final JsonObjectSpec contentSpec = new JsonObjectSpec() { | |
| 81 | @Override |
|
80 | @Override | |
| 82 | public void set(String key, Object value) { |
|
81 | public void set(String key, Object value) { | |
| 83 | Properties.putMapEntry(getContent(), key, value); |
|
82 | Properties.putMapEntry(getContent(), key, value); | |
| 84 | } |
|
83 | } | |
| 85 | }; |
|
84 | }; | |
| 86 |
|
85 | |||
| 87 | @Input |
|
86 | @Input | |
| 88 | @Optional |
|
87 | @Optional | |
| 89 | public abstract MapProperty<String, Object> getContent(); |
|
88 | public abstract MapProperty<String, Object> getContent(); | |
| 90 |
|
89 | |||
| 91 | @OutputFile |
|
90 | @OutputFile | |
| 92 | public abstract RegularFileProperty getOutputFile(); |
|
91 | public abstract RegularFileProperty getOutputFile(); | |
| 93 |
|
92 | |||
| 94 | @Internal |
|
93 | @Internal | |
| 95 | public Provider<String> getJson() { |
|
94 | public Provider<String> getJson() { | |
| 96 | return getContent().map(Json::stringify); |
|
95 | return getContent().map(Json::stringify); | |
| 97 | } |
|
96 | } | |
| 98 |
|
97 | |||
| 99 | @Inject |
|
98 | @Inject | |
| 100 | public WriteJson(ProviderFactory providers) { |
|
99 | public WriteJson(ProviderFactory providers) { | |
| 101 | this.providers = providers; |
|
100 | this.providers = providers; | |
| 102 | } |
|
101 | } | |
| 103 |
|
102 | |||
| 104 | public void content(Action<? super JsonObjectSpec> configure) { |
|
103 | public void content(Action<? super JsonObjectSpec> configure) { | |
| 105 | configure.execute(contentSpec); |
|
104 | configure.execute(contentSpec); | |
| 106 | } |
|
105 | } | |
| 107 |
|
106 | |||
| 108 | public void content(Closure<?> configure) { |
|
107 | public void content(Closure<?> configure) { | |
| 109 | Closures.apply(configure, contentSpec); |
|
108 | Closures.apply(configure, contentSpec); | |
| 110 | } |
|
109 | } | |
| 111 |
|
110 | |||
| 112 | public void from(RegularFile file, Closure<?> configure) { |
|
111 | public void from(RegularFile file, Closure<?> configure) { | |
| 113 | importContents(file, MapImportSpec.buildPredicate(configure)); |
|
112 | importContents(file, MapImportSpec.buildPredicate(configure)); | |
| 114 | } |
|
113 | } | |
| 115 |
|
114 | |||
| 116 | public void from(RegularFile file, Action<? super MapImportSpec> configure) { |
|
115 | public void from(RegularFile file, Action<? super MapImportSpec> configure) { | |
| 117 | importContents(file, MapImportSpec.buildPredicate(configure)); |
|
116 | importContents(file, MapImportSpec.buildPredicate(configure)); | |
| 118 | } |
|
117 | } | |
| 119 |
|
118 | |||
| 120 | public void from(RegularFile file) { |
|
119 | public void from(RegularFile file) { | |
| 121 | importContents(file, x -> true); |
|
120 | importContents(file, x -> true); | |
| 122 | } |
|
121 | } | |
| 123 |
|
122 | |||
| 124 | public void from(Provider<RegularFile> file, Closure<?> configure) { |
|
123 | public void from(Provider<RegularFile> file, Closure<?> configure) { | |
| 125 | importContents(file, MapImportSpec.buildPredicate(configure)); |
|
124 | importContents(file, MapImportSpec.buildPredicate(configure)); | |
| 126 | } |
|
125 | } | |
| 127 |
|
126 | |||
| 128 | public void from(Provider<RegularFile> file, Action<? super MapImportSpec> configure) { |
|
127 | public void from(Provider<RegularFile> file, Action<? super MapImportSpec> configure) { | |
| 129 | importContents(file, MapImportSpec.buildPredicate(configure)); |
|
128 | importContents(file, MapImportSpec.buildPredicate(configure)); | |
| 130 | } |
|
129 | } | |
| 131 |
|
130 | |||
| 132 | public void from(Provider<RegularFile> file) { |
|
131 | public void from(Provider<RegularFile> file) { | |
| 133 | importContents(file, x -> true); |
|
132 | importContents(file, x -> true); | |
| 134 | } |
|
133 | } | |
| 135 |
|
134 | |||
| 136 | public void putAll(Provider<Map<String, Object>> mapProvider, Action<? super MapImportSpec> configure) { |
|
135 | public void putAll(Provider<Map<String, Object>> mapProvider, Action<? super MapImportSpec> configure) { | |
| 137 | importMap(mapProvider, MapImportSpec.buildPredicate(configure)); |
|
136 | importMap(mapProvider, MapImportSpec.buildPredicate(configure)); | |
| 138 | } |
|
137 | } | |
| 139 |
|
138 | |||
| 140 | public void putAll(Provider<Map<String, Object>> mapProvider, Closure<?> configure) { |
|
139 | public void putAll(Provider<Map<String, Object>> mapProvider, Closure<?> configure) { | |
| 141 | importMap(mapProvider, MapImportSpec.buildPredicate(configure)); |
|
140 | importMap(mapProvider, MapImportSpec.buildPredicate(configure)); | |
| 142 | } |
|
141 | } | |
| 143 |
|
142 | |||
| 144 | public void putAll(Provider<Map<String, Object>> mapProvider) { |
|
143 | public void putAll(Provider<Map<String, Object>> mapProvider) { | |
| 145 | importMap(mapProvider, x -> true); |
|
144 | importMap(mapProvider, x -> true); | |
| 146 | } |
|
145 | } | |
| 147 |
|
146 | |||
| 148 | private void importContents(Provider<RegularFile> file, Predicate<String> keyPredicate) { |
|
147 | private void importContents(Provider<RegularFile> file, Predicate<String> keyPredicate) { | |
| 149 | var jsonProvider = providers.fileContents(file).getAsText() |
|
148 | var jsonProvider = providers.fileContents(file).getAsText() | |
| 150 | .map(Json.mapParser(Object.class)::apply); |
|
149 | .map(Json.mapParser(Object.class)::apply); | |
| 151 |
|
150 | |||
| 152 | importMap(jsonProvider, keyPredicate); |
|
151 | importMap(jsonProvider, keyPredicate); | |
| 153 | } |
|
152 | } | |
| 154 |
|
153 | |||
| 155 | private void importContents(RegularFile file, Predicate<String> keyPredicate) { |
|
154 | private void importContents(RegularFile file, Predicate<String> keyPredicate) { | |
| 156 | var jsonProvider = providers.fileContents(file).getAsText() |
|
155 | var jsonProvider = providers.fileContents(file).getAsText() | |
| 157 | .map(Json.mapParser(Object.class)::apply); |
|
156 | .map(Json.mapParser(Object.class)::apply); | |
| 158 |
|
157 | |||
| 159 | importMap(jsonProvider, keyPredicate); |
|
158 | importMap(jsonProvider, keyPredicate); | |
| 160 | } |
|
159 | } | |
| 161 |
|
160 | |||
| 162 | private void importMap(Provider<Map<String, Object>> mapProvider, Predicate<? super String> keyPredicate) { |
|
161 | private void importMap(Provider<Map<String, Object>> mapProvider, Predicate<? super String> keyPredicate) { | |
| 163 | var filteredProvider = mapProvider.map(v -> v.entrySet().stream() |
|
162 | var filteredProvider = mapProvider.map(v -> v.entrySet().stream() | |
| 164 | .filter(pair -> keyPredicate.test(pair.getKey()) && Objects.nonNull(pair.getValue())) |
|
163 | .filter(pair -> keyPredicate.test(pair.getKey()) && Objects.nonNull(pair.getValue())) | |
| 165 | .collect(Collectors.toMap( |
|
164 | .collect(Collectors.toMap( | |
| 166 | Map.Entry::getKey, |
|
165 | Map.Entry::getKey, | |
| 167 | Map.Entry::getValue, |
|
166 | Map.Entry::getValue, | |
| 168 | (a, b) -> b, |
|
167 | (a, b) -> b, | |
| 169 | LinkedHashMap::new))); |
|
168 | LinkedHashMap::new))); | |
| 170 |
|
169 | |||
| 171 | getContent().putAll(filteredProvider); |
|
170 | getContent().putAll(filteredProvider); | |
| 172 | } |
|
171 | } | |
| 173 |
|
172 | |||
| 174 | @TaskAction |
|
173 | @TaskAction | |
| 175 | public void run() { |
|
174 | public void run() { | |
| 176 | var data = getContent().getOrElse(Map.of()); |
|
175 | var data = getContent().getOrElse(Map.of()); | |
| 177 | var file = getOutputFile().getAsFile().get(); |
|
176 | var file = getOutputFile().getAsFile().get(); | |
| 178 | Json.write(file, data); |
|
177 | Json.write(file, data); | |
| 179 | } |
|
178 | } | |
| 180 |
|
179 | |||
| 181 | } |
|
180 | } | |
| @@ -1,136 +1,208 | |||||
| 1 |
package org.implab.gradle.common. |
|
1 | package org.implab.gradle.common.sources; | |
| 2 |
|
2 | |||
| 3 | import java.io.File; |
|
3 | import java.io.File; | |
| 4 | import java.nio.file.Paths; |
|
4 | import java.nio.file.Paths; | |
| 5 | import java.util.HashSet; |
|
5 | import java.util.HashSet; | |
|
|
6 | import java.util.LinkedHashMap; | |||
| 6 | import java.util.List; |
|
7 | import java.util.List; | |
|
|
8 | import java.util.Map; | |||
| 7 | import java.util.Objects; |
|
9 | import java.util.Objects; | |
| 8 | import java.util.Set; |
|
10 | import java.util.Set; | |
| 9 | import java.util.concurrent.Callable; |
|
11 | import java.util.concurrent.Callable; | |
|
|
12 | import java.util.function.Function; | |||
| 10 | import java.util.stream.Collectors; |
|
13 | import java.util.stream.Collectors; | |
| 11 |
|
14 | |||
| 12 | import javax.inject.Inject; |
|
15 | import javax.inject.Inject; | |
| 13 |
|
16 | |||
|
|
17 | import org.gradle.api.InvalidUserDataException; | |||
| 14 | import org.gradle.api.Named; |
|
18 | import org.gradle.api.Named; | |
| 15 | import org.gradle.api.NamedDomainObjectContainer; |
|
19 | import org.gradle.api.NamedDomainObjectContainer; | |
|
|
20 | import org.gradle.api.Task; | |||
|
|
21 | import org.gradle.api.file.ConfigurableFileCollection; | |||
| 16 | import org.gradle.api.file.DirectoryProperty; |
|
22 | import org.gradle.api.file.DirectoryProperty; | |
| 17 | import org.gradle.api.file.FileCollection; |
|
23 | import org.gradle.api.file.FileCollection; | |
| 18 | import org.gradle.api.file.ProjectLayout; |
|
24 | import org.gradle.api.file.ProjectLayout; | |
| 19 | import org.gradle.api.file.SourceDirectorySet; |
|
25 | import org.gradle.api.file.SourceDirectorySet; | |
| 20 | import org.gradle.api.logging.Logger; |
|
|||
| 21 | import org.gradle.api.logging.Logging; |
|
|||
| 22 | import org.gradle.api.model.ObjectFactory; |
|
26 | import org.gradle.api.model.ObjectFactory; | |
|
|
27 | import org.gradle.api.tasks.TaskProvider; | |||
| 23 | import org.gradle.util.Configurable; |
|
28 | import org.gradle.util.Configurable; | |
| 24 |
import org.implab.gradle.common. |
|
29 | import org.implab.gradle.common.core.lang.Closures; | |
| 25 |
|
30 | |||
| 26 | import groovy.lang.Closure; |
|
31 | import groovy.lang.Closure; | |
| 27 |
|
32 | |||
|
|
33 | /** | |||
|
|
34 | * A configurable source set abstraction with named output roles. | |||
|
|
35 | * | |||
|
|
36 | * <p> | |||
|
|
37 | * Each instance aggregates multiple {@link SourceDirectorySet source sets} | |||
|
|
38 | * under a shared name and exposes typed outputs that must be declared up front. | |||
|
|
39 | * Default locations are {@code src/<name>} for sources and | |||
|
|
40 | * {@code build/<name>} for outputs, both of which can be customized via the | |||
|
|
41 | * exposed {@link DirectoryProperty} setters. | |||
|
|
42 | * </p> | |||
|
|
43 | * | |||
|
|
44 | * <p> | |||
|
|
45 | * Outputs are grouped by roles to make task wiring explicit. A role must be | |||
|
|
46 | * declared with {@link #declareRoles(String, String...)} (or the synonym | |||
|
|
47 | * {@link #declareOutputs(String, String...)}) before files can be registered | |||
|
|
48 | * against it. Attempting to register or retrieve an undeclared role results in | |||
|
|
49 | * {@link InvalidUserDataException}. | |||
|
|
50 | * </p> | |||
|
|
51 | */ | |||
| 28 | public abstract class GenericSourceSet |
|
52 | public abstract class GenericSourceSet | |
| 29 | implements Named, Configurable<GenericSourceSet> { |
|
53 | implements Named, Configurable<GenericSourceSet> { | |
| 30 | private final Logger logger = Logging.getLogger(GenericSourceSet.class); |
|
|||
| 31 |
|
||||
| 32 | private final String name; |
|
54 | private final String name; | |
| 33 |
|
55 | |||
| 34 | private final NamedDomainObjectContainer<SourceDirectorySet> sourceDirectorySets; |
|
56 | private final NamedDomainObjectContainer<SourceDirectorySet> sourceDirectorySets; | |
| 35 |
|
57 | |||
| 36 | private final NamedDomainObjectContainer<GenericSourceSetOutput> outputs; |
|
58 | private final Map<String, ConfigurableFileCollection> outputs; | |
| 37 |
|
59 | |||
| 38 | private final FileCollection allOutputs; |
|
60 | private final FileCollection allOutputs; | |
| 39 |
|
61 | |||
| 40 | private final FileCollection allSourceDirectories; |
|
62 | private final FileCollection allSourceDirectories; | |
| 41 |
|
63 | |||
| 42 | private final ObjectFactory objects; |
|
64 | private final ObjectFactory objects; | |
| 43 |
|
65 | |||
| 44 |
private final Set<String> declared |
|
66 | private final Set<String> declaredRoles = new HashSet<>(); | |
| 45 |
|
67 | |||
| 46 | @Inject |
|
68 | @Inject | |
| 47 | public GenericSourceSet(String name, ObjectFactory objects, ProjectLayout layout) { |
|
69 | public GenericSourceSet(String name, ObjectFactory objects, ProjectLayout layout) { | |
| 48 | this.name = name; |
|
70 | this.name = name; | |
| 49 | this.objects = objects; |
|
71 | this.objects = objects; | |
| 50 |
|
72 | |||
| 51 | sourceDirectorySets = objects.domainObjectContainer( |
|
73 | sourceDirectorySets = objects.domainObjectContainer( | |
| 52 | SourceDirectorySet.class, |
|
74 | SourceDirectorySet.class, | |
| 53 | this::createSourceDirectorySet); |
|
75 | this::createSourceDirectorySet); | |
| 54 |
|
76 | |||
| 55 | outputs = objects.domainObjectContainer(GenericSourceSetOutput.class); |
|
77 | outputs = new LinkedHashMap<>(); | |
| 56 |
|
78 | |||
| 57 | allSourceDirectories = objects.fileCollection().from(sourceDirectoriesProvider()); |
|
79 | allSourceDirectories = objects.fileCollection().from(sourceDirectoriesProvider()); | |
| 58 |
|
80 | |||
| 59 | allOutputs = objects.fileCollection().from(outputsProvider()); |
|
81 | allOutputs = objects.fileCollection().from(outputsProvider()); | |
| 60 |
|
82 | |||
| 61 | outputs.addRule("Register a declared set on demand", this::registerOutputOnDemand); |
|
|||
| 62 |
|
||||
| 63 | getSourceSetDir().convention(layout |
|
83 | getSourceSetDir().convention(layout | |
| 64 | .getProjectDirectory() |
|
84 | .getProjectDirectory() | |
| 65 | .dir(Paths.get("src", name).toString())); |
|
85 | .dir(Paths.get("src", name).toString())); | |
| 66 |
|
86 | |||
| 67 | getOutputsDir().convention(layout |
|
87 | getOutputsDir().convention(layout | |
| 68 | .getBuildDirectory() |
|
88 | .getBuildDirectory() | |
| 69 | .dir(name)); |
|
89 | .dir(name)); | |
| 70 | } |
|
90 | } | |
| 71 |
|
91 | |||
| 72 | @Override |
|
92 | @Override | |
| 73 | public String getName() { |
|
93 | public String getName() { | |
| 74 | return name; |
|
94 | return name; | |
| 75 | } |
|
95 | } | |
| 76 |
|
96 | |||
|
|
97 | /** | |||
|
|
98 | * Base directory for this source set. Defaults to {@code src/<name>} under | |||
|
|
99 | * the project directory. | |||
|
|
100 | */ | |||
| 77 | public abstract DirectoryProperty getSourceSetDir(); |
|
101 | public abstract DirectoryProperty getSourceSetDir(); | |
| 78 |
|
102 | |||
|
|
103 | /** | |||
|
|
104 | * Base directory for outputs of this source set. Defaults to | |||
|
|
105 | * {@code build/<name>}. | |||
|
|
106 | */ | |||
| 79 | public abstract DirectoryProperty getOutputsDir(); |
|
107 | public abstract DirectoryProperty getOutputsDir(); | |
| 80 |
|
108 | |||
|
|
109 | /** | |||
|
|
110 | * The container of {@link SourceDirectorySet} instances that belong to this | |||
|
|
111 | * logical source set. | |||
|
|
112 | */ | |||
| 81 | public NamedDomainObjectContainer<SourceDirectorySet> getSets() { |
|
113 | public NamedDomainObjectContainer<SourceDirectorySet> getSets() { | |
| 82 | return sourceDirectorySets; |
|
114 | return sourceDirectorySets; | |
| 83 | } |
|
115 | } | |
| 84 |
|
116 | |||
| 85 | public NamedDomainObjectContainer<GenericSourceSetOutput> getOutputs() { |
|
117 | /** | |
| 86 | return outputs; |
|
118 | * All registered outputs grouped across roles. | |
| 87 |
|
|
119 | */ | |
| 88 |
|
||||
| 89 | public FileCollection getAllOutputs() { |
|
120 | public FileCollection getAllOutputs() { | |
| 90 | return allOutputs; |
|
121 | return allOutputs; | |
| 91 | } |
|
122 | } | |
| 92 |
|
123 | |||
|
|
124 | /** | |||
|
|
125 | * All source directories from every contained {@link SourceDirectorySet}. | |||
|
|
126 | */ | |||
| 93 | public FileCollection getAllSourceDirectories() { |
|
127 | public FileCollection getAllSourceDirectories() { | |
| 94 | return allSourceDirectories; |
|
128 | return allSourceDirectories; | |
| 95 | } |
|
129 | } | |
| 96 |
|
130 | |||
| 97 | public FileCollection output(String name) { |
|
131 | /** | |
| 98 | return getOutputs().getAt(name).getFileCollection(); |
|
132 | * Returns the file collection for the specified output role, creating it | |
|
|
133 | * if necessary. | |||
|
|
134 | * | |||
|
|
135 | * @throws InvalidUserDataException if the role was not declared | |||
|
|
136 | */ | |||
|
|
137 | public ConfigurableFileCollection output(String name) { | |||
|
|
138 | requireDeclaredRole(name); | |||
|
|
139 | return outputs.computeIfAbsent(name, key -> objects.fileCollection()); | |||
| 99 | } |
|
140 | } | |
| 100 |
|
141 | |||
| 101 | public void declareOutputs(String name, String... extra) { |
|
142 | /** | |
| 102 | declaredOutputs.add(Objects.requireNonNull(name, "declareOutputs: The output name cannot be null")); |
|
143 | * Declares allowed output roles. Roles must be declared before registering | |
|
|
144 | * files under them. | |||
|
|
145 | */ | |||
|
|
146 | public void declareRoles(String name, String... extra) { | |||
|
|
147 | declaredRoles.add(Objects.requireNonNull(name, "declareRoles: The output name cannot be null")); | |||
| 103 | for (var x : extra) |
|
148 | for (var x : extra) | |
| 104 |
declared |
|
149 | declaredRoles.add(Objects.requireNonNull(x, "declareRoles: The output name cannot be null")); | |
| 105 | } |
|
150 | } | |
| 106 |
|
151 | |||
| 107 | @Override |
|
152 | /** | |
| 108 | public GenericSourceSet configure(@SuppressWarnings("rawtypes") Closure configure) { |
|
153 | * Alias for {@link #declareRoles(String, String...)} kept for DSL clarity. | |
| 109 | Closures.apply(configure, this); |
|
154 | */ | |
| 110 | return this; |
|
155 | public void declareOutputs(String name, String... extra) { | |
|
|
156 | declareRoles(name, extra); | |||
|
|
157 | } | |||
|
|
158 | ||||
|
|
159 | /** | |||
|
|
160 | * Registers files produced elsewhere under the given role. | |||
|
|
161 | */ | |||
|
|
162 | public void registerOutput(String name, Object... files) { | |||
|
|
163 | output(name).from(files); | |||
| 111 | } |
|
164 | } | |
| 112 |
|
165 | |||
| 113 | private void registerOutputOnDemand(String outputName) { |
|
166 | /** | |
| 114 | logger.info("SourceSet '{}': registerOutputOnDemand '{}'", name, outputName); |
|
167 | * Registers output files produced by a task, using a mapper to extract the | |
|
|
168 | * output from the task. The task will be added as a build dependency of this | |||
|
|
169 | * output. | |||
|
|
170 | */ | |||
|
|
171 | public <T extends Task> void registerOutput(String name, TaskProvider<T> task, | |||
|
|
172 | Function<? super T, ?> mapper) { | |||
|
|
173 | output(name).from(task.map(mapper::apply)) | |||
|
|
174 | .builtBy(task); | |||
|
|
175 | } | |||
| 115 |
|
176 | |||
| 116 | if (declaredOutputs.contains(outputName)) |
|
177 | /** | |
| 117 | outputs.register(outputName); |
|
178 | * Applies a Groovy closure to this source set, enabling DSL-style | |
|
|
179 | * configuration. | |||
|
|
180 | */ | |||
|
|
181 | @Override | |||
|
|
182 | public GenericSourceSet configure(Closure configure) { | |||
|
|
183 | Closures.apply(configure, this); | |||
|
|
184 | return this; | |||
| 118 | } |
|
185 | } | |
| 119 |
|
186 | |||
| 120 | private SourceDirectorySet createSourceDirectorySet(String name) { |
|
187 | private SourceDirectorySet createSourceDirectorySet(String name) { | |
| 121 | return objects.sourceDirectorySet(name, name); |
|
188 | return objects.sourceDirectorySet(name, name); | |
| 122 | } |
|
189 | } | |
| 123 |
|
190 | |||
|
|
191 | private void requireDeclaredRole(String roleName) { | |||
|
|
192 | if (!declaredRoles.contains(roleName)) { | |||
|
|
193 | throw new InvalidUserDataException( | |||
|
|
194 | "Output role '" + roleName + "' is not declared for source set '" + name + "'"); | |||
|
|
195 | } | |||
|
|
196 | } | |||
|
|
197 | ||||
| 124 | private Callable<List<? extends FileCollection>> outputsProvider() { |
|
198 | private Callable<List<? extends FileCollection>> outputsProvider() { | |
| 125 | return () -> outputs.stream() |
|
199 | return () -> outputs.values().stream().toList(); | |
| 126 | .map(GenericSourceSetOutput::getFileCollection) |
|
|||
| 127 | .toList(); |
|
|||
| 128 | } |
|
200 | } | |
| 129 |
|
201 | |||
| 130 | private Callable<Set<File>> sourceDirectoriesProvider() { |
|
202 | private Callable<Set<File>> sourceDirectoriesProvider() { | |
| 131 | return () -> sourceDirectorySets.stream() |
|
203 | return () -> sourceDirectorySets.stream() | |
| 132 | .flatMap(x -> x.getSrcDirs().stream()) |
|
204 | .flatMap(x -> x.getSrcDirs().stream()) | |
| 133 | .collect(Collectors.toSet()); |
|
205 | .collect(Collectors.toSet()); | |
| 134 | } |
|
206 | } | |
| 135 |
|
207 | |||
| 136 | } |
|
208 | } | |
| @@ -1,60 +1,59 | |||||
| 1 | package org.implab.gradle.common; |
|
1 | package org.implab.gradle.common.sources; | |
| 2 |
|
2 | |||
| 3 | import org.gradle.api.Action; |
|
3 | import org.gradle.api.Action; | |
| 4 | import org.gradle.api.Plugin; |
|
4 | import org.gradle.api.Plugin; | |
| 5 | import org.gradle.api.Project; |
|
5 | import org.gradle.api.Project; | |
| 6 | import org.gradle.api.logging.Logger; |
|
6 | import org.gradle.api.logging.Logger; | |
| 7 | import org.gradle.api.logging.Logging; |
|
7 | import org.gradle.api.logging.Logging; | |
| 8 | import org.gradle.api.tasks.Copy; |
|
8 | import org.gradle.api.tasks.Copy; | |
| 9 | import org.gradle.api.tasks.Delete; |
|
9 | import org.gradle.api.tasks.Delete; | |
| 10 | import org.gradle.api.tasks.TaskContainer; |
|
10 | import org.gradle.api.tasks.TaskContainer; | |
| 11 | import org.gradle.api.tasks.TaskProvider; |
|
11 | import org.gradle.api.tasks.TaskProvider; | |
| 12 | import org.gradle.language.base.plugins.LifecycleBasePlugin; |
|
12 | import org.gradle.language.base.plugins.LifecycleBasePlugin; | |
| 13 |
import org.implab.gradle.common. |
|
13 | import org.implab.gradle.common.core.lang.Strings; | |
| 14 | import org.implab.gradle.common.utils.Strings; |
|
|||
| 15 |
|
14 | |||
| 16 | /** |
|
15 | /** | |
| 17 | * This plugin creates a {@code sources} extension which is |
|
16 | * This plugin creates a {@code sources} extension which is | |
| 18 | * a container for {@link GenericSourceSet}. |
|
17 | * a container for {@link GenericSourceSet}. | |
| 19 | * |
|
18 | * | |
| 20 | */ |
|
19 | */ | |
| 21 | public abstract class SourcesPlugin implements Plugin<Project> { |
|
20 | public abstract class SourcesPlugin implements Plugin<Project> { | |
| 22 | private final Logger logger = Logging.getLogger(SourcesPlugin.class); |
|
21 | private final Logger logger = Logging.getLogger(SourcesPlugin.class); | |
| 23 |
|
22 | |||
| 24 | private final String POLICY_NAME = "SourcesPolicy"; |
|
23 | private final String POLICY_NAME = "SourcesPolicy"; | |
| 25 |
|
24 | |||
| 26 | private final String EXTENSION_NAME = "sources"; |
|
25 | private final String EXTENSION_NAME = "sources"; | |
| 27 |
|
26 | |||
| 28 | @Override |
|
27 | @Override | |
| 29 | public void apply(Project target) { |
|
28 | public void apply(Project target) { | |
| 30 | target.getPlugins().apply(LifecycleBasePlugin.class); |
|
29 | target.getPlugins().apply(LifecycleBasePlugin.class); | |
| 31 |
|
30 | |||
| 32 | var sources = target.getObjects().domainObjectContainer(GenericSourceSet.class); |
|
31 | var sources = target.getObjects().domainObjectContainer(GenericSourceSet.class); | |
| 33 | var tasks = target.getTasks(); |
|
32 | var tasks = target.getTasks(); | |
| 34 | var clean = tasks.named(LifecycleBasePlugin.CLEAN_TASK_NAME); |
|
33 | var clean = tasks.named(LifecycleBasePlugin.CLEAN_TASK_NAME); | |
| 35 |
|
34 | |||
| 36 | target.getExtensions().add(EXTENSION_NAME, sources); |
|
35 | target.getExtensions().add(EXTENSION_NAME, sources); | |
| 37 |
|
36 | |||
| 38 | sources.configureEach(configureSourceSet(tasks, clean)); |
|
37 | sources.configureEach(configureSourceSet(tasks, clean)); | |
| 39 | } |
|
38 | } | |
| 40 |
|
39 | |||
| 41 | private Action<GenericSourceSet> configureSourceSet(TaskContainer tasks, TaskProvider<?> clean) { |
|
40 | private Action<GenericSourceSet> configureSourceSet(TaskContainer tasks, TaskProvider<?> clean) { | |
| 42 | return sourceSet -> { |
|
41 | return sourceSet -> { | |
| 43 | var name = sourceSet.getName(); |
|
42 | var name = sourceSet.getName(); | |
| 44 |
|
43 | |||
| 45 | logger.info("{}: Processing source set '{}'", POLICY_NAME, name); |
|
44 | logger.info("{}: Processing source set '{}'", POLICY_NAME, name); | |
| 46 |
|
45 | |||
| 47 | var taskName = "clean" + Strings.capitalize(sourceSet.getName()); |
|
46 | var taskName = "clean" + Strings.capitalize(sourceSet.getName()); | |
| 48 |
|
47 | |||
| 49 | logger.info("{}: Register task '{}' of type [{}]", POLICY_NAME, taskName, Copy.class.getTypeName()); |
|
48 | logger.info("{}: Register task '{}' of type [{}]", POLICY_NAME, taskName, Copy.class.getTypeName()); | |
| 50 |
|
49 | |||
| 51 | var task = tasks.register(taskName, Delete.class, self -> { |
|
50 | var task = tasks.register(taskName, Delete.class, self -> { | |
| 52 | self.delete(sourceSet.getOutputsDir()); |
|
51 | self.delete(sourceSet.getOutputsDir()); | |
| 53 | }); |
|
52 | }); | |
| 54 |
|
53 | |||
| 55 | clean.configure(self -> { |
|
54 | clean.configure(self -> { | |
| 56 | self.dependsOn(task); |
|
55 | self.dependsOn(task); | |
| 57 | }); |
|
56 | }); | |
| 58 | }; |
|
57 | }; | |
| 59 | } |
|
58 | } | |
| 60 | } |
|
59 | } | |
| 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
