# HG changeset patch # User cin # Date 2026-03-01 21:47:20 # Node ID b379fb9b52c4001a73fd9afb5dc8cb8302c8d935 # Parent 1a0b4caf9976fc444ea3a5b74689245c4f2e1cf2 variants API cleanup diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,12 @@ +# AGENTS.md + +## Проектные договоренности + +### Публичное API библиотек + +- Предпочтителен `non-null` подход. +- Там, где значение живет в Gradle Provider API, возвращается `Provider` (не `null`). +- Там, где lookup синхронный, возвращается `Optional` (не `null`). +- `find*` рассматривается как синоним legacy `get*` (поиск без `fail-fast`). +- `require*` это `find*` + `fail-fast` с понятной ошибкой в месте вызова. +- Для нового API предпочтительны формы `find/require`; новые `get*` по возможности не добавлять. diff --git a/common/src/main/java/org/implab/gradle/common/sources/BuildVariant.java b/common/src/main/java/org/implab/gradle/common/sources/BuildVariant.java --- a/common/src/main/java/org/implab/gradle/common/sources/BuildVariant.java +++ b/common/src/main/java/org/implab/gradle/common/sources/BuildVariant.java @@ -4,9 +4,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Set; +import java.util.Optional; import javax.inject.Inject; @@ -101,8 +100,14 @@ public abstract class BuildVariant imple }); } - public BuildRole getRoleByName(String name) { - return roles.get(name); + public Optional findRole(String name) { + return Optional.ofNullable(roles.get(name)); + } + + public BuildRole requireRole(String name) { + return findRole(name) + .orElseThrow(() -> new InvalidUserDataException( + "Variant '" + this.name + "' doesn't define role '" + name + "'")); } public Collection getLinks() { @@ -157,17 +162,14 @@ public abstract class BuildVariant imple return artifactSlot(name, Closures.action(configure)); } - public BuildArtifactSlot getArtifactSlotByName(String name) { - return artifactSlots.get(name); + public Optional findArtifactSlot(String name) { + return Optional.ofNullable(artifactSlots.get(name)); } - Set declaredLayerNames() { - var result = new LinkedHashSet(); - - for (var role : roles.values()) - result.addAll(role.getLayers().getOrElse(java.util.List.of())); - - return result; + public BuildArtifactSlot requireArtifactSlot(String name) { + return findArtifactSlot(name) + .orElseThrow(() -> new InvalidUserDataException( + "Variant '" + this.name + "' doesn't define artifact slot '" + name + "'")); } void finalizeModel() { @@ -218,8 +220,12 @@ public abstract class BuildVariant imple return BuildVariant.this.getRoles(); } - public BuildRole getByName(String name) { - return BuildVariant.this.getRoleByName(name); + public Optional find(String name) { + return BuildVariant.this.findRole(name); + } + + public BuildRole require(String name) { + return BuildVariant.this.requireRole(name); } } @@ -250,8 +256,12 @@ public abstract class BuildVariant imple return BuildVariant.this.getArtifactSlots(); } - public BuildArtifactSlot getByName(String name) { - return BuildVariant.this.getArtifactSlotByName(name); + public Optional find(String name) { + return BuildVariant.this.findArtifactSlot(name); + } + + public BuildArtifactSlot require(String name) { + return BuildVariant.this.requireArtifactSlot(name); } } diff --git a/common/src/main/java/org/implab/gradle/common/sources/BuildVariantsExtension.java b/common/src/main/java/org/implab/gradle/common/sources/BuildVariantsExtension.java --- a/common/src/main/java/org/implab/gradle/common/sources/BuildVariantsExtension.java +++ b/common/src/main/java/org/implab/gradle/common/sources/BuildVariantsExtension.java @@ -9,6 +9,7 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import javax.inject.Inject; @@ -117,8 +118,13 @@ public abstract class BuildVariantsExten return Collections.unmodifiableList(all); } - public BuildVariant getByName(String name) { - return variants.findByName(name); + public Optional find(String name) { + return Optional.ofNullable(variants.findByName(name)); + } + + public BuildVariant require(String name) { + return find(name) + .orElseThrow(() -> new InvalidUserDataException("Variant '" + name + "' isn't defined")); } public void whenFinalized(Action action) { diff --git a/common/src/main/java/org/implab/gradle/common/sources/VariantAttributes.java b/common/src/main/java/org/implab/gradle/common/sources/VariantAttributes.java --- a/common/src/main/java/org/implab/gradle/common/sources/VariantAttributes.java +++ b/common/src/main/java/org/implab/gradle/common/sources/VariantAttributes.java @@ -15,10 +15,12 @@ import org.gradle.api.provider.Provider; public final class VariantAttributes { private final ProviderFactory providers; private final LinkedHashMap, Provider> values = new LinkedHashMap<>(); + private final Provider emptyValueProvider; private boolean finalized; VariantAttributes(ProviderFactory providers) { this.providers = providers; + this.emptyValueProvider = providers.provider(() -> null); } public void attribute(Attribute key, T value) { @@ -32,8 +34,18 @@ public final class VariantAttributes { } @SuppressWarnings("unchecked") - public Provider get(Attribute key) { - return (Provider) values.get(key); + public Provider find(Attribute key) { + return providers + .provider(() -> (Provider) values.getOrDefault(key, emptyValueProvider)) + .flatMap(provider -> provider); + } + + public Provider require(Attribute key) { + var value = find(key); + if (!value.isPresent()) + throw new InvalidUserDataException("Attribute '" + key.getName() + "' doesn't have a value"); + + return value; } public boolean contains(Attribute key) { diff --git a/common/src/test/java/org/implab/gradle/common/sources/VariantsPluginFunctionalTest.java b/common/src/test/java/org/implab/gradle/common/sources/VariantsPluginFunctionalTest.java --- a/common/src/test/java/org/implab/gradle/common/sources/VariantsPluginFunctionalTest.java +++ b/common/src/test/java/org/implab/gradle/common/sources/VariantsPluginFunctionalTest.java @@ -53,9 +53,9 @@ class VariantsPluginFunctionalTest { } } - tasks.register('probe') { - doLast { - def browser = variants.getByName('browser') + tasks.register('probe') { + doLast { + def browser = variants.require('browser') println('attributes=' + browser.attributes.size()) println('roles=' + browser.roles.size()) println('links=' + browser.links.size()) @@ -277,7 +277,7 @@ class VariantsPluginFunctionalTest { } afterEvaluate { - variants.getByName('browser').role('late') { layers('a') } + variants.require('browser').role('late') { layers('a') } } """, "Variant 'browser' is finalized and cannot configure roles"); } diff --git a/common/variants-plugin.md b/common/variants-plugin.md --- a/common/variants-plugin.md +++ b/common/variants-plugin.md @@ -99,7 +99,7 @@ Typed-атрибуты (`Attribute -> Provider`) для передачи параметров в - `variant(...)` — объявление или конфигурация `BuildVariant`. - `layers { ... }`, `variants { ... }` — контейнерный DSL. - `all(...)` — callback для всех вариантов. -- `getAll()`, `getByName(name)` — доступ к вариантам. +- `getAll()`, `find(name)`, `require(name)` — доступ к вариантам. - `validate()` — явный запуск валидации. - `finalizeModel()` — валидация + финализация модели. - `whenFinalized(...)` — callback по завершенной модели (replayable).