diff --git a/.vscode/settings.json b/.vscode/settings.json --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,7 @@ "java.compile.nullAnalysis.mode": "automatic", "cSpell.words": [ "implab", + "materializer", "rawtypes" ] } \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md --- a/AGENTS.md +++ b/AGENTS.md @@ -10,3 +10,33 @@ - `find*` рассматривается как синоним legacy `get*` (поиск без `fail-fast`). - `require*` это `find*` + `fail-fast` с понятной ошибкой в месте вызова. - Для нового API предпочтительны формы `find/require`; новые `get*` по возможности не добавлять. + +## Identity-first modeling + +Prefer an **identity-first** split between: + +- **identity objects** used for discovery and selection +- **stateful/materialized objects** obtained through separate API calls + +### Rules + +- Objects intended for replayable observation (`all(...)`, similar collection APIs) should be **identity objects**. +- Identity objects must be: + - cheap to create + - effectively immutable + - limited to identity and cheap selection metadata +- Heavy, computed, provider-based, or runtime-bound state must be resolved separately. +- Aggregate content should be accessed through dedicated lookup/materialization APIs. +- Eager observation of identity is acceptable. +- Eager observation of computed state is not. + +### Do not + +- Do not store heavy computed state inside identity objects. +- Do not store runtime references to foreign domains inside identity objects. +- Do not use custom events when replayable identity registries plus on-demand lookup are sufficient. + +### Rule of thumb + +**Identity objects contain selection metadata. +Aggregate content is obtained separately, on demand.** diff --git a/common/src/main/java/org/implab/gradle/common/core/lang/LazyValue.java b/common/src/main/java/org/implab/gradle/common/core/lang/LazyValue.java --- a/common/src/main/java/org/implab/gradle/common/core/lang/LazyValue.java +++ b/common/src/main/java/org/implab/gradle/common/core/lang/LazyValue.java @@ -5,6 +5,7 @@ import java.util.function.Supplier; public class LazyValue implements Supplier { private volatile T value; + private volatile boolean initialized = false; private final Supplier innerSupplier; @@ -15,7 +16,7 @@ public class LazyValue implements Sup @Override public T get() { var v = value; - if (v != null) { + if (initialized) { return v; } diff --git a/common/src/main/java/org/implab/gradle/common/core/lang/Strings.java b/common/src/main/java/org/implab/gradle/common/core/lang/Strings.java --- a/common/src/main/java/org/implab/gradle/common/core/lang/Strings.java +++ b/common/src/main/java/org/implab/gradle/common/core/lang/Strings.java @@ -50,6 +50,11 @@ public class Strings { throw new IllegalArgumentException(String.format("Argument %s can't be null or blank", argumentName)); } + public static String requireNonBlank(String value) { + argumentNotNullOrBlank(value, "value"); + return value; + } + public static String sanitizeName(String value) { return INVALID_NAME_CHAR.matcher(value).replaceAll("_"); } diff --git a/variants/src/main/java/org/implab/gradle/variants/VariantsPlugin.java b/variants/src/main/java/org/implab/gradle/variants/VariantsPlugin.java --- a/variants/src/main/java/org/implab/gradle/variants/VariantsPlugin.java +++ b/variants/src/main/java/org/implab/gradle/variants/VariantsPlugin.java @@ -7,11 +7,9 @@ import org.implab.gradle.variants.model. public abstract class VariantsPlugin implements Plugin { @Override public void apply(Project target) { - var extension = target.getExtensions().create("variants", VariantsExtension.class); - target.afterEvaluate(project -> { - - }); + target.getExtensions().create("variants", VariantsExtension.class); } + } diff --git a/variants/src/main/java/org/implab/gradle/variants/model/RoleBinding.java b/variants/src/main/java/org/implab/gradle/variants/model/RoleLayerBinding.java rename from variants/src/main/java/org/implab/gradle/variants/model/RoleBinding.java rename to variants/src/main/java/org/implab/gradle/variants/model/RoleLayerBinding.java --- a/variants/src/main/java/org/implab/gradle/variants/model/RoleBinding.java +++ b/variants/src/main/java/org/implab/gradle/variants/model/RoleLayerBinding.java @@ -1,35 +1,8 @@ package org.implab.gradle.variants.model; -import org.gradle.api.Named; -import org.gradle.api.provider.SetProperty; - -/** - * Binds a role to a set of layers inside a particular variant. +/** A binding between a role and a layer inside a specific variant. * - * The binding name is the role name, e.g. "production", "test", "tool". - */ -public interface RoleBinding extends Named { - - /** - * Layer names participating in this (variant, role) selection. - * - * Core model keeps names here deliberately: - * source/materialization semantics live elsewhere. - */ - SetProperty getLayerNames(); - - /** - * Adds one layer to this binding. - */ - void layer(String name); - - /** - * Adds several layers to this binding. - */ - void layers(String... names); - - /** - * Adds several layers to this binding. - */ - void layers(Iterable names); + * @see {@link VariantDefinition} for the context of this binding. +*/ +public record RoleLayerBinding(String name, String layerName) { } \ No newline at end of file diff --git a/variants/src/main/java/org/implab/gradle/variants/model/Variant.java b/variants/src/main/java/org/implab/gradle/variants/model/Variant.java --- a/variants/src/main/java/org/implab/gradle/variants/model/Variant.java +++ b/variants/src/main/java/org/implab/gradle/variants/model/Variant.java @@ -1,11 +1,6 @@ package org.implab.gradle.variants.model; -import org.gradle.api.Action; import org.gradle.api.Named; -import org.gradle.api.NamedDomainObjectContainer; -import org.implab.gradle.common.core.lang.Closures; - -import groovy.lang.Closure; /** * A named variant, e.g. "browser", "electron". @@ -14,31 +9,4 @@ import groovy.lang.Closure; * It owns a set of role bindings. */ public interface Variant extends Named { - - /** - * Role bindings declared inside this variant. - * - * The binding name is the role name. - */ - NamedDomainObjectContainer getRoleBindings(); - - /** - * Creates or returns an existing role binding and configures it. - */ - default RoleBinding role(String name) { - return getRoleBindings().maybeCreate(name); - } - - /** - * Creates or returns an existing role binding and configures it. - */ - default RoleBinding role(String name, Action action) { - var role = role(name); - action.execute(role); - return role; - } - - default RoleBinding role(String name, Closure closure) { - return role(name, Closures.action(closure)); - } } \ No newline at end of file diff --git a/variants/src/main/java/org/implab/gradle/variants/model/VariantsExtension.java b/variants/src/main/java/org/implab/gradle/variants/model/VariantsExtension.java --- a/variants/src/main/java/org/implab/gradle/variants/model/VariantsExtension.java +++ b/variants/src/main/java/org/implab/gradle/variants/model/VariantsExtension.java @@ -10,14 +10,14 @@ import groovy.lang.Closure; * Root extension: * * variants { - * layers { ... } - * roles { ... } + * layers { ... } + * roles { ... } * - * variant("browser") { - * role("production") { - * layers("main", "generated", "mainRjs") - * } - * } + * variant("browser") { + * role("production") { + * layers("main", "generated", "mainRjs") + * } + * } * } */ public interface VariantsExtension { @@ -33,27 +33,33 @@ public interface VariantsExtension { NamedDomainObjectContainer getRoles(); /** + * Domain of variants. + */ + NamedDomainObjectContainer getVariants(); + + /** * Declared variants. */ - NamedDomainObjectContainer getVariantDefinitions(); + NamedDomainObjectContainer getVariantDefinitions(); /** * Creates or returns an existing variant and configures it. */ - default Variant variant(String name) { + default VariantDefinition variant(String name) { + return getVariantDefinitions().maybeCreate(name); } /** * Creates or returns an existing variant and configures it. */ - default Variant variant(String name, Action action) { + default VariantDefinition variant(String name, Action action) { var variant = variant(name); action.execute(variant); return variant; } - default Variant variant(String name, Closure closure) { + default VariantDefinition variant(String name, Closure closure) { return variant(name, Closures.action(closure)); } } \ No newline at end of file