##// END OF EJS Templates
Refactoring, removed GenericSourceSetOutput.
cin -
r23:8e57f0a2d4de default
parent child
Show More
@@ -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.utils;
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.utils;
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.tasks;
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.utils;
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.utils;
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.utils;
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 t = reference.get();
17 var v = value;
18 if (t != null)
18 if (v != null) {
19 return t;
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.utils;
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.utils;
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.utils;
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.utils.os;
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.utils.os;
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.utils.OperatingSystem;
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.utils.os;
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.utils.os;
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.utils;
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.utils.os;
1 package org.implab.gradle.common.core.os;
2
2
3 import org.implab.gradle.common.utils.LazyValue;
3 import org.implab.gradle.common.core.lang.LazyValue;
4 import org.implab.gradle.common.utils.OperatingSystem;
5
4
6 public class SystemResolver {
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.utils.os;
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.exec.RedirectFrom;
10 import org.implab.gradle.common.core.lang.Closures;
11 import org.implab.gradle.common.utils.Closures;
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.exec.RedirectTo;
13 import org.implab.gradle.common.core.lang.Closures;
14 import org.implab.gradle.common.utils.Closures;
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.utils.Properties;
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.utils.Closures;
9 import org.implab.gradle.common.core.gradle.Properties;
10 import org.implab.gradle.common.utils.Properties;
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.utils.Values;
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.utils.Values;
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.utils.Exceptions;
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.dsl.RedirectFromSpec;
14 import org.implab.gradle.common.core.lang.Exceptions;
15 import org.implab.gradle.common.dsl.RedirectToSpec;
15 import org.implab.gradle.common.core.lang.Values;
16 import org.implab.gradle.common.dsl.TaskEnvSpecMixin;
16 import org.implab.gradle.common.exec.dsl.RedirectFromSpec;
17 import org.implab.gradle.common.dsl.TaskPipeSpecMixin;
17 import org.implab.gradle.common.exec.dsl.RedirectToSpec;
18 import org.implab.gradle.common.exec.RedirectTo;
18 import org.implab.gradle.common.exec.dsl.TaskEnvSpecMixin;
19 import org.implab.gradle.common.exec.ShellExec;
19 import org.implab.gradle.common.exec.dsl.TaskPipeSpecMixin;
20 import org.implab.gradle.common.utils.Exceptions;
20 import org.implab.gradle.common.exec.runtime.RedirectTo;
21 import org.implab.gradle.common.utils.Values;
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.utils.LazyValue;
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.utils.Closures;
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.json.MapImportSpec;
24 import org.implab.gradle.common.core.gradle.Properties;
26 import org.implab.gradle.common.json.Json;
25 import org.implab.gradle.common.core.lang.Closures;
27 import org.implab.gradle.common.json.JsonObjectSpec;
26 import org.implab.gradle.common.json.core.Json;
28 import org.implab.gradle.common.utils.Closures;
27 import org.implab.gradle.common.json.core.MapImportSpec;
29 import org.implab.gradle.common.utils.Properties;
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.files;
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.utils.Closures;
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> declaredOutputs = new HashSet<>();
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 declaredOutputs.add(Objects.requireNonNull(x, "declareOutputs: The output name cannot be null"));
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.files.GenericSourceSet;
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