##// END OF EJS Templates
WIP Variants model
cin -
r39:fdd8b56c7024 default
parent child
Show More
@@ -1,8 +1,9
1 {
1 {
2 "java.configuration.updateBuildConfiguration": "automatic",
2 "java.configuration.updateBuildConfiguration": "automatic",
3 "java.compile.nullAnalysis.mode": "automatic",
3 "java.compile.nullAnalysis.mode": "automatic",
4 "cSpell.words": [
4 "cSpell.words": [
5 "implab",
5 "implab",
6 "materializer",
6 "rawtypes"
7 "rawtypes"
7 ]
8 ]
8 } No newline at end of file
9 }
@@ -1,12 +1,42
1 # AGENTS.md
1 # AGENTS.md
2
2
3 ## ΠŸΡ€ΠΎΠ΅ΠΊΡ‚Π½Ρ‹Π΅ договорСнности
3 ## ΠŸΡ€ΠΎΠ΅ΠΊΡ‚Π½Ρ‹Π΅ договорСнности
4
4
5 ### ΠŸΡƒΠ±Π»ΠΈΡ‡Π½ΠΎΠ΅ API Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊ
5 ### ΠŸΡƒΠ±Π»ΠΈΡ‡Π½ΠΎΠ΅ API Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊ
6
6
7 - ΠŸΡ€Π΅Π΄ΠΏΠΎΡ‡Ρ‚ΠΈΡ‚Π΅Π»Π΅Π½ `non-null` ΠΏΠΎΠ΄Ρ…ΠΎΠ΄.
7 - ΠŸΡ€Π΅Π΄ΠΏΠΎΡ‡Ρ‚ΠΈΡ‚Π΅Π»Π΅Π½ `non-null` ΠΏΠΎΠ΄Ρ…ΠΎΠ΄.
8 - Π’Π°ΠΌ, Π³Π΄Π΅ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΆΠΈΠ²Π΅Ρ‚ Π² Gradle Provider API, возвращаСтся `Provider<T>` (Π½Π΅ `null`).
8 - Π’Π°ΠΌ, Π³Π΄Π΅ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΆΠΈΠ²Π΅Ρ‚ Π² Gradle Provider API, возвращаСтся `Provider<T>` (Π½Π΅ `null`).
9 - Π’Π°ΠΌ, Π³Π΄Π΅ lookup синхронный, возвращаСтся `Optional<T>` (Π½Π΅ `null`).
9 - Π’Π°ΠΌ, Π³Π΄Π΅ lookup синхронный, возвращаСтся `Optional<T>` (Π½Π΅ `null`).
10 - `find*` рассматриваСтся ΠΊΠ°ΠΊ синоним legacy `get*` (поиск Π±Π΅Π· `fail-fast`).
10 - `find*` рассматриваСтся ΠΊΠ°ΠΊ синоним legacy `get*` (поиск Π±Π΅Π· `fail-fast`).
11 - `require*` это `find*` + `fail-fast` с понятной ошибкой Π² мСстС Π²Ρ‹Π·ΠΎΠ²Π°.
11 - `require*` это `find*` + `fail-fast` с понятной ошибкой Π² мСстС Π²Ρ‹Π·ΠΎΠ²Π°.
12 - Для Π½ΠΎΠ²ΠΎΠ³ΠΎ API ΠΏΡ€Π΅Π΄ΠΏΠΎΡ‡Ρ‚ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ Ρ„ΠΎΡ€ΠΌΡ‹ `find/require`; Π½ΠΎΠ²Ρ‹Π΅ `get*` ΠΏΠΎ возмоТности Π½Π΅ Π΄ΠΎΠ±Π°Π²Π»ΡΡ‚ΡŒ.
12 - Для Π½ΠΎΠ²ΠΎΠ³ΠΎ API ΠΏΡ€Π΅Π΄ΠΏΠΎΡ‡Ρ‚ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ Ρ„ΠΎΡ€ΠΌΡ‹ `find/require`; Π½ΠΎΠ²Ρ‹Π΅ `get*` ΠΏΠΎ возмоТности Π½Π΅ Π΄ΠΎΠ±Π°Π²Π»ΡΡ‚ΡŒ.
13
14 ## Identity-first modeling
15
16 Prefer an **identity-first** split between:
17
18 - **identity objects** used for discovery and selection
19 - **stateful/materialized objects** obtained through separate API calls
20
21 ### Rules
22
23 - Objects intended for replayable observation (`all(...)`, similar collection APIs) should be **identity objects**.
24 - Identity objects must be:
25 - cheap to create
26 - effectively immutable
27 - limited to identity and cheap selection metadata
28 - Heavy, computed, provider-based, or runtime-bound state must be resolved separately.
29 - Aggregate content should be accessed through dedicated lookup/materialization APIs.
30 - Eager observation of identity is acceptable.
31 - Eager observation of computed state is not.
32
33 ### Do not
34
35 - Do not store heavy computed state inside identity objects.
36 - Do not store runtime references to foreign domains inside identity objects.
37 - Do not use custom events when replayable identity registries plus on-demand lookup are sufficient.
38
39 ### Rule of thumb
40
41 **Identity objects contain selection metadata.
42 Aggregate content is obtained separately, on demand.**
@@ -1,34 +1,35
1 package org.implab.gradle.common.core.lang;
1 package org.implab.gradle.common.core.lang;
2
2
3 import java.util.Objects;
3 import java.util.Objects;
4 import java.util.function.Supplier;
4 import java.util.function.Supplier;
5
5
6 public class LazyValue<T> implements Supplier<T> {
6 public class LazyValue<T> implements Supplier<T> {
7 private volatile T value;
7 private volatile T value;
8 private volatile boolean initialized = false;
8
9
9 private final Supplier<T> innerSupplier;
10 private final Supplier<T> innerSupplier;
10
11
11 public LazyValue(Supplier<T> supplier) {
12 public LazyValue(Supplier<T> supplier) {
12 this.innerSupplier = supplier;
13 this.innerSupplier = supplier;
13 }
14 }
14
15
15 @Override
16 @Override
16 public T get() {
17 public T get() {
17 var v = value;
18 var v = value;
18 if (v != null) {
19 if (initialized) {
19 return v;
20 return v;
20 }
21 }
21
22
22 synchronized (this) {
23 synchronized (this) {
23 v = value;
24 v = value;
24 if (v == null) {
25 if (v == null) {
25 v = Objects.requireNonNull(
26 v = Objects.requireNonNull(
26 innerSupplier.get(),
27 innerSupplier.get(),
27 "LazyValue supplier returned null");
28 "LazyValue supplier returned null");
28 value = v;
29 value = v;
29 }
30 }
30 return v;
31 return v;
31 }
32 }
32 }
33 }
33
34
34 }
35 }
@@ -1,65 +1,70
1 package org.implab.gradle.common.core.lang;
1 package org.implab.gradle.common.core.lang;
2
2
3 import java.util.regex.Pattern;
3 import java.util.regex.Pattern;
4
4
5 import org.eclipse.jdt.annotation.NonNullByDefault;
5 import org.eclipse.jdt.annotation.NonNullByDefault;
6 import org.gradle.api.provider.Provider;
6 import org.gradle.api.provider.Provider;
7
7
8 @NonNullByDefault
8 @NonNullByDefault
9 public class Strings {
9 public class Strings {
10
10
11 private static final Pattern firstLetter = Pattern.compile("^\\w");
11 private static final Pattern firstLetter = Pattern.compile("^\\w");
12
12
13 private static final Pattern INVALID_NAME_CHAR = Pattern.compile("[^A-Za-z0-9_.-]");
13 private static final Pattern INVALID_NAME_CHAR = Pattern.compile("[^A-Za-z0-9_.-]");
14
14
15 public static String capitalize(String string) {
15 public static String capitalize(String string) {
16 return string == null ? null
16 return string == null ? null
17 : string.length() == 0 ? string
17 : string.length() == 0 ? string
18 : firstLetter.matcher(string).replaceFirst(m -> m.group().toUpperCase());
18 : firstLetter.matcher(string).replaceFirst(m -> m.group().toUpperCase());
19 }
19 }
20
20
21 public static String toCamelCase(String name) {
21 public static String toCamelCase(String name) {
22 if (name == null || name.isEmpty())
22 if (name == null || name.isEmpty())
23 return name;
23 return name;
24 StringBuilder out = new StringBuilder(name.length());
24 StringBuilder out = new StringBuilder(name.length());
25 boolean up = false;
25 boolean up = false;
26 boolean first = true;
26 boolean first = true;
27 for (int i = 0; i < name.length(); i++) {
27 for (int i = 0; i < name.length(); i++) {
28 char c = name.charAt(i);
28 char c = name.charAt(i);
29 switch (c) {
29 switch (c) {
30 case '-', '_', ' ', '.' -> up = true;
30 case '-', '_', ' ', '.' -> up = true;
31 default -> {
31 default -> {
32 out.append(
32 out.append(
33 first ? Character.toLowerCase(c)
33 first ? Character.toLowerCase(c)
34 : up ? Character.toUpperCase(c): c);
34 : up ? Character.toUpperCase(c): c);
35 up = false;
35 up = false;
36 first = false;
36 first = false;
37 }
37 }
38 }
38 }
39 }
39 }
40 return out.toString();
40 return out.toString();
41 }
41 }
42
42
43 public static void argumentNotNullOrEmpty(String value, String argumentName) {
43 public static void argumentNotNullOrEmpty(String value, String argumentName) {
44 if (value == null || value.length() == 0)
44 if (value == null || value.length() == 0)
45 throw new IllegalArgumentException(String.format("Argument %s can't be null or empty", argumentName));
45 throw new IllegalArgumentException(String.format("Argument %s can't be null or empty", argumentName));
46 }
46 }
47
47
48 public static void argumentNotNullOrBlank(String value, String argumentName) {
48 public static void argumentNotNullOrBlank(String value, String argumentName) {
49 if (value == null || value.trim().length() == 0)
49 if (value == null || value.trim().length() == 0)
50 throw new IllegalArgumentException(String.format("Argument %s can't be null or blank", argumentName));
50 throw new IllegalArgumentException(String.format("Argument %s can't be null or blank", argumentName));
51 }
51 }
52
52
53 public static String requireNonBlank(String value) {
54 argumentNotNullOrBlank(value, "value");
55 return value;
56 }
57
53 public static String sanitizeName(String value) {
58 public static String sanitizeName(String value) {
54 return INVALID_NAME_CHAR.matcher(value).replaceAll("_");
59 return INVALID_NAME_CHAR.matcher(value).replaceAll("_");
55 }
60 }
56
61
57 public static String asString(Object value) {
62 public static String asString(Object value) {
58 if (value == null)
63 if (value == null)
59 return null;
64 return null;
60 if (value instanceof Provider<?> provider)
65 if (value instanceof Provider<?> provider)
61 return asString(provider.get());
66 return asString(provider.get());
62 else
67 else
63 return value.toString();
68 return value.toString();
64 }
69 }
65 }
70 }
@@ -1,17 +1,15
1 package org.implab.gradle.variants;
1 package org.implab.gradle.variants;
2
2
3 import org.gradle.api.Plugin;
3 import org.gradle.api.Plugin;
4 import org.gradle.api.Project;
4 import org.gradle.api.Project;
5 import org.implab.gradle.variants.model.VariantsExtension;
5 import org.implab.gradle.variants.model.VariantsExtension;
6
6
7 public abstract class VariantsPlugin implements Plugin<Project> {
7 public abstract class VariantsPlugin implements Plugin<Project> {
8 @Override
8 @Override
9 public void apply(Project target) {
9 public void apply(Project target) {
10 var extension = target.getExtensions().create("variants", VariantsExtension.class);
11
10
12 target.afterEvaluate(project -> {
11 target.getExtensions().create("variants", VariantsExtension.class);
13
14 });
15
12
16 }
13 }
14
17 }
15 }
@@ -1,35 +1,8
1 package org.implab.gradle.variants.model;
1 package org.implab.gradle.variants.model;
2
2
3 import org.gradle.api.Named;
3 /** A binding between a role and a layer inside a specific variant.
4 import org.gradle.api.provider.SetProperty;
5
6 /**
7 * Binds a role to a set of layers inside a particular variant.
8 *
4 *
9 * The binding name is the role name, e.g. "production", "test", "tool".
5 * @see {@link VariantDefinition} for the context of this binding.
10 */
6 */
11 public interface RoleBinding extends Named {
7 public record RoleLayerBinding(String name, String layerName) {
12
13 /**
14 * Layer names participating in this (variant, role) selection.
15 *
16 * Core model keeps names here deliberately:
17 * source/materialization semantics live elsewhere.
18 */
19 SetProperty<String> getLayerNames();
20
21 /**
22 * Adds one layer to this binding.
23 */
24 void layer(String name);
25
26 /**
27 * Adds several layers to this binding.
28 */
29 void layers(String... names);
30
31 /**
32 * Adds several layers to this binding.
33 */
34 void layers(Iterable<String> names);
35 } No newline at end of file
8 }
@@ -1,44 +1,12
1 package org.implab.gradle.variants.model;
1 package org.implab.gradle.variants.model;
2
2
3 import org.gradle.api.Action;
4 import org.gradle.api.Named;
3 import org.gradle.api.Named;
5 import org.gradle.api.NamedDomainObjectContainer;
6 import org.implab.gradle.common.core.lang.Closures;
7
8 import groovy.lang.Closure;
9
4
10 /**
5 /**
11 * A named variant, e.g. "browser", "electron".
6 * A named variant, e.g. "browser", "electron".
12 *
7 *
13 * A variant does not "have a role" directly.
8 * A variant does not "have a role" directly.
14 * It owns a set of role bindings.
9 * It owns a set of role bindings.
15 */
10 */
16 public interface Variant extends Named {
11 public interface Variant extends Named {
17
18 /**
19 * Role bindings declared inside this variant.
20 *
21 * The binding name is the role name.
22 */
23 NamedDomainObjectContainer<RoleBinding> getRoleBindings();
24
25 /**
26 * Creates or returns an existing role binding and configures it.
27 */
28 default RoleBinding role(String name) {
29 return getRoleBindings().maybeCreate(name);
30 }
31
32 /**
33 * Creates or returns an existing role binding and configures it.
34 */
35 default RoleBinding role(String name, Action<? super RoleBinding> action) {
36 var role = role(name);
37 action.execute(role);
38 return role;
39 }
40
41 default RoleBinding role(String name, Closure<?> closure) {
42 return role(name, Closures.action(closure));
43 }
44 } No newline at end of file
12 }
@@ -1,59 +1,65
1 package org.implab.gradle.variants.model;
1 package org.implab.gradle.variants.model;
2
2
3 import org.gradle.api.Action;
3 import org.gradle.api.Action;
4 import org.gradle.api.NamedDomainObjectContainer;
4 import org.gradle.api.NamedDomainObjectContainer;
5 import org.implab.gradle.common.core.lang.Closures;
5 import org.implab.gradle.common.core.lang.Closures;
6
6
7 import groovy.lang.Closure;
7 import groovy.lang.Closure;
8
8
9 /**
9 /**
10 * Root extension:
10 * Root extension:
11 *
11 *
12 * variants {
12 * variants {
13 * layers { ... }
13 * layers { ... }
14 * roles { ... }
14 * roles { ... }
15 *
15 *
16 * variant("browser") {
16 * variant("browser") {
17 * role("production") {
17 * role("production") {
18 * layers("main", "generated", "mainRjs")
18 * layers("main", "generated", "mainRjs")
19 * }
19 * }
20 * }
20 * }
21 * }
21 * }
22 */
22 */
23 public interface VariantsExtension {
23 public interface VariantsExtension {
24
24
25 /**
25 /**
26 * Domain of layers.
26 * Domain of layers.
27 */
27 */
28 NamedDomainObjectContainer<Layer> getLayers();
28 NamedDomainObjectContainer<Layer> getLayers();
29
29
30 /**
30 /**
31 * Domain of roles.
31 * Domain of roles.
32 */
32 */
33 NamedDomainObjectContainer<Role> getRoles();
33 NamedDomainObjectContainer<Role> getRoles();
34
34
35 /**
35 /**
36 * Domain of variants.
37 */
38 NamedDomainObjectContainer<Variant> getVariants();
39
40 /**
36 * Declared variants.
41 * Declared variants.
37 */
42 */
38 NamedDomainObjectContainer<Variant> getVariantDefinitions();
43 NamedDomainObjectContainer<VariantDefinition> getVariantDefinitions();
39
44
40 /**
45 /**
41 * Creates or returns an existing variant and configures it.
46 * Creates or returns an existing variant and configures it.
42 */
47 */
43 default Variant variant(String name) {
48 default VariantDefinition variant(String name) {
49
44 return getVariantDefinitions().maybeCreate(name);
50 return getVariantDefinitions().maybeCreate(name);
45 }
51 }
46
52
47 /**
53 /**
48 * Creates or returns an existing variant and configures it.
54 * Creates or returns an existing variant and configures it.
49 */
55 */
50 default Variant variant(String name, Action<? super Variant> action) {
56 default VariantDefinition variant(String name, Action<? super VariantDefinition> action) {
51 var variant = variant(name);
57 var variant = variant(name);
52 action.execute(variant);
58 action.execute(variant);
53 return variant;
59 return variant;
54 }
60 }
55
61
56 default Variant variant(String name, Closure<?> closure) {
62 default VariantDefinition variant(String name, Closure<?> closure) {
57 return variant(name, Closures.action(closure));
63 return variant(name, Closures.action(closure));
58 }
64 }
59 } No newline at end of file
65 }
General Comments 0
You need to be logged in to leave comments. Login now