| @@ -0,0 +1,60 | |||
|
|
1 | package org.implab.gradle.common; | |
|
|
2 | ||
|
|
3 | import org.gradle.api.Action; | |
|
|
4 | import org.gradle.api.Plugin; | |
|
|
5 | import org.gradle.api.Project; | |
|
|
6 | import org.gradle.api.logging.Logger; | |
|
|
7 | import org.gradle.api.logging.Logging; | |
|
|
8 | import org.gradle.api.tasks.Copy; | |
|
|
9 | import org.gradle.api.tasks.Delete; | |
|
|
10 | import org.gradle.api.tasks.TaskContainer; | |
|
|
11 | import org.gradle.api.tasks.TaskProvider; | |
|
|
12 | import org.gradle.language.base.plugins.LifecycleBasePlugin; | |
|
|
13 | import org.implab.gradle.common.files.GenericSourceSet; | |
|
|
14 | import org.implab.gradle.common.utils.Strings; | |
|
|
15 | ||
|
|
16 | /** | |
|
|
17 | * This plugin creates a {@code sources} extension which is | |
|
|
18 | * a container for {@link GenericSourceSet}. | |
|
|
19 | * | |
|
|
20 | */ | |
|
|
21 | public abstract class SourcesPlugin implements Plugin<Project> { | |
|
|
22 | private final Logger logger = Logging.getLogger(SourcesPlugin.class); | |
|
|
23 | ||
|
|
24 | private final String POLICY_NAME = "SourcesPolicy"; | |
|
|
25 | ||
|
|
26 | private final String EXTENSION_NAME = "sources"; | |
|
|
27 | ||
|
|
28 | @Override | |
|
|
29 | public void apply(Project target) { | |
|
|
30 | target.getPlugins().apply(LifecycleBasePlugin.class); | |
|
|
31 | ||
|
|
32 | var sources = target.getObjects().domainObjectContainer(GenericSourceSet.class); | |
|
|
33 | var tasks = target.getTasks(); | |
|
|
34 | var clean = tasks.named(LifecycleBasePlugin.CLEAN_TASK_NAME); | |
|
|
35 | ||
|
|
36 | target.getExtensions().add(EXTENSION_NAME, sources); | |
|
|
37 | ||
|
|
38 | sources.configureEach(configureSourceSet(tasks, clean)); | |
|
|
39 | } | |
|
|
40 | ||
|
|
41 | private Action<GenericSourceSet> configureSourceSet(TaskContainer tasks, TaskProvider<?> clean) { | |
|
|
42 | return sourceSet -> { | |
|
|
43 | var name = sourceSet.getName(); | |
|
|
44 | ||
|
|
45 | logger.info("{}: Processing source set '{}'", POLICY_NAME, name); | |
|
|
46 | ||
|
|
47 | var taskName = "clean" + Strings.capitalize(sourceSet.getName()); | |
|
|
48 | ||
|
|
49 | logger.info("{}: Register task '{}' of type [{}]", POLICY_NAME, taskName, Copy.class.getTypeName()); | |
|
|
50 | ||
|
|
51 | var task = tasks.register(taskName, Delete.class, self -> { | |
|
|
52 | self.delete(sourceSet.getOutputsDir()); | |
|
|
53 | }); | |
|
|
54 | ||
|
|
55 | clean.configure(self -> { | |
|
|
56 | self.dependsOn(task); | |
|
|
57 | }); | |
|
|
58 | }; | |
|
|
59 | } | |
|
|
60 | } | |
| @@ -0,0 +1,136 | |||
|
|
1 | package org.implab.gradle.common.files; | |
|
|
2 | ||
|
|
3 | import java.io.File; | |
|
|
4 | import java.nio.file.Paths; | |
|
|
5 | import java.util.HashSet; | |
|
|
6 | import java.util.List; | |
|
|
7 | import java.util.Objects; | |
|
|
8 | import java.util.Set; | |
|
|
9 | import java.util.concurrent.Callable; | |
|
|
10 | import java.util.stream.Collectors; | |
|
|
11 | ||
|
|
12 | import javax.inject.Inject; | |
|
|
13 | ||
|
|
14 | import org.gradle.api.Named; | |
|
|
15 | import org.gradle.api.NamedDomainObjectContainer; | |
|
|
16 | import org.gradle.api.file.DirectoryProperty; | |
|
|
17 | import org.gradle.api.file.FileCollection; | |
|
|
18 | import org.gradle.api.file.ProjectLayout; | |
|
|
19 | 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; | |
|
|
23 | import org.gradle.util.Configurable; | |
|
|
24 | import org.implab.gradle.common.utils.Closures; | |
|
|
25 | ||
|
|
26 | import groovy.lang.Closure; | |
|
|
27 | ||
|
|
28 | public abstract class GenericSourceSet | |
|
|
29 | implements Named, Configurable<GenericSourceSet> { | |
|
|
30 | private final Logger logger = Logging.getLogger(GenericSourceSet.class); | |
|
|
31 | ||
|
|
32 | private final String name; | |
|
|
33 | ||
|
|
34 | private final NamedDomainObjectContainer<SourceDirectorySet> sourceDirectorySets; | |
|
|
35 | ||
|
|
36 | private final NamedDomainObjectContainer<GenericSourceSetOutput> outputs; | |
|
|
37 | ||
|
|
38 | private final FileCollection allOutputs; | |
|
|
39 | ||
|
|
40 | private final FileCollection allSourceDirectories; | |
|
|
41 | ||
|
|
42 | private final ObjectFactory objects; | |
|
|
43 | ||
|
|
44 | private final Set<String> declaredOutputs = new HashSet<>(); | |
|
|
45 | ||
|
|
46 | @Inject | |
|
|
47 | public GenericSourceSet(String name, ObjectFactory objects, ProjectLayout layout) { | |
|
|
48 | this.name = name; | |
|
|
49 | this.objects = objects; | |
|
|
50 | ||
|
|
51 | sourceDirectorySets = objects.domainObjectContainer( | |
|
|
52 | SourceDirectorySet.class, | |
|
|
53 | this::createSourceDirectorySet); | |
|
|
54 | ||
|
|
55 | outputs = objects.domainObjectContainer(GenericSourceSetOutput.class); | |
|
|
56 | ||
|
|
57 | allSourceDirectories = objects.fileCollection().from(sourceDirectoriesProvider()); | |
|
|
58 | ||
|
|
59 | allOutputs = objects.fileCollection().from(outputsProvider()); | |
|
|
60 | ||
|
|
61 | outputs.addRule("Register a declared set on demand", this::registerOutputOnDemand); | |
|
|
62 | ||
|
|
63 | getSourceSetDir().convention(layout | |
|
|
64 | .getProjectDirectory() | |
|
|
65 | .dir(Paths.get("src", name).toString())); | |
|
|
66 | ||
|
|
67 | getOutputsDir().convention(layout | |
|
|
68 | .getBuildDirectory() | |
|
|
69 | .dir(name)); | |
|
|
70 | } | |
|
|
71 | ||
|
|
72 | @Override | |
|
|
73 | public String getName() { | |
|
|
74 | return name; | |
|
|
75 | } | |
|
|
76 | ||
|
|
77 | public abstract DirectoryProperty getSourceSetDir(); | |
|
|
78 | ||
|
|
79 | public abstract DirectoryProperty getOutputsDir(); | |
|
|
80 | ||
|
|
81 | public NamedDomainObjectContainer<SourceDirectorySet> getSets() { | |
|
|
82 | return sourceDirectorySets; | |
|
|
83 | } | |
|
|
84 | ||
|
|
85 | public NamedDomainObjectContainer<GenericSourceSetOutput> getOutputs() { | |
|
|
86 | return outputs; | |
|
|
87 | } | |
|
|
88 | ||
|
|
89 | public FileCollection getAllOutputs() { | |
|
|
90 | return allOutputs; | |
|
|
91 | } | |
|
|
92 | ||
|
|
93 | public FileCollection getAllSourceDirectories() { | |
|
|
94 | return allSourceDirectories; | |
|
|
95 | } | |
|
|
96 | ||
|
|
97 | public FileCollection output(String name) { | |
|
|
98 | return getOutputs().getAt(name).getFileCollection(); | |
|
|
99 | } | |
|
|
100 | ||
|
|
101 | public void declareOutputs(String name, String... extra) { | |
|
|
102 | declaredOutputs.add(Objects.requireNonNull(name, "declareOutputs: The output name cannot be null")); | |
|
|
103 | for (var x : extra) | |
|
|
104 | declaredOutputs.add(Objects.requireNonNull(x, "declareOutputs: The output name cannot be null")); | |
|
|
105 | } | |
|
|
106 | ||
|
|
107 | @Override | |
|
|
108 | public GenericSourceSet configure(@SuppressWarnings("rawtypes") Closure configure) { | |
|
|
109 | Closures.apply(configure, this); | |
|
|
110 | return this; | |
|
|
111 | } | |
|
|
112 | ||
|
|
113 | private void registerOutputOnDemand(String outputName) { | |
|
|
114 | logger.info("SourceSet '{}': registerOutputOnDemand '{}'", name, outputName); | |
|
|
115 | ||
|
|
116 | if (declaredOutputs.contains(outputName)) | |
|
|
117 | outputs.register(outputName); | |
|
|
118 | } | |
|
|
119 | ||
|
|
120 | private SourceDirectorySet createSourceDirectorySet(String name) { | |
|
|
121 | return objects.sourceDirectorySet(name, name); | |
|
|
122 | } | |
|
|
123 | ||
|
|
124 | private Callable<List<? extends FileCollection>> outputsProvider() { | |
|
|
125 | return () -> outputs.stream() | |
|
|
126 | .map(GenericSourceSetOutput::getFileCollection) | |
|
|
127 | .toList(); | |
|
|
128 | } | |
|
|
129 | ||
|
|
130 | private Callable<Set<File>> sourceDirectoriesProvider() { | |
|
|
131 | return () -> sourceDirectorySets.stream() | |
|
|
132 | .flatMap(x -> x.getSrcDirs().stream()) | |
|
|
133 | .collect(Collectors.toSet()); | |
|
|
134 | } | |
|
|
135 | ||
|
|
136 | } | |
| @@ -0,0 +1,46 | |||
|
|
1 | package org.implab.gradle.common.files; | |
|
|
2 | ||
|
|
3 | import org.gradle.api.Action; | |
|
|
4 | import org.gradle.api.Named; | |
|
|
5 | import org.gradle.api.file.ConfigurableFileCollection; | |
|
|
6 | import org.gradle.api.file.FileCollection; | |
|
|
7 | import org.gradle.api.model.ObjectFactory; | |
|
|
8 | import org.gradle.util.Configurable; | |
|
|
9 | import org.implab.gradle.common.utils.Closures; | |
|
|
10 | ||
|
|
11 | import groovy.lang.Closure; | |
|
|
12 | ||
|
|
13 | /** Simple wrapper to add {@link Named} to {@link FileCollection} */ | |
|
|
14 | public abstract class GenericSourceSetOutput | |
|
|
15 | implements Named, Configurable<GenericSourceSetOutput> { | |
|
|
16 | ||
|
|
17 | private final String name; | |
|
|
18 | ||
|
|
19 | private final ConfigurableFileCollection outputFileCollection; | |
|
|
20 | ||
|
|
21 | public GenericSourceSetOutput(String name, ObjectFactory objects) { | |
|
|
22 | this.name = name; | |
|
|
23 | outputFileCollection = objects.fileCollection(); | |
|
|
24 | } | |
|
|
25 | ||
|
|
26 | public FileCollection getFileCollection() { | |
|
|
27 | return outputFileCollection; | |
|
|
28 | } | |
|
|
29 | ||
|
|
30 | @Override | |
|
|
31 | public String getName() { | |
|
|
32 | return name; | |
|
|
33 | } | |
|
|
34 | ||
|
|
35 | @Override | |
|
|
36 | public GenericSourceSetOutput configure(@SuppressWarnings("rawtypes") Closure cl) { | |
|
|
37 | Closures.apply(cl, outputFileCollection); | |
|
|
38 | return this; | |
|
|
39 | } | |
|
|
40 | ||
|
|
41 | public GenericSourceSetOutput configure(Action<ConfigurableFileCollection> cl) { | |
|
|
42 | cl.execute(outputFileCollection); | |
|
|
43 | return this; | |
|
|
44 | } | |
|
|
45 | ||
|
|
46 | } | |
| @@ -0,0 +1,29 | |||
|
|
1 | package org.implab.gradle.common.json; | |
|
|
2 | ||
|
|
3 | ||
|
|
4 | import org.gradle.api.provider.Provider; | |
|
|
5 | ||
|
|
6 | import java.util.ArrayList; | |
|
|
7 | import java.util.Collections; | |
|
|
8 | import java.util.List; | |
|
|
9 | ||
|
|
10 | ||
|
|
11 | public class DefaultJsonArraySpec implements JsonArraySpec { | |
|
|
12 | ||
|
|
13 | private final List<Object> values = new ArrayList<>(); | |
|
|
14 | ||
|
|
15 | @Override | |
|
|
16 | public void add(Object value) { | |
|
|
17 | if (value instanceof Provider<?>) { | |
|
|
18 | throw new IllegalArgumentException( | |
|
|
19 | "Providers are not allowed inside JSON arrays; " + | |
|
|
20 | "use top-level metadata.set(\"key\", provider) instead." | |
|
|
21 | ); | |
|
|
22 | } | |
|
|
23 | values.add(value); | |
|
|
24 | } | |
|
|
25 | ||
|
|
26 | public List<Object> toList() { | |
|
|
27 | return Collections.unmodifiableList(values); | |
|
|
28 | } | |
|
|
29 | } No newline at end of file | |
| @@ -0,0 +1,26 | |||
|
|
1 | package org.implab.gradle.common.json; | |
|
|
2 | ||
|
|
3 | import org.gradle.api.provider.Provider; | |
|
|
4 | ||
|
|
5 | import java.util.Collections; | |
|
|
6 | import java.util.LinkedHashMap; | |
|
|
7 | import java.util.Map; | |
|
|
8 | ||
|
|
9 | public class DefaultJsonObjectSpec implements GroovyObjectSpec { | |
|
|
10 | ||
|
|
11 | private final Map<String, Object> values = new LinkedHashMap<>(); | |
|
|
12 | ||
|
|
13 | @Override | |
|
|
14 | public void set(String key, Object value) { | |
|
|
15 | if (value instanceof Provider<?>) { | |
|
|
16 | throw new IllegalArgumentException( | |
|
|
17 | "Providers are not allowed inside nested JSON objects; " + | |
|
|
18 | "use top-level metadata.set(\"" + key + "\", provider) instead."); | |
|
|
19 | } | |
|
|
20 | values.put(key, value); | |
|
|
21 | } | |
|
|
22 | ||
|
|
23 | public Map<String, Object> toMap() { | |
|
|
24 | return Collections.unmodifiableMap(values); | |
|
|
25 | } | |
|
|
26 | } No newline at end of file | |
| @@ -0,0 +1,38 | |||
|
|
1 | package org.implab.gradle.common.json; | |
|
|
2 | ||
|
|
3 | import java.util.Arrays; | |
|
|
4 | ||
|
|
5 | import groovy.lang.Closure; | |
|
|
6 | import groovy.lang.MissingMethodException; | |
|
|
7 | ||
|
|
8 | public interface GroovyObjectSpec extends JsonObjectSpec { | |
|
|
9 | ||
|
|
10 | default void propertyMissing(String name, Object value) { | |
|
|
11 | set(name, value); | |
|
|
12 | } | |
|
|
13 | ||
|
|
14 | default Object methodMissing(String name, Object args) { | |
|
|
15 | Object[] arr = (Object[]) args; | |
|
|
16 | ||
|
|
17 | // author { ... }, repository { ... } | |
|
|
18 | if (arr.length == 1 && arr[0] instanceof Closure<?>) { | |
|
|
19 | DefaultJsonObjectSpec spec = new DefaultJsonObjectSpec(); | |
|
|
20 | Closure<?> cl = (Closure<?>) arr[0]; | |
|
|
21 | cl.setDelegate(spec); | |
|
|
22 | cl.setResolveStrategy(Closure.DELEGATE_FIRST); | |
|
|
23 | cl.call(); | |
|
|
24 | set(name, spec.toMap()); | |
|
|
25 | return null; | |
|
|
26 | } else { | |
|
|
27 | boolean hasClosure = Arrays.stream(arr) | |
|
|
28 | .anyMatch(a -> a instanceof Closure<?>); | |
|
|
29 | ||
|
|
30 | if (!hasClosure) { | |
|
|
31 | set(name, Arrays.asList(arr)); | |
|
|
32 | return null; | |
|
|
33 | } | |
|
|
34 | } | |
|
|
35 | ||
|
|
36 | throw new MissingMethodException(name, getClass(), arr); | |
|
|
37 | } | |
|
|
38 | } | |
| @@ -0,0 +1,20 | |||
|
|
1 | package org.implab.gradle.common.json; | |
|
|
2 | ||
|
|
3 | import org.gradle.api.Action; | |
|
|
4 | ||
|
|
5 | public interface JsonArraySpec { | |
|
|
6 | ||
|
|
7 | void add(Object value); | |
|
|
8 | ||
|
|
9 | default void obj(Action<? super JsonObjectSpec> action) { | |
|
|
10 | DefaultJsonObjectSpec child = new DefaultJsonObjectSpec(); | |
|
|
11 | action.execute(child); | |
|
|
12 | add(child.toMap()); | |
|
|
13 | } | |
|
|
14 | ||
|
|
15 | default void arr(Action<? super JsonArraySpec> action) { | |
|
|
16 | DefaultJsonArraySpec child = new DefaultJsonArraySpec(); | |
|
|
17 | action.execute(child); | |
|
|
18 | add(child.toList()); | |
|
|
19 | } | |
|
|
20 | } No newline at end of file | |
| @@ -0,0 +1,23 | |||
|
|
1 | package org.implab.gradle.common.json; | |
|
|
2 | ||
|
|
3 | import java.io.File; | |
|
|
4 | ||
|
|
5 | import org.gradle.api.Action; | |
|
|
6 | import org.gradle.api.file.RegularFile; | |
|
|
7 | import org.gradle.api.provider.Provider; | |
|
|
8 | ||
|
|
9 | public interface JsonMapSpec extends GroovyObjectSpec { | |
|
|
10 | void from(File file, Action<? super MapImportSpec> action); | |
|
|
11 | ||
|
|
12 | default void from(File file) { | |
|
|
13 | from(file, spec -> { | |
|
|
14 | }); | |
|
|
15 | } | |
|
|
16 | ||
|
|
17 | void from(Provider<RegularFile> fileProvider, Action<? super MapImportSpec> action); | |
|
|
18 | ||
|
|
19 | default void from(Provider<RegularFile> fileProvider) { | |
|
|
20 | from(fileProvider, spec -> { | |
|
|
21 | }); | |
|
|
22 | } | |
|
|
23 | } | |
| @@ -0,0 +1,20 | |||
|
|
1 | package org.implab.gradle.common.json; | |
|
|
2 | ||
|
|
3 | import org.gradle.api.Action; | |
|
|
4 | ||
|
|
5 | public interface JsonObjectSpec { | |
|
|
6 | ||
|
|
7 | void set(String key, Object value); | |
|
|
8 | ||
|
|
9 | default void obj(String key, Action<? super JsonObjectSpec> action) { | |
|
|
10 | DefaultJsonObjectSpec child = new DefaultJsonObjectSpec(); | |
|
|
11 | action.execute(child); | |
|
|
12 | set(key, child.toMap()); | |
|
|
13 | } | |
|
|
14 | ||
|
|
15 | default void arr(String key, Action<? super JsonArraySpec> action) { | |
|
|
16 | DefaultJsonArraySpec child = new DefaultJsonArraySpec(); | |
|
|
17 | action.execute(child); | |
|
|
18 | set(key, child.toList()); | |
|
|
19 | } | |
|
|
20 | } No newline at end of file | |
| @@ -0,0 +1,66 | |||
|
|
1 | package org.implab.gradle.common.json; | |
|
|
2 | ||
|
|
3 | import java.util.Arrays; | |
|
|
4 | import java.util.LinkedHashSet; | |
|
|
5 | import java.util.Set; | |
|
|
6 | import java.util.function.Predicate; | |
|
|
7 | ||
|
|
8 | import org.gradle.api.Action; | |
|
|
9 | import org.implab.gradle.common.utils.Closures; | |
|
|
10 | ||
|
|
11 | import groovy.lang.Closure; | |
|
|
12 | ||
|
|
13 | /** | |
|
|
14 | * Простая include/exclude-маска по ключам верхнего уровня. | |
|
|
15 | */ | |
|
|
16 | public class MapImportSpec { | |
|
|
17 | ||
|
|
18 | private final Set<String> includes = new LinkedHashSet<>(); | |
|
|
19 | private final Set<String> excludes = new LinkedHashSet<>(); | |
|
|
20 | ||
|
|
21 | public void include(String... keys) { | |
|
|
22 | includes.addAll(Arrays.asList(keys)); | |
|
|
23 | } | |
|
|
24 | ||
|
|
25 | public void exclude(String... keys) { | |
|
|
26 | excludes.addAll(Arrays.asList(keys)); | |
|
|
27 | } | |
|
|
28 | ||
|
|
29 | public Set<String> getIncludes() { | |
|
|
30 | return includes; | |
|
|
31 | } | |
|
|
32 | ||
|
|
33 | public Set<String> getExcludes() { | |
|
|
34 | return excludes; | |
|
|
35 | } | |
|
|
36 | ||
|
|
37 | public boolean hasIncludes() { | |
|
|
38 | return !includes.isEmpty(); | |
|
|
39 | } | |
|
|
40 | ||
|
|
41 | public boolean isExcluded(String key) { | |
|
|
42 | return excludes.contains(key); | |
|
|
43 | } | |
|
|
44 | ||
|
|
45 | public boolean isIncluded(String key) { | |
|
|
46 | return !hasIncludes() || includes.contains(key); | |
|
|
47 | } | |
|
|
48 | ||
|
|
49 | public boolean matches(String key) { | |
|
|
50 | return isIncluded(key) && !isExcluded(key); | |
|
|
51 | } | |
|
|
52 | ||
|
|
53 | public Predicate<String> toPredicate() { | |
|
|
54 | return this::matches; | |
|
|
55 | } | |
|
|
56 | ||
|
|
57 | public static Predicate<String> buildPredicate(Action<? super MapImportSpec> action) { | |
|
|
58 | var spec = new MapImportSpec(); | |
|
|
59 | action.execute(spec); | |
|
|
60 | return spec.toPredicate(); | |
|
|
61 | } | |
|
|
62 | ||
|
|
63 | public static Predicate<String> buildPredicate(Closure<?> closure) { | |
|
|
64 | return buildPredicate(Closures.action(closure)); | |
|
|
65 | } | |
|
|
66 | } No newline at end of file | |
| @@ -0,0 +1,181 | |||
|
|
1 | package org.implab.gradle.common.tasks; | |
|
|
2 | ||
|
|
3 | import java.util.LinkedHashMap; | |
|
|
4 | import java.util.Map; | |
|
|
5 | import java.util.Objects; | |
|
|
6 | import java.util.function.Predicate; | |
|
|
7 | import java.util.stream.Collectors; | |
|
|
8 | ||
|
|
9 | import javax.inject.Inject; | |
|
|
10 | ||
|
|
11 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
|
|
12 | import org.gradle.api.Action; | |
|
|
13 | import org.gradle.api.DefaultTask; | |
|
|
14 | import org.gradle.api.file.RegularFile; | |
|
|
15 | import org.gradle.api.file.RegularFileProperty; | |
|
|
16 | import org.gradle.api.provider.MapProperty; | |
|
|
17 | import org.gradle.api.provider.Provider; | |
|
|
18 | import org.gradle.api.provider.ProviderFactory; | |
|
|
19 | import org.gradle.api.tasks.Input; | |
|
|
20 | import org.gradle.api.tasks.Internal; | |
|
|
21 | import org.gradle.api.tasks.Optional; | |
|
|
22 | import org.gradle.api.tasks.OutputFile; | |
|
|
23 | import org.gradle.api.tasks.SkipWhenEmpty; | |
|
|
24 | import org.gradle.api.tasks.TaskAction; | |
|
|
25 | import org.implab.gradle.common.json.MapImportSpec; | |
|
|
26 | import org.implab.gradle.common.json.Json; | |
|
|
27 | import org.implab.gradle.common.json.JsonObjectSpec; | |
|
|
28 | import org.implab.gradle.common.utils.Closures; | |
|
|
29 | import org.implab.gradle.common.utils.Properties; | |
|
|
30 | ||
|
|
31 | import groovy.lang.Closure; | |
|
|
32 | ||
|
|
33 | /** | |
|
|
34 | * A Gradle task that writes JSON content to a file. | |
|
|
35 | * | |
|
|
36 | * This task allows you to build up a JSON object through various methods and | |
|
|
37 | * write it to a file. | |
|
|
38 | * You can add content directly, import from other JSON files, or merge in map | |
|
|
39 | * data with optional filtering. | |
|
|
40 | * | |
|
|
41 | * <h3>Usage Example:</h3> | |
|
|
42 | * | |
|
|
43 | * <pre> | |
|
|
44 | * tasks.register('generateConfig', WriteJson) { | |
|
|
45 | * outputFile = file('build/config.json') | |
|
|
46 | * content { | |
|
|
47 | * version = '1.0.0' | |
|
|
48 | * name = 'myapp' | |
|
|
49 | * } | |
|
|
50 | * from file('src/base-config.json') | |
|
|
51 | * putAll providers.provider { ['debug': true] } | |
|
|
52 | * } | |
|
|
53 | * </pre> | |
|
|
54 | * | |
|
|
55 | * <h3>Properties:</h3> | |
|
|
56 | * <ul> | |
|
|
57 | * <li><strong>content</strong> - A map of string keys to object values that | |
|
|
58 | * will be serialized to JSON</li> | |
|
|
59 | * <li><strong>outputFile</strong> - The file where the JSON will be | |
|
|
60 | * written</li> | |
|
|
61 | * </ul> | |
|
|
62 | * | |
|
|
63 | * <h3>Methods:</h3> | |
|
|
64 | * <ul> | |
|
|
65 | * <li><strong>content()</strong> - Configure content using a closure or | |
|
|
66 | * action</li> | |
|
|
67 | * <li><strong>from()</strong> - Import JSON content from a file with optional | |
|
|
68 | * key filtering</li> | |
|
|
69 | * <li><strong>putAll()</strong> - Merge a map provider with optional key | |
|
|
70 | * filtering</li> | |
|
|
71 | * </ul> | |
|
|
72 | * | |
|
|
73 | * @since 1.0 | |
|
|
74 | */ | |
|
|
75 | @NonNullByDefault | |
|
|
76 | public abstract class WriteJson extends DefaultTask { | |
|
|
77 | ||
|
|
78 | private final ProviderFactory providers; | |
|
|
79 | ||
|
|
80 | private final JsonObjectSpec contentSpec = new JsonObjectSpec() { | |
|
|
81 | @Override | |
|
|
82 | public void set(String key, Object value) { | |
|
|
83 | Properties.putMapEntry(getContent(), key, value); | |
|
|
84 | } | |
|
|
85 | }; | |
|
|
86 | ||
|
|
87 | @Input | |
|
|
88 | @Optional | |
|
|
89 | public abstract MapProperty<String, Object> getContent(); | |
|
|
90 | ||
|
|
91 | @OutputFile | |
|
|
92 | public abstract RegularFileProperty getOutputFile(); | |
|
|
93 | ||
|
|
94 | @Internal | |
|
|
95 | public Provider<String> getJson() { | |
|
|
96 | return getContent().map(Json::stringify); | |
|
|
97 | } | |
|
|
98 | ||
|
|
99 | @Inject | |
|
|
100 | public WriteJson(ProviderFactory providers) { | |
|
|
101 | this.providers = providers; | |
|
|
102 | } | |
|
|
103 | ||
|
|
104 | public void content(Action<? super JsonObjectSpec> configure) { | |
|
|
105 | configure.execute(contentSpec); | |
|
|
106 | } | |
|
|
107 | ||
|
|
108 | public void content(Closure<?> configure) { | |
|
|
109 | Closures.apply(configure, contentSpec); | |
|
|
110 | } | |
|
|
111 | ||
|
|
112 | public void from(RegularFile file, Closure<?> configure) { | |
|
|
113 | importContents(file, MapImportSpec.buildPredicate(configure)); | |
|
|
114 | } | |
|
|
115 | ||
|
|
116 | public void from(RegularFile file, Action<? super MapImportSpec> configure) { | |
|
|
117 | importContents(file, MapImportSpec.buildPredicate(configure)); | |
|
|
118 | } | |
|
|
119 | ||
|
|
120 | public void from(RegularFile file) { | |
|
|
121 | importContents(file, x -> true); | |
|
|
122 | } | |
|
|
123 | ||
|
|
124 | public void from(Provider<RegularFile> file, Closure<?> configure) { | |
|
|
125 | importContents(file, MapImportSpec.buildPredicate(configure)); | |
|
|
126 | } | |
|
|
127 | ||
|
|
128 | public void from(Provider<RegularFile> file, Action<? super MapImportSpec> configure) { | |
|
|
129 | importContents(file, MapImportSpec.buildPredicate(configure)); | |
|
|
130 | } | |
|
|
131 | ||
|
|
132 | public void from(Provider<RegularFile> file) { | |
|
|
133 | importContents(file, x -> true); | |
|
|
134 | } | |
|
|
135 | ||
|
|
136 | public void putAll(Provider<Map<String, Object>> mapProvider, Action<? super MapImportSpec> configure) { | |
|
|
137 | importMap(mapProvider, MapImportSpec.buildPredicate(configure)); | |
|
|
138 | } | |
|
|
139 | ||
|
|
140 | public void putAll(Provider<Map<String, Object>> mapProvider, Closure<?> configure) { | |
|
|
141 | importMap(mapProvider, MapImportSpec.buildPredicate(configure)); | |
|
|
142 | } | |
|
|
143 | ||
|
|
144 | public void putAll(Provider<Map<String, Object>> mapProvider) { | |
|
|
145 | importMap(mapProvider, x -> true); | |
|
|
146 | } | |
|
|
147 | ||
|
|
148 | private void importContents(Provider<RegularFile> file, Predicate<String> keyPredicate) { | |
|
|
149 | var jsonProvider = providers.fileContents(file).getAsText() | |
|
|
150 | .map(Json.mapParser(Object.class)::apply); | |
|
|
151 | ||
|
|
152 | importMap(jsonProvider, keyPredicate); | |
|
|
153 | } | |
|
|
154 | ||
|
|
155 | private void importContents(RegularFile file, Predicate<String> keyPredicate) { | |
|
|
156 | var jsonProvider = providers.fileContents(file).getAsText() | |
|
|
157 | .map(Json.mapParser(Object.class)::apply); | |
|
|
158 | ||
|
|
159 | importMap(jsonProvider, keyPredicate); | |
|
|
160 | } | |
|
|
161 | ||
|
|
162 | private void importMap(Provider<Map<String, Object>> mapProvider, Predicate<? super String> keyPredicate) { | |
|
|
163 | var filteredProvider = mapProvider.map(v -> v.entrySet().stream() | |
|
|
164 | .filter(pair -> keyPredicate.test(pair.getKey()) && Objects.nonNull(pair.getValue())) | |
|
|
165 | .collect(Collectors.toMap( | |
|
|
166 | Map.Entry::getKey, | |
|
|
167 | Map.Entry::getValue, | |
|
|
168 | (a, b) -> b, | |
|
|
169 | LinkedHashMap::new))); | |
|
|
170 | ||
|
|
171 | getContent().putAll(filteredProvider); | |
|
|
172 | } | |
|
|
173 | ||
|
|
174 | @TaskAction | |
|
|
175 | public void run() { | |
|
|
176 | var data = getContent().getOrElse(Map.of()); | |
|
|
177 | var file = getOutputFile().getAsFile().get(); | |
|
|
178 | Json.write(file, data); | |
|
|
179 | } | |
|
|
180 | ||
|
|
181 | } | |
| @@ -0,0 +1,15 | |||
|
|
1 | package org.implab.gradle.common.utils; | |
|
|
2 | ||
|
|
3 | import java.util.function.Consumer; | |
|
|
4 | ||
|
|
5 | import org.gradle.api.plugins.ExtensionContainer; | |
|
|
6 | ||
|
|
7 | public final class Extensions { | |
|
|
8 | private Extensions() { | |
|
|
9 | } | |
|
|
10 | ||
|
|
11 | public static Consumer<Class<?>> registerClass(ExtensionContainer extensions) { | |
|
|
12 | var extra = extensions.getExtraProperties(); | |
|
|
13 | return clazz -> extra.set(clazz.getSimpleName(), clazz); | |
|
|
14 | } | |
|
|
15 | } | |
| @@ -0,0 +1,316 | |||
|
|
1 | package org.implab.gradle.common.utils; | |
|
|
2 | ||
|
|
3 | import com.fasterxml.jackson.annotation.JsonCreator; | |
|
|
4 | import com.fasterxml.jackson.annotation.JsonValue; | |
|
|
5 | ||
|
|
6 | import java.util.Objects; | |
|
|
7 | import java.util.Optional; | |
|
|
8 | import java.util.regex.Matcher; | |
|
|
9 | import java.util.regex.Pattern; | |
|
|
10 | ||
|
|
11 | /** | |
|
|
12 | * Immutable Semantic Version (SemVer 2.0.0) of the form: | |
|
|
13 | * | |
|
|
14 | * <pre> | |
|
|
15 | * MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD] | |
|
|
16 | * </pre> | |
|
|
17 | * | |
|
|
18 | * Ordering (as defined by {@link #compareTo(SemVersion)}) follows | |
|
|
19 | * the SemVer 2.0.0 precedence rules: | |
|
|
20 | * <ul> | |
|
|
21 | * <li>Compare {@code major}, then {@code minor}, then {@code patch}.</li> | |
|
|
22 | * <li>Pre-release versions have lower precedence than the corresponding | |
|
|
23 | * normal version.</li> | |
|
|
24 | * <li>Build metadata does not affect precedence.</li> | |
|
|
25 | * </ul> | |
|
|
26 | * | |
|
|
27 | * Public API does not use {@code null} for semantic values: | |
|
|
28 | * {@link Optional} is used for pre-release and build metadata. | |
|
|
29 | */ | |
|
|
30 | public record SemVersion( | |
|
|
31 | int major, | |
|
|
32 | int minor, | |
|
|
33 | int patch, | |
|
|
34 | Optional<String> preRelease, | |
|
|
35 | Optional<String> buildMetadata) implements Comparable<SemVersion> { | |
|
|
36 | ||
|
|
37 | // Pattern close to the official SemVer 2.0.0 recommendation. | |
|
|
38 | // Groups: | |
|
|
39 | // 1 - major | |
|
|
40 | // 2 - minor | |
|
|
41 | // 3 - patch | |
|
|
42 | // 4 - pre-release (without '-') | |
|
|
43 | // 5 - build metadata (without '+') | |
|
|
44 | private static final Pattern SEMVER_PATTERN = Pattern.compile( | |
|
|
45 | "^(0|[1-9]\\d*)" + // major | |
|
|
46 | "\\.(0|[1-9]\\d*)" + // minor | |
|
|
47 | "\\.(0|[1-9]\\d*)" + // patch | |
|
|
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-]*))*" + | |
|
|
50 | "))?" + // pre-release | |
|
|
51 | "(?:\\+([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?$" // build metadata | |
|
|
52 | ); | |
|
|
53 | ||
|
|
54 | /** | |
|
|
55 | * Compact constructor with basic invariants. | |
|
|
56 | */ | |
|
|
57 | public SemVersion { | |
|
|
58 | if (major < 0 || minor < 0 || patch < 0) { | |
|
|
59 | throw new IllegalArgumentException("Version numbers must be >= 0"); | |
|
|
60 | } | |
|
|
61 | // Be tolerant internally, but never expose null Optionals. | |
|
|
62 | preRelease = (preRelease != null) ? preRelease : Optional.empty(); | |
|
|
63 | buildMetadata = (buildMetadata != null) ? buildMetadata : Optional.empty(); | |
|
|
64 | } | |
|
|
65 | ||
|
|
66 | /** | |
|
|
67 | * Creates a version without pre-release and build metadata. | |
|
|
68 | * | |
|
|
69 | * @param major non-negative MAJOR number | |
|
|
70 | * @param minor non-negative MINOR number | |
|
|
71 | * @param patch non-negative PATCH number | |
|
|
72 | */ | |
|
|
73 | public static SemVersion of(int major, int minor, int patch) { | |
|
|
74 | return new SemVersion(major, minor, patch, Optional.empty(), Optional.empty()); | |
|
|
75 | } | |
|
|
76 | ||
|
|
77 | /** | |
|
|
78 | * Creates a version from components. | |
|
|
79 | * | |
|
|
80 | * @param major non-negative MAJOR number | |
|
|
81 | * @param minor non-negative MINOR number | |
|
|
82 | * @param patch non-negative PATCH number | |
|
|
83 | * @param preRelease optional pre-release part (without '-') | |
|
|
84 | * @param buildMetadata optional build metadata (without '+') | |
|
|
85 | */ | |
|
|
86 | public static SemVersion of( | |
|
|
87 | int major, | |
|
|
88 | int minor, | |
|
|
89 | int patch, | |
|
|
90 | Optional<String> preRelease, | |
|
|
91 | Optional<String> buildMetadata) { | |
|
|
92 | return new SemVersion( | |
|
|
93 | major, | |
|
|
94 | minor, | |
|
|
95 | patch, | |
|
|
96 | Objects.requireNonNull(preRelease, "preRelease"), | |
|
|
97 | Objects.requireNonNull(buildMetadata, "buildMetadata")); | |
|
|
98 | } | |
|
|
99 | ||
|
|
100 | /** | |
|
|
101 | * Parses a SemVer string of the form | |
|
|
102 | * {@code MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]}. | |
|
|
103 | * | |
|
|
104 | * @param value the string to parse (must not be {@code null}) | |
|
|
105 | * @return a new {@code SemVersion} | |
|
|
106 | * @throws IllegalArgumentException if the string is not a valid SemVer | |
|
|
107 | */ | |
|
|
108 | public static SemVersion parse(String value) { | |
|
|
109 | Objects.requireNonNull(value, "value"); | |
|
|
110 | ||
|
|
111 | Matcher m = SEMVER_PATTERN.matcher(value); | |
|
|
112 | if (!m.matches()) { | |
|
|
113 | throw new IllegalArgumentException("Invalid SemVer: " + value); | |
|
|
114 | } | |
|
|
115 | ||
|
|
116 | int major = Integer.parseInt(m.group(1)); | |
|
|
117 | int minor = Integer.parseInt(m.group(2)); | |
|
|
118 | int patch = Integer.parseInt(m.group(3)); | |
|
|
119 | ||
|
|
120 | String pre = m.group(4); | |
|
|
121 | String meta = m.group(5); | |
|
|
122 | ||
|
|
123 | Optional<String> preOpt = Optional.ofNullable(pre); | |
|
|
124 | Optional<String> metaOpt = Optional.ofNullable(meta); | |
|
|
125 | ||
|
|
126 | // Extra validation for numeric pre-release identifiers (no leading zeros) | |
|
|
127 | preOpt.ifPresent(SemVersion::validatePreReleaseIdentifiers); | |
|
|
128 | ||
|
|
129 | return new SemVersion(major, minor, patch, preOpt, metaOpt); | |
|
|
130 | } | |
|
|
131 | ||
|
|
132 | private static void validatePreReleaseIdentifiers(String preRelease) { | |
|
|
133 | String[] parts = preRelease.split("\\."); | |
|
|
134 | for (String p : parts) { | |
|
|
135 | if (isNumericIdentifier(p)) { | |
|
|
136 | // numeric identifiers must not have leading zeros (except "0") | |
|
|
137 | if (p.length() > 1 && p.charAt(0) == '0') { | |
|
|
138 | throw new IllegalArgumentException( | |
|
|
139 | "Numeric pre-release identifier must not contain leading zeros: " + p); | |
|
|
140 | } | |
|
|
141 | } | |
|
|
142 | } | |
|
|
143 | } | |
|
|
144 | ||
|
|
145 | private static boolean isNumericIdentifier(String s) { | |
|
|
146 | int len = s.length(); | |
|
|
147 | if (len == 0) { | |
|
|
148 | return false; | |
|
|
149 | } | |
|
|
150 | for (int i = 0; i < len; i++) { | |
|
|
151 | char c = s.charAt(i); | |
|
|
152 | if (c < '0' || c > '9') { | |
|
|
153 | return false; | |
|
|
154 | } | |
|
|
155 | } | |
|
|
156 | return true; | |
|
|
157 | } | |
|
|
158 | ||
|
|
159 | /** | |
|
|
160 | * Returns {@code true} if this version has a pre-release part. | |
|
|
161 | * | |
|
|
162 | * @return {@code true} if {@link #preRelease()} is present | |
|
|
163 | */ | |
|
|
164 | public boolean isPreRelease() { | |
|
|
165 | return preRelease().isPresent(); | |
|
|
166 | } | |
|
|
167 | ||
|
|
168 | /** | |
|
|
169 | * Returns {@code true} if this version is considered "stable". | |
|
|
170 | * <p> | |
|
|
171 | * By convention: | |
|
|
172 | * <ul> | |
|
|
173 | * <li>MAJOR must be greater than 0</li> | |
|
|
174 | * <li>No pre-release part is present</li> | |
|
|
175 | * </ul> | |
|
|
176 | * | |
|
|
177 | * @return {@code true} if this version is a stable release | |
|
|
178 | */ | |
|
|
179 | public boolean isStable() { | |
|
|
180 | return !isPreRelease() && major() > 0; | |
|
|
181 | } | |
|
|
182 | ||
|
|
183 | /** | |
|
|
184 | * Returns {@code true} if this version has strictly lower precedence | |
|
|
185 | * than the given {@code other} version according to | |
|
|
186 | * {@link #compareTo(SemVersion)}. | |
|
|
187 | * | |
|
|
188 | * @param other the version to compare to | |
|
|
189 | * @return {@code true} if {@code this.compareTo(other) < 0} | |
|
|
190 | */ | |
|
|
191 | public boolean isBefore(SemVersion other) { | |
|
|
192 | return compareTo(other) < 0; | |
|
|
193 | } | |
|
|
194 | ||
|
|
195 | /** | |
|
|
196 | * Returns {@code true} if this version has strictly higher precedence | |
|
|
197 | * than the given {@code other} version according to | |
|
|
198 | * {@link #compareTo(SemVersion)}. | |
|
|
199 | * | |
|
|
200 | * @param other the version to compare to | |
|
|
201 | * @return {@code true} if {@code this.compareTo(other) > 0} | |
|
|
202 | */ | |
|
|
203 | public boolean isAfter(SemVersion other) { | |
|
|
204 | return compareTo(other) > 0; | |
|
|
205 | } | |
|
|
206 | ||
|
|
207 | /** | |
|
|
208 | * Canonical SemVer string representation. | |
|
|
209 | * <p> | |
|
|
210 | * This method is also used by Jackson during serialization. | |
|
|
211 | * | |
|
|
212 | * @return canonical SemVer string, e.g. {@code "1.2.3-alpha+build.1"} | |
|
|
213 | */ | |
|
|
214 | @JsonValue | |
|
|
215 | public String asString() { | |
|
|
216 | StringBuilder sb = new StringBuilder() | |
|
|
217 | .append(major).append('.') | |
|
|
218 | .append(minor).append('.') | |
|
|
219 | .append(patch); | |
|
|
220 | ||
|
|
221 | preRelease.ifPresent(pr -> sb.append('-').append(pr)); | |
|
|
222 | buildMetadata.ifPresent(md -> sb.append('+').append(md)); | |
|
|
223 | ||
|
|
224 | return sb.toString(); | |
|
|
225 | } | |
|
|
226 | ||
|
|
227 | /** | |
|
|
228 | * Creates a {@code SemVersion} from a canonical SemVer string. | |
|
|
229 | * <p> | |
|
|
230 | * Jackson will use this factory method when deserializing from a JSON string. | |
|
|
231 | * | |
|
|
232 | * @param value canonical SemVer string | |
|
|
233 | * @return parsed {@code SemVersion} | |
|
|
234 | */ | |
|
|
235 | @JsonCreator(mode = JsonCreator.Mode.DELEGATING) | |
|
|
236 | public static SemVersion fromJson(String value) { | |
|
|
237 | return parse(value); | |
|
|
238 | } | |
|
|
239 | ||
|
|
240 | /** | |
|
|
241 | * Compares this version to another {@link SemVersion} according to the | |
|
|
242 | * SemVer 2.0.0 precedence rules. | |
|
|
243 | * <p> | |
|
|
244 | * Build metadata is ignored for ordering. | |
|
|
245 | */ | |
|
|
246 | @Override | |
|
|
247 | public int compareTo(SemVersion other) { | |
|
|
248 | // 1. major / minor / patch | |
|
|
249 | int c = Integer.compare(this.major, other.major); | |
|
|
250 | if (c != 0) | |
|
|
251 | return c; | |
|
|
252 | ||
|
|
253 | c = Integer.compare(this.minor, other.minor); | |
|
|
254 | if (c != 0) | |
|
|
255 | return c; | |
|
|
256 | ||
|
|
257 | c = Integer.compare(this.patch, other.patch); | |
|
|
258 | if (c != 0) | |
|
|
259 | return c; | |
|
|
260 | ||
|
|
261 | // 2. pre-release (absence > presence) | |
|
|
262 | boolean thisHasPre = this.preRelease.isPresent(); | |
|
|
263 | boolean otherHasPre = other.preRelease.isPresent(); | |
|
|
264 | ||
|
|
265 | if (!thisHasPre && !otherHasPre) { | |
|
|
266 | return 0; | |
|
|
267 | } else if (!thisHasPre) { | |
|
|
268 | // normal version > pre-release | |
|
|
269 | return 1; | |
|
|
270 | } else if (!otherHasPre) { | |
|
|
271 | return -1; | |
|
|
272 | } | |
|
|
273 | ||
|
|
274 | // 3. both have pre-release: compare identifiers | |
|
|
275 | return comparePreRelease(this.preRelease.get(), other.preRelease.get()); | |
|
|
276 | } | |
|
|
277 | ||
|
|
278 | private static int comparePreRelease(String a, String b) { | |
|
|
279 | String[] aParts = a.split("\\."); | |
|
|
280 | String[] bParts = b.split("\\."); | |
|
|
281 | ||
|
|
282 | int len = Math.min(aParts.length, bParts.length); | |
|
|
283 | for (int i = 0; i < len; i++) { | |
|
|
284 | String ai = aParts[i]; | |
|
|
285 | String bi = bParts[i]; | |
|
|
286 | ||
|
|
287 | boolean aNum = isNumericIdentifier(ai); | |
|
|
288 | boolean bNum = isNumericIdentifier(bi); | |
|
|
289 | ||
|
|
290 | if (aNum && bNum) { | |
|
|
291 | int aiVal = Integer.parseInt(ai); | |
|
|
292 | int biVal = Integer.parseInt(bi); | |
|
|
293 | int c = Integer.compare(aiVal, biVal); | |
|
|
294 | if (c != 0) | |
|
|
295 | return c; | |
|
|
296 | } else if (aNum && !bNum) { | |
|
|
297 | // numeric identifiers have lower precedence than non-numeric | |
|
|
298 | return -1; | |
|
|
299 | } else if (!aNum && bNum) { | |
|
|
300 | return 1; | |
|
|
301 | } else { | |
|
|
302 | int c = ai.compareTo(bi); | |
|
|
303 | if (c != 0) | |
|
|
304 | return c; | |
|
|
305 | } | |
|
|
306 | } | |
|
|
307 | ||
|
|
308 | // If all common identifiers are equal, the shorter one has lower precedence | |
|
|
309 | return Integer.compare(aParts.length, bParts.length); | |
|
|
310 | } | |
|
|
311 | ||
|
|
312 | @Override | |
|
|
313 | public String toString() { | |
|
|
314 | return asString(); | |
|
|
315 | } | |
|
|
316 | } | |
| @@ -1,43 +1,42 | |||
|
|
1 | 1 | plugins { |
|
|
2 | 2 | id "java-library" |
|
|
3 | 3 | id "ivy-publish" |
|
|
4 | 4 | } |
|
|
5 | 5 | |
|
|
6 | 6 | java { |
|
|
7 | 7 | withJavadocJar() |
|
|
8 | 8 | withSourcesJar() |
|
|
9 | 9 | toolchain { |
|
|
10 | 10 | languageVersion = JavaLanguageVersion.of(21) |
|
|
11 | 11 | } |
|
|
12 | 12 | } |
|
|
13 | 13 | |
|
|
14 | 14 | dependencies { |
|
|
15 | 15 | compileOnly libs.jdt.annotations |
|
|
16 | 16 | |
|
|
17 | implementation libs.bundles.jackson | |
|
|
18 | ||
|
|
19 | api gradleApi() | |
|
|
17 | api gradleApi(), | |
|
|
18 | libs.bundles.jackson | |
|
|
20 | 19 | } |
|
|
21 | 20 | |
|
|
22 | 21 | task printVersion{ |
|
|
23 | 22 | doLast { |
|
|
24 | 23 | println "project: $project.group:$project.name:$project.version" |
|
|
25 | 24 | println "jar: ${->jar.archiveFileName.get()}" |
|
|
26 | 25 | } |
|
|
27 | 26 | } |
|
|
28 | 27 | |
|
|
29 | 28 | publishing { |
|
|
30 | 29 | repositories { |
|
|
31 | 30 | ivy { |
|
|
32 | 31 | url "${System.properties["user.home"]}/ivy-repo" |
|
|
33 | 32 | } |
|
|
34 | 33 | } |
|
|
35 | 34 | publications { |
|
|
36 | 35 | ivy(IvyPublication) { |
|
|
37 | 36 | from components.java |
|
|
38 | 37 | descriptor.description { |
|
|
39 | 38 | text = providers.provider({ description }) |
|
|
40 | 39 | } |
|
|
41 | 40 | } |
|
|
42 | 41 | } |
|
|
43 | 42 | } No newline at end of file |
| @@ -1,49 +1,61 | |||
|
|
1 | 1 | package org.implab.gradle.common.dsl; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.io.File; |
|
|
4 | 4 | import java.io.InputStream; |
|
|
5 | 5 | import java.util.Optional; |
|
|
6 | 6 | import java.util.function.Supplier; |
|
|
7 | 7 | |
|
|
8 | 8 | import org.gradle.api.provider.Provider; |
|
|
9 | import org.gradle.util.Configurable; | |
|
|
9 | 10 | import org.implab.gradle.common.exec.RedirectFrom; |
|
|
11 | import org.implab.gradle.common.utils.Closures; | |
|
|
10 | 12 | |
|
|
11 | public class RedirectFromSpec { | |
|
|
13 | import groovy.lang.Closure; | |
|
|
14 | ||
|
|
15 | public class RedirectFromSpec implements Configurable<RedirectFromSpec> { | |
|
|
12 | 16 | private Supplier<RedirectFrom> streamRedirect; |
|
|
13 | 17 | |
|
|
14 | 18 | public boolean isRedirected() { |
|
|
15 | 19 | return streamRedirect != null; |
|
|
16 | 20 | } |
|
|
17 | 21 | |
|
|
18 | 22 | public Optional<RedirectFrom> getRedirection() { |
|
|
19 | 23 | return streamRedirect != null ? Optional.ofNullable(streamRedirect.get()) : Optional.empty(); |
|
|
20 | 24 | } |
|
|
21 | 25 | |
|
|
22 | 26 | public void fromFile(File file) { |
|
|
23 | 27 | streamRedirect = () -> RedirectFrom.file(file); |
|
|
24 | 28 | } |
|
|
25 | 29 | |
|
|
26 | 30 | public void fromFile(Provider<File> fileProvider) { |
|
|
27 | 31 | streamRedirect = fileProvider.map(RedirectFrom::file)::getOrNull; |
|
|
28 | 32 | } |
|
|
29 | 33 | |
|
|
30 | 34 | public void fromStream(InputStream stream) { |
|
|
31 | 35 | streamRedirect = () -> RedirectFrom.stream(stream); |
|
|
32 | 36 | } |
|
|
33 | 37 | |
|
|
34 | 38 | public void fromStream(Provider<InputStream> streamProvider) { |
|
|
35 | 39 | streamRedirect = streamProvider.map(RedirectFrom::stream)::getOrNull; |
|
|
36 | 40 | } |
|
|
37 | 41 | |
|
|
38 | 42 | public void from(Object input) { |
|
|
39 | 43 | if (input instanceof Provider<?> inputProvider) { |
|
|
40 | 44 | streamRedirect = inputProvider.map(RedirectFrom::any)::get; |
|
|
41 | 45 | } else { |
|
|
42 | 46 | streamRedirect = () -> RedirectFrom.any(input); |
|
|
43 | 47 | } |
|
|
44 | 48 | } |
|
|
45 | 49 | |
|
|
46 | 50 | public void empty() { |
|
|
47 | 51 | streamRedirect = () -> null; |
|
|
48 | 52 | } |
|
|
53 | ||
|
|
54 | @Override | |
|
|
55 | public RedirectFromSpec configure(Closure cl) { | |
|
|
56 | Closures.apply(cl, this); | |
|
|
57 | return this; | |
|
|
58 | } | |
|
|
59 | ||
|
|
60 | ||
|
|
49 | 61 | } |
| @@ -1,65 +1,75 | |||
|
|
1 | 1 | package org.implab.gradle.common.dsl; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.io.File; |
|
|
4 | 4 | import java.io.OutputStream; |
|
|
5 | 5 | import java.util.Optional; |
|
|
6 | 6 | import java.util.function.Consumer; |
|
|
7 | 7 | import java.util.function.Supplier; |
|
|
8 | 8 | |
|
|
9 | 9 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
|
10 | 10 | import org.eclipse.jdt.annotation.Nullable; |
|
|
11 | 11 | import org.gradle.api.provider.Provider; |
|
|
12 | import org.gradle.util.Configurable; | |
|
|
12 | 13 | import org.implab.gradle.common.exec.RedirectTo; |
|
|
14 | import org.implab.gradle.common.utils.Closures; | |
|
|
15 | ||
|
|
16 | import groovy.lang.Closure; | |
|
|
13 | 17 | |
|
|
14 | 18 | @NonNullByDefault |
|
|
15 | public class RedirectToSpec { | |
|
|
19 | public class RedirectToSpec implements Configurable<RedirectToSpec> { | |
|
|
16 | 20 | private Supplier<RedirectTo> streamRedirect; |
|
|
17 | 21 | |
|
|
18 | 22 | public boolean isRedirected() { |
|
|
19 | 23 | return getRedirection().isPresent(); |
|
|
20 | 24 | } |
|
|
21 | 25 | |
|
|
22 | 26 | public Optional<RedirectTo> getRedirection() { |
|
|
23 | 27 | return streamRedirect != null ? Optional.ofNullable(streamRedirect.get()) : Optional.empty(); |
|
|
24 | 28 | } |
|
|
25 | 29 | |
|
|
26 | 30 | public @Nullable RedirectTo getRedirectionOrNull() { |
|
|
27 | 31 | return streamRedirect != null ? streamRedirect.get() : null; |
|
|
28 | 32 | } |
|
|
29 | 33 | |
|
|
30 | 34 | public void toFile(File file) { |
|
|
31 | 35 | streamRedirect = () -> RedirectTo.file(file); |
|
|
32 | 36 | } |
|
|
33 | 37 | |
|
|
34 | 38 | public void toFile(Provider<File> fileProvider) { |
|
|
35 | 39 | streamRedirect = fileProvider.map(RedirectTo::file)::getOrNull; |
|
|
36 | 40 | } |
|
|
37 | 41 | |
|
|
38 | 42 | public void toStream(OutputStream stream) { |
|
|
39 | 43 | streamRedirect = () -> RedirectTo.stream(stream); |
|
|
40 | 44 | } |
|
|
41 | 45 | |
|
|
42 | 46 | public void toStream(Provider<OutputStream> streamProvider) { |
|
|
43 | 47 | streamRedirect = streamProvider.map(RedirectTo::stream)::getOrNull; |
|
|
44 | 48 | } |
|
|
45 | 49 | |
|
|
46 | 50 | public void to(Object output) { |
|
|
47 | 51 | if (output instanceof Provider<?> outputProvider) { |
|
|
48 | 52 | streamRedirect = outputProvider.map(RedirectTo::any)::get; |
|
|
49 | 53 | } else { |
|
|
50 | 54 | streamRedirect = () -> RedirectTo.any(output); |
|
|
51 | 55 | } |
|
|
52 | 56 | } |
|
|
53 | 57 | |
|
|
54 | 58 | public void eachLine(Consumer<String> consumer) { |
|
|
55 | 59 | streamRedirect = () -> RedirectTo.eachLine(consumer); |
|
|
56 | 60 | } |
|
|
57 | 61 | |
|
|
58 | 62 | public void allText(Consumer<String> consumer) { |
|
|
59 | 63 | streamRedirect = () -> RedirectTo.allText(consumer); |
|
|
60 | 64 | } |
|
|
61 | 65 | |
|
|
62 | 66 | public void discard() { |
|
|
63 | 67 | streamRedirect = () -> null; |
|
|
64 | 68 | } |
|
|
69 | ||
|
|
70 | @Override | |
|
|
71 | public RedirectToSpec configure(Closure cl) { | |
|
|
72 | Closures.apply(cl, this); | |
|
|
73 | return this; | |
|
|
74 | } | |
|
|
65 | 75 | } |
| @@ -1,126 +1,142 | |||
|
|
1 |
package org.implab.gradle.common. |
|
|
|
1 | package org.implab.gradle.common.json; | |
|
|
2 | 2 | |
|
|
3 | 3 | import java.io.File; |
|
|
4 | 4 | import java.io.IOException; |
|
|
5 | import java.util.Map; | |
|
|
5 | 6 | import java.util.function.Consumer; |
|
|
7 | import java.util.function.Function; | |
|
|
6 | 8 | |
|
|
7 | 9 | import org.implab.gradle.common.utils.LazyValue; |
|
|
8 | 10 | |
|
|
9 | 11 | import com.fasterxml.jackson.annotation.JsonInclude.Include; |
|
|
10 | 12 | import com.fasterxml.jackson.core.JsonProcessingException; |
|
|
11 | 13 | import com.fasterxml.jackson.core.type.TypeReference; |
|
|
12 | 14 | import com.fasterxml.jackson.core.util.DefaultIndenter; |
|
|
13 | 15 | import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; |
|
|
14 | 16 | import com.fasterxml.jackson.databind.JsonMappingException; |
|
|
15 | 17 | import com.fasterxml.jackson.databind.ObjectMapper; |
|
|
16 | 18 | import com.fasterxml.jackson.databind.SerializationFeature; |
|
|
17 | 19 | import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; |
|
|
18 | 20 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; |
|
|
19 | 21 | |
|
|
20 | 22 | public class Json { |
|
|
21 | 23 | |
|
|
22 | 24 | private final static LazyValue<ObjectMapper> mapper = new LazyValue<>(Json::createMapper); |
|
|
23 | 25 | |
|
|
24 | 26 | private static ObjectMapper createMapper() { |
|
|
25 | 27 | DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter(); |
|
|
26 | 28 | prettyPrinter.indentArraysWith(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE); |
|
|
27 | 29 | |
|
|
28 | 30 | return new ObjectMapper() |
|
|
29 | 31 | .registerModule(new Jdk8Module()) |
|
|
30 | 32 | .registerModule(new JavaTimeModule()) |
|
|
31 | 33 | .setSerializationInclusion(Include.NON_EMPTY) |
|
|
32 | 34 | .enable(SerializationFeature.INDENT_OUTPUT) |
|
|
33 | 35 | .setDefaultPrettyPrinter(prettyPrinter); |
|
|
34 | 36 | } |
|
|
35 | 37 | |
|
|
36 | 38 | public static ObjectMapper jsonMapper() { |
|
|
37 | 39 | return mapper.get(); |
|
|
38 | 40 | } |
|
|
39 | 41 | |
|
|
40 |
public static Consumer< |
|
|
|
41 |
return |
|
|
|
42 | public static Consumer<Object> fileWriter(File file) { | |
|
|
43 | return value -> { | |
|
|
42 | 44 | try { |
|
|
43 | 45 | jsonMapper().writeValue(file, value); |
|
|
44 | 46 | } catch (IOException e) { |
|
|
45 | 47 | throw new SerializationException(e); |
|
|
46 | 48 | } |
|
|
47 | 49 | }; |
|
|
48 | 50 | } |
|
|
49 | 51 | |
|
|
50 | 52 | public static void write(File file, Object value) { |
|
|
51 | 53 | try { |
|
|
52 | 54 | jsonMapper().writeValue(file, value); |
|
|
53 | 55 | } catch (IOException e) { |
|
|
54 | 56 | throw new SerializationException(e); |
|
|
55 | 57 | } |
|
|
56 | 58 | } |
|
|
57 | 59 | |
|
|
58 | 60 | public static String stringify(Object value) { |
|
|
59 | 61 | try { |
|
|
60 | 62 | return jsonMapper().writeValueAsString(value); |
|
|
61 | 63 | } catch (JsonProcessingException e) { |
|
|
62 | 64 | throw new SerializationException(e); |
|
|
63 | 65 | } |
|
|
64 | 66 | } |
|
|
65 | 67 | |
|
|
66 | 68 | public static <T> T parse(String json, Class<T> clazz) { |
|
|
67 | 69 | try { |
|
|
68 | 70 | return jsonMapper().readValue(json, clazz); |
|
|
69 | 71 | } catch (JsonProcessingException e) { |
|
|
70 | 72 | throw new SerializationException(e); |
|
|
71 | 73 | } |
|
|
72 | 74 | } |
|
|
73 | 75 | |
|
|
74 | 76 | public static <T> T parse(String json, TypeReference<T> type) { |
|
|
75 | 77 | try { |
|
|
76 | 78 | return jsonMapper().readValue(json, type); |
|
|
77 | 79 | } catch (JsonProcessingException e) { |
|
|
78 | 80 | throw new SerializationException(e); |
|
|
79 | 81 | } |
|
|
80 | 82 | } |
|
|
81 | 83 | |
|
|
84 | public static <V> Map<String, V> parseMap(String json, Class<V> clazz) { | |
|
|
85 | return parse(json, new TypeReference<>() { | |
|
|
86 | }); | |
|
|
87 | } | |
|
|
88 | ||
|
|
89 | public static <V> Function<String, Map<String, V>> mapParser(Class<V> clazz) { | |
|
|
90 | return json -> parseMap(json, clazz); | |
|
|
91 | } | |
|
|
92 | ||
|
|
82 | 93 | public static <T> T read(File file, TypeReference<T> type) { |
|
|
83 | 94 | try { |
|
|
84 | 95 | return jsonMapper().readValue(file, type); |
|
|
85 | 96 | } catch (IOException e) { |
|
|
86 | 97 | throw new SerializationException(e); |
|
|
87 | 98 | } |
|
|
88 | 99 | } |
|
|
89 | 100 | |
|
|
101 | public static <V> Map<String, V> readMap(File file, Class<V> clazz) { | |
|
|
102 | return read(file, new TypeReference<>() { | |
|
|
103 | }); | |
|
|
104 | } | |
|
|
105 | ||
|
|
90 | 106 | public static <T> T updateValue(T target, Object source) { |
|
|
91 | 107 | try { |
|
|
92 | 108 | return jsonMapper().updateValue(target, source); |
|
|
93 | 109 | } catch (JsonMappingException e) { |
|
|
94 | 110 | throw new SerializationException(e); |
|
|
95 | 111 | } |
|
|
96 | 112 | } |
|
|
97 | 113 | |
|
|
98 | 114 | public static <T> T convertValue(Object source, Class<T> clazz) { |
|
|
99 | 115 | var mapper = jsonMapper(); |
|
|
100 | 116 | var buf = mapper.getSerializerProvider().bufferForValueConversion(jsonMapper()); |
|
|
101 | 117 | |
|
|
102 | 118 | try { |
|
|
103 | 119 | mapper.writeValue(buf, source); |
|
|
104 | 120 | return mapper.readValue(buf.asParser(), clazz); |
|
|
105 | 121 | } catch (IOException e) { |
|
|
106 | 122 | throw new SerializationException(e); |
|
|
107 | 123 | } |
|
|
108 | 124 | } |
|
|
109 | 125 | |
|
|
110 | 126 | public static class SerializationException extends RuntimeException { |
|
|
111 | 127 | public SerializationException() { |
|
|
112 | 128 | } |
|
|
113 | 129 | |
|
|
114 | 130 | public SerializationException(String message) { |
|
|
115 | 131 | super(message); |
|
|
116 | 132 | } |
|
|
117 | 133 | |
|
|
118 | 134 | public SerializationException(Throwable cause) { |
|
|
119 | 135 | super(cause); |
|
|
120 | 136 | } |
|
|
121 | 137 | |
|
|
122 | 138 | public SerializationException(String message, Throwable cause) { |
|
|
123 | 139 | super(message, cause); |
|
|
124 | 140 | } |
|
|
125 | 141 | } |
|
|
126 | 142 | } |
| @@ -1,32 +1,31 | |||
|
|
1 | 1 | package org.implab.gradle.common.utils; |
|
|
2 | 2 | |
|
|
3 | 3 | import groovy.lang.Closure; |
|
|
4 | 4 | |
|
|
5 | 5 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
|
6 | 6 | import org.gradle.api.Action; |
|
|
7 | 7 | |
|
|
8 | 8 | @NonNullByDefault |
|
|
9 | 9 | public final class Closures { |
|
|
10 | 10 | private Closures() { |
|
|
11 | 11 | } |
|
|
12 | 12 | |
|
|
13 | 13 | /** |
|
|
14 | 14 | * Wraps {@link Action} around the specified closure. The parameter |
|
|
15 | 15 | * of the action will be used as delegate in the specified closure. |
|
|
16 | 16 | * |
|
|
17 | 17 | * @param <T> The type of the action parameter |
|
|
18 | 18 | * @param closure The closure |
|
|
19 | 19 | * @return |
|
|
20 | 20 | */ |
|
|
21 | 21 | public static <T> Action<T> action(Closure<?> closure) { |
|
|
22 | 22 | return arg -> apply(closure, arg); |
|
|
23 | 23 | } |
|
|
24 | 24 | |
|
|
25 | 25 | public static void apply(Closure<?> action, Object target) { |
|
|
26 | 26 | var c = (Closure<?>)action.clone(); |
|
|
27 | 27 | c.setResolveStrategy(Closure.DELEGATE_FIRST); |
|
|
28 | 28 | c.setDelegate(target); |
|
|
29 | 29 | c.call(target); |
|
|
30 | ||
|
|
31 | 30 | } |
|
|
32 | 31 | } |
| @@ -1,35 +1,33 | |||
|
|
1 | 1 | package org.implab.gradle.common.utils; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.io.File; |
|
|
4 | 4 | import java.util.Optional; |
|
|
5 | 5 | |
|
|
6 | 6 | import org.implab.gradle.common.utils.os.SystemResolver; |
|
|
7 | 7 | |
|
|
8 | 8 | public interface OperatingSystem { |
|
|
9 | 9 | |
|
|
10 | public static OperatingSystem CURRENT = SystemResolver.current(); | |
|
|
11 | ||
|
|
12 | 10 | public static final String WINDOWS_FAMILY = "windows"; |
|
|
13 | 11 | |
|
|
14 | 12 | public static final String LINUX_FAMILY = "linux"; |
|
|
15 | 13 | |
|
|
16 | 14 | public static final String FREE_BSD_FAMILY = "freebsd"; |
|
|
17 | 15 | |
|
|
18 | 16 | public static final String MAC_OS_FAMILY = "os x"; |
|
|
19 | 17 | |
|
|
20 | 18 | public static final String UNKNOWN_FAMILY = "unknown"; |
|
|
21 | 19 | |
|
|
22 | 20 | String family(); |
|
|
23 | 21 | |
|
|
24 | 22 | String name(); |
|
|
25 | 23 | |
|
|
26 | 24 | String version(); |
|
|
27 | 25 | |
|
|
28 | 26 | Optional<File> which(String cmd); |
|
|
29 | 27 | |
|
|
30 | 28 | Optional<File> which(String cmd, Iterable<? extends File> paths); |
|
|
31 | 29 | |
|
|
32 | 30 | public static OperatingSystem current() { |
|
|
33 | return CURRENT; | |
|
|
31 | return SystemResolver.current(); | |
|
|
34 | 32 | } |
|
|
35 | 33 | } |
| @@ -1,51 +1,69 | |||
|
|
1 | 1 | package org.implab.gradle.common.utils; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.util.HashMap; |
|
|
4 | 4 | import java.util.Map; |
|
|
5 | 5 | import java.util.function.Function; |
|
|
6 | ||
|
|
7 | 6 | import org.gradle.api.Action; |
|
|
8 | 7 | import org.gradle.api.provider.ListProperty; |
|
|
9 | 8 | import org.gradle.api.provider.MapProperty; |
|
|
10 | 9 | import org.gradle.api.provider.Provider; |
|
|
11 | 10 | |
|
|
12 | 11 | public final class Properties { |
|
|
13 | 12 | private Properties() { |
|
|
14 | 13 | } |
|
|
15 | 14 | |
|
|
16 | 15 | public static <K> void mergeMap(MapProperty<K, Object> property, Map<K, Object> map) { |
|
|
17 | 16 | mergeMap(property, map, Function.identity()); |
|
|
18 | 17 | } |
|
|
19 | 18 | |
|
|
20 |
public static <K, V> void |
|
|
|
19 | public static <K, V> void putMapEntry(MapProperty<K, V> property, K key, Object value, | |
|
|
20 | Function<Object, ? extends V> mapper) { | |
|
|
21 | if (value instanceof Provider<?>) | |
|
|
22 | property.put(key, ((Provider<?>) value).map(mapper::apply)); | |
|
|
23 | else | |
|
|
24 | property.put(key, mapper.apply(value)); | |
|
|
25 | } | |
|
|
26 | ||
|
|
27 | public static <K> void putMapEntry(MapProperty<K, Object> property, K key, Object value) { | |
|
|
28 | putMapEntry(property, key, value, Function.identity()); | |
|
|
29 | } | |
|
|
30 | ||
|
|
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); | |
|
|
33 | } | |
|
|
34 | ||
|
|
35 | public static <K, V> void mergeMap(MapProperty<K, V> property, Map<K, ?> map, | |
|
|
36 | Function<Object, ? extends V> mapper) { | |
|
|
21 | 37 | map.forEach((k, v) -> { |
|
|
22 | 38 | if (v instanceof Provider<?>) |
|
|
23 | 39 | property.put(k, ((Provider<?>) v).map(mapper::apply)); |
|
|
24 | 40 | else |
|
|
25 | 41 | property.put(k, mapper.apply(v)); |
|
|
26 | 42 | }); |
|
|
27 | 43 | } |
|
|
28 | 44 | |
|
|
29 | 45 | public static void mergeList(ListProperty<Object> property, Iterable<Object> values) { |
|
|
30 | 46 | mergeList(property, values, Function.identity()); |
|
|
31 | 47 | } |
|
|
32 | 48 | |
|
|
33 |
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) { | |
|
|
34 | 51 | values.forEach(v -> { |
|
|
35 | 52 | if (v instanceof Provider<?>) |
|
|
36 | 53 | property.add(((Provider<?>) v).map(mapper::apply)); |
|
|
37 | 54 | else |
|
|
38 | 55 | property.add(mapper.apply(v)); |
|
|
39 | 56 | }); |
|
|
40 | 57 | } |
|
|
41 | 58 | |
|
|
42 | public static <K> void configureMap(MapProperty<K, Object> prop, Action<Map<K, Object>> configure) { | |
|
|
59 | public static <K> void configureMap(MapProperty<K, Object> prop, Action<? super Map<K, Object>> configure) { | |
|
|
43 | 60 | configureMap(prop, configure, Function.identity()); |
|
|
44 | 61 | } |
|
|
45 | 62 | |
|
|
46 |
public static <K, V> void configureMap(MapProperty<K, V> prop, Action<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) { | |
|
|
47 | 65 | var map = new HashMap<K, Object>(); |
|
|
48 | 66 | configure.execute(map); |
|
|
49 | 67 | mergeMap(prop, map, mapper); |
|
|
50 | 68 | } |
|
|
51 | 69 | } |
| @@ -1,83 +1,98 | |||
|
|
1 | 1 | package org.implab.gradle.common.utils; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.text.MessageFormat; |
|
|
4 | 4 | import java.util.Iterator; |
|
|
5 | 5 | import java.util.Spliterators; |
|
|
6 | 6 | import java.util.Optional; |
|
|
7 | import java.util.Set; | |
|
|
8 | import java.util.function.Function; | |
|
|
9 | 7 | import java.util.function.Supplier; |
|
|
10 | import java.util.stream.Collectors; | |
|
|
11 | 8 | import java.util.stream.Stream; |
|
|
12 | 9 | import java.util.stream.StreamSupport; |
|
|
13 | 10 | |
|
|
14 | 11 | import org.gradle.api.provider.Provider; |
|
|
15 | 12 | |
|
|
16 | 13 | public final class Values { |
|
|
17 | 14 | |
|
|
18 | 15 | private Values() { |
|
|
19 | 16 | } |
|
|
20 | 17 | |
|
|
21 | 18 | /** |
|
|
22 | 19 | * Converts the supplied value to a string. |
|
|
23 | 20 | */ |
|
|
24 | 21 | public static String toString(Object value) { |
|
|
25 | 22 | if (value == null) { |
|
|
26 | 23 | return null; |
|
|
27 | 24 | } else if (value instanceof String string) { |
|
|
28 | 25 | return string; |
|
|
29 | 26 | } else if (value instanceof Provider<?> provider) { |
|
|
30 | 27 | return toString(provider.getOrNull()); |
|
|
31 | 28 | } else if (value instanceof Supplier<?> supplier) { |
|
|
32 | 29 | return toString(supplier.get()); |
|
|
33 | 30 | } else { |
|
|
34 | 31 | return value.toString(); |
|
|
35 | 32 | } |
|
|
36 | 33 | } |
|
|
37 | 34 | |
|
|
38 | 35 | public static <T> Stream<T> stream(Iterator<T> remaining) { |
|
|
39 | 36 | return StreamSupport.stream( |
|
|
40 | 37 | Spliterators.spliteratorUnknownSize(remaining, 0), |
|
|
41 | 38 | false); |
|
|
42 | 39 | } |
|
|
43 | 40 | |
|
|
44 | 41 | public static <T> Optional<T> take(Iterator<T> iterator) { |
|
|
45 | 42 | return iterator.hasNext() ? Optional.of(iterator.next()) : Optional.empty(); |
|
|
46 | 43 | } |
|
|
47 | 44 | |
|
|
48 | 45 | public static <T> Iterable<T> iterable(T[] values) { |
|
|
49 | 46 | return () -> new ArrayIterator<>(values); |
|
|
50 | 47 | } |
|
|
51 | 48 | |
|
|
52 | 49 | public static <T> Optional<T> optional(Provider<T> provider) { |
|
|
53 | 50 | return provider.isPresent() ? Optional.of(provider.get()) : Optional.empty(); |
|
|
54 | 51 | } |
|
|
55 | 52 | |
|
|
56 | 53 | public static <T> T required(Provider<T> provider, String providerName) { |
|
|
57 | 54 | if (!provider.isPresent()) |
|
|
58 | 55 | throw new IllegalStateException( |
|
|
59 | 56 | MessageFormat.format("The value for the '{0}' provider must be specified", providerName)); |
|
|
60 | 57 | return provider.get(); |
|
|
61 | 58 | } |
|
|
62 | 59 | |
|
|
60 | public static boolean parseBoolean(Object value) { | |
|
|
61 | if (value instanceof Boolean) { | |
|
|
62 | return (Boolean) value; | |
|
|
63 | } else { | |
|
|
64 | var text = toString(value); | |
|
|
65 | switch (text != null ? text.toLowerCase() : "") { | |
|
|
66 | case "true", "yes", "1" -> { | |
|
|
67 | return true; | |
|
|
68 | } | |
|
|
69 | case "false", "no", "0", "" -> { | |
|
|
70 | return false; | |
|
|
71 | } | |
|
|
72 | default -> throw new IllegalArgumentException( | |
|
|
73 | MessageFormat.format("Cannot parse boolean value from ''{0}''", text)); | |
|
|
74 | } | |
|
|
75 | } | |
|
|
76 | } | |
|
|
77 | ||
|
|
63 | 78 | private static class ArrayIterator<T> implements Iterator<T> { |
|
|
64 | 79 | private final T[] data; |
|
|
65 | 80 | |
|
|
66 | 81 | private int pos = 0; |
|
|
67 | 82 | |
|
|
68 | 83 | ArrayIterator(T[] data) { |
|
|
69 | 84 | this.data = data; |
|
|
70 | 85 | } |
|
|
71 | 86 | |
|
|
72 | 87 | @Override |
|
|
73 | 88 | public boolean hasNext() { |
|
|
74 | 89 | return pos < data.length; |
|
|
75 | 90 | } |
|
|
76 | 91 | |
|
|
77 | 92 | @Override |
|
|
78 | 93 | public T next() { |
|
|
79 | 94 | return data[pos++]; |
|
|
80 | 95 | } |
|
|
81 | 96 | } |
|
|
82 | 97 | |
|
|
83 | 98 | } |
| @@ -1,29 +1,37 | |||
|
|
1 | 1 | package org.implab.gradle.common.utils.os; |
|
|
2 | 2 | |
|
|
3 | import org.implab.gradle.common.utils.LazyValue; | |
|
|
3 | 4 | import org.implab.gradle.common.utils.OperatingSystem; |
|
|
4 | 5 | |
|
|
5 | 6 | public class SystemResolver { |
|
|
6 | public static OperatingSystem current() { | |
|
|
7 | ||
|
|
8 | private final static LazyValue<OperatingSystem> current = new LazyValue<>(SystemResolver::resolveCurrent); | |
|
|
9 | ||
|
|
10 | private static OperatingSystem resolveCurrent() { | |
|
|
7 | 11 | var osName = System.getProperty("os.name"); |
|
|
8 | 12 | var osVersion = System.getProperty("os.version"); |
|
|
9 | 13 | |
|
|
10 | 14 | return forName(osName, osVersion); |
|
|
11 | 15 | } |
|
|
12 | 16 | |
|
|
17 | public static OperatingSystem current() { | |
|
|
18 | return current.get(); | |
|
|
19 | } | |
|
|
20 | ||
|
|
13 | 21 | public static OperatingSystem forName(String os, String version) { |
|
|
14 | 22 | var osName = os.toLowerCase(); |
|
|
15 | 23 | |
|
|
16 | 24 | if (osName.contains("windows")) { |
|
|
17 | 25 | return new Windows(osName, version); |
|
|
18 | 26 | } else if (osName.contains("mac os x") || osName.contains("darwin") || osName.contains("osx")) { |
|
|
19 | 27 | return new MacOs(osName, version); |
|
|
20 | 28 | } else if (osName.contains("linux")) { |
|
|
21 | 29 | return new Linux(osName, version); |
|
|
22 | 30 | } else if (osName.contains("freebsd")) { |
|
|
23 | 31 | return new FreeBsd(osName, version); |
|
|
24 | 32 | } else { |
|
|
25 | 33 | // Not strictly true |
|
|
26 | 34 | return new GenericSystem(osName, version); |
|
|
27 | 35 | } |
|
|
28 | 36 | } |
|
|
29 | 37 | } |
|
|
1 | NO CONTENT: file was removed |
|
|
1 | NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now
