| @@ -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 | } | |
| @@ -14,9 +14,8 java { | |||
|
|
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{ |
| @@ -6,9 +6,13 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() { |
| @@ -46,4 +50,12 public class RedirectFromSpec { | |||
|
|
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; | |
|
|
49 | 58 | } |
|
|
59 | ||
|
|
60 | ||
|
|
61 | } | |
| @@ -9,10 +9,14 import java.util.function.Supplier; | |||
|
|
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() { |
| @@ -62,4 +66,10 public class RedirectToSpec { | |||
|
|
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; | |
|
|
65 | 74 | } |
|
|
75 | } | |
| @@ -1,8 +1,10 | |||
|
|
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 | |
| @@ -37,8 +39,8 public class Json { | |||
|
|
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) { |
| @@ -79,6 +81,15 public class Json { | |||
|
|
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); |
| @@ -87,6 +98,11 public class Json { | |||
|
|
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); |
| @@ -27,6 +27,5 public final class Closures { | |||
|
|
27 | 27 | c.setResolveStrategy(Closure.DELEGATE_FIRST); |
|
|
28 | 28 | c.setDelegate(target); |
|
|
29 | 29 | c.call(target); |
|
|
30 | ||
|
|
31 | 30 | } |
|
|
32 | 31 | } |
| @@ -7,8 +7,6 import org.implab.gradle.common.utils.os | |||
|
|
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"; |
| @@ -30,6 +28,6 public interface OperatingSystem { | |||
|
|
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 | } |
| @@ -3,7 +3,6 package org.implab.gradle.common.utils; | |||
|
|
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; |
| @@ -17,7 +16,24 public final class Properties { | |||
|
|
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)); |
| @@ -30,7 +46,8 public final class Properties { | |||
|
|
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)); |
| @@ -39,11 +56,12 public final class Properties { | |||
|
|
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); |
| @@ -4,10 +4,7 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 | |
| @@ -60,6 +57,24 public final class Values { | |||
|
|
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 | |
| @@ -1,15 +1,23 | |||
|
|
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 | |
|
|
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
