##// END OF EJS Templates
Removed layer links from variants
cin -
r30:2dd3356774b2 default
parent child
Show More
@@ -18,7 +18,6 variants {
18
18
19 variant('browser') {
19 variant('browser') {
20 role('main') { layers('mainBase', 'mainAmd') }
20 role('main') { layers('mainBase', 'mainAmd') }
21 link('mainBase', 'mainAmd', 'ts:api')
22 }
21 }
23 }
22 }
24
23
@@ -54,13 +53,12 variantSources {
54 ### variants
53 ### variants
55
54
56 `variants` задает структуру пространства сборки: какие есть слои, какие роли
55 `variants` задает структуру пространства сборки: какие есть слои, какие роли
57 используют эти слои в каждом варианте, какие направленные связи между слоями
56 используют эти слои в каждом варианте, какие есть атрибуты и artifact slots.
58 существуют. Модель не создает задачи и не привязана к TS/JS.
57 Модель не создает задачи и не привязана к TS/JS.
59
58
60 Практический смысл:
59 Практический смысл:
61
60
62 - формализовать архитектуру сборки;
61 - формализовать архитектуру сборки;
63 - централизовать валидацию связей;
64 - дать адаптерам единый источник правды.
62 - дать адаптерам единый источник правды.
65
63
66 ### sources
64 ### sources
@@ -90,9 +88,8 outputs. Это уже "физический" уровень, к которому удобно привязывать задачи,
90 ## DOMAIN MODEL
88 ## DOMAIN MODEL
91
89
92 - `BuildLayer` — глобальный идентификатор слоя.
90 - `BuildLayer` — глобальный идентификатор слоя.
93 - `BuildVariant` — агрегат ролей, связей, атрибутов, артефактных слотов.
91 - `BuildVariant` — агрегат ролей, атрибутов, артефактных слотов.
94 - `BuildRole` — роль внутри варианта, содержит ссылки на layer names.
92 - `BuildRole` — роль внутри варианта, содержит ссылки на layer names.
95 - `LayerLink` — ориентированная связь `from -> to` в графе определенного `kind`.
96 - `GenericSourceSet` — зарегистрированный набор исходников и outputs.
93 - `GenericSourceSet` — зарегистрированный набор исходников и outputs.
97 - `BuildLayerBinding` — правила registration source set для конкретного layer.
94 - `BuildLayerBinding` — правила registration source set для конкретного layer.
98 - `SourceSetContext` — контекст callback-событий registration.
95 - `SourceSetContext` — контекст callback-событий registration.
@@ -115,7 +112,7 Closure callbacks работают в delegate-first режиме (`@DelegatesTo`). Для
115 - `GenericSourceSet` — модель источников/outputs для конкретного имени.
112 - `GenericSourceSet` — модель источников/outputs для конкретного имени.
116 - `VariantsPlugin` — регистрирует extension `variants` и lifecycle finalize.
113 - `VariantsPlugin` — регистрирует extension `variants` и lifecycle finalize.
117 - `BuildVariantsExtension` — корневой API модели вариантов.
114 - `BuildVariantsExtension` — корневой API модели вариантов.
118 - `BuildVariant` — API ролей, links, attributes и artifact slots варианта.
115 - `BuildVariant` — API ролей, attributes и artifact slots варианта.
119 - `VariantsSourcesPlugin` — применяет `variants` + `sources` и запускает адаптер.
116 - `VariantsSourcesPlugin` — применяет `variants` + `sources` и запускает адаптер.
120 - `VariantSourcesExtension` — API bind/events registration.
117 - `VariantSourcesExtension` — API bind/events registration.
121 - `BuildLayerBinding` — слой-конкретный DSL для имени и конфигурации source set.
118 - `BuildLayerBinding` — слой-конкретный DSL для имени и конфигурации source set.
@@ -10,6 +10,8 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_.-]");
14
13 public static String capitalize(String string) {
15 public static String capitalize(String string) {
14 return string == null ? null
16 return string == null ? null
15 : string.length() == 0 ? string
17 : string.length() == 0 ? string
@@ -43,6 +45,15 public class Strings {
43 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));
44 }
46 }
45
47
48 public static void argumentNotNullOrBlank(String value, String argumentName) {
49 if (value == null || value.trim().length() == 0)
50 throw new IllegalArgumentException(String.format("Argument %s can't be null or blank", argumentName));
51 }
52
53 public static String sanitizeName(String value) {
54 return INVALID_NAME_CHAR.matcher(value).replaceAll("_");
55 }
56
46 public static String asString(Object value) {
57 public static String asString(Object value) {
47 if (value == null)
58 if (value == null)
48 return null;
59 return null;
@@ -1,10 +1,8
1 package org.implab.gradle.common.sources;
1 package org.implab.gradle.common.sources;
2
2
3 import java.util.ArrayList;
4 import java.util.Collection;
3 import java.util.Collection;
5 import java.util.Collections;
4 import java.util.Collections;
6 import java.util.LinkedHashMap;
5 import java.util.LinkedHashMap;
7 import java.util.List;
8 import java.util.Optional;
6 import java.util.Optional;
9
7
10 import javax.inject.Inject;
8 import javax.inject.Inject;
@@ -30,7 +28,6 public abstract class BuildVariant imple
30 */
28 */
31 private final VariantAttributes attributes;
29 private final VariantAttributes attributes;
32 private final LinkedHashMap<String, BuildRole> roles = new LinkedHashMap<>();
30 private final LinkedHashMap<String, BuildRole> roles = new LinkedHashMap<>();
33 private final List<LayerLink> links = new ArrayList<>();
34 private final LinkedHashMap<String, BuildArtifactSlot> artifactSlots = new LinkedHashMap<>();
31 private final LinkedHashMap<String, BuildArtifactSlot> artifactSlots = new LinkedHashMap<>();
35
32
36 @Inject
33 @Inject
@@ -110,29 +107,6 public abstract class BuildVariant imple
110 "Variant '" + this.name + "' doesn't define role '" + name + "'"));
107 "Variant '" + this.name + "' doesn't define role '" + name + "'"));
111 }
108 }
112
109
113 public Collection<LayerLink> getLinks() {
114 return Collections.unmodifiableList(links);
115 }
116
117 public void links(Action<? super LinksSpec> action) {
118 ensureMutable("configure links");
119 action.execute(new LinksSpec());
120 }
121
122 public void links(Closure<?> configure) {
123 links(Closures.action(configure));
124 }
125
126 public LayerLink link(String from, String to, String kind) {
127 ensureMutable("add links");
128 var link = new LayerLink(
129 requireLinkValue("from", from),
130 requireLinkValue("to", to),
131 requireLinkValue("kind", kind));
132 links.add(link);
133 return link;
134 }
135
136 public Collection<BuildArtifactSlot> getArtifactSlots() {
110 public Collection<BuildArtifactSlot> getArtifactSlots() {
137 return Collections.unmodifiableCollection(artifactSlots.values());
111 return Collections.unmodifiableCollection(artifactSlots.values());
138 }
112 }
@@ -196,13 +170,6 public abstract class BuildVariant imple
196 throw new InvalidUserDataException("Variant '" + name + "' is finalized and cannot " + operation);
170 throw new InvalidUserDataException("Variant '" + name + "' is finalized and cannot " + operation);
197 }
171 }
198
172
199 private static String requireLinkValue(String field, String value) {
200 if (value == null || value.trim().isEmpty())
201 throw new InvalidUserDataException("Link '" + field + "' must not be null or blank");
202
203 return value.trim();
204 }
205
206 public final class RolesSpec {
173 public final class RolesSpec {
207 public BuildRole role(String name, Action<? super BuildRole> configure) {
174 public BuildRole role(String name, Action<? super BuildRole> configure) {
208 return BuildVariant.this.role(name, configure);
175 return BuildVariant.this.role(name, configure);
@@ -229,16 +196,6 public abstract class BuildVariant imple
229 }
196 }
230 }
197 }
231
198
232 public final class LinksSpec {
233 public LayerLink link(String from, String to, String kind) {
234 return BuildVariant.this.link(from, to, kind);
235 }
236
237 public Collection<LayerLink> getAll() {
238 return BuildVariant.this.getLinks();
239 }
240 }
241
242 public final class ArtifactSlotsSpec {
199 public final class ArtifactSlotsSpec {
243 public BuildArtifactSlot artifactSlot(String name, Action<? super BuildArtifactSlot> configure) {
200 public BuildArtifactSlot artifactSlot(String name, Action<? super BuildArtifactSlot> configure) {
244 return BuildVariant.this.artifactSlot(name, configure);
201 return BuildVariant.this.artifactSlot(name, configure);
@@ -3,14 +3,11 package org.implab.gradle.common.sources
3 import java.util.ArrayList;
3 import java.util.ArrayList;
4 import java.util.Collection;
4 import java.util.Collection;
5 import java.util.Collections;
5 import java.util.Collections;
6 import java.util.HashMap;
7 import java.util.HashSet;
8 import java.util.LinkedHashMap;
6 import java.util.LinkedHashMap;
9 import java.util.LinkedHashSet;
7 import java.util.LinkedHashSet;
10 import java.util.List;
8 import java.util.List;
11 import java.util.Map;
9 import java.util.Map;
12 import java.util.Optional;
10 import java.util.Optional;
13 import java.util.Set;
14
11
15 import javax.inject.Inject;
12 import javax.inject.Inject;
16
13
@@ -197,8 +194,7 public abstract class BuildVariantsExten
197 }
194 }
198
195
199 validateRoleAndArtifactNames(variant, errors);
196 validateRoleAndArtifactNames(variant, errors);
200 var variantLayers = validateRoleMappings(variant, layersByName, errors);
197 validateRoleMappings(variant, layersByName, errors);
201 validateLinks(variant, variantLayers, errors);
202 }
198 }
203
199
204 private static void validateRoleAndArtifactNames(BuildVariant variant, List<String> errors) {
200 private static void validateRoleAndArtifactNames(BuildVariant variant, List<String> errors) {
@@ -227,10 +223,8 public abstract class BuildVariantsExten
227 }
223 }
228 }
224 }
229
225
230 private static Set<String> validateRoleMappings(BuildVariant variant, Map<String, BuildLayer> layersByName,
226 private static void validateRoleMappings(BuildVariant variant, Map<String, BuildLayer> layersByName,
231 List<String> errors) {
227 List<String> errors) {
232 var variantLayers = new LinkedHashSet<String>();
233
234 for (var role : variant.getRoles()) {
228 for (var role : variant.getRoles()) {
235 var seenLayers = new LinkedHashSet<String>();
229 var seenLayers = new LinkedHashSet<String>();
236 for (var layerName : role.getLayers().getOrElse(List.of())) {
230 for (var layerName : role.getLayers().getOrElse(List.of())) {
@@ -249,88 +243,9 public abstract class BuildVariantsExten
249 if (!seenLayers.add(normalizedLayerName)) {
243 if (!seenLayers.add(normalizedLayerName)) {
250 errors.add("Variant '" + variant.getName() + "', role '" + role.getName()
244 errors.add("Variant '" + variant.getName() + "', role '" + role.getName()
251 + "' contains duplicated layer reference '" + normalizedLayerName + "'");
245 + "' contains duplicated layer reference '" + normalizedLayerName + "'");
252 continue;
253 }
254
255 variantLayers.add(normalizedLayerName);
256 }
257 }
258
259 return variantLayers;
260 }
261
262 private static void validateLinks(BuildVariant variant, Set<String> variantLayers, List<String> errors) {
263 var seenLinks = new HashSet<String>();
264 var edgesByKind = new HashMap<String, Map<String, Set<String>>>();
265
266 for (var link : variant.getLinks()) {
267 var from = normalize(link.from());
268 var to = normalize(link.to());
269 var kind = normalize(link.kind());
270
271 if (from == null || to == null || kind == null) {
272 errors.add("Variant '" + variant.getName() + "' has incomplete link (from/to/kind are required)");
273 continue;
274 }
275
276 if (!variantLayers.contains(from)) {
277 errors.add("Variant '" + variant.getName() + "' link references unknown source layer '"
278 + from + "'");
279 continue;
280 }
281
282 if (!variantLayers.contains(to)) {
283 errors.add("Variant '" + variant.getName() + "' link references unknown target layer '"
284 + to + "'");
285 continue;
286 }
287
288 var linkKey = from + "\u0000" + to + "\u0000" + kind;
289 if (!seenLinks.add(linkKey)) {
290 errors.add("Variant '" + variant.getName() + "' has duplicated link tuple (from='" + from
291 + "', to='" + to + "', kind='" + kind + "')");
292 }
293
294 edgesByKind
295 .computeIfAbsent(kind, x -> new LinkedHashMap<>())
296 .computeIfAbsent(from, x -> new LinkedHashSet<>())
297 .add(to);
298 }
299
300 for (var entry : edgesByKind.entrySet()) {
301 if (hasCycle(variantLayers, entry.getValue())) {
302 errors.add("Variant '" + variant.getName() + "' contains cycle in links with kind '" + entry.getKey() + "'");
303 }
246 }
304 }
247 }
305 }
248 }
306
307 private static boolean hasCycle(Set<String> nodes, Map<String, Set<String>> edges) {
308 var state = new HashMap<String, Integer>();
309
310 for (var node : nodes) {
311 if (dfs(node, state, edges))
312 return true;
313 }
314
315 return false;
316 }
317
318 private static boolean dfs(String node, Map<String, Integer> state, Map<String, Set<String>> edges) {
319 var current = state.getOrDefault(node, 0);
320 if (current == 1)
321 return true;
322 if (current == 2)
323 return false;
324
325 state.put(node, 1);
326
327 for (var next : edges.getOrDefault(node, Set.of())) {
328 if (dfs(next, state, edges))
329 return true;
330 }
331
332 state.put(node, 2);
333 return false;
334 }
249 }
335
250
336 private static String normalize(String value) {
251 private static String normalize(String value) {
@@ -341,10 +256,6 public abstract class BuildVariantsExten
341 return trimmed.isEmpty() ? null : trimmed;
256 return trimmed.isEmpty() ? null : trimmed;
342 }
257 }
343
258
344 private static boolean isBlank(String value) {
345 return normalize(value) == null;
346 }
347
348 private void ensureMutable(String operation) {
259 private void ensureMutable(String operation) {
349 if (finalized)
260 if (finalized)
350 throw new InvalidUserDataException("Variants model is finalized and cannot " + operation);
261 throw new InvalidUserDataException("Variants model is finalized and cannot " + operation);
@@ -12,6 +12,7 import javax.inject.Inject;
12 import org.implab.gradle.common.core.lang.Closures;
12 import org.implab.gradle.common.core.lang.Closures;
13 import org.implab.gradle.common.core.lang.Strings;
13 import org.implab.gradle.common.core.lang.Strings;
14 import org.eclipse.jdt.annotation.NonNullByDefault;
14 import org.eclipse.jdt.annotation.NonNullByDefault;
15 import org.eclipse.jdt.annotation.Nullable;
15 import org.gradle.api.Action;
16 import org.gradle.api.Action;
16 import org.gradle.api.InvalidUserDataException;
17 import org.gradle.api.InvalidUserDataException;
17 import org.gradle.api.NamedDomainObjectContainer;
18 import org.gradle.api.NamedDomainObjectContainer;
@@ -23,16 +24,16 import org.gradle.api.logging.Logging;
23 import groovy.lang.Closure;
24 import groovy.lang.Closure;
24 import groovy.lang.DelegatesTo;
25 import groovy.lang.DelegatesTo;
25
26
27 import static org.implab.gradle.common.core.lang.Strings.sanitizeName;
28
26 /**
29 /**
27 * Adapter extension that registers source sets for variant/layer pairs.
30 * Adapter extension that registers source sets for variant/layer pairs.
28 */
31 */
29 @NonNullByDefault
32 @NonNullByDefault
30 public abstract class VariantSourcesExtension {
33 public abstract class VariantSourcesExtension {
31 private static final Logger logger = Logging.getLogger(VariantSourcesExtension.class);
34 private static final Logger logger = Logging.getLogger(VariantSourcesExtension.class);
32 private static final Pattern INVALID_NAME_CHAR = Pattern.compile("[^A-Za-z0-9_.-]");
33 private static final Pattern SOURCE_SET_NAME_TOKEN = Pattern.compile("\\{([A-Za-z][A-Za-z0-9]*)\\}");
35 private static final Pattern SOURCE_SET_NAME_TOKEN = Pattern.compile("\\{([A-Za-z][A-Za-z0-9]*)\\}");
34
36
35 private final ObjectFactory objects;
36 private final NamedDomainObjectContainer<BuildLayerBinding> bindings;
37 private final NamedDomainObjectContainer<BuildLayerBinding> bindings;
37 private final List<Action<? super SourceSetContext>> registeredActions = new ArrayList<>();
38 private final List<Action<? super SourceSetContext>> registeredActions = new ArrayList<>();
38 private final List<Action<? super SourceSetContext>> boundActions = new ArrayList<>();
39 private final List<Action<? super SourceSetContext>> boundActions = new ArrayList<>();
@@ -44,7 +45,6 public abstract class VariantSourcesExte
44
45
45 @Inject
46 @Inject
46 public VariantSourcesExtension(ObjectFactory objects) {
47 public VariantSourcesExtension(ObjectFactory objects) {
47 this.objects = objects;
48 bindings = objects.domainObjectContainer(BuildLayerBinding.class);
48 bindings = objects.domainObjectContainer(BuildLayerBinding.class);
49 }
49 }
50
50
@@ -62,7 +62,7 public abstract class VariantSourcesExte
62 }
62 }
63
63
64 public BuildLayerBinding bind(String layer) {
64 public BuildLayerBinding bind(String layer) {
65 return bindings.maybeCreate(normalize(layer));
65 return bindings.maybeCreate(normalize(layer, "Layer name must not be null or blank"));
66 }
66 }
67
67
68 /**
68 /**
@@ -173,7 +173,7 public abstract class VariantSourcesExte
173 .map(layerName -> new LayerUsage(
173 .map(layerName -> new LayerUsage(
174 variant.getName(),
174 variant.getName(),
175 role.getName(),
175 role.getName(),
176 normalize(layerName)))));
176 normalize(layerName, "Layer name in variant '" + variant.getName() + "' and role '" + role.getName() + "' must not be null or blank")))));
177 }
177 }
178
178
179 private void registerLayerUsage(LayerUsage usage, NamedDomainObjectContainer<GenericSourceSet> sources) {
179 private void registerLayerUsage(LayerUsage usage, NamedDomainObjectContainer<GenericSourceSet> sources) {
@@ -255,7 +255,7 public abstract class VariantSourcesExte
255 private static String sourceSetName(LayerUsage usage, String pattern) {
255 private static String sourceSetName(LayerUsage usage, String pattern) {
256 var normalizedPattern = normalize(pattern, "sourceSetNamePattern must not be null or blank");
256 var normalizedPattern = normalize(pattern, "sourceSetNamePattern must not be null or blank");
257 var resolved = resolveSourceSetNamePattern(normalizedPattern, usage);
257 var resolved = resolveSourceSetNamePattern(normalizedPattern, usage);
258 var result = sanitize(resolved);
258 var result = sanitizeName(resolved);
259
259
260 if (result.isEmpty())
260 if (result.isEmpty())
261 throw new InvalidUserDataException("sourceSetNamePattern '" + pattern + "' resolved to empty source set name");
261 throw new InvalidUserDataException("sourceSetNamePattern '" + pattern + "' resolved to empty source set name");
@@ -278,26 +278,18 public abstract class VariantSourcesExte
278
278
279 private static String tokenValue(String token, LayerUsage usage) {
279 private static String tokenValue(String token, LayerUsage usage) {
280 return switch (token) {
280 return switch (token) {
281 case "variant" -> sanitize(usage.variantName());
281 case "variant" -> sanitizeName(usage.variantName());
282 case "variantCap" -> Strings.capitalize(sanitize(usage.variantName()));
282 case "variantCap" -> Strings.capitalize(sanitizeName(usage.variantName()));
283 case "role" -> sanitize(usage.roleName());
283 case "role" -> sanitizeName(usage.roleName());
284 case "roleCap" -> Strings.capitalize(sanitize(usage.roleName()));
284 case "roleCap" -> Strings.capitalize(sanitizeName(usage.roleName()));
285 case "layer" -> sanitize(usage.layerName());
285 case "layer" -> sanitizeName(usage.layerName());
286 case "layerCap" -> Strings.capitalize(sanitize(usage.layerName()));
286 case "layerCap" -> Strings.capitalize(sanitizeName(usage.layerName()));
287 default -> throw new InvalidUserDataException(
287 default -> throw new InvalidUserDataException(
288 "sourceSetNamePattern contains unsupported token '{" + token + "}'");
288 "sourceSetNamePattern contains unsupported token '{" + token + "}'");
289 };
289 };
290 }
290 }
291
291
292 private static String sanitize(String value) {
292 private static String normalize(@Nullable String value, String errorMessage) {
293 return INVALID_NAME_CHAR.matcher(value).replaceAll("_");
294 }
295
296 private static String normalize(String value) {
297 return normalize(value, "Value must not be null or blank");
298 }
299
300 private static String normalize(String value, String errorMessage) {
301 if (value == null)
293 if (value == null)
302 throw new InvalidUserDataException(errorMessage);
294 throw new InvalidUserDataException(errorMessage);
303 var trimmed = value.trim();
295 var trimmed = value.trim();
@@ -48,7 +48,6 class VariantsPluginFunctionalTest {
48 role('main') {
48 role('main') {
49 layers('mainBase', 'mainAmd')
49 layers('mainBase', 'mainAmd')
50 }
50 }
51 link('mainBase', 'mainAmd', 'ts:api')
52 artifactSlot('mainCompiled')
51 artifactSlot('mainCompiled')
53 }
52 }
54 }
53 }
@@ -58,7 +57,6 class VariantsPluginFunctionalTest {
58 def browser = variants.require('browser')
57 def browser = variants.require('browser')
59 println('attributes=' + browser.attributes.size())
58 println('attributes=' + browser.attributes.size())
60 println('roles=' + browser.roles.size())
59 println('roles=' + browser.roles.size())
61 println('links=' + browser.links.size())
62 println('slots=' + browser.artifactSlots.size())
60 println('slots=' + browser.artifactSlots.size())
63 }
61 }
64 }
62 }
@@ -68,7 +66,6 class VariantsPluginFunctionalTest {
68
66
69 assertTrue(result.getOutput().contains("attributes=2"));
67 assertTrue(result.getOutput().contains("attributes=2"));
70 assertTrue(result.getOutput().contains("roles=1"));
68 assertTrue(result.getOutput().contains("roles=1"));
71 assertTrue(result.getOutput().contains("links=1"));
72 assertTrue(result.getOutput().contains("slots=1"));
69 assertTrue(result.getOutput().contains("slots=1"));
73 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
70 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
74 }
71 }
@@ -94,28 +91,6 class VariantsPluginFunctionalTest {
94 }
91 }
95
92
96 @Test
93 @Test
97 void failsOnCycleInLinksByKind() throws Exception {
98 assertBuildFails("""
99 plugins {
100 id 'org.implab.gradle-variants'
101 }
102
103 variants {
104 layer('a')
105 layer('b')
106
107 variant('browser') {
108 role('main') {
109 layers('a', 'b')
110 }
111 link('a', 'b', 'ts:api')
112 link('b', 'a', 'ts:api')
113 }
114 }
115 """, "contains cycle in links with kind 'ts:api'");
116 }
117
118 @Test
119 void allowsUsingLayerFromDifferentVariantRole() throws Exception {
94 void allowsUsingLayerFromDifferentVariantRole() throws Exception {
120 writeFile(SETTINGS_FILE, ROOT_NAME);
95 writeFile(SETTINGS_FILE, ROOT_NAME);
121 writeFile(BUILD_FILE, """
96 writeFile(BUILD_FILE, """
@@ -139,91 +114,6 class VariantsPluginFunctionalTest {
139 }
114 }
140
115
141 @Test
116 @Test
142 void failsOnIncompleteLink() throws Exception {
143 assertBuildFails("""
144 plugins {
145 id 'org.implab.gradle-variants'
146 }
147
148 variants {
149 layer('a')
150 layer('b')
151
152 variant('browser') {
153 role('main') {
154 layers('a', 'b')
155 }
156 link('a', 'b', null)
157 }
158 }
159 """, "Link 'kind' must not be null or blank");
160 }
161
162 @Test
163 void failsOnDuplicatedLinkTuple() throws Exception {
164 assertBuildFails("""
165 plugins {
166 id 'org.implab.gradle-variants'
167 }
168
169 variants {
170 layer('a')
171 layer('b')
172
173 variant('browser') {
174 role('main') {
175 layers('a', 'b')
176 }
177 link('a', 'b', 'ts:api')
178 link('a', 'b', 'ts:api')
179 }
180 }
181 """, "has duplicated link tuple (from='a', to='b', kind='ts:api')");
182 }
183
184 @Test
185 void failsOnUnknownSourceLayerInLink() throws Exception {
186 assertBuildFails("""
187 plugins {
188 id 'org.implab.gradle-variants'
189 }
190
191 variants {
192 layer('a') {
193 }
194
195 variant('browser') {
196 role('main') {
197 layers('a')
198 }
199 link('missing', 'a', 'ts:api')
200 }
201 }
202 """, "references unknown source layer 'missing'");
203 }
204
205 @Test
206 void failsOnUnknownTargetLayerInLink() throws Exception {
207 assertBuildFails("""
208 plugins {
209 id 'org.implab.gradle-variants'
210 }
211
212 variants {
213 layer('a') {
214 }
215
216 variant('browser') {
217 role('main') {
218 layers('a')
219 }
220 link('a', 'missing', 'ts:api')
221 }
222 }
223 """, "references unknown target layer 'missing'");
224 }
225
226 @Test
227 void failsOnDuplicatedLayerReferenceInRole() throws Exception {
117 void failsOnDuplicatedLayerReferenceInRole() throws Exception {
228 assertBuildFails("""
118 assertBuildFails("""
229 plugins {
119 plugins {
@@ -25,7 +25,6 variants {
25 layers('mainBase', 'mainAmd')
25 layers('mainBase', 'mainAmd')
26 }
26 }
27
27
28 link('mainBase', 'mainAmd', 'ts:api')
29 artifactSlot('mainCompiled')
28 artifactSlot('mainCompiled')
30 }
29 }
31 }
30 }
@@ -39,31 +38,18 variants {
39 ### layers
38 ### layers
40
39
41 Глобальные логические слои. Служат единым словарем имен, на которые затем
40 Глобальные логические слои. Служат единым словарем имен, на которые затем
42 ссылаются роли и связи.
41 ссылаются роли.
43
42
44 ### variants
43 ### variants
45
44
46 Именованные варианты исполнения/пакетирования (`browser`, `node`, и т.д.).
45 Именованные варианты исполнения/пакетирования (`browser`, `node`, и т.д.).
47 Вариант агрегирует роли, связи, атрибуты и artifact slots.
46 Вариант агрегирует роли, атрибуты и artifact slots.
48
47
49 ### roles
48 ### roles
50
49
51 Роль описывает набор слоев в пределах варианта (`main`, `test`, `tools`).
50 Роль описывает набор слоев в пределах варианта (`main`, `test`, `tools`).
52 Одна роль может ссылаться на несколько слоев.
51 Одна роль может ссылаться на несколько слоев.
53
52
54 ### links
55
56 `link(from, to, kind)` — ориентированная связь между слоями внутри варианта.
57
58 `kind` задает независимый тип графа (например `ts:api`, `bundle:runtime`). Это
59 позволяет вести несколько параллельных графов зависимостей над теми же слоями.
60
61 Практические сценарии использования `link` в адаптерах:
62
63 - расчет topological order по выбранному `kind`;
64 - wiring task inputs/outputs между слоями;
65 - проверка допустимости дополнительных pipeline-зависимостей.
66
67 ### attributes
53 ### attributes
68
54
69 Typed-атрибуты (`Attribute<T> -> Provider<T>`) для передачи параметров в
55 Typed-атрибуты (`Attribute<T> -> Provider<T>`) для передачи параметров в
@@ -80,10 +66,8 Typed-атрибуты (`Attribute<T> -> Provider<T>`) для передачи параметров в
80
66
81 - роль не может ссылаться на неизвестный layer;
67 - роль не может ссылаться на неизвестный layer;
82 - пустые имена layer запрещены;
68 - пустые имена layer запрещены;
83 - у link обязательны `from`, `to`, `kind`;
69 - имена ролей в варианте должны быть уникальны;
84 - `from`/`to` должны входить в слойную область варианта;
70 - имена artifact slots в варианте должны быть уникальны.
85 - tuple `(from, to, kind)` должен быть уникален;
86 - циклы в графе одного `kind` запрещены.
87
71
88 ## LIFECYCLE
72 ## LIFECYCLE
89
73
@@ -108,7 +92,6 Typed-атрибуты (`Attribute<T> -> Provider<T>`) для передачи параметров в
108
92
109 - `attributes { ... }` — атрибуты варианта (+ sugar `string/bool/integer`).
93 - `attributes { ... }` — атрибуты варианта (+ sugar `string/bool/integer`).
110 - `role(...)`, `roles { ... }` — роли варианта.
94 - `role(...)`, `roles { ... }` — роли варианта.
111 - `link(...)`, `links { ... }` — связи слоев внутри варианта.
112 - `artifactSlot(...)`, `artifactSlots { ... }` — артефактные слоты.
95 - `artifactSlot(...)`, `artifactSlots { ... }` — артефактные слоты.
113
96
114 ## KEY CLASSES
97 ## KEY CLASSES
@@ -118,7 +101,6 Typed-атрибуты (`Attribute<T> -> Provider<T>`) для передачи параметров в
118 - `BuildVariant` — агрегатная модель варианта.
101 - `BuildVariant` — агрегатная модель варианта.
119 - `BuildLayer` — модель слоя.
102 - `BuildLayer` — модель слоя.
120 - `BuildRole` — модель роли.
103 - `BuildRole` — модель роли.
121 - `LayerLink` — модель направленной связи.
122 - `BuildArtifactSlot` — модель артефактного слота.
104 - `BuildArtifactSlot` — модель артефактного слота.
123 - `VariantAttributes` — typed wrapper для variant attributes.
105 - `VariantAttributes` — typed wrapper для variant attributes.
124
106
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now